rc-js-util
Version:
A collection of TS and C++ utilities to help writing performant and correct applications, achieved through strict typing and (removable) invariant checking.
148 lines • 7.64 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkerPool = exports.WorkerPoolErrorCause = exports.EWorkerPoolOverflowMode = void 0;
const null_pointer_js_1 = require("../emscripten/null-pointer.js");
const _production_js_1 = require("../../production/_production.js");
const promise_poll_js_1 = require("../../promise/impl/promise-poll.js");
const _debug_js_1 = require("../../debug/_debug.js");
const nested_error_js_1 = require("../../error-handling/nested-error.js");
const wasm_error_cause_js_1 = require("../wasm-error-cause.js");
const shared_object_cleanup_js_1 = require("../shared-memory/shared-object-cleanup.js");
/**
* @public
* How to handle jobs which don't "overflow", i.e. the workers cannot keep up with the work being sent.
*/
var EWorkerPoolOverflowMode;
(function (EWorkerPoolOverflowMode) {
/**
* Delete the job and then throw a {@link NestedError} with a cause of {@link WorkerPoolErrorCause.overflow}.
* @remarks This is intended mainly for unit tests.
* @remarks Ownership of the job is transferred to the job queue.
*/
EWorkerPoolOverflowMode[EWorkerPoolOverflowMode["Throw"] = 1] = "Throw";
/**
* If no worker is able to accept the job, the job runs on the producer (caller) thread. This automatically
* "fixes" backpressure by throttling the caller thread. This will result in degraded performance on the producer thread
* (often the UI thread) which is not always desirable.
* @remarks {@link IWorkerPool.addJob} will return false where the job ran synchronously.
* @remarks Ownership of the job is transferred to the job queue.
*/
EWorkerPoolOverflowMode[EWorkerPoolOverflowMode["Synchronous"] = 2] = "Synchronous";
/**
* Do nothing, it's up to you to choose an action.
* @remarks {@link IWorkerPool.addJob} will return false where the job did not run.
* @remarks Ownership of the job is NOT transferred to the job queue, the caller must clean up.
*/
EWorkerPoolOverflowMode[EWorkerPoolOverflowMode["Noop"] = 3] = "Noop";
})(EWorkerPoolOverflowMode || (exports.EWorkerPoolOverflowMode = EWorkerPoolOverflowMode = {}));
/**
* @public
* The static members are the cause in {@link INestedError}.
*/
class WorkerPoolErrorCause {
}
exports.WorkerPoolErrorCause = WorkerPoolErrorCause;
WorkerPoolErrorCause.overflow = "WorkerPoolErrorCause.overflow";
/**
* @public
* {@inheritDoc IWorkerPool}
*/
class WorkerPool {
static createRoundRobin(wrapper, config, bindToReference, allocationFailThrows = true) {
return createRoundRobinImpl(wrapper, config, bindToReference, allocationFailThrows);
}
getWrapper() {
return this.wrapper;
}
start() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
const started = this.wrapper.instance._workerPool_start(this.pointer);
return (0, promise_poll_js_1.promisePoll)(() => this.wrapper.instance._workerPool_isAcceptingJobs(this.pointer))
.getPromise()
.then(() => started);
}
stop() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
this.wrapper.instance._workerPool_stop(this.pointer, false);
return (0, promise_poll_js_1.promisePoll)(() => !this.wrapper.instance._workerPool_isAnyWorkerRunning(this.pointer))
.getPromise()
.then(() => undefined);
}
isRunning() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
return Boolean(this.wrapper.instance._workerPool_isAnyWorkerRunning(this.pointer));
}
isBatchDone() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
return Boolean(this.wrapper.instance._workerPool_isBatchDone(this.pointer));
}
setBatchEnd() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
this.wrapper.instance._workerPool_setBatchEndPoint(this.pointer);
}
invalidateBatch() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
this.wrapper.instance._workerPool_invalidateBatch(this.pointer);
}
areWorkersSynced() {
return Boolean(this.wrapper.instance._workerPool_areWorkersSynced(this.pointer));
}
hasPendingWork() {
_BUILD.DEBUG && _debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
return Boolean(this.wrapper.instance._workerPool_hasPendingWork(this.pointer));
}
addJob(jobPtr) {
_BUILD.DEBUG && _debug_js_1._Debug.runBlock(() => {
_debug_js_1._Debug.assert(!this.resourceHandle.getIsDestroyed(), "use after free");
_debug_js_1._Debug.assert(jobPtr !== null_pointer_js_1.nullPtr, "expected job, got nullptr");
});
const added = this.wrapper.instance._workerPool_addJob(this.pointer, jobPtr);
if (!added) {
switch (this.overflowMode) {
case EWorkerPoolOverflowMode.Noop: // intentional fallthrough
case EWorkerPoolOverflowMode.Synchronous:
break;
case EWorkerPoolOverflowMode.Throw:
{
throw new nested_error_js_1.NestedError("WorkerPool job queue overflowed.", WorkerPoolErrorCause.overflow);
}
default:
_production_js_1._Production.assertValueIsNever(this.overflowMode);
}
}
return added;
}
// @internal
constructor(wrapper, ownerNode, pointer, overflowMode) {
this.wrapper = wrapper;
this.resourceHandle = wrapper.lifecycleStrategy.createNode(ownerNode);
this.pointer = pointer;
this.overflowMode = overflowMode;
this.cleanup = new shared_object_cleanup_js_1.SharedObjectCleanup(this, shared_object_cleanup_js_1.ESharedObjectOwnerKind.SharedMemoryOwner);
shared_object_cleanup_js_1.SharedObjectCleanup.registerCleanup(this, this.cleanup, new shared_object_cleanup_js_1.SharedObjectCleanup.Options("WorkerPool", null, shared_object_cleanup_js_1.ESharedObjectOwnerKind.SharedMemoryOwner));
}
}
exports.WorkerPool = WorkerPool;
function createRoundRobinImpl(wrapper, config, bindToReference, allocationFailThrows = true) {
var _a;
_BUILD.DEBUG && wrapper.debugUtils.onAllocate.emit();
const overflowMode = (_a = config.overflowMode) !== null && _a !== void 0 ? _a : EWorkerPoolOverflowMode.Synchronous;
const maxSize = 0xFFFF;
if (config.workerCount > maxSize) {
throw _production_js_1._Production.createError(`Requested pool size ${config.workerCount}, exceeds limit ${maxSize}.`);
}
if (config.queueSize > maxSize) {
throw _production_js_1._Production.createError(`Requested queue size ${config.queueSize}, exceeds limit ${maxSize}.`);
}
const pointer = wrapper.instance._workerPool_createRoundRobin(config.workerCount, config.queueSize, overflowMode === EWorkerPoolOverflowMode.Synchronous);
if (pointer == null_pointer_js_1.nullPtr) {
if (allocationFailThrows) {
throw new nested_error_js_1.NestedError("Failed to allocate memory for worker pool.", wasm_error_cause_js_1.WasmErrorCause.allocationFailure);
}
else {
return null;
}
}
return new WorkerPool(wrapper, bindToReference, pointer, overflowMode);
}
//# sourceMappingURL=worker-pool.js.map