| import type { MorticeImplementation, MorticeOptions } from './index.js'; | ||
| declare const _default: (options: Required<MorticeOptions>) => MorticeImplementation | EventTarget; | ||
| export default _default; | ||
| //# sourceMappingURL=browser.d.ts.map |
| {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAW,MAAM,YAAY,CAAA;kCAiGvD,SAAS,cAAc,CAAC,KAAG,qBAAqB,GAAG,WAAW;AAAvF,wBAkBC"} |
| import { nanoid } from 'nanoid'; | ||
| import { WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK } from './constants.js'; | ||
| import observer from 'observable-webworkers'; | ||
| const handleWorkerLockRequest = (emitter, masterEvent, requestType, releaseType, grantType) => { | ||
| return (worker, event) => { | ||
| if (event.data.type !== requestType) { | ||
| return; | ||
| } | ||
| const requestEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| }; | ||
| emitter.dispatchEvent(new MessageEvent(masterEvent, { | ||
| data: { | ||
| name: requestEvent.name, | ||
| handler: async () => { | ||
| // grant lock to worker | ||
| worker.postMessage({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }); | ||
| // wait for worker to finish | ||
| return await new Promise((resolve) => { | ||
| const releaseEventListener = (event) => { | ||
| if (event == null || event.data == null) { | ||
| return; | ||
| } | ||
| const releaseEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| }; | ||
| if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeEventListener('message', releaseEventListener); | ||
| resolve(); | ||
| } | ||
| }; | ||
| worker.addEventListener('message', releaseEventListener); | ||
| }); | ||
| } | ||
| } | ||
| })); | ||
| }; | ||
| }; | ||
| const makeWorkerLockRequest = (name, requestType, grantType, releaseType) => { | ||
| return async () => { | ||
| const id = nanoid(); | ||
| globalThis.postMessage({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }); | ||
| return await new Promise((resolve) => { | ||
| const listener = (event) => { | ||
| if (event == null || event.data == null) { | ||
| return; | ||
| } | ||
| const responseEvent = { | ||
| type: event.data.type, | ||
| identifier: event.data.identifier | ||
| }; | ||
| if (responseEvent.type === grantType && responseEvent.identifier === id) { | ||
| globalThis.removeEventListener('message', listener); | ||
| // grant lock | ||
| resolve(() => { | ||
| // release lock | ||
| globalThis.postMessage({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }); | ||
| }); | ||
| } | ||
| }; | ||
| globalThis.addEventListener('message', listener); | ||
| }); | ||
| }; | ||
| }; | ||
| const defaultOptions = { | ||
| singleProcess: false | ||
| }; | ||
| export default (options) => { | ||
| options = Object.assign({}, defaultOptions, options); | ||
| const isPrimary = Boolean(globalThis.document) || options.singleProcess; | ||
| if (isPrimary) { | ||
| const emitter = new EventTarget(); | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)); | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)); | ||
| return emitter; | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| }; | ||
| }; | ||
| //# sourceMappingURL=browser.js.map |
| {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"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"} |
| export declare const WORKER_REQUEST_READ_LOCK = "lock:worker:request-read"; | ||
| export declare const WORKER_RELEASE_READ_LOCK = "lock:worker:release-read"; | ||
| export declare const MASTER_GRANT_READ_LOCK = "lock:master:grant-read"; | ||
| export declare const WORKER_REQUEST_WRITE_LOCK = "lock:worker:request-write"; | ||
| export declare const WORKER_RELEASE_WRITE_LOCK = "lock:worker:release-write"; | ||
| export declare const MASTER_GRANT_WRITE_LOCK = "lock:master:grant-write"; | ||
| //# sourceMappingURL=constants.d.ts.map |
| {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAClE,eAAO,MAAM,wBAAwB,6BAA6B,CAAA;AAClE,eAAO,MAAM,sBAAsB,2BAA2B,CAAA;AAE9D,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,yBAAyB,8BAA8B,CAAA;AACpE,eAAO,MAAM,uBAAuB,4BAA4B,CAAA"} |
| export const WORKER_REQUEST_READ_LOCK = 'lock:worker:request-read'; | ||
| export const WORKER_RELEASE_READ_LOCK = 'lock:worker:release-read'; | ||
| export const MASTER_GRANT_READ_LOCK = 'lock:master:grant-read'; | ||
| export const WORKER_REQUEST_WRITE_LOCK = 'lock:worker:request-write'; | ||
| export const WORKER_RELEASE_WRITE_LOCK = 'lock:worker:release-write'; | ||
| export const MASTER_GRANT_WRITE_LOCK = 'lock:master:grant-write'; | ||
| //# sourceMappingURL=constants.js.map |
| {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;AAClE,MAAM,CAAC,MAAM,wBAAwB,GAAG,0BAA0B,CAAA;AAClE,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAA;AAE9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAA;AACpE,MAAM,CAAC,MAAM,uBAAuB,GAAG,yBAAyB,CAAA"} |
| export interface MorticeOptions { | ||
| name?: string; | ||
| timeout?: number; | ||
| concurrency?: number; | ||
| singleProcess?: boolean; | ||
| global?: typeof globalThis; | ||
| } | ||
| export interface Mortice { | ||
| readLock: () => Promise<Release>; | ||
| writeLock: () => Promise<Release>; | ||
| } | ||
| export interface Release { | ||
| (): void; | ||
| } | ||
| export interface MorticeImplementation { | ||
| isWorker: boolean; | ||
| readLock: (name: string, options: MorticeOptions) => Mortice['readLock']; | ||
| writeLock: (name: string, options: MorticeOptions) => Mortice['writeLock']; | ||
| } | ||
| export default function createMortice(options?: MorticeOptions): Mortice; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,OAAO,UAAU,CAAA;CAC3B;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAChC,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,IAAI,CAAA;CACT;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,WAAW,CAAC,CAAA;CAC3E;AA6FD,MAAM,CAAC,OAAO,UAAU,aAAa,CAAE,OAAO,CAAC,EAAE,cAAc,WAiC9D"} |
| import PQueue from 'p-queue'; | ||
| import pTimeout from 'p-timeout'; | ||
| import impl from './node.js'; | ||
| const mutexes = {}; | ||
| let implementation; | ||
| async function createReleaseable(queue, options) { | ||
| let res; | ||
| const p = new Promise((resolve) => { | ||
| res = resolve; | ||
| }); | ||
| void queue.add(async () => await pTimeout((async () => { | ||
| return await new Promise((resolve) => { | ||
| res(() => { | ||
| resolve(); | ||
| }); | ||
| }); | ||
| })(), options.timeout)); | ||
| return await p; | ||
| } | ||
| const createMutex = (name, options) => { | ||
| if (implementation.isWorker === true) { | ||
| return { | ||
| readLock: implementation.readLock(name, options), | ||
| writeLock: implementation.writeLock(name, options) | ||
| }; | ||
| } | ||
| const masterQueue = new PQueue({ concurrency: 1 }); | ||
| let readQueue; | ||
| return { | ||
| async readLock() { | ||
| // If there's already a read queue, just add the task to it | ||
| if (readQueue != null) { | ||
| return await createReleaseable(readQueue, options); | ||
| } | ||
| // Create a new read queue | ||
| readQueue = new PQueue({ | ||
| concurrency: options.concurrency, | ||
| autoStart: false | ||
| }); | ||
| const localReadQueue = readQueue; | ||
| // Add the task to the read queue | ||
| const readPromise = createReleaseable(readQueue, options); | ||
| void masterQueue.add(async () => { | ||
| // Start the task only once the master queue has completed processing | ||
| // any previous tasks | ||
| localReadQueue.start(); | ||
| // Once all the tasks in the read queue have completed, remove it so | ||
| // that the next read lock will occur after any write locks that were | ||
| // started in the interim | ||
| return await localReadQueue.onIdle() | ||
| .then(() => { | ||
| if (readQueue === localReadQueue) { | ||
| readQueue = null; | ||
| } | ||
| }); | ||
| }); | ||
| return await readPromise; | ||
| }, | ||
| async writeLock() { | ||
| // Remove the read queue reference, so that any later read locks will be | ||
| // added to a new queue that starts after this write lock has been | ||
| // released | ||
| readQueue = null; | ||
| return await createReleaseable(masterQueue, options); | ||
| } | ||
| }; | ||
| }; | ||
| const defaultOptions = { | ||
| name: 'lock', | ||
| concurrency: Infinity, | ||
| timeout: 84600000, | ||
| global: globalThis, | ||
| singleProcess: false | ||
| }; | ||
| export default function createMortice(options) { | ||
| const opts = Object.assign({}, defaultOptions, options); | ||
| if (implementation == null) { | ||
| implementation = impl(opts); | ||
| if (implementation.isWorker !== true) { | ||
| // we are master, set up worker requests | ||
| implementation.addEventListener('requestReadLock', (event) => { | ||
| if (mutexes[event.data.name] == null) { | ||
| return; | ||
| } | ||
| void mutexes[event.data.name].readLock() | ||
| .then(async (release) => await event.data.handler().finally(() => release())); | ||
| }); | ||
| implementation.addEventListener('requestWriteLock', async (event) => { | ||
| if (mutexes[event.data.name] == null) { | ||
| return; | ||
| } | ||
| void mutexes[event.data.name].writeLock() | ||
| .then(async (release) => await event.data.handler().finally(() => release())); | ||
| }); | ||
| } | ||
| } | ||
| if (mutexes[opts.name] == null) { | ||
| mutexes[opts.name] = createMutex(opts.name, opts); | ||
| } | ||
| return mutexes[opts.name]; | ||
| } | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"} |
| import type { MorticeImplementation, MorticeOptions } from './index.js'; | ||
| declare const _default: (options: Required<MorticeOptions>) => MorticeImplementation | EventTarget | undefined; | ||
| export default _default; | ||
| //# sourceMappingURL=node.d.ts.map |
| {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/node.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAW,MAAM,YAAY,CAAA;kCAkFvD,SAAS,cAAc,CAAC,KAAG,qBAAqB,GAAG,WAAW,GAAG,SAAS;AAAnG,wBAeC"} |
| import { nanoid } from 'nanoid'; | ||
| import { WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK } from './constants.js'; | ||
| import cluster from 'cluster'; | ||
| const handleWorkerLockRequest = (emitter, masterEvent, requestType, releaseType, grantType) => { | ||
| return (worker, requestEvent) => { | ||
| if (requestEvent != null && requestEvent.type === requestType) { | ||
| emitter.dispatchEvent(new MessageEvent(masterEvent, { | ||
| data: { | ||
| name: requestEvent.name, | ||
| handler: async () => { | ||
| // grant lock to worker | ||
| worker.send({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }); | ||
| // wait for worker to finish | ||
| return await new Promise((resolve) => { | ||
| const releaseEventListener = (releaseEvent) => { | ||
| if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeListener('message', releaseEventListener); | ||
| resolve(); | ||
| } | ||
| }; | ||
| worker.on('message', releaseEventListener); | ||
| }); | ||
| } | ||
| } | ||
| })); | ||
| } | ||
| }; | ||
| }; | ||
| const makeWorkerLockRequest = (name, requestType, grantType, releaseType) => { | ||
| return async () => { | ||
| const id = nanoid(); | ||
| if (process.send == null) { | ||
| throw new Error('No send method on process - are we a cluster worker?'); | ||
| } | ||
| process.send({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }); | ||
| return await new Promise((resolve) => { | ||
| const listener = (event) => { | ||
| if (event.type === grantType && event.identifier === id) { | ||
| process.removeListener('message', listener); | ||
| // grant lock | ||
| resolve(() => { | ||
| if (process.send == null) { | ||
| throw new Error('No send method on process - are we a cluster worker?'); | ||
| } | ||
| // release lock | ||
| process.send({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }); | ||
| }); | ||
| } | ||
| }; | ||
| process.on('message', listener); | ||
| }); | ||
| }; | ||
| }; | ||
| export default (options) => { | ||
| if (cluster.isPrimary || options.singleProcess) { | ||
| const emitter = new EventTarget(); | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)); | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)); | ||
| return emitter; | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| }; | ||
| }; | ||
| //# sourceMappingURL=node.js.map |
| {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/node.ts"],"names":[],"mappings":"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"} |
+4
| This project is dual licensed under MIT and Apache-2.0. | ||
| MIT: https://www.opensource.org/licenses/mit | ||
| Apache-2.0: https://www.apache.org/licenses/license-2.0 |
+126
| import { nanoid } from 'nanoid' | ||
| import { | ||
| WORKER_REQUEST_READ_LOCK, | ||
| WORKER_RELEASE_READ_LOCK, | ||
| MASTER_GRANT_READ_LOCK, | ||
| WORKER_REQUEST_WRITE_LOCK, | ||
| WORKER_RELEASE_WRITE_LOCK, | ||
| MASTER_GRANT_WRITE_LOCK | ||
| } from './constants.js' | ||
| import observer from 'observable-webworkers' | ||
| import type { MorticeImplementation, MorticeOptions, Release } from './index.js' | ||
| const handleWorkerLockRequest = (emitter: EventTarget, masterEvent: string, requestType: string, releaseType: string, grantType: string) => { | ||
| return (worker: Worker, event: MessageEvent) => { | ||
| if (event.data.type !== requestType) { | ||
| return | ||
| } | ||
| const requestEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| } | ||
| emitter.dispatchEvent(new MessageEvent(masterEvent, { | ||
| data: { | ||
| name: requestEvent.name, | ||
| handler: async (): Promise<void> => { | ||
| // grant lock to worker | ||
| worker.postMessage({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }) | ||
| // wait for worker to finish | ||
| return await new Promise<void>((resolve) => { | ||
| const releaseEventListener = (event: MessageEvent) => { | ||
| if (event == null || event.data == null) { | ||
| return | ||
| } | ||
| const releaseEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| } | ||
| if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeEventListener('message', releaseEventListener) | ||
| resolve() | ||
| } | ||
| } | ||
| worker.addEventListener('message', releaseEventListener) | ||
| }) | ||
| } | ||
| } | ||
| })) | ||
| } | ||
| } | ||
| const makeWorkerLockRequest = (name: string, requestType: string, grantType: string, releaseType: string) => { | ||
| return async () => { | ||
| const id = nanoid() | ||
| globalThis.postMessage({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| return await new Promise<Release>((resolve) => { | ||
| const listener = (event: MessageEvent) => { | ||
| if (event == null || event.data == null) { | ||
| return | ||
| } | ||
| const responseEvent = { | ||
| type: event.data.type, | ||
| identifier: event.data.identifier | ||
| } | ||
| if (responseEvent.type === grantType && responseEvent.identifier === id) { | ||
| globalThis.removeEventListener('message', listener) | ||
| // grant lock | ||
| resolve(() => { | ||
| // release lock | ||
| globalThis.postMessage({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| globalThis.addEventListener('message', listener) | ||
| }) | ||
| } | ||
| } | ||
| const defaultOptions = { | ||
| singleProcess: false | ||
| } | ||
| export default (options: Required<MorticeOptions>): MorticeImplementation | EventTarget => { | ||
| options = Object.assign({}, defaultOptions, options) | ||
| const isPrimary = Boolean(globalThis.document) || options.singleProcess | ||
| if (isPrimary) { | ||
| const emitter = new EventTarget() | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)) | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)) | ||
| return emitter | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| } | ||
| } |
| export const WORKER_REQUEST_READ_LOCK = 'lock:worker:request-read' | ||
| export const WORKER_RELEASE_READ_LOCK = 'lock:worker:release-read' | ||
| export const MASTER_GRANT_READ_LOCK = 'lock:master:grant-read' | ||
| export const WORKER_REQUEST_WRITE_LOCK = 'lock:worker:request-write' | ||
| export const WORKER_RELEASE_WRITE_LOCK = 'lock:worker:release-write' | ||
| export const MASTER_GRANT_WRITE_LOCK = 'lock:master:grant-write' |
+152
| import PQueue from 'p-queue' | ||
| import pTimeout from 'p-timeout' | ||
| import impl from './node.js' | ||
| export interface MorticeOptions { | ||
| name?: string | ||
| timeout?: number | ||
| concurrency?: number | ||
| singleProcess?: boolean | ||
| global?: typeof globalThis | ||
| } | ||
| export interface Mortice { | ||
| readLock: () => Promise<Release> | ||
| writeLock: () => Promise<Release> | ||
| } | ||
| export interface Release { | ||
| (): void | ||
| } | ||
| export interface MorticeImplementation { | ||
| isWorker: boolean | ||
| readLock: (name: string, options: MorticeOptions) => Mortice['readLock'] | ||
| writeLock: (name: string, options: MorticeOptions) => Mortice['writeLock'] | ||
| } | ||
| const mutexes: Record<string, Mortice> = {} | ||
| let implementation: any | ||
| async function createReleaseable (queue: PQueue, options: Required<MorticeOptions>): Promise<Release> { | ||
| let res: (release: Release) => void | ||
| const p = new Promise<Release>((resolve) => { | ||
| res = resolve | ||
| }) | ||
| void queue.add(async () => await pTimeout((async () => { | ||
| return await new Promise<void>((resolve) => { | ||
| res(() => { | ||
| resolve() | ||
| }) | ||
| }) | ||
| })(), options.timeout)) | ||
| return await p | ||
| } | ||
| const createMutex = (name: string, options: Required<MorticeOptions>): Mortice => { | ||
| if (implementation.isWorker === true) { | ||
| return { | ||
| readLock: implementation.readLock(name, options), | ||
| writeLock: implementation.writeLock(name, options) | ||
| } | ||
| } | ||
| const masterQueue = new PQueue({ concurrency: 1 }) | ||
| let readQueue: PQueue | null | ||
| return { | ||
| async readLock () { | ||
| // If there's already a read queue, just add the task to it | ||
| if (readQueue != null) { | ||
| return await createReleaseable(readQueue, options) | ||
| } | ||
| // Create a new read queue | ||
| readQueue = new PQueue({ | ||
| concurrency: options.concurrency, | ||
| autoStart: false | ||
| }) | ||
| const localReadQueue = readQueue | ||
| // Add the task to the read queue | ||
| const readPromise = createReleaseable(readQueue, options) | ||
| void masterQueue.add(async () => { | ||
| // Start the task only once the master queue has completed processing | ||
| // any previous tasks | ||
| localReadQueue.start() | ||
| // Once all the tasks in the read queue have completed, remove it so | ||
| // that the next read lock will occur after any write locks that were | ||
| // started in the interim | ||
| return await localReadQueue.onIdle() | ||
| .then(() => { | ||
| if (readQueue === localReadQueue) { | ||
| readQueue = null | ||
| } | ||
| }) | ||
| }) | ||
| return await readPromise | ||
| }, | ||
| async writeLock () { | ||
| // Remove the read queue reference, so that any later read locks will be | ||
| // added to a new queue that starts after this write lock has been | ||
| // released | ||
| readQueue = null | ||
| return await createReleaseable(masterQueue, options) | ||
| } | ||
| } | ||
| } | ||
| const defaultOptions = { | ||
| name: 'lock', | ||
| concurrency: Infinity, | ||
| timeout: 84600000, | ||
| global: globalThis, | ||
| singleProcess: false | ||
| } | ||
| interface EventData { | ||
| name: string | ||
| handler: () => Promise<void> | ||
| } | ||
| export default function createMortice (options?: MorticeOptions) { | ||
| const opts: Required<MorticeOptions> = Object.assign({}, defaultOptions, options) | ||
| if (implementation == null) { | ||
| implementation = impl(opts) | ||
| if (implementation.isWorker !== true) { | ||
| // we are master, set up worker requests | ||
| implementation.addEventListener('requestReadLock', (event: MessageEvent<EventData>) => { | ||
| if (mutexes[event.data.name] == null) { | ||
| return | ||
| } | ||
| void mutexes[event.data.name].readLock() | ||
| .then(async release => await event.data.handler().finally(() => release())) | ||
| }) | ||
| implementation.addEventListener('requestWriteLock', async (event: MessageEvent<EventData>) => { | ||
| if (mutexes[event.data.name] == null) { | ||
| return | ||
| } | ||
| void mutexes[event.data.name].writeLock() | ||
| .then(async release => await event.data.handler().finally(() => release())) | ||
| }) | ||
| } | ||
| } | ||
| if (mutexes[opts.name] == null) { | ||
| mutexes[opts.name] = createMutex(opts.name, opts) | ||
| } | ||
| return mutexes[opts.name] | ||
| } |
+107
| import { nanoid } from 'nanoid' | ||
| import { | ||
| WORKER_REQUEST_READ_LOCK, | ||
| WORKER_RELEASE_READ_LOCK, | ||
| MASTER_GRANT_READ_LOCK, | ||
| WORKER_REQUEST_WRITE_LOCK, | ||
| WORKER_RELEASE_WRITE_LOCK, | ||
| MASTER_GRANT_WRITE_LOCK | ||
| } from './constants.js' | ||
| import type { MorticeImplementation, MorticeOptions, Release } from './index.js' | ||
| import cluster from 'cluster' | ||
| import type { Worker } from 'cluster' | ||
| interface RequestEvent { | ||
| type: string | ||
| identifier: string | ||
| name: string | ||
| } | ||
| const handleWorkerLockRequest = (emitter: EventTarget, masterEvent: string, requestType: string, releaseType: string, grantType: string) => { | ||
| return (worker: Worker, requestEvent: RequestEvent) => { | ||
| if (requestEvent != null && requestEvent.type === requestType) { | ||
| emitter.dispatchEvent(new MessageEvent(masterEvent, { | ||
| data: { | ||
| name: requestEvent.name, | ||
| handler: async () => { | ||
| // grant lock to worker | ||
| worker.send({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }) | ||
| // wait for worker to finish | ||
| return await new Promise<void>((resolve) => { | ||
| const releaseEventListener = (releaseEvent: RequestEvent) => { | ||
| if (releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeListener('message', releaseEventListener) | ||
| resolve() | ||
| } | ||
| } | ||
| worker.on('message', releaseEventListener) | ||
| }) | ||
| } | ||
| } | ||
| })) | ||
| } | ||
| } | ||
| } | ||
| const makeWorkerLockRequest = (name: string, requestType: string, grantType: string, releaseType: string) => { | ||
| return async () => { | ||
| const id = nanoid() | ||
| if (process.send == null) { | ||
| throw new Error('No send method on process - are we a cluster worker?') | ||
| } | ||
| process.send({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| return await new Promise<Release>((resolve) => { | ||
| const listener = (event: RequestEvent) => { | ||
| if (event.type === grantType && event.identifier === id) { | ||
| process.removeListener('message', listener) | ||
| // grant lock | ||
| resolve(() => { | ||
| if (process.send == null) { | ||
| throw new Error('No send method on process - are we a cluster worker?') | ||
| } | ||
| // release lock | ||
| process.send({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| process.on('message', listener) | ||
| }) | ||
| } | ||
| } | ||
| export default (options: Required<MorticeOptions>): MorticeImplementation | EventTarget | undefined => { | ||
| if (cluster.isPrimary || options.singleProcess) { | ||
| const emitter = new EventTarget() | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)) | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)) | ||
| return emitter | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| } | ||
| } |
+135
-31
| { | ||
| "name": "mortice", | ||
| "version": "2.0.1", | ||
| "version": "3.0.0", | ||
| "description": "Isomorphic read/write lock that works in single processes, node clusters and web workers", | ||
| "main": "lib/index.js", | ||
| "scripts": { | ||
| "test": "ava", | ||
| "lint": "standard" | ||
| }, | ||
| "keywords": [ | ||
| "read-write", | ||
| "cluster", | ||
| "mutex", | ||
| "lock", | ||
| "await", | ||
| "async" | ||
| ], | ||
| "author": "Alex Potsides <alex@achingbrain.net>", | ||
| "license": "ISC", | ||
| "license": "Apache-2.0 OR MIT", | ||
| "homepage": "https://github.com/achingbrain/mortice#readme", | ||
| "repository": { | ||
@@ -27,26 +15,142 @@ "type": "git", | ||
| }, | ||
| "homepage": "https://github.com/achingbrain/mortice", | ||
| "devDependencies": { | ||
| "ava": "^3.15.0", | ||
| "browserify": "^17.0.0", | ||
| "delay": "^5.0.0", | ||
| "execa": "^5.0.0", | ||
| "run-headless": "^2.0.1", | ||
| "standard": "^16.0.3", | ||
| "webworkify": "^1.5.0" | ||
| "keywords": [ | ||
| "async", | ||
| "await", | ||
| "cluster", | ||
| "lock", | ||
| "mutex", | ||
| "read-write" | ||
| ], | ||
| "engines": { | ||
| "node": ">=16.0.0", | ||
| "npm": ">=7.0.0" | ||
| }, | ||
| "ava": { | ||
| "files": [ | ||
| "test/*.test.js" | ||
| "type": "module", | ||
| "types": "./dist/src/index.d.ts", | ||
| "files": [ | ||
| "src", | ||
| "dist/src", | ||
| "!dist/test", | ||
| "!**/*.tsbuildinfo" | ||
| ], | ||
| "exports": { | ||
| ".": { | ||
| "import": "./dist/src/index.js" | ||
| } | ||
| }, | ||
| "eslintConfig": { | ||
| "extends": "ipfs", | ||
| "parserOptions": { | ||
| "sourceType": "module" | ||
| } | ||
| }, | ||
| "release": { | ||
| "branches": [ | ||
| "master" | ||
| ], | ||
| "plugins": [ | ||
| [ | ||
| "@semantic-release/commit-analyzer", | ||
| { | ||
| "preset": "conventionalcommits", | ||
| "releaseRules": [ | ||
| { | ||
| "breaking": true, | ||
| "release": "major" | ||
| }, | ||
| { | ||
| "revert": true, | ||
| "release": "patch" | ||
| }, | ||
| { | ||
| "type": "feat", | ||
| "release": "minor" | ||
| }, | ||
| { | ||
| "type": "fix", | ||
| "release": "patch" | ||
| }, | ||
| { | ||
| "type": "chore", | ||
| "release": "patch" | ||
| }, | ||
| { | ||
| "type": "docs", | ||
| "release": "patch" | ||
| }, | ||
| { | ||
| "type": "test", | ||
| "release": "patch" | ||
| }, | ||
| { | ||
| "scope": "no-release", | ||
| "release": false | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| [ | ||
| "@semantic-release/release-notes-generator", | ||
| { | ||
| "preset": "conventionalcommits", | ||
| "presetConfig": { | ||
| "types": [ | ||
| { | ||
| "type": "feat", | ||
| "section": "Features" | ||
| }, | ||
| { | ||
| "type": "fix", | ||
| "section": "Bug Fixes" | ||
| }, | ||
| { | ||
| "type": "chore", | ||
| "section": "Trivial Changes" | ||
| }, | ||
| { | ||
| "type": "docs", | ||
| "section": "Trivial Changes" | ||
| }, | ||
| { | ||
| "type": "test", | ||
| "section": "Tests" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ], | ||
| "@semantic-release/changelog", | ||
| "@semantic-release/npm", | ||
| "@semantic-release/github", | ||
| "@semantic-release/git" | ||
| ] | ||
| }, | ||
| "scripts": { | ||
| "lint": "aegir lint", | ||
| "pretest": "npm run build", | ||
| "test": "aegir test", | ||
| "test:node": "npm run test -- -f dist/test/node.js -t node --cov", | ||
| "test:chrome": "npm run test -- -f dist/test/browser.js -t browser --cov", | ||
| "test:firefox": "npm run test -- -f dist/test/browser.js -t browser -- --browser firefox", | ||
| "test:electron-main": "npm run test -- -f dist/test/node.js -t electron-main", | ||
| "build": "tsc && node esbuild.js", | ||
| "release": "semantic-release" | ||
| }, | ||
| "dependencies": { | ||
| "nanoid": "^3.1.20", | ||
| "observable-webworkers": "^1.0.0", | ||
| "p-queue": "^6.0.0", | ||
| "promise-timeout": "^1.3.0" | ||
| "observable-webworkers": "^2.0.1", | ||
| "p-queue": "^7.2.0", | ||
| "p-timeout": "^5.0.2" | ||
| }, | ||
| "devDependencies": { | ||
| "aegir": "^36.1.3", | ||
| "delay": "^5.0.0", | ||
| "esbuild": "^0.14.21", | ||
| "execa": "^6.0.0" | ||
| }, | ||
| "browser": { | ||
| "cluster": false | ||
| "cluster": false, | ||
| "./dist/src/node.js": "./dist/src/browser.js", | ||
| "./src/node.js": "./src/browser.js" | ||
| } | ||
| } |
+8
-20
| # mortice | ||
| [](https://travis-ci.org/achingbrain/mortice) | ||
| [](https://github.com/achingbrain/mortice/actions/workflows/js-test-and-release.yml) | ||
| Isomorphic read/write lock that works in single processes, node clusters and web workers. | ||
| > Isomorphic read/write lock that works in single processes, node clusters and web workers. | ||
@@ -24,4 +24,4 @@ ## Features | ||
| ```javascript | ||
| const mortice = require('mortice') | ||
| const delay = require('delay') | ||
| import mortice from 'mortice' | ||
| import delay from 'delay' | ||
@@ -98,4 +98,4 @@ // the lock name & options objects are both optional | ||
| // main.js | ||
| const mortice = require('mortice') | ||
| const observe = require('observable-webworkers') | ||
| import mortice from 'mortice' | ||
| import observe from 'observable-webworkers' | ||
@@ -112,4 +112,4 @@ // create our lock on the main thread, it will be held here | ||
| // worker.js | ||
| const mortice = require('mortice') | ||
| const delay = require('delay') | ||
| import mortice from 'mortice' | ||
| import delay from 'delay' | ||
@@ -126,13 +126,1 @@ const mutex = mortice() | ||
| ``` | ||
| Alternatively you can use the bundled `mortice.Worker` to create web workers and save yourself an extra dependency. | ||
| ```javascript | ||
| const mortice = require('mortice') | ||
| const Worker = mortice.Worker | ||
| // create our lock on the main thread, it will be held here | ||
| const mutex = mortice() | ||
| const worker = new Worker('worker.js') | ||
| ``` |
| sudo: required | ||
| language: node_js | ||
| addons: | ||
| chrome: stable | ||
| node_js: | ||
| - '10' | ||
| - '12' |
-121
| const EventEmitter = require('events').EventEmitter | ||
| const { nanoid } = require('nanoid') | ||
| const { | ||
| WORKER_REQUEST_READ_LOCK, | ||
| WORKER_RELEASE_READ_LOCK, | ||
| MASTER_GRANT_READ_LOCK, | ||
| WORKER_REQUEST_WRITE_LOCK, | ||
| WORKER_RELEASE_WRITE_LOCK, | ||
| MASTER_GRANT_WRITE_LOCK | ||
| } = require('./constants') | ||
| const observer = require('observable-webworkers') | ||
| const handleWorkerLockRequest = (emitter, masterEvent, requestType, releaseType, grantType) => { | ||
| return (worker, event) => { | ||
| if (!event || !event.data || event.data.type !== requestType) { | ||
| return | ||
| } | ||
| const requestEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| } | ||
| emitter.emit(masterEvent, requestEvent.name, () => { | ||
| // grant lock to worker | ||
| worker.postMessage({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }) | ||
| // wait for worker to finish | ||
| return new Promise((resolve) => { | ||
| const releaseEventListener = (event) => { | ||
| if (!event || !event.data) { | ||
| return | ||
| } | ||
| const releaseEvent = { | ||
| type: event.data.type, | ||
| name: event.data.name, | ||
| identifier: event.data.identifier | ||
| } | ||
| if (releaseEvent && releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeEventListener('message', releaseEventListener) | ||
| resolve() | ||
| } | ||
| } | ||
| worker.addEventListener('message', releaseEventListener) | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| const makeWorkerLockRequest = (name, requestType, grantType, releaseType) => { | ||
| return () => { | ||
| const id = nanoid() | ||
| globalThis.postMessage({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| return new Promise((resolve) => { | ||
| const listener = (event) => { | ||
| if (!event || !event.data) { | ||
| return | ||
| } | ||
| const responseEvent = { | ||
| type: event.data.type, | ||
| identifier: event.data.identifier | ||
| } | ||
| if (responseEvent && responseEvent.type === grantType && responseEvent.identifier === id) { | ||
| globalThis.removeEventListener('message', listener) | ||
| // grant lock | ||
| resolve(() => { | ||
| // release lock | ||
| globalThis.postMessage({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| globalThis.addEventListener('message', listener) | ||
| }) | ||
| } | ||
| } | ||
| const defaultOptions = { | ||
| singleProcess: false | ||
| } | ||
| module.exports = (options) => { | ||
| options = Object.assign({}, defaultOptions, options) | ||
| const isMaster = !!globalThis.document || options.singleProcess | ||
| if (isMaster) { | ||
| const emitter = new EventEmitter() | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)) | ||
| observer.addEventListener('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)) | ||
| return emitter | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| } | ||
| } |
| module.exports = { | ||
| WORKER_REQUEST_READ_LOCK: 'lock:worker:request-read', | ||
| WORKER_RELEASE_READ_LOCK: 'lock:worker:release-read', | ||
| MASTER_GRANT_READ_LOCK: 'lock:master:grant-read', | ||
| WORKER_REQUEST_WRITE_LOCK: 'lock:worker:request-write', | ||
| WORKER_RELEASE_WRITE_LOCK: 'lock:worker:release-write', | ||
| MASTER_GRANT_WRITE_LOCK: 'lock:master:grant-write' | ||
| } |
-159
| const node = require('./node') | ||
| const browser = require('./browser') | ||
| const { default: Queue } = require('p-queue') | ||
| const { timeout } = require('promise-timeout') | ||
| const observe = require('observable-webworkers') | ||
| const mutexes = {} | ||
| let implementation | ||
| function createReleaseable (queue, options) { | ||
| let res | ||
| const p = new Promise((resolve) => { | ||
| res = resolve | ||
| }) | ||
| queue.add(() => timeout((() => { | ||
| return new Promise((resolve) => { | ||
| res(() => { | ||
| resolve() | ||
| }) | ||
| }) | ||
| })(), options.timeout)) | ||
| return p | ||
| } | ||
| const createMutex = (name, options) => { | ||
| if (implementation.isWorker) { | ||
| return { | ||
| readLock: implementation.readLock(name, options), | ||
| writeLock: implementation.writeLock(name, options) | ||
| } | ||
| } | ||
| const masterQueue = new Queue({ concurrency: 1 }) | ||
| let readQueue = null | ||
| return { | ||
| readLock: () => { | ||
| // If there's already a read queue, just add the task to it | ||
| if (readQueue) { | ||
| return createReleaseable(readQueue, options) | ||
| } | ||
| // Create a new read queue | ||
| readQueue = new Queue({ | ||
| concurrency: options.concurrency, | ||
| autoStart: false | ||
| }) | ||
| const localReadQueue = readQueue | ||
| // Add the task to the read queue | ||
| const readPromise = createReleaseable(readQueue, options) | ||
| masterQueue.add(() => { | ||
| // Start the task only once the master queue has completed processing | ||
| // any previous tasks | ||
| localReadQueue.start() | ||
| // Once all the tasks in the read queue have completed, remove it so | ||
| // that the next read lock will occur after any write locks that were | ||
| // started in the interim | ||
| return localReadQueue.onIdle() | ||
| .then(() => { | ||
| if (readQueue === localReadQueue) { | ||
| readQueue = null | ||
| } | ||
| }) | ||
| }) | ||
| return readPromise | ||
| }, | ||
| writeLock: () => { | ||
| // Remove the read queue reference, so that any later read locks will be | ||
| // added to a new queue that starts after this write lock has been | ||
| // released | ||
| readQueue = null | ||
| return createReleaseable(masterQueue, options) | ||
| } | ||
| } | ||
| } | ||
| const defaultOptions = { | ||
| concurrency: Infinity, | ||
| timeout: 84600000, | ||
| global: global, | ||
| singleProcess: false | ||
| } | ||
| module.exports = (name, options) => { | ||
| if (!options) { | ||
| options = {} | ||
| } | ||
| if (typeof name === 'object') { | ||
| options = name | ||
| name = 'lock' | ||
| } | ||
| if (!name) { | ||
| name = 'lock' | ||
| } | ||
| options = Object.assign({}, defaultOptions, options) | ||
| if (!implementation) { | ||
| implementation = node(options) || browser(options) | ||
| if (!implementation.isWorker) { | ||
| // we are master, set up worker requests | ||
| implementation.on('requestReadLock', (name, fn) => { | ||
| if (!mutexes[name]) { | ||
| return | ||
| } | ||
| mutexes[name].readLock() | ||
| .then(release => fn().finally(() => release())) | ||
| }) | ||
| implementation.on('requestWriteLock', async (name, fn) => { | ||
| if (!mutexes[name]) { | ||
| return | ||
| } | ||
| mutexes[name].writeLock() | ||
| .then(release => fn().finally(() => release())) | ||
| }) | ||
| } | ||
| } | ||
| if (!mutexes[name]) { | ||
| mutexes[name] = createMutex(name, options) | ||
| } | ||
| return mutexes[name] | ||
| } | ||
| module.exports.Worker = function (script, Impl) { | ||
| Impl = Impl || global.Worker | ||
| let worker | ||
| try { | ||
| worker = new Impl(script) | ||
| } catch (error) { | ||
| if (error.message.includes('not a constructor')) { | ||
| worker = Impl(script) | ||
| } | ||
| } | ||
| if (!worker) { | ||
| throw new Error('Could not create Worker from', Impl) | ||
| } | ||
| observe(worker) | ||
| return worker | ||
| } |
-97
| const EventEmitter = require('events').EventEmitter | ||
| const { nanoid } = require('nanoid') | ||
| const { | ||
| WORKER_REQUEST_READ_LOCK, | ||
| WORKER_RELEASE_READ_LOCK, | ||
| MASTER_GRANT_READ_LOCK, | ||
| WORKER_REQUEST_WRITE_LOCK, | ||
| WORKER_RELEASE_WRITE_LOCK, | ||
| MASTER_GRANT_WRITE_LOCK | ||
| } = require('./constants') | ||
| let cluster | ||
| const handleWorkerLockRequest = (emitter, masterEvent, requestType, releaseType, grantType) => { | ||
| return (worker, requestEvent) => { | ||
| if (requestEvent && requestEvent.type === requestType) { | ||
| emitter.emit(masterEvent, requestEvent.name, () => { | ||
| // grant lock to worker | ||
| worker.send({ | ||
| type: grantType, | ||
| name: requestEvent.name, | ||
| identifier: requestEvent.identifier | ||
| }) | ||
| // wait for worker to finish | ||
| return new Promise((resolve) => { | ||
| const releaseEventListener = (releaseEvent) => { | ||
| if (releaseEvent && releaseEvent.type === releaseType && releaseEvent.identifier === requestEvent.identifier) { | ||
| worker.removeListener('message', releaseEventListener) | ||
| resolve() | ||
| } | ||
| } | ||
| worker.on('message', releaseEventListener) | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| } | ||
| const makeWorkerLockRequest = (name, requestType, grantType, releaseType) => { | ||
| return () => { | ||
| const id = nanoid() | ||
| process.send({ | ||
| type: requestType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| return new Promise((resolve) => { | ||
| const listener = (event) => { | ||
| if (event && event.type === grantType && event.identifier === id) { | ||
| process.removeListener('message', listener) | ||
| // grant lock | ||
| resolve(() => { | ||
| // release lock | ||
| process.send({ | ||
| type: releaseType, | ||
| identifier: id, | ||
| name | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
| process.on('message', listener) | ||
| }) | ||
| } | ||
| } | ||
| module.exports = (options) => { | ||
| try { | ||
| cluster = require('cluster') | ||
| if (!Object.keys(cluster).length) { | ||
| return | ||
| } | ||
| } catch (_) { | ||
| return | ||
| } | ||
| if (cluster.isMaster || options.singleProcess) { | ||
| const emitter = new EventEmitter() | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestReadLock', WORKER_REQUEST_READ_LOCK, WORKER_RELEASE_READ_LOCK, MASTER_GRANT_READ_LOCK)) | ||
| cluster.on('message', handleWorkerLockRequest(emitter, 'requestWriteLock', WORKER_REQUEST_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK)) | ||
| return emitter | ||
| } | ||
| return { | ||
| isWorker: true, | ||
| readLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_READ_LOCK, MASTER_GRANT_READ_LOCK, WORKER_RELEASE_READ_LOCK), | ||
| writeLock: (name) => makeWorkerLockRequest(name, WORKER_REQUEST_WRITE_LOCK, MASTER_GRANT_WRITE_LOCK, WORKER_RELEASE_WRITE_LOCK) | ||
| } | ||
| } |
| const test = require('ava') | ||
| const exec = require('execa') | ||
| const path = require('path') | ||
| const browserify = require('browserify') | ||
| test('executes locks in correct order', async (t) => { | ||
| const result = await exec('run-headless', { | ||
| input: browserify([path.join(__dirname, 'fixtures', 'browser.js')]).bundle() | ||
| }) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) |
| const test = require('ava') | ||
| const exec = require('execa') | ||
| const path = require('path') | ||
| test('executes locks in correct order', async (t) => { | ||
| const result = await exec('node', [path.join(__dirname, 'fixtures', 'cluster.js')]) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) | ||
| test('executes locks in correct order on a single process', async (t) => { | ||
| const result = await exec('node', [path.join(__dirname, 'fixtures', 'cluster-single-thread.js')]) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) |
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| console.info(`${type} ${index} waiting`) | ||
| const release = await muxex[`${type}Lock`]() | ||
| console.info(`${type} ${index} start`) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| console.info(`${type} ${index} complete`) | ||
| release() | ||
| if (type === 'read' && index === 4) { | ||
| global.__close__() | ||
| } | ||
| } | ||
| async function run () { | ||
| const mutex = mortice() | ||
| // queue up read/write requests, the third read should block the second write | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } | ||
| run() |
| const cluster = require('cluster') | ||
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| console.info(`${type} ${index} waiting`) | ||
| const release = await muxex[`${type}Lock`]() | ||
| console.info(`${type} ${index} start`) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| console.info(`${type} ${index} complete`) | ||
| release() | ||
| if (type === 'read' && index === 4) { | ||
| process.send('done') | ||
| } | ||
| } | ||
| async function run () { | ||
| const mutex = mortice({ | ||
| singleProcess: true | ||
| }) | ||
| if (cluster.isMaster) { | ||
| cluster.on('message', (worker, message) => { | ||
| if (message === 'done') { | ||
| worker.kill() | ||
| } | ||
| }) | ||
| cluster.fork() | ||
| } else { | ||
| // queue up read/write requests, the third read should block the second write | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } | ||
| } | ||
| run() | ||
| .then(() => {}) |
| const cluster = require('cluster') | ||
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| console.info(`${type} ${index} waiting`) | ||
| const release = await muxex[`${type}Lock`]() | ||
| console.info(`${type} ${index} start`) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| console.info(`${type} ${index} complete`) | ||
| release() | ||
| if (type === 'read' && index === 4) { | ||
| process.send('done') | ||
| } | ||
| } | ||
| async function run () { | ||
| const mutex = mortice() | ||
| if (cluster.isMaster) { | ||
| cluster.on('message', (worker, message) => { | ||
| if (message === 'done') { | ||
| worker.kill() | ||
| } | ||
| }) | ||
| cluster.fork() | ||
| } else { | ||
| // queue up read/write requests, the third read should block the second write | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } | ||
| } | ||
| run() | ||
| .then(() => {}) |
| const work = require('webworkify') | ||
| const observe = require('observable-webworkers') | ||
| const mortice = require('../../') | ||
| const Worker = mortice.Worker | ||
| mortice() | ||
| new Worker(require('./worker.js'), work) // eslint-disable-line | ||
| observe.addEventListener('message', (worker, event) => { | ||
| if (event.data) { | ||
| if (event.data.type === 'log') { | ||
| console.info(event.data.message) | ||
| } | ||
| if (event.data.type === 'done') { | ||
| worker.terminate() | ||
| return global.__close__() | ||
| } | ||
| } | ||
| }) |
| const mortice = require('../../') | ||
| async function read (muxex) { | ||
| const release = await muxex.readLock() | ||
| try { | ||
| console.info('read 1') | ||
| throw new Error('err') | ||
| } finally { | ||
| release() | ||
| } | ||
| } | ||
| async function write (muxex) { | ||
| const release = await muxex.writeLock() | ||
| await new Promise((resolve) => { | ||
| console.info('write 1') | ||
| resolve() | ||
| }) | ||
| release() | ||
| } | ||
| async function run () { | ||
| const mutex = mortice() | ||
| read(mutex) | ||
| .catch(() => {}) | ||
| write(mutex) | ||
| } | ||
| run() | ||
| .then(() => {}) |
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| console.info(`${type} ${index} waiting`) | ||
| const release = await muxex[`${type}Lock`]() | ||
| console.info(`${type} ${index} start`) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| console.info(`${type} ${index} complete`) | ||
| release() | ||
| } | ||
| async function run () { | ||
| const mutex = mortice() | ||
| // read should complete before write | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| } | ||
| run() | ||
| .then(() => {}) |
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| console.info(`${type} ${index} waiting`) | ||
| const release = await muxex[`${type}Lock`]() | ||
| console.info(`${type} ${index} start`) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| console.info(`${type} ${index} complete`) | ||
| release() | ||
| } | ||
| async function run () { | ||
| const mutex = mortice() | ||
| // queue up read/write requests, the third read should block the second write | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } | ||
| run() | ||
| .then(() => {}) |
| const work = require('webworkify') | ||
| const mortice = require('../../') | ||
| mortice() | ||
| const observe = require('observable-webworkers') | ||
| const worker = work(require('./worker-single-thread.js')) | ||
| observe(worker) | ||
| observe.addEventListener('message', (worker, event) => { | ||
| if (event.data) { | ||
| if (event.data.type === 'log') { | ||
| console.info(event.data.message) | ||
| } | ||
| if (event.data.type === 'done') { | ||
| worker.terminate() | ||
| return global.__close__() | ||
| } | ||
| } | ||
| }) |
| const work = require('webworkify') | ||
| const mortice = require('../../') | ||
| mortice() | ||
| const observe = require('observable-webworkers') | ||
| const worker = work(require('./worker.js')) | ||
| observe(worker) | ||
| observe.addEventListener('message', (worker, event) => { | ||
| if (event.data) { | ||
| if (event.data.type === 'log') { | ||
| console.info(event.data.message) | ||
| } | ||
| if (event.data.type === 'done') { | ||
| worker.terminate() | ||
| return global.__close__() | ||
| } | ||
| } | ||
| }) |
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} waiting` | ||
| }) | ||
| const release = await muxex[`${type}Lock`]() | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} start` | ||
| }) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} complete` | ||
| }) | ||
| release() | ||
| if (type === 'read' && index === 4) { | ||
| globalThis.postMessage({ | ||
| type: 'done' | ||
| }) | ||
| } | ||
| } | ||
| module.exports = () => { | ||
| const mutex = mortice({ | ||
| singleProcess: true | ||
| }) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } |
| const mortice = require('../../') | ||
| const delay = require('delay') | ||
| const counts = { | ||
| read: 0, | ||
| write: 0 | ||
| } | ||
| async function lock (type, muxex, timeout = 0) { | ||
| counts[type]++ | ||
| const index = counts[type] | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} waiting` | ||
| }) | ||
| const release = await muxex[`${type}Lock`]() | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} start` | ||
| }) | ||
| if (timeout) { | ||
| await delay(timeout) | ||
| } | ||
| globalThis.postMessage({ | ||
| type: 'log', | ||
| message: `${type} ${index} complete` | ||
| }) | ||
| release() | ||
| if (type === 'read' && index === 4) { | ||
| globalThis.postMessage({ | ||
| type: 'done' | ||
| }) | ||
| } | ||
| } | ||
| module.exports = () => { | ||
| const mutex = mortice() | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex) | ||
| lock('read', mutex, 500) | ||
| lock('write', mutex) | ||
| lock('read', mutex) | ||
| } |
| const test = require('ava') | ||
| const exec = require('execa') | ||
| const path = require('path') | ||
| const browserify = require('browserify') | ||
| test('execute locks in correct order', async (t) => { | ||
| const result = await exec('run-headless', { | ||
| input: browserify([path.join(__dirname, 'fixtures', 'mortice-workers.js')]).bundle() | ||
| }) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) |
| const test = require('ava') | ||
| const exec = require( 'execa') | ||
| const path = require('path') | ||
| test('executes locks in correct order', async (t) => { | ||
| const result = await exec('node', [path.join(__dirname, 'fixtures', 'process.js')]) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) | ||
| test('executes read then waits to start write', async (t) => { | ||
| const result = await exec('node', [path.join(__dirname, 'fixtures', 'process-read-then-write.js')]) | ||
| t.is(result.stdout, `read 1 waiting | ||
| write 1 waiting | ||
| read 1 start | ||
| read 1 complete | ||
| write 1 start | ||
| write 1 complete`) | ||
| }) | ||
| test('continues processing after error', async (t) => { | ||
| const result = await exec('node', [path.join(__dirname, 'fixtures', 'process-error-handling.js')]) | ||
| t.is(result.stdout, `read 1 | ||
| write 1`) | ||
| }) |
| const test = require('ava') | ||
| const exec = require('execa') | ||
| const path = require('path') | ||
| const browserify = require('browserify') | ||
| test('execute locks in correct order', async (t) => { | ||
| const result = await exec('run-headless', { | ||
| input: browserify([path.join(__dirname, 'fixtures', 'web-workers.js')]).bundle() | ||
| }) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) | ||
| test('execute locks in correct order on single thread', async (t) => { | ||
| const result = await exec('run-headless', { | ||
| input: browserify([path.join(__dirname, 'fixtures', 'web-workers-single-thread.js')]).bundle() | ||
| }) | ||
| t.is(result.stdout, `write 1 waiting | ||
| read 1 waiting | ||
| read 2 waiting | ||
| read 3 waiting | ||
| write 2 waiting | ||
| read 4 waiting | ||
| write 1 start | ||
| write 1 complete | ||
| read 1 start | ||
| read 1 complete | ||
| read 2 start | ||
| read 2 complete | ||
| read 3 start | ||
| read 3 complete | ||
| write 2 start | ||
| write 2 complete | ||
| read 4 start | ||
| read 4 complete`) | ||
| }) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Mixed license
LicensePackage contains multiple licenses.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
43282
63.2%4
-42.86%Yes
NaN1
Infinity%643
-23.82%123
-8.89%2
100%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated