UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

412 lines 18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FLimitInMemory = void 0; const f_cancellation_token_js_1 = require("../cancellation/f_cancellation_token.js"); const index_js_1 = require("../exception/index.js"); const index_js_2 = require("../lifecycle/index.js"); const f_limit_exception_js_1 = require("./f_limit_exception.js"); const f_intrenal_limit_exception_assert_js_1 = require("./internal/f_intrenal_limit_exception_assert.js"); const f_limit_internal_parallel_limit_js_1 = require("./internal/f_limit_internal_parallel_limit.js"); const f_limit_internal_time_span_limit_js_1 = require("./internal/f_limit_internal_time_span_limit.js"); function buildInnerLimits(opts) { const innerLimits = []; if (opts.perHour) { let count = opts.perHour; if (count <= 0) { throw new f_limit_exception_js_1.FLimitException("perHour count value should be above zero integer"); } innerLimits.push(new f_limit_internal_time_span_limit_js_1.FLimitInternalTimeSpanLimit(1000 * 60 * 60 /* 1 hour */, count)); } if (opts.perMinute) { let count = opts.perMinute; if (count <= 0) { throw new f_limit_exception_js_1.FLimitException("perMinute count value should be above zero integer"); } innerLimits.push(new f_limit_internal_time_span_limit_js_1.FLimitInternalTimeSpanLimit(1000 * 60 /* 1 minute */, count)); } if (opts.perSecond) { const count = opts.perSecond; if (count <= 0) { throw new f_limit_exception_js_1.FLimitException("perSecond count value should be above zero integer"); } innerLimits.push(new f_limit_internal_time_span_limit_js_1.FLimitInternalTimeSpanLimit(1000 /* 1 second */, count)); } if (opts.perTimespan) { const count = opts.perTimespan.count; const delay = opts.perTimespan.delay; if (count <= 0) { throw new f_limit_exception_js_1.FLimitException("perTimespan count value should be above zero integer"); } if (delay <= 0) { throw new f_limit_exception_js_1.FLimitException("perTimespan delay value should be above zero integer"); } innerLimits.push(new f_limit_internal_time_span_limit_js_1.FLimitInternalTimeSpanLimit(delay, count)); } if (opts.parallel) { let count = opts.parallel; if (count <= 0) { throw new f_limit_exception_js_1.FLimitException("parallel count value should be above zero integer"); } innerLimits.push(new f_limit_internal_parallel_limit_js_1.FLimitInternalParallelLimit(count)); } return innerLimits; } function isCancellationToken(ct) { if (ct instanceof f_cancellation_token_js_1.FCancellationToken) { return true; } return typeof ct === "object" && typeof ct.addCancelListener === "function" && typeof ct.removeCancelListener === "function"; } function limitFactory(opts) { const innerLimits = buildInnerLimits(opts); const busyLimits = []; const waitForTokenCallbacks = []; let disposing = false; function onBusyLimitsReleased(weight) { while (waitForTokenCallbacks.length > 0) { const token = _accrueAggregatedToken(weight); if (token === null) { break; } const tulpe = waitForTokenCallbacks.shift(); if (!tulpe) { throw new f_intrenal_limit_exception_assert_js_1.FIntrenalLimitExceptionAssert(); } const [cb, timerOrRemoveListener] = tulpe; if (typeof timerOrRemoveListener === "function") { timerOrRemoveListener(); } else { clearTimeout(timerOrRemoveListener); } cb(undefined, token); } } function _accrueAggregatedToken(weight) { if (busyLimits.length > 0) { return null; } if (disposing) { return null; } const innerTokens = []; for (let innerLimitIndex = 0; innerLimitIndex < innerLimits.length; innerLimitIndex++) { const innerLimit = innerLimits[innerLimitIndex]; if (innerLimit.availableWeight < weight) { busyLimits.push(innerLimit); } else { innerTokens.push(innerLimit.accrueToken(weight)); } } if (innerLimits.length === innerTokens.length) { return { commit: () => { innerTokens.forEach(it => it.commit()); }, rollback: () => { innerTokens.forEach(it => it.rollback()); } }; } else { innerTokens.forEach(it => it.rollback()); busyLimits.forEach(bl => { function onReleaseBusyLimit() { bl.removeReleaseTokenListener(onReleaseBusyLimit); const blIndex = busyLimits.indexOf(bl); busyLimits.splice(blIndex, 1); if (busyLimits.length === 0) { onBusyLimitsReleased(weight); } } bl.addReleaseTokenListener(onReleaseBusyLimit); }); return null; } } function accrueTokenImmediately(weight) { if (disposing) { throw new Error("Wrong operation on disposed object"); } const aggregatedToken = _accrueAggregatedToken(weight !== undefined ? weight : 1); if (aggregatedToken != null) { return aggregatedToken; } throw new f_limit_exception_js_1.FLimitException("No available tokens"); } async function accrueTokenLazyWithCancellationTokenPromise(weight, ct) { return new Promise((resolve, reject) => { accrueTokenLazyWithCancellationTokenCallback(weight, ct, (err, token) => { if (err) { reject(err); } else { resolve(token); } }); }); } async function accrueTokenLazyWithTimeoutPromise(weight, timeout) { return new Promise((resolve, reject) => { accrueTokenLazyWithTimeoutCallback(weight, timeout, (err, token) => { if (err) { reject(err); } else { resolve(token); } }); }); } async function accrueTokenLazyPromise(weight, timeout, ct) { return new Promise((resolve, reject) => { accrueTokenLazyCallback(weight, timeout, ct, (err, token) => { if (err) { reject(err); } else { resolve(token); } }); }); } // tslint:disable-next-line: max-line-length function accrueTokenLazyWithCancellationTokenCallback(weight, ct, cb) { const token = _accrueAggregatedToken(weight); if (token !== null) { cb(undefined, token); return; } let tuple; const cancelCallback = () => { const tupleIndex = waitForTokenCallbacks.indexOf(tuple); if (tupleIndex < 0) { throw new f_intrenal_limit_exception_assert_js_1.FIntrenalLimitExceptionAssert(); } waitForTokenCallbacks.splice(tupleIndex, 1); tuple[1](); try { ct.throwIfCancellationRequested(); // Token should raise error // Guard from invalid token implementation. Fallback to FLimitException. cb(new f_limit_exception_js_1.FLimitException(`Timeout: Token was not accrued due cancel request`)); } catch (e) { cb(e); } }; ct.addCancelListener(cancelCallback); const removeListener = () => ct.removeCancelListener(cancelCallback); tuple = [cb, removeListener]; waitForTokenCallbacks.push(tuple); } function accrueTokenLazyWithTimeoutCallback(weight, timeout, cb) { const token = _accrueAggregatedToken(weight); if (token !== null) { cb(undefined, token); return; } // Timeout let tuple; const timer = setTimeout(() => { const tupleIndex = waitForTokenCallbacks.indexOf(tuple); if (tupleIndex < 0) { throw new f_intrenal_limit_exception_assert_js_1.FIntrenalLimitExceptionAssert(); } waitForTokenCallbacks.splice(tupleIndex, 1); cb(new f_limit_exception_js_1.FLimitException(`Timeout: Token was not accrued in ${timeout} ms`)); }, timeout); const removeTimer = () => clearTimeout(timer); tuple = [cb, removeTimer]; waitForTokenCallbacks.push(tuple); } function accrueTokenLazyCallback(weight, timeout, ct, cb) { const token = _accrueAggregatedToken(weight); if (token !== null) { cb(undefined, token); return; } // Timeout let tuple; const timer = setTimeout(() => { const tupleIndex = waitForTokenCallbacks.indexOf(tuple); if (tupleIndex < 0) { throw new f_intrenal_limit_exception_assert_js_1.FIntrenalLimitExceptionAssert(); } waitForTokenCallbacks.splice(tupleIndex, 1); cb(new f_limit_exception_js_1.FLimitException(`Timeout: Token was not accrued in ${timeout} ms`)); }, timeout); // Callback const cancelCallback = () => { const tupleIndex = waitForTokenCallbacks.indexOf(tuple); if (tupleIndex < 0) { throw new f_intrenal_limit_exception_assert_js_1.FIntrenalLimitExceptionAssert(); } waitForTokenCallbacks.splice(tupleIndex, 1); tuple[1](); try { ct.throwIfCancellationRequested(); // Token should raise error // Guard from invalid token implementation. Fallback to FLimitException. cb(new f_limit_exception_js_1.FLimitException(`Timeout: Token was not accrued due cancel request`)); } catch (e) { cb(e); } }; ct.addCancelListener(cancelCallback); const removeListenerAndTimer = () => { clearTimeout(timer); ct.removeCancelListener(cancelCallback); }; tuple = [cb, removeListenerAndTimer]; waitForTokenCallbacks.push(tuple); } function accrueTokenLazyOverrides(...args) { if (disposing) { throw new index_js_1.FExceptionInvalidOperation("Wrong operation on disposed object"); } if (args.length === 1) { const arg0 = args[0]; if (typeof arg0 === "number") { const timeout = arg0; // CASE 1: accrueTokenLazy(timeout: number): Promise<FLimit.Token> return accrueTokenLazyWithTimeoutPromise(1 /* weight */, timeout); } if (isCancellationToken(arg0)) { // CASE 2: accrueTokenLazy(cancellationToken: CancellationToken): Promise<FLimit.Token> const cancellationToken = arg0; return accrueTokenLazyWithCancellationTokenPromise(1 /* weight */, cancellationToken); } } else if (args.length === 2) { const [arg0, arg1] = args; if (typeof arg0 === "number") { const possibleWeightOrTimeout = arg0; if (typeof arg1 === "function") { // CASE 3: accrueTokenLazy(timeout: number, cb: TokenLazyCallback): void const timeout = possibleWeightOrTimeout; const callback = arg1; return accrueTokenLazyWithTimeoutCallback(1 /* weight */, timeout, callback); } if (typeof arg1 === "number") { // CASE 6: accrueTokenLazy(tokenWeight: FLimit.Weight, timeout: number): Promise<FLimit.Token> const tokenWeight = possibleWeightOrTimeout; const timeout = arg1; return accrueTokenLazyWithTimeoutPromise(tokenWeight, timeout); } if (isCancellationToken(arg1)) { // CASE 5: accrueTokenLazy(timeout: number, cancellationToken: CancellationToken): Promise<FLimit.Token> const timeout = possibleWeightOrTimeout; const cancellationToken = arg1; return accrueTokenLazyPromise(1 /* weight */, timeout, cancellationToken); } } else if (isCancellationToken(arg0)) { if (typeof arg1 === "function") { const cancellationToken = arg0; const callback = arg1; // CASE 4: accrueTokenLazy(cancellationToken: CancellationToken, cb: TokenLazyCallback): void return accrueTokenLazyWithCancellationTokenCallback(1 /* weight */, cancellationToken, callback); } } } else if (args.length === 3) { const [arg0, arg1, arg2] = args; if (typeof arg0 === "number") { if (isCancellationToken(arg1) && typeof arg2 === "function") { // CASE 7: accrueTokenLazy(timeout: number, cancellationToken: CancellationToken, cb: TokenLazyCallback): void const timeout = arg0; const cancellationToken = arg1; const callback = arg2; return accrueTokenLazyCallback(1 /* weight */, timeout, cancellationToken, callback); } if (typeof arg1 === "number") { if (typeof arg2 === "function") { // CASE 8: accrueTokenLazy(tokenWeight: FLimit.Weight, timeout: number, cb: TokenLazyCallback): void const tokenWeight = arg0; const timeout = arg1; const callback = arg2; return accrueTokenLazyWithTimeoutCallback(tokenWeight, timeout, callback); } if (isCancellationToken(arg2)) { // CASE 9: accrueTokenLazy(tokenWeight: FLimit.Weight, timeout: number, cancellationToken: CancellationToken): Promise<FLimit.Token> const tokenWeight = arg0; const timeout = arg1; const cancellationToken = arg2; return accrueTokenLazyPromise(tokenWeight, timeout, cancellationToken); } } } } else if (args.length === 4) { const [arg0, arg1, arg2, arg3] = args; if (typeof arg0 === "number" && typeof arg1 === "number" && isCancellationToken(arg2) && typeof arg3 === "function") { // tslint:disable-next-line:max-line-length // CASE 10: accrueTokenLazy(tokenWeight: FLimit.Weight, timeout: number, cancellationToken: CancellationToken, cb: TokenLazyCallback): void const tokenWeight = arg0; const timeout = arg1; const cancellationToken = arg2; const callback = arg3; return accrueTokenLazyCallback(tokenWeight, timeout, cancellationToken, callback); } } throw new index_js_1.FExceptionArgument("Wrong arguments"); } function dispose() { disposing = true; waitForTokenCallbacks.slice().forEach(waitForTokenCallback => { const tupleIndex = waitForTokenCallbacks.indexOf(waitForTokenCallback); if (tupleIndex !== -1) { waitForTokenCallbacks.splice(tupleIndex, 1); } const [cb, timerOrRemoveListener] = waitForTokenCallback; if (typeof timerOrRemoveListener === "function") { timerOrRemoveListener(); } else { clearTimeout(timerOrRemoveListener); } cb(new f_limit_exception_js_1.FLimitException(`Timeout: Token was not accrued due disposing`)); }); return Promise.resolve().then(async () => { await Promise.all(innerLimits.map(il => il.dispose())); }); } return { get maxWeight() { return Math.min(...innerLimits.map(f => f.maxWeight)); }, get availableWeight() { return Math.min(...innerLimits.map(f => f.availableWeight)); }, accrueTokenImmediately, accrueTokenLazy: accrueTokenLazyOverrides, dispose, [Symbol.asyncDispose]() { return dispose(); }, }; } class FLimitInMemory extends index_js_2.FDisposableBase { _wrap; constructor(opts) { super(); this._wrap = limitFactory(opts); } get availableWeight() { return this._wrap.availableWeight; } get maxWeight() { return this._wrap.maxWeight; } accrueTokenImmediately(tokenWeight) { return this._wrap.accrueTokenImmediately(tokenWeight); } accrueTokenLazy(...args) { if (args.length > 3) { return this._wrap.accrueTokenLazy(args[0], args[1], args[2], args[3]); } else if (args.length > 2) { return this._wrap.accrueTokenLazy(args[0], args[1], args[2]); } else if (args.length > 1) { return this._wrap.accrueTokenLazy(args[0], args[1]); } else { return this._wrap.accrueTokenLazy(args[0]); } } onDispose() { return this._wrap.dispose(); } } exports.FLimitInMemory = FLimitInMemory; //# sourceMappingURL=f_limit_in_memory.js.map