mortice
Version:
Isomorphic read/write lock that works in single processes, node clusters and web workers
108 lines (92 loc) • 3.46 kB
text/typescript
import cluster from 'cluster'
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'
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
await new Promise<void>((resolve) => {
const releaseEventListener = (releaseEvent: RequestEvent): void => {
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 new Promise<Release>((resolve) => {
const listener = (event: RequestEvent): void => {
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)
}
}