45 lines
4.9 KiB
TypeScript
45 lines
4.9 KiB
TypeScript
/**
|
|
* 只读摘要计算池:active 限并发,waiting 按 Map 插入顺序 FIFO 等待,超队列预算明确拒绝。
|
|
* 它不保存上传/导入 operation,不持久化任务;workspace 拥有生命周期,业务完整性证据由调用者采用结果。
|
|
* 取消先拒绝调用者,但 active 槽只有执行 Promise 真正结束才释放,避免执行器无视 abort 时超发计算。
|
|
*/
|
|
import {fault} from '../canvas-core/values.ts';
|
|
import {importSourceDigest} from './import-integrity.ts';
|
|
/** 可替换摘要端口,输出完整 Blob 的小写 SHA256;close 终止本池,不清业务数据或外部回执。 */
|
|
export type DigestPort={digest(bytes:Blob,signal?:AbortSignal):Promise<string>;close():void};
|
|
/** Read-only compute scheduling: bounded active slots + FIFO Map, never durable operation facts. */
|
|
/** 默认 2 并发/64 等待,上限 8 并发;可注入 execute,默认 importSourceDigest 分块计算。
|
|
* maxQueued 只计算 waiting,不含 active;不限制 Blob 大小/MIME,不做相同文件去重或结果缓存。
|
|
* execute 负责计算真实完整摘要,池仅用正则检查输出格式,不能证明输出确实对应输入字节。 */
|
|
export function createDigestPool(options:{execute?:(bytes:Blob,signal:AbortSignal)=>Promise<string>;concurrency?:number;maxQueued?:number}={}):DigestPort{
|
|
const concurrency=options.concurrency??2,maxQueued=options.maxQueued??64,execute=options.execute??importSourceDigest;
|
|
if(!Number.isSafeInteger(concurrency)||concurrency<1||concurrency>8||!Number.isSafeInteger(maxQueued)||maxQueued<1)throw fault('invalid_digest_budget');
|
|
/** 内存计算句柄:独立 AbortController、一次性完成标记与外部 abort 监听清理,不是 ledger 意图。 */
|
|
type Task={id:number;bytes:Blob;controller:AbortController;resolve:(value:string)=>void;reject:(error:unknown)=>void;cleanup:()=>void;done:boolean};
|
|
// Map 既保留入队次序,又可在等待期取消时 O(1) 删除;active 保留真实未结束执行的容量占用。
|
|
const waiting=new Map<number,Task>(),active=new Map<number,Task>();let next=0,closed=false;
|
|
/** 只 settle 一次并移除 waiting/外部监听,不在这里 active.delete,迟到结果不能二次采纳。
|
|
* error 用真值分支,execute 应以 Error 等真值拒绝;falsy rejection 不保证被当成失败处理。 */
|
|
function finish(task:Task,error?:unknown,value?:string){if(task.done)return;task.done=true;task.cleanup();waiting.delete(task.id);if(error)task.reject(error);else task.resolve(value!);}
|
|
/** 从 Map 首项 FIFO 取任务,先占 active 再微任务调用 execute;启动前、结果返回后均复查 abort。
|
|
* execute 的拒绝由第二个 then 回调处理,结果校验抛错再由 catch 处理,最终统一归还槽位并泵下一项。
|
|
* 正则会按 JS 规则强制转字符串,未另做 typeof 检查;注入执行器应遵守 Promise<string> 合同。
|
|
* 无视 abort 且不 settle 的执行器会一直占槽;池没有超时或强制终止通用执行器的能力。 */
|
|
function pump(){
|
|
if(closed)return;
|
|
while(active.size<concurrency&&waiting.size){const task=waiting.values().next().value!;waiting.delete(task.id);active.set(task.id,task);
|
|
void Promise.resolve().then(()=>{if(task.controller.signal.aborted)throw fault('import_aborted');return execute(task.bytes,task.controller.signal);}).then(value=>{if(task.controller.signal.aborted)throw fault('import_aborted');if(!/^[a-f0-9]{64}$/.test(value))throw fault('invalid_digest_response');finish(task,undefined,value);},error=>finish(task,error)).catch(error=>finish(task,error)).finally(()=>{active.delete(task.id);pump();});
|
|
}
|
|
}
|
|
return Object.freeze({
|
|
/** 检查生命周期、取消、Blob 与容量,建立独立任务;abort 同时通知执行器并取消调用者等待。 */
|
|
digest(bytes:Blob,signal?:AbortSignal):Promise<string>{
|
|
if(closed)return Promise.reject(fault('digest_pool_closed'));if(signal?.aborted)return Promise.reject(fault('import_aborted'));if(!(bytes instanceof Blob))return Promise.reject(fault('invalid_digest_input'));if(active.size>=concurrency&&waiting.size>=maxQueued)return Promise.reject(fault('digest_capacity'));
|
|
return new Promise((resolve,reject)=>{const task:Task={id:++next,bytes,controller:new AbortController(),resolve,reject,cleanup:()=>signal?.removeEventListener('abort',abort),done:false},abort=()=>{task.controller.abort();finish(task,fault('import_aborted'));pump();};signal?.addEventListener('abort',abort,{once:true});waiting.set(task.id,task);pump();});
|
|
},
|
|
/** 幂等关闭并取消 waiting/active 的调用结果,停止接受新任务;不返回 drain Promise。
|
|
* 无视 signal 的执行仍由自身 Promise 收尾后释放 active,close 返回不证明底层计算已停止。 */
|
|
close(){if(closed)return;closed=true;for(const task of [...waiting.values(),...active.values()]){task.controller.abort();finish(task,fault('import_aborted'));}},
|
|
});
|
|
}
|