mortice
Version:
Isomorphic read/write lock that works in single processes, node clusters and web workers
99 lines • 4.3 kB
JavaScript
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';
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
await new Promise((resolve) => {
const releaseEventListener = (event) => {
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, requestType, grantType, releaseType) => {
return async () => {
const id = nanoid();
globalThis.postMessage({
type: requestType,
identifier: id,
name
});
return new Promise((resolve) => {
const listener = (event) => {
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) => {
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