@elsikora/cladi
Version:
Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.
105 lines (101 loc) • 4.1 kB
JavaScript
'use strict';
var error_class = require('../../base/error.class.js');
class DisposalManager {
activeAsyncResolutions;
DISPOSAL_WAITERS;
DISPOSE_INTERNAL;
GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS;
GET_DISPOSE_PROMISE;
IS_DISPOSED;
IS_DISPOSING;
SET_DISPOSE_PROMISE;
SET_IS_DISPOSING;
constructor(options) {
this.DISPOSE_INTERNAL = options.disposeInternal;
this.GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS = options.getAsyncResolutionDrainTimeoutMs;
this.GET_DISPOSE_PROMISE = options.getDisposePromise;
this.IS_DISPOSED = options.isDisposed;
this.IS_DISPOSING = options.isDisposing;
this.SET_DISPOSE_PROMISE = options.setDisposePromise;
this.SET_IS_DISPOSING = options.setIsDisposing;
this.activeAsyncResolutions = 0;
this.DISPOSAL_WAITERS = [];
}
async dispose() {
if (this.IS_DISPOSED()) {
return;
}
if (this.IS_DISPOSING()) {
await this.GET_DISPOSE_PROMISE();
return;
}
this.SET_IS_DISPOSING(true);
const disposePromise = this.DISPOSE_INTERNAL();
this.SET_DISPOSE_PROMISE(disposePromise);
await disposePromise;
}
onAsyncResolutionEnd() {
this.activeAsyncResolutions = Math.max(0, this.activeAsyncResolutions - 1);
this.releaseDisposalWaitersIfIdle();
}
onAsyncResolutionStart() {
this.activeAsyncResolutions += 1;
}
async waitForInFlightAsyncResolutions() {
const asyncResolutionDrainTimeoutMs = this.GET_ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS();
const hasTimeout = typeof asyncResolutionDrainTimeoutMs === "number" && Number.isFinite(asyncResolutionDrainTimeoutMs) && asyncResolutionDrainTimeoutMs >= 0;
const normalizedTimeoutMs = hasTimeout ? asyncResolutionDrainTimeoutMs : undefined;
const deadlineAtMs = normalizedTimeoutMs === undefined ? undefined : Date.now() + normalizedTimeoutMs;
while (this.activeAsyncResolutions > 0) {
await this.waitForIdleSignal(deadlineAtMs, normalizedTimeoutMs);
}
}
createAsyncResolutionDrainTimeoutError(asyncResolutionDrainTimeoutMs) {
return new error_class.BaseError("Timed out waiting for in-flight async resolutions during disposal", {
code: "SCOPE_DISPOSE_ASYNC_DRAIN_TIMEOUT",
context: {
activeAsyncResolutions: this.activeAsyncResolutions,
timeoutMs: asyncResolutionDrainTimeoutMs,
},
source: "DIContainer",
});
}
releaseDisposalWaitersIfIdle() {
if (this.activeAsyncResolutions > 0 || this.DISPOSAL_WAITERS.length === 0) {
return;
}
const pendingWaiters = [...this.DISPOSAL_WAITERS];
this.DISPOSAL_WAITERS.length = 0;
for (const waiter of pendingWaiters) {
waiter();
}
}
async waitForIdleSignal(deadlineAtMs, configuredTimeoutMs) {
if (deadlineAtMs === undefined) {
await new Promise((resolve) => {
this.DISPOSAL_WAITERS.push(resolve);
});
return;
}
const remainingTimeoutMs = deadlineAtMs - Date.now();
if (remainingTimeoutMs <= 0) {
throw this.createAsyncResolutionDrainTimeoutError(configuredTimeoutMs ?? 0);
}
await new Promise((resolve, reject) => {
const waiter = () => {
clearTimeout(timeoutHandle);
resolve();
};
const timeoutHandle = setTimeout(() => {
const waiterIndex = this.DISPOSAL_WAITERS.indexOf(waiter);
if (waiterIndex !== -1) {
this.DISPOSAL_WAITERS.splice(waiterIndex, 1);
}
reject(this.createAsyncResolutionDrainTimeoutError(configuredTimeoutMs ?? remainingTimeoutMs));
}, remainingTimeoutMs);
this.DISPOSAL_WAITERS.push(waiter);
});
}
}
exports.DisposalManager = DisposalManager;
//# sourceMappingURL=manager.class.js.map