parallel.es
Version:
Simple parallelization for EcmaScript
151 lines (118 loc) • 6.18 kB
text/typescript
import {DefaultThreadPool} from "../../../src/common/thread-pool/default-thread-pool";
import {WorkerTask} from "../../../src/common/task/worker-task";
import {FunctionCall} from "../../../src/common/function/function-call";
describe("DefaultThreadPool", function () {
let spawn: jasmine.Spy;
let serializeCallSpy: jasmine.Spy;
let threadPool: DefaultThreadPool;
beforeEach(function () {
spawn = jasmine.createSpy("spawn");
const workerThreadFactory = { spawn };
serializeCallSpy = jasmine.createSpy("serializeCall");
threadPool = new DefaultThreadPool(workerThreadFactory, { serializeFunctionCall: serializeCallSpy } as any, { maxConcurrencyLevel: 2 });
});
describe("schedule", function () {
it("registers the function in the function lookup table", function () {
// arrange
const func = function () { /* ignore */ };
serializeCallSpy.and.returnValue({ functionId: "test-1" });
// act
threadPool.schedule(func);
// assert
expect(serializeCallSpy).toHaveBeenCalledWith(FunctionCall.create(func));
});
it("spawns a new worker until the concurrency limit is reached", function () {
// arrange
const func = function () { /* ignore */ };
serializeCallSpy.and.returnValue({ functionId: "test-1" });
// act
threadPool.schedule(func);
threadPool.schedule(func);
threadPool.schedule(func);
// assert
expect(spawn).toHaveBeenCalledTimes(2);
});
it("executes the task in a worker thread", function () {
// arrange
const func = function (value: number) { return value; };
const runOnSpy = spyOn(WorkerTask.prototype, "runOn");
serializeCallSpy.and.returnValue({ functionId: "test-1" });
const worker = { run: jasmine.createSpy("run") };
spawn.and.returnValue(worker);
// act
threadPool.schedule(func, 10);
// assert
expect(runOnSpy).toHaveBeenCalledWith(worker);
});
it("enqueues the task if no worker thread is available", function () {
// arrange
const func = function () { /* ignore */ };
serializeCallSpy.and.returnValue({ functionId: "test-1" });
const worker1 = { run: jasmine.createSpy("run1"), stop: jasmine.createSpy("stop") };
const worker2 = { run: jasmine.createSpy("run2"), stop: jasmine.createSpy("stop") };
spawn.and.returnValues(worker1, worker2);
// schedule worker until no worker is available...
threadPool.schedule(func);
threadPool.schedule(func);
// act, should be queued.
threadPool.schedule(func);
// assert
expect(worker1.run).toHaveBeenCalled();
expect(worker2.run).toHaveBeenCalled();
});
it("schedules queued tasks when a worker gets available", function () {
// arrange
const func = function () { /* ignore */ };
const func2 = function add(x: number, y: number): number { return x + y; };
serializeCallSpy.and.returnValues({ functionId: "test-1" }, { functionId: "test-1" }, { functionId: "test-2" });
const worker1 = { run: jasmine.createSpy("run1"), stop: jasmine.createSpy("stop") };
const worker2 = { run: jasmine.createSpy("run2"), stop: jasmine.createSpy("stop") };
const runOnSpy = spyOn(WorkerTask.prototype, "runOn");
const alwaysSpy = spyOn(WorkerTask.prototype, "always");
spyOn(WorkerTask.prototype, "releaseWorker").and.returnValue(worker1);
spawn.and.returnValues(worker1, worker2);
// schedule worker until no worker is available...
threadPool.schedule(func);
threadPool.schedule(func);
threadPool.schedule(func2); // queue third function
// act
// complete task of first worker so that the third task is scheduled on worker 1
alwaysSpy.calls.argsFor(0)[0].call(undefined, 10);
// assert
expect(runOnSpy.calls.count()).toEqual(3);
expect(runOnSpy.calls.argsFor(0)).toEqual([worker1]);
expect(runOnSpy.calls.argsFor(1)).toEqual([worker2]);
expect(runOnSpy.calls.argsFor(2)).toEqual([worker1]);
});
it("reuses an idle worker if available", function () {
// arrange
const worker1 = { run: jasmine.createSpy("run1"), stop: jasmine.createSpy("stop") };
const worker2 = { run: jasmine.createSpy("run2"), stop: jasmine.createSpy("stop") };
serializeCallSpy.and.returnValue({ functionId: "test-1" });
spawn.and.returnValues(worker1, worker2);
const runOnSpy = spyOn(WorkerTask.prototype, "runOn");
const alwaysSpy = spyOn(WorkerTask.prototype, "always");
spyOn(WorkerTask.prototype, "releaseWorker").and.returnValues(worker1, worker2);
// spawn all workers by scheduling tasks up to concurrency limit
const func = function () { /* ignore */ };
threadPool.schedule(func);
threadPool.schedule(func);
// complete first task, third task can now be scheduled on worker1 as this worker is idle
alwaysSpy.calls.argsFor(0)[0].call(undefined, 10);
// act
threadPool.schedule(func);
expect(runOnSpy).toHaveBeenCalledTimes(3);
expect(runOnSpy.calls.argsFor(0)).toEqual([ worker1 ]);
expect(runOnSpy.calls.argsFor(1)).toEqual([ worker2]);
expect(runOnSpy.calls.argsFor(0)).toEqual([ worker1 ]);
});
});
describe("createFunctionSerializer", function () {
it("returns the instance", function () {
// arrange
const serializer = threadPool.getFunctionSerializer();
// assert
expect(serializer).not.toBeUndefined();
});
});
});