const { Job, JobQueue, JobQueueCapacityPolicy } = require('./JobQueue');
/**
* @extends {JobQueue}
* @author Sebastian Hönel <development@hoenel.net>
*/
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 the total cost of all jobs that need to be run.
*
* @type {Number}
*/
get backlogCost() {
return this.queue.map(job => job.cost).reduce((a, b) => a + b, 0);
};
/**
* Returns 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.
*
* @type {Number}
*/
get load() {
const totalCost = this.backlogCost + this.capabilitiesUsed;
return totalCost / this.capabilities;
};
/**
* Returns 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].
*
* @type {Number}
*/
get utilization() {
return this.capabilitiesUsed / this.capabilities;
};
/**
* Only returns true, iff the remaining capabilities are exactly 0 (zero).
*
* @type {Boolean}
*/
get isBusy() {
return this.capabilitiesFree === 0;
};
/**
* Return the accumulated cost of all currently running jobs.
*
* @type {Number}
*/
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).
*
* @type {Number}
*/
get capabilitiesFree() {
return Math.max(0, this.capabilities - this.capabilitiesUsed);
};
/**
* @template T
* @override
* @inheritdoc
* @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);
};
/**
* @protected
* @override
* @inheritdoc
*/
_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
});