mortice
Version:
Isomorphic read/write lock that works in single processes, node clusters and web workers
123 lines (104 loc) • 3.24 kB
text/typescript
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,
WORKER_ABORT_READ_LOCK_REQUEST,
WORKER_ABORT_WRITE_LOCK_REQUEST,
MASTER_READ_LOCK_ERROR,
MASTER_WRITE_LOCK_ERROR,
WORKER_FINALIZE,
BROADCAST_CHANNEL_NAME
} from '../constants.js'
import { nanoid } from '../utils.js'
import type { Mortice, Release } from '../index.js'
import type { AbortOptions } from 'abort-error'
export class MorticeChannelWorker implements Mortice {
private name: string
private channel: BroadcastChannel
constructor (name: string) {
this.name = name
this.channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME)
}
readLock (options?: AbortOptions): Promise<Release> {
return this.sendRequest(
WORKER_REQUEST_READ_LOCK,
WORKER_ABORT_READ_LOCK_REQUEST,
MASTER_GRANT_READ_LOCK,
MASTER_READ_LOCK_ERROR,
WORKER_RELEASE_READ_LOCK,
options
)
}
writeLock (options?: AbortOptions): Promise<Release> {
return this.sendRequest(
WORKER_REQUEST_WRITE_LOCK,
WORKER_ABORT_WRITE_LOCK_REQUEST,
MASTER_GRANT_WRITE_LOCK,
MASTER_WRITE_LOCK_ERROR,
WORKER_RELEASE_WRITE_LOCK,
options
)
}
finalize (): void {
this.channel.postMessage({
type: WORKER_FINALIZE,
name: this.name
})
this.channel.close()
}
private async sendRequest (requestType: string, abortType: string, grantType: string, errorType: string, releaseType: string, options?: AbortOptions): Promise<Release> {
options?.signal?.throwIfAborted()
const id = nanoid()
this.channel.postMessage({
type: requestType,
identifier: id,
name: this.name
})
return new Promise<Release>((resolve, reject) => {
const abortListener = (): void => {
this.channel.postMessage({
type: abortType,
identifier: id,
name: this.name
})
}
options?.signal?.addEventListener('abort', abortListener, {
once: true
})
const listener = (event: MessageEvent): void => {
if (event.data?.identifier !== id) {
return
}
if (event.data?.type === grantType) {
this.channel.removeEventListener('message', listener)
options?.signal?.removeEventListener('abort', abortListener)
// lock granted
resolve(() => {
// release lock
this.channel.postMessage({
type: releaseType,
identifier: id,
name: this.name
})
})
}
if (event.data.type === errorType) {
this.channel.removeEventListener('message', listener)
options?.signal?.removeEventListener('abort', abortListener)
// error while waiting for grant of lock
const err = new Error()
if (event.data.error != null) {
err.message = event.data.error.message
err.name = event.data.error.name
err.stack = event.data.error.stack
}
reject(err)
}
}
this.channel.addEventListener('message', listener)
})
}
}