31 lines
1.6 KiB
JavaScript
31 lines
1.6 KiB
JavaScript
'use strict';
|
|
// A deadline race cannot stop an underlying worker. Keep its admission slot
|
|
// until its real promise settles; unresponsive work consumes capacity and
|
|
// subsequent work fails closed. Worker termination requires separate proof.
|
|
class WorkAdmissionError extends Error{
|
|
constructor(code){super(code);this.name='WorkAdmissionError';this.code=code;}
|
|
}
|
|
function createWorkAdmission({maxJobs=8}={}){
|
|
if(!Number.isSafeInteger(maxJobs)||maxJobs<1||maxJobs>64)throw new WorkAdmissionError('work_configuration_invalid');
|
|
let active=0,closed=false;
|
|
return Object.freeze({
|
|
run(fn,{payload}={}){
|
|
if(typeof fn!=='function'||(payload!==undefined&&!Buffer.isBuffer(payload)))throw new WorkAdmissionError('work_configuration_invalid');
|
|
if(closed){payload?.fill(0);throw new WorkAdmissionError('work_admission_closed');}
|
|
if(active>=maxJobs){payload?.fill(0);throw new WorkAdmissionError('work_capacity_exhausted');}
|
|
active++;
|
|
let settled=false;
|
|
const release=()=>{if(settled)return;settled=true;try{payload?.fill(0);}finally{active--;}};
|
|
try{
|
|
// The private payload is transferred to this admission job. Caller
|
|
// must not reuse it; scanner may use it until underlying settlement.
|
|
const result=fn();
|
|
return Promise.resolve(result).then(value=>{release();return value;},error=>{release();throw error;});
|
|
}catch(error){release();throw error;}
|
|
},
|
|
status:()=>Object.freeze({active,maxJobs,closed,available:closed?0:maxJobs-active}),
|
|
close(){closed=true;}
|
|
});
|
|
}
|
|
module.exports={createWorkAdmission,WorkAdmissionError};
|