UNPKG

mortice

Version:

Isomorphic read/write lock that works in single processes, node clusters and web workers

127 lines (106 loc) 3.88 kB
import observer from 'observable-webworkers' 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 { nanoid } from './utils.js' 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 await new Promise<void>((resolve) => { const releaseEventListener = (event: MessageEvent): void => { if (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 new Promise<Release>((resolve) => { const listener = (event: MessageEvent): void => { if (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) } }