@chriscdn/promise-semaphore 3.1.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/promise-semaphore.cjs +1 -1
- package/lib/promise-semaphore.cjs.map +1 -1
- package/lib/promise-semaphore.modern.js +1 -1
- package/lib/promise-semaphore.modern.js.map +1 -1
- package/lib/promise-semaphore.module.js +1 -1
- package/lib/promise-semaphore.module.js.map +1 -1
- package/package.json +6 -4
- package/__tests__/semaphore.test.ts +0 -174
- package/src/group-semaphore.ts +0 -46
- package/src/index.ts +0 -2
- package/src/semaphore.ts +0 -200
- package/tsconfig.json +0 -5
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}();exports.GroupSemaphore=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}(),exports.Semaphore=o;
|
|
1
|
+
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){if(void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}();exports.GroupSemaphore=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}(),exports.Semaphore=o;
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.cjs","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","count","this","_proto","prototype","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.cjs","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","_createClass","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","Error","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e="_default",t=e=>["string","number"].includes(typeof e),s=s=>{var
|
|
1
|
+
const e="_default",t=e=>["string","number"].includes(typeof e),s=s=>{var r;return null!=(r=t(s)?s:s.key)?r:e};class r{constructor(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}get canAcquire(){return this.count<this.maxConcurrent}incrementCount(){this.count++}decrementCount(){this.count--}acquire(e){return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(t=>{this.queue.push({resolve:t,priority:e}),this.queue.sort((e,t)=>t.priority-e.priority)})}release(){const e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()}}class n{constructor(e=1){if(this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}hasSemaphoreInstance(t=e){return Boolean(this.semaphoreInstances[t])}getSemaphoreInstance(t=e){return this.hasSemaphoreInstance(t)||(this.semaphoreInstances[t]=new r(this.maxConcurrent)),this.semaphoreInstances[t]}tidy(t=e){this.hasSemaphoreInstance(t)&&0===this.getSemaphoreInstance(t).count&&delete this.semaphoreInstances[t]}canAcquire(t=e){const r=s(t);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire}acquire(r=e){const n=s(r),i=null!=(o=t(a=r)?0:a.priority)?o:0;var a,o;return this.getSemaphoreInstance(n).acquire(i)}release(t=e){const r=s(t);this.getSemaphoreInstance(r).release(),this.tidy(r)}count(t=e){const r=s(t);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0}hasTasks(t=e){return this.count(t)>0}async request(t,s=e){try{return await this.acquire(s),await t()}finally{this.release(s)}}async requestIfAvailable(t,s=e){return this.canAcquire(s)?this.request(t,s):null}}class i{constructor(){this._semaphore=new n,this._activeCounts={},this._groupWaiters={}}async acquire(e){var t,s;const r=null!=(t=this._activeCounts[e])?t:0;this._activeCounts[e]=r+1;const n=null!=(s=this._groupWaiters[e])?s:this._semaphore.acquire();this._groupWaiters[e]=n,await n}release(e){const t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1}}export{i as GroupSemaphore,n as Semaphore};
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.modern.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.modern.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","constructor","maxConcurrent","this","queue","count","canAcquire","incrementCount","decrementCount","acquire","priority","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","Semaphore","semaphoreInstances","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_priority","_ref2","hasTasks","request","fn","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.modern.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","constructor","maxConcurrent","queue","this","count","canAcquire","incrementCount","decrementCount","acquire","priority","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","Semaphore","semaphoreInstances","Error","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_priority","_ref2","hasTasks","request","fn","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}(),s=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}();export{s as GroupSemaphore,o as Semaphore};
|
|
1
|
+
function e(e){var t=function(e){if("object"!=typeof e||!e)return e;var t=e[Symbol.toPrimitive];if(void 0!==t){var r=t.call(e,"string");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(e)}(e);return"symbol"==typeof t?t:t+""}var t="_default",r=function(e){return["string","number"].includes(typeof e)},n=function(e){var n;return null!=(n=r(e)?e:e.key)?n:t},i=/*#__PURE__*/function(){function t(e){this.queue=void 0,this.maxConcurrent=void 0,this.count=void 0,this.queue=[],this.maxConcurrent=e,this.count=0}var r,n,i=t.prototype;return i.incrementCount=function(){this.count++},i.decrementCount=function(){this.count--},i.acquire=function(e){var t=this;return this.canAcquire?(this.incrementCount(),Promise.resolve()):new Promise(function(r){t.queue.push({resolve:r,priority:e}),t.queue.sort(function(e,t){return t.priority-e.priority})})},i.release=function(){var e=this.queue.shift();e?setTimeout(e.resolve,0):this.decrementCount()},r=t,(n=[{key:"canAcquire",get:function(){return this.count<this.maxConcurrent}}])&&function(t,r){for(var n=0;n<r.length;n++){var i=r[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,e(i.key),i)}}(r.prototype,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(),o=/*#__PURE__*/function(){function e(e){if(void 0===e&&(e=1),this.semaphoreInstances=void 0,this.maxConcurrent=void 0,this.semaphoreInstances={},this.maxConcurrent=e,e<1)throw new Error("The maxConcurrent must be 1 or greater.")}var o=e.prototype;return o.hasSemaphoreInstance=function(e){return void 0===e&&(e=t),Boolean(this.semaphoreInstances[e])},o.getSemaphoreInstance=function(e){return void 0===e&&(e=t),this.hasSemaphoreInstance(e)||(this.semaphoreInstances[e]=new i(this.maxConcurrent)),this.semaphoreInstances[e]},o.tidy=function(e){void 0===e&&(e=t),this.hasSemaphoreInstance(e)&&0===this.getSemaphoreInstance(e).count&&delete this.semaphoreInstances[e]},o.canAcquire=function(e){void 0===e&&(e=t);var r=n(e);return!this.hasSemaphoreInstance(r)||this.getSemaphoreInstance(r).canAcquire},o.acquire=function(e){void 0===e&&(e=t);var i,o,s=n(e),u=null!=(o=r(i=e)?0:i.priority)?o:0;return this.getSemaphoreInstance(s).acquire(u)},o.release=function(e){void 0===e&&(e=t);var r=n(e);this.getSemaphoreInstance(r).release(),this.tidy(r)},o.count=function(e){void 0===e&&(e=t);var r=n(e);return this.hasSemaphoreInstance(r)?this.getSemaphoreInstance(r).count:0},o.hasTasks=function(e){return void 0===e&&(e=t),this.count(e)>0},o.request=function(e,r){void 0===r&&(r=t);try{var n=this;return Promise.resolve(function(t,i){try{var o=Promise.resolve(n.acquire(r)).then(function(){return Promise.resolve(e())})}catch(e){return i(!0,e)}return o&&o.then?o.then(i.bind(null,!1),i.bind(null,!0)):i(!1,o)}(0,function(e,t){if(n.release(r),e)throw t;return t}))}catch(e){return Promise.reject(e)}},o.requestIfAvailable=function(e,r){void 0===r&&(r=t);try{return this.canAcquire(r)?Promise.resolve(this.request(e,r)):Promise.resolve(null)}catch(e){return Promise.reject(e)}},e}(),s=/*#__PURE__*/function(){function e(){this._semaphore=new o,this._activeCounts={},this._groupWaiters={}}var t=e.prototype;return t.acquire=function(e){try{var t,r,n=this,i=null!=(t=n._activeCounts[e])?t:0;n._activeCounts[e]=i+1;var o=null!=(r=n._groupWaiters[e])?r:n._semaphore.acquire();return n._groupWaiters[e]=o,Promise.resolve(o).then(function(){})}catch(e){return Promise.reject(e)}},t.release=function(e){var t=this._activeCounts[e];1===t?(this._semaphore.release(),delete this._activeCounts[e],delete this._groupWaiters[e]):this._activeCounts[e]=t-1},e}();export{s as GroupSemaphore,o as Semaphore};
|
|
2
2
|
//# sourceMappingURL=promise-semaphore.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-semaphore.module.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","count","this","_proto","prototype","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promise-semaphore.module.js","sources":["../src/semaphore.ts","../src/group-semaphore.ts"],"sourcesContent":["const defaultKey = \"_default\";\n\ntype KeyPrimitive = string | number;\n\ntype Key = KeyPrimitive | { key?: KeyPrimitive };\ntype KeyOptions = Key & { priority?: number };\n\nconst _isPrimitiveKey = (item: Key): item is KeyPrimitive =>\n [\"string\", \"number\"].includes(typeof item);\n\nconst resolveKey = (item: Key): KeyPrimitive =>\n (_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;\n\nconst resolvePriority = (item: KeyOptions) =>\n (_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;\n\nclass SemaphoreItem {\n private queue: Array<{\n resolve: Function;\n priority: number;\n }>;\n private maxConcurrent: number;\n\n /**\n * The number of locks.\n */\n public count: number;\n\n constructor(maxConcurrent: number) {\n this.queue = [];\n this.maxConcurrent = maxConcurrent;\n this.count = 0;\n }\n\n get canAcquire(): boolean {\n return this.count < this.maxConcurrent;\n }\n\n private incrementCount() {\n this.count++;\n }\n\n private decrementCount() {\n this.count--;\n }\n\n acquire(priority: number): Promise<void> {\n if (this.canAcquire) {\n this.incrementCount();\n return Promise.resolve();\n } else {\n return new Promise((resolve) => {\n this.queue.push({ resolve, priority });\n this.queue.sort((a, b) => b.priority - a.priority);\n });\n }\n }\n\n release(): void {\n const resolveFunc = this.queue.shift();\n\n if (resolveFunc) {\n // Give the micro task queue a small break instead of calling resolveFunc() directly\n setTimeout(resolveFunc.resolve, 0);\n } else {\n this.decrementCount();\n }\n }\n}\n\nclass Semaphore {\n private semaphoreInstances: Record<string | number, SemaphoreItem>;\n private maxConcurrent: number;\n\n /**\n * @param {number} [maxConcurrent] The maximum number of concurrent locks.\n */\n constructor(maxConcurrent: number = 1) {\n this.semaphoreInstances = {};\n this.maxConcurrent = maxConcurrent;\n\n if (maxConcurrent < 1) {\n throw new Error(\"The maxConcurrent must be 1 or greater.\");\n }\n }\n\n private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n return Boolean(this.semaphoreInstances[key]);\n }\n\n private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {\n if (!this.hasSemaphoreInstance(key)) {\n this.semaphoreInstances[key] = new SemaphoreItem(\n this.maxConcurrent,\n );\n }\n return this.semaphoreInstances[key];\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n private tidy(key: KeyPrimitive = defaultKey): void {\n if (\n this.hasSemaphoreInstance(key) &&\n this.getSemaphoreInstance(key).count === 0\n ) {\n delete this.semaphoreInstances[key];\n }\n }\n\n /**\n * A synchronous function to determine whether a lock can be acquired.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} Returns true if the lock on `key` can be acquired, false\n * otherwise.\n */\n canAcquire(key: Key = defaultKey): boolean {\n const _key = resolveKey(key);\n\n return !this.hasSemaphoreInstance(_key) ||\n this.getSemaphoreInstance(_key).canAcquire;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n acquire(key: KeyOptions = defaultKey) {\n const _key = resolveKey(key);\n const _priority = resolvePriority(key);\n\n return this.getSemaphoreInstance(_key).acquire(_priority);\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n release(key: Key = defaultKey): void {\n const _key = resolveKey(key);\n\n this.getSemaphoreInstance(_key).release();\n this.tidy(_key);\n }\n\n /**\n * The number of active locks. Will always be less or equal to `max`.\n *\n * @param {string | number} [key]- Optional, the semaphore key.\n */\n count(key: Key = defaultKey): number {\n const _key = resolveKey(key);\n\n return (this.hasSemaphoreInstance(_key))\n ? this.getSemaphoreInstance(_key).count\n : 0;\n }\n\n /**\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {boolean} True if the semaphore and key has locks, false otherwise.\n */\n hasTasks(key: Key = defaultKey): boolean {\n return this.count(key) > 0;\n }\n\n /**\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async request<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T> {\n try {\n await this.acquire(key);\n return await fn();\n } finally {\n this.release(key);\n }\n }\n\n /**\n * Asynchronously executes `fn` if a lock can be immediately acquired.\n * Otherwise, returns null.\n *\n * @param {Function<T>} fn The function to execute.\n * @param {string | number} [key]- Optional, the semaphore key.\n * @returns {Promise<T>}\n */\n async requestIfAvailable<T>(\n fn: Function,\n key: KeyOptions = defaultKey,\n ): Promise<T | null> {\n if (this.canAcquire(key)) {\n return this.request(fn, key);\n } else {\n return null;\n }\n }\n}\n\nexport { Semaphore };\n","import { Semaphore } from \"./semaphore\";\n\n/**\n * GroupSemaphore manages a shared semaphore for different groups of tasks. Each\n * group is identified by a unique key, and the semaphore ensures only one group\n * can run its tasks concurrently.\n *\n * - acquire(key): Increments the active count for the given group. If it's the\n * first task for the group (active count is 0), it acquires the global\n * semaphore, ensuring only one group's tasks can proceed at a time.\n * Subsequent calls in the group increment the count and are permitted to run.\n * - release(key): Decrements the active count for the group. If the last task\n * for that group is released, it releases the global semaphore, allowing\n * other groups to proceed.\n *\n * This ensures that only one group can execute concurrently, but multiple tasks\n * within the same group can run as long as no other tasks from different groups\n * are active.\n */\nclass GroupSemaphore {\n private _semaphore = new Semaphore();\n private _activeCounts: Record<string, number> = {};\n private _groupWaiters: Record<string, Promise<void>> = {};\n\n async acquire(key: string) {\n const activeCount = this._activeCounts[key] ?? 0;\n this._activeCounts[key] = activeCount + 1;\n const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();\n this._groupWaiters[key] = waiter;\n await waiter;\n }\n\n release(key: string) {\n const activeCount = this._activeCounts[key];\n\n if (activeCount === 1) {\n this._semaphore.release();\n delete this._activeCounts[key];\n delete this._groupWaiters[key];\n } else {\n this._activeCounts[key] = activeCount - 1;\n }\n }\n}\n\nexport { GroupSemaphore };\n"],"names":["defaultKey","_isPrimitiveKey","item","includes","resolveKey","_ref","key","SemaphoreItem","maxConcurrent","queue","this","count","_proto","prototype","_createClass","incrementCount","decrementCount","acquire","priority","_this","canAcquire","Promise","resolve","push","sort","a","b","release","resolveFunc","shift","setTimeout","get","Semaphore","semaphoreInstances","Error","_proto2","hasSemaphoreInstance","Boolean","getSemaphoreInstance","tidy","_key","_ref2","_priority","hasTasks","request","fn","_this2","then","_finallyRethrows","_wasThrown","_result","e","reject","requestIfAvailable","GroupSemaphore","_semaphore","_activeCounts","_groupWaiters","_this$_activeCounts$k","_this$_groupWaiters$k","activeCount","waiter"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chriscdn/promise-semaphore",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Limit or throttle the simultaneous execution of asynchronous code in separate iterations of the event loop.",
|
|
5
5
|
"repository": "https://github.com/chriscdn/promise-semaphore",
|
|
6
6
|
"author": "Christopher Meyer <chris@schwiiz.org>",
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"microbundle": "^0.15.1",
|
|
26
|
-
"vitest": "^
|
|
26
|
+
"vitest": "^4.0.5"
|
|
27
27
|
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib"
|
|
30
|
+
],
|
|
28
31
|
"keywords": [
|
|
29
32
|
"promise",
|
|
30
33
|
"semaphore",
|
|
@@ -32,6 +35,5 @@
|
|
|
32
35
|
"mutex",
|
|
33
36
|
"async",
|
|
34
37
|
"throttle"
|
|
35
|
-
]
|
|
36
|
-
"dependencies": {}
|
|
38
|
+
]
|
|
37
39
|
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { Semaphore } from "../src/index";
|
|
4
|
-
|
|
5
|
-
const pause = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
-
|
|
7
|
-
describe("All test", () => {
|
|
8
|
-
it("Acquire & Release Basic", async () => {
|
|
9
|
-
const semaphore = new Semaphore();
|
|
10
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
11
|
-
await semaphore.acquire();
|
|
12
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
13
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
14
|
-
expect(semaphore.count()).toBe(1);
|
|
15
|
-
semaphore.release();
|
|
16
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
17
|
-
expect(semaphore.count()).toBe(0);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("Semaphore 1", async () => {
|
|
21
|
-
const semaphore = new Semaphore();
|
|
22
|
-
|
|
23
|
-
semaphore
|
|
24
|
-
.acquire()
|
|
25
|
-
.then(async () => await pause(1000))
|
|
26
|
-
.finally(() => semaphore.release());
|
|
27
|
-
|
|
28
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
29
|
-
|
|
30
|
-
await pause(600);
|
|
31
|
-
|
|
32
|
-
expect(semaphore.canAcquire()).toBe(false);
|
|
33
|
-
|
|
34
|
-
await pause(600);
|
|
35
|
-
|
|
36
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("Semaphore 2", async () => {
|
|
40
|
-
const semaphore = new Semaphore();
|
|
41
|
-
|
|
42
|
-
let tester = 0;
|
|
43
|
-
|
|
44
|
-
semaphore
|
|
45
|
-
.acquire()
|
|
46
|
-
.then(() => pause(1000))
|
|
47
|
-
.then(() => (tester = 10))
|
|
48
|
-
.finally(() => semaphore.release());
|
|
49
|
-
|
|
50
|
-
// tests acquire waits for previous to complete
|
|
51
|
-
await semaphore
|
|
52
|
-
.acquire()
|
|
53
|
-
.then(() => expect(tester).toBe(10))
|
|
54
|
-
.finally(() => semaphore.release());
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("Semaphore 3", async () => {
|
|
58
|
-
const semaphore = new Semaphore(2);
|
|
59
|
-
|
|
60
|
-
let tester = 0;
|
|
61
|
-
|
|
62
|
-
semaphore
|
|
63
|
-
.acquire()
|
|
64
|
-
.then(() => pause(1000))
|
|
65
|
-
.then(() => (tester = 20))
|
|
66
|
-
.finally(() => semaphore.release());
|
|
67
|
-
|
|
68
|
-
semaphore
|
|
69
|
-
.acquire()
|
|
70
|
-
.then(() => pause(500))
|
|
71
|
-
.then(() => (tester = 10))
|
|
72
|
-
.finally(() => semaphore.release());
|
|
73
|
-
|
|
74
|
-
expect(semaphore.count()).toBe(2);
|
|
75
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
76
|
-
|
|
77
|
-
// expect 10 since this next block will run before the first has completed
|
|
78
|
-
await semaphore
|
|
79
|
-
.acquire()
|
|
80
|
-
.then(() => expect(tester).toBe(10))
|
|
81
|
-
.finally(() => semaphore.release());
|
|
82
|
-
|
|
83
|
-
expect(semaphore.count()).toBe(1);
|
|
84
|
-
expect(semaphore.hasTasks()).toBe(true);
|
|
85
|
-
expect(semaphore.canAcquire()).toBe(true);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("Request 1", async () => {
|
|
89
|
-
const semaphore = new Semaphore();
|
|
90
|
-
|
|
91
|
-
let tester = 0;
|
|
92
|
-
|
|
93
|
-
semaphore.request(async () => {
|
|
94
|
-
await pause(1000);
|
|
95
|
-
tester = 20;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
semaphore.request(async () => {
|
|
99
|
-
await pause(500);
|
|
100
|
-
tester = 10;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
await semaphore.request(async () => {
|
|
104
|
-
expect(tester).toBe(10);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("Request 2", async () => {
|
|
109
|
-
const semaphore = new Semaphore(2);
|
|
110
|
-
|
|
111
|
-
let tester = 0;
|
|
112
|
-
|
|
113
|
-
semaphore.request(async () => {
|
|
114
|
-
await pause(1000);
|
|
115
|
-
tester = 20;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
semaphore.request(async () => {
|
|
119
|
-
await pause(500);
|
|
120
|
-
tester = 10;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
await semaphore.request(async () => {
|
|
124
|
-
expect(tester).toBe(10);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("Request 3", async () => {
|
|
129
|
-
const semaphore = new Semaphore(3);
|
|
130
|
-
|
|
131
|
-
let tester = 0;
|
|
132
|
-
|
|
133
|
-
semaphore.request(async () => {
|
|
134
|
-
await pause(1000);
|
|
135
|
-
tester = 20;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
semaphore.request(async () => {
|
|
139
|
-
await pause(500);
|
|
140
|
-
tester = 10;
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
await semaphore.request(async () => {
|
|
144
|
-
expect(tester).toBe(0);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("Priority", () => {
|
|
150
|
-
it("Priority", async () => {
|
|
151
|
-
const semaphore = new Semaphore(1);
|
|
152
|
-
let tester = 0;
|
|
153
|
-
let key = "yyz";
|
|
154
|
-
|
|
155
|
-
// this one is executed immediately since it's the first object
|
|
156
|
-
const p1 = semaphore.request(async () => {
|
|
157
|
-
tester = 5;
|
|
158
|
-
}, { key, priority: 0 });
|
|
159
|
-
|
|
160
|
-
// this one gets queued with priority 20
|
|
161
|
-
const p2 = semaphore.request(async () => {
|
|
162
|
-
tester = 10;
|
|
163
|
-
}, { key, priority: 20 });
|
|
164
|
-
|
|
165
|
-
// this one gets queued with priority 30, which should execute before p2
|
|
166
|
-
const p3 = semaphore.request(async () => {
|
|
167
|
-
expect(tester).toBe(5);
|
|
168
|
-
}, { key, priority: 30 });
|
|
169
|
-
|
|
170
|
-
semaphore.acquire({ priority: 5 });
|
|
171
|
-
|
|
172
|
-
await Promise.all([p1, p2, p3]);
|
|
173
|
-
});
|
|
174
|
-
});
|
package/src/group-semaphore.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Semaphore } from "./semaphore";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GroupSemaphore manages a shared semaphore for different groups of tasks. Each
|
|
5
|
-
* group is identified by a unique key, and the semaphore ensures only one group
|
|
6
|
-
* can run its tasks concurrently.
|
|
7
|
-
*
|
|
8
|
-
* - acquire(key): Increments the active count for the given group. If it's the
|
|
9
|
-
* first task for the group (active count is 0), it acquires the global
|
|
10
|
-
* semaphore, ensuring only one group's tasks can proceed at a time.
|
|
11
|
-
* Subsequent calls in the group increment the count and are permitted to run.
|
|
12
|
-
* - release(key): Decrements the active count for the group. If the last task
|
|
13
|
-
* for that group is released, it releases the global semaphore, allowing
|
|
14
|
-
* other groups to proceed.
|
|
15
|
-
*
|
|
16
|
-
* This ensures that only one group can execute concurrently, but multiple tasks
|
|
17
|
-
* within the same group can run as long as no other tasks from different groups
|
|
18
|
-
* are active.
|
|
19
|
-
*/
|
|
20
|
-
class GroupSemaphore {
|
|
21
|
-
private _semaphore = new Semaphore();
|
|
22
|
-
private _activeCounts: Record<string, number> = {};
|
|
23
|
-
private _groupWaiters: Record<string, Promise<void>> = {};
|
|
24
|
-
|
|
25
|
-
async acquire(key: string) {
|
|
26
|
-
const activeCount = this._activeCounts[key] ?? 0;
|
|
27
|
-
this._activeCounts[key] = activeCount + 1;
|
|
28
|
-
const waiter = this._groupWaiters[key] ?? this._semaphore.acquire();
|
|
29
|
-
this._groupWaiters[key] = waiter;
|
|
30
|
-
await waiter;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
release(key: string) {
|
|
34
|
-
const activeCount = this._activeCounts[key];
|
|
35
|
-
|
|
36
|
-
if (activeCount === 1) {
|
|
37
|
-
this._semaphore.release();
|
|
38
|
-
delete this._activeCounts[key];
|
|
39
|
-
delete this._groupWaiters[key];
|
|
40
|
-
} else {
|
|
41
|
-
this._activeCounts[key] = activeCount - 1;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { GroupSemaphore };
|
package/src/index.ts
DELETED
package/src/semaphore.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
const defaultKey = "_default";
|
|
2
|
-
|
|
3
|
-
type KeyPrimitive = string | number;
|
|
4
|
-
|
|
5
|
-
type Key = KeyPrimitive | { key?: KeyPrimitive };
|
|
6
|
-
type KeyOptions = Key & { priority?: number };
|
|
7
|
-
|
|
8
|
-
const _isPrimitiveKey = (item: Key): item is KeyPrimitive =>
|
|
9
|
-
["string", "number"].includes(typeof item);
|
|
10
|
-
|
|
11
|
-
const resolveKey = (item: Key): KeyPrimitive =>
|
|
12
|
-
(_isPrimitiveKey(item) ? item : item.key) ?? defaultKey;
|
|
13
|
-
|
|
14
|
-
const resolvePriority = (item: KeyOptions) =>
|
|
15
|
-
(_isPrimitiveKey(item) ? 0 : item.priority) ?? 0;
|
|
16
|
-
|
|
17
|
-
class SemaphoreItem {
|
|
18
|
-
private queue: Array<{
|
|
19
|
-
resolve: Function;
|
|
20
|
-
priority: number;
|
|
21
|
-
}>;
|
|
22
|
-
private maxConcurrent: number;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The number of locks.
|
|
26
|
-
*/
|
|
27
|
-
public count: number;
|
|
28
|
-
|
|
29
|
-
constructor(maxConcurrent: number) {
|
|
30
|
-
this.queue = [];
|
|
31
|
-
this.maxConcurrent = maxConcurrent;
|
|
32
|
-
this.count = 0;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get canAcquire(): boolean {
|
|
36
|
-
return this.count < this.maxConcurrent;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private incrementCount() {
|
|
40
|
-
this.count++;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private decrementCount() {
|
|
44
|
-
this.count--;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
acquire(priority: number): Promise<void> {
|
|
48
|
-
if (this.canAcquire) {
|
|
49
|
-
this.incrementCount();
|
|
50
|
-
return Promise.resolve();
|
|
51
|
-
} else {
|
|
52
|
-
return new Promise((resolve) => {
|
|
53
|
-
this.queue.push({ resolve, priority });
|
|
54
|
-
this.queue.sort((a, b) => b.priority - a.priority);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
release(): void {
|
|
60
|
-
const resolveFunc = this.queue.shift();
|
|
61
|
-
|
|
62
|
-
if (resolveFunc) {
|
|
63
|
-
// Give the micro task queue a small break instead of calling resolveFunc() directly
|
|
64
|
-
setTimeout(resolveFunc.resolve, 0);
|
|
65
|
-
} else {
|
|
66
|
-
this.decrementCount();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class Semaphore {
|
|
72
|
-
private semaphoreInstances: Record<string | number, SemaphoreItem>;
|
|
73
|
-
private maxConcurrent: number;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {number} [maxConcurrent] The maximum number of concurrent locks.
|
|
77
|
-
*/
|
|
78
|
-
constructor(maxConcurrent: number = 1) {
|
|
79
|
-
this.semaphoreInstances = {};
|
|
80
|
-
this.maxConcurrent = maxConcurrent;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private hasSemaphoreInstance(key: KeyPrimitive = defaultKey) {
|
|
84
|
-
return Boolean(this.semaphoreInstances[key]);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private getSemaphoreInstance(key: KeyPrimitive = defaultKey) {
|
|
88
|
-
if (!this.hasSemaphoreInstance(key)) {
|
|
89
|
-
this.semaphoreInstances[key] = new SemaphoreItem(
|
|
90
|
-
this.maxConcurrent,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
return this.semaphoreInstances[key];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
98
|
-
*/
|
|
99
|
-
private tidy(key: KeyPrimitive = defaultKey): void {
|
|
100
|
-
if (
|
|
101
|
-
this.hasSemaphoreInstance(key) &&
|
|
102
|
-
this.getSemaphoreInstance(key).count === 0
|
|
103
|
-
) {
|
|
104
|
-
delete this.semaphoreInstances[key];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* A synchronous function to determine whether a lock can be acquired.
|
|
110
|
-
*
|
|
111
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
112
|
-
* @returns {boolean} Returns true if the lock on `key` can be acquired, false
|
|
113
|
-
* otherwise.
|
|
114
|
-
*/
|
|
115
|
-
canAcquire(key: Key = defaultKey): boolean {
|
|
116
|
-
const _key = resolveKey(key);
|
|
117
|
-
|
|
118
|
-
return !this.hasSemaphoreInstance(_key) ||
|
|
119
|
-
this.getSemaphoreInstance(_key).canAcquire;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
124
|
-
*/
|
|
125
|
-
acquire(key: KeyOptions = defaultKey) {
|
|
126
|
-
const _key = resolveKey(key);
|
|
127
|
-
const _priority = resolvePriority(key);
|
|
128
|
-
|
|
129
|
-
return this.getSemaphoreInstance(_key).acquire(_priority);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
134
|
-
*/
|
|
135
|
-
release(key: Key = defaultKey): void {
|
|
136
|
-
const _key = resolveKey(key);
|
|
137
|
-
|
|
138
|
-
this.getSemaphoreInstance(_key).release();
|
|
139
|
-
this.tidy(_key);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* The number of active locks. Will always be less or equal to `max`.
|
|
144
|
-
*
|
|
145
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
146
|
-
*/
|
|
147
|
-
count(key: Key = defaultKey): number {
|
|
148
|
-
const _key = resolveKey(key);
|
|
149
|
-
|
|
150
|
-
return (this.hasSemaphoreInstance(_key))
|
|
151
|
-
? this.getSemaphoreInstance(_key).count
|
|
152
|
-
: 0;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
157
|
-
* @returns {boolean} True if the semaphore and key has locks, false otherwise.
|
|
158
|
-
*/
|
|
159
|
-
hasTasks(key: Key = defaultKey): boolean {
|
|
160
|
-
return this.count(key) > 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* @param {Function<T>} fn The function to execute.
|
|
165
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
166
|
-
* @returns {Promise<T>}
|
|
167
|
-
*/
|
|
168
|
-
async request<T>(
|
|
169
|
-
fn: Function,
|
|
170
|
-
key: KeyOptions = defaultKey,
|
|
171
|
-
): Promise<T> {
|
|
172
|
-
try {
|
|
173
|
-
await this.acquire(key);
|
|
174
|
-
return await fn();
|
|
175
|
-
} finally {
|
|
176
|
-
this.release(key);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Asynchronously executes `fn` if a lock can be immediately acquired.
|
|
182
|
-
* Otherwise, returns null.
|
|
183
|
-
*
|
|
184
|
-
* @param {Function<T>} fn The function to execute.
|
|
185
|
-
* @param {string | number} [key]- Optional, the semaphore key.
|
|
186
|
-
* @returns {Promise<T>}
|
|
187
|
-
*/
|
|
188
|
-
async requestIfAvailable<T>(
|
|
189
|
-
fn: Function,
|
|
190
|
-
key: KeyOptions = defaultKey,
|
|
191
|
-
): Promise<T | null> {
|
|
192
|
-
if (this.canAcquire(key)) {
|
|
193
|
-
return this.request(fn, key);
|
|
194
|
-
} else {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export { Semaphore };
|