UNPKG

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
"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