const { Job, JobQueue, JobQueueCapacityPolicy } = require('./JobQueue');
class JobQueueCapabilities extends JobQueue {
/**
* @param {number} capabilities
* @param {boolean} [allowExclusiveJobs] Optional. Defaults to false. A
* boolean that indicates whether jobs that require the whole queue's
* capabilities are allowed on this queue. Such jobs have a cost equal
* to or higher than the queue's capabilities.
* @param {Number} [capacity] Optional. The maximum capacity of this
* JobQueue. Only has an effect if the chosen policy is not set to
* ignore excess items. The maximum capacity is the total amount of
* Jobs either currently running or in the backlog this JobQueue can
* accomodate.
* @param {JobQueueCapacityPolicy|Number} Optional. The policy to
* use when the maximum capacity is reached, and new items are being
* enqueued.
*/
constructor(capabilities, allowExclusiveJobs = false, capacity = Number.MAX_SAFE_INTEGER, capacityPolicy = JobQueueCapacityPolicy.Ignore) {
super(Number.MAX_SAFE_INTEGER, capacity, capacityPolicy);
if (isNaN(capabilities) || capabilities <= 0) {
throw new Error('The capabilities must be a positive number.');
}
this.capabilities = capabilities;
this.allowExclusiveJobs = !!allowExclusiveJobs;
};
/**
* @returns {number} the total cost of all jobs that need to be run.
*/
get backlogCost() {
return this.queue.map(job => job.cost).reduce((a, b) => a + b, 0);
};
/**
* @returns {number} A number that indicates the ratio between currently
* running and enqueued jobs and this queue's capabilities. 0 means that
* the queue is idle; a value close or equal to one means that the queue
* runs at or near its full capacity and a value greater than one means
* that there are exclusive jobs or a non-empty backlog of waiting jobs.
* Albeit overwritten, this property can be perfectly used to compare the
* load of parallel job queues.
*/
get load() {
const totalCost = this.backlogCost + this.capabilitiesUsed;
return totalCost / this.capabilities;
};
/**
* @returns {number} A number indicating the utilization of this queue in
* terms of a ratio between used capabilities and available capabilities.
* Note that, if this queue allows exclusive jobs, the utilization may be
* greater than 100%. The range therefore is [0, infinity].
*/
get utilization() {
return this.capabilitiesUsed / this.capabilities;
};
/**
* Only returns true, iff the remaining capabilities are exactly 0 (zero).
*/
get isBusy() {
return this.capabilitiesFree === 0;
};
/**
* @returns {number} the accumulated cost of all currently running jobs.
*/
get capabilitiesUsed() {
return this.currentJobs.map(j => j.cost).reduce((a, b) => a + b, 0);
};
/**
* Returns the remaining capabilities. Exclusive jobs may use more capabilities
* than this queue provides. However, this property can at minimum only return
* 0 (zero) (i.e. not negative values).
*/
get capabilitiesFree() {
return Math.max(0, this.capabilities - this.capabilitiesUsed);
};
/**
* @template T
* @param {Job|producerHandler.<Promise.<T>>} job
* @param {number} [cost]
*/
addJob(job, cost = void 0) {
if (!(job instanceof Job)) {
if (job instanceof Function) {
job = new Job(job);
} else {
throw new Error(`The given Job is not an instance of Job nor is it an instance of Function.`);
}
}
if (!job.hasCost && (Object.prototype.toString.call(cost) !== '[object Number]' || cost <= 0)) {
throw new Error(`You must provide a valid value for parameter cost. Given: '${cost}'`);
}
job.cost = cost || job.cost;
if (job.cost >= this.capabilities && !this.allowExclusiveJobs) {
throw new Error(`The job's cost of ${job.cost} exceeds the queue's capabilities of ${this.capabilities} and this queue does not allow such (exclusive) jobs.`);
}
return super.addJob(job);
};
_runNext() {
// Check next job's cost
if (this.backlog > 0 && this.queue.peekIndex(0).cost > this.capabilitiesFree && this.numJobsRunning > 0) {
return; // Wait for more jobs to finish
}
super._runNext();
};
};
module.exports = Object.freeze({
JobQueueCapabilities
});