mega-plant/lib/clock-lock.ts

24 lines
1.3 KiB
TypeScript

import { ClockError } from './clock';
export interface LockBackend {
create(employee:string):Promise<{id:string}>;
find(employee:string):Promise<{id:string;created:string}|null>;
remove(id:string):Promise<unknown>;
}
/** Record ID is the ownership token. Never release a lock by employee filter. */
export async function acquireEmployeeLock(backend:LockBackend,employee:string,now=Date.now()) {
try{return (await backend.create(employee)).id;}
catch(error) {
if(![400,409].includes((error as {status:number}).status))throw error;
const previous=await backend.find(employee);
if(!previous)throw error;
const age=now-Date.parse(previous.created);
if(Number.isFinite(age) && age>120_000) {
// Concurrent recoverers can only delete this exact expired record. The
// unique employee lock and unique event predecessor also fence old writers.
try{await backend.remove(previous.id);}catch(e){if((e as {status:number}).status!==404)throw e;}
try{return (await backend.create(employee)).id;}
catch(e){if(![400,409].includes((e as {status:number}).status))throw e;}
}
throw new ClockError('Another scan is being saved for this worker. Retry this confirmation; an interrupted scan lock clears after two minutes.',423);
}
}