async-scheduler
Version:
 
246 lines (245 loc) • 10.4 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
require("mocha");
const Scheduler_1 = __importDefault(require("./Scheduler"));
const SchedulableTask_1 = require("./SchedulableTask");
function wait(timeout) {
return new Promise((resolve) => {
setTimeout(resolve, timeout);
});
}
describe('Scheduler', () => {
it('should execute tasks in numerical order', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(5);
// Create array with numbers from 0 to 20
let sortedNumbers = [];
let shuffledNumbers = [];
for (let i = 20; i >= 0; i--) {
sortedNumbers.push(i);
shuffledNumbers.push(i);
}
// Shuffle array
shuffledNumbers.sort(() => Math.random() - 0.5);
let executionOrder = [];
let promises = shuffledNumbers.map((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr), 10 * Math.random());
});
},
onPreExecute() {
executionOrder.push(nbr);
}
}));
yield Promise.all(promises);
chai_1.expect(executionOrder).to.eql(sortedNumbers);
}));
it('should run only a limited amount of tasks in parallel', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(2);
let numbers = [1, 2, 3, 4, 5];
let executionOrder = [];
numbers.forEach((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr), 10);
});
},
onPreExecute() {
executionOrder.push(nbr);
}
}));
yield wait(5);
chai_1.expect(scheduler.executingTasks).to.equal(2);
chai_1.expect(executionOrder).to.eql([5, 4]);
yield wait(10);
chai_1.expect(scheduler.executingTasks).to.equal(2);
chai_1.expect(executionOrder).to.eql([5, 4, 3, 2]);
yield wait(10);
chai_1.expect(scheduler.executingTasks).to.equal(1);
chai_1.expect(executionOrder).to.eql([5, 4, 3, 2, 1]);
}));
it('should be ready to execute again after all tasks have been executed', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(2);
let promises = [1, 2, 3, 4, 5].map((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => resolve(nbr));
}
}));
let result = yield Promise.all(promises);
chai_1.expect(result).to.eql([1, 2, 3, 4, 5]);
promises = [6, 7, 8, 9, 10].map((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => resolve(nbr));
}
}));
result = yield Promise.all(promises);
chai_1.expect(result).to.eql([6, 7, 8, 9, 10]);
}));
it('should accept and execute new tasks while executing other ones', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(2);
let executionOrder = [];
let promises = [1, 2, 3, 6, 7].map((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr), 10);
});
},
onPreExecute() {
executionOrder.push(nbr);
}
}));
yield wait(5);
promises = [
...promises,
...([4, 5].map((nbr) => scheduler.enqueue({
priority: nbr,
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr), 10);
});
},
onPreExecute() {
executionOrder.push(nbr);
}
})))
];
yield Promise.all(promises);
chai_1.expect(executionOrder).to.eql([7, 6, 5, 4, 3, 2, 1]);
}));
it('should cancel tasks based on mutex collisions', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(2);
let allPromises = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((nbr) => scheduler.enqueue({
priority: 0,
mutex: 1 << Math.floor(nbr / 2),
meta: {
extraVarNumber: nbr,
},
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr));
});
},
onTaskCollision(other) {
chai_1.expect(other.meta).to.be.ok;
if (other.meta.extraVarNumber <= nbr) {
return SchedulableTask_1.TaskCollisionStrategy.KEEP_OTHER;
}
else {
return SchedulableTask_1.TaskCollisionStrategy.KEEP_THIS;
}
}
}).catch(() => -1 * nbr));
let result = yield Promise.all(allPromises);
chai_1.expect(result).to.eql([0, -1, 2, -3, 4, -5, 6, -7, 8, -9]);
}));
it('should allow tasks to resolve each other in case of collision', () => __awaiter(void 0, void 0, void 0, function* () {
let scheduler = new Scheduler_1.default(2);
let allPromises = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((nbr) => scheduler.enqueue({
priority: 0,
mutex: 1 << Math.floor(nbr / 2),
meta: {
extraVarNumber: nbr,
},
execute() {
return new Promise((resolve) => {
setTimeout(() => resolve(nbr));
});
},
onTaskCollision(other) {
chai_1.expect(other.meta).to.be.ok;
if (other.meta.extraVarNumber <= nbr) {
return SchedulableTask_1.TaskCollisionStrategy.RESOLVE_OTHER;
}
else {
return SchedulableTask_1.TaskCollisionStrategy.RESOLVE_THIS;
}
}
}));
let result = yield Promise.all(allPromises);
chai_1.expect(result).to.eql([0, 0, 2, 2, 4, 4, 6, 6, 8, 8]);
}));
it('should trigger idle listeners correctly when every task succeeds', () => __awaiter(void 0, void 0, void 0, function* () {
const scheduler = new Scheduler_1.default(2);
const executed = [];
[0, 1, 2, 3].forEach(i => {
scheduler.enqueue(() => new Promise((resolve) => {
setTimeout(() => {
[0, 1, 2, 3].forEach(j => {
scheduler.enqueue(() => new Promise((subResolve) => {
setTimeout(() => {
executed.push((i * 10) + j);
subResolve();
});
}));
});
resolve();
});
}));
});
yield scheduler.waitForIdle();
executed.sort((a, b) => a - b);
chai_1.expect(executed).to.deep.equal([0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33]);
}));
it('should trigger idle listeners correctly when some tasks fail', () => __awaiter(void 0, void 0, void 0, function* () {
const scheduler = new Scheduler_1.default(2);
const executed = [];
[0, 1, 2, 3].forEach(i => {
scheduler.enqueue(() => new Promise((resolve) => {
setTimeout(() => {
[0, 1, 2, 3].forEach(j => {
scheduler.enqueue(() => new Promise((subResolve, subReject) => {
setTimeout(() => {
const n = (i * 10) + j;
if (n % 2 === 0) {
executed.push(n);
subResolve();
}
else {
subReject(new Error('oof'));
}
});
})).catch(() => { });
});
resolve();
});
}));
});
yield scheduler.waitForIdle();
executed.sort((a, b) => a - b);
chai_1.expect(executed).to.deep.equal([0, 2, 10, 12, 20, 22, 30, 32]);
}));
it('should resolve waitForIdle directly if already idle', () => __awaiter(void 0, void 0, void 0, function* () {
const scheduler = new Scheduler_1.default(2);
yield scheduler.waitForIdle();
}));
it('should resolve waitForIdle only when tasks have finished if n(tasks) < n(maxTasks)', () => __awaiter(void 0, void 0, void 0, function* () {
const scheduler = new Scheduler_1.default(4);
const results = [];
scheduler.enqueue(() => new Promise((resolve) => {
setTimeout(() => {
results.push(42);
resolve();
});
})).then(() => { });
yield scheduler.waitForIdle();
chai_1.expect(results).to.deep.equal([42]);
}));
});