Check import backpressure after ready parser publication

This commit is contained in:
2026-11-08 21:27:34 +08:00
parent 9769f80cf5
commit b0afe8aa8c
3 changed files with 11 additions and 6 deletions
+1 -1
View File
@@ -387,7 +387,7 @@ export async function openProjectEngine(options: EngineOptions) {
const importSource=source.kind==='duplicate-plan'?createDuplicatePlanSource(copyDocument??await decodeCopySnapshot(bytes,options.scope.projectId,options.registry,preparationController.signal),options.registry,source.plan!,job.sourceFingerprint,indexed?.readElement):createNdjsonImportSource({bytes,fingerprint:job.sourceFingerprint});
if(closing||closed||preparationController.signal.aborted||getImport(id).generation!==job.generation)return;
if(importHolds.size){deferredImportLoads.add(id);return;}
const runner=createImportRunner({optimisticLocal:true,awaitDurabilityBudget:()=>persistenceFailed||pending.size>=32?persist():undefined,source:importSource,getJob:()=>getImport(id),createId,
const runner=createImportRunner({optimisticLocal:true,awaitDurabilityBudget:stage=>pending.size>=32||stage!=='parsed'&&persistenceFailed?persist():undefined,source:importSource,getJob:()=>getImport(id),createId,
publish:publishImport,prepare:(line,job,reserve)=>['duplicate-ndjson','duplicate-plan'].includes(job.source?.kind??'')?prepareDuplicateImport(line,job,reserve,options.registry):prepareNativeImport(line,job,reserve,editor.getSnapshot().document,options.registry),persist,onError:report,
commit(units,next,expected){
assertWritable();if(importing)throw fault('reentrant_dispatch');const current=getImport(id);if(current.generation!==expected)throw fault('stale_import_job');
+3 -3
View File
@@ -102,7 +102,7 @@ export type ImportRunnerPorts={
/** Host must durably gate every network write on the same document/job cut. */
optimisticLocal?:boolean;
/** Host backpressure for speculative local cuts. Network remains durably gated by persist(). */
awaitDurabilityBudget?():void|Promise<void>;
awaitDurabilityBudget?(stage?:'parsed'|'committed'):void|Promise<void>;
now?:()=>number;yieldTask?:()=>Promise<void>;frameBudgetMs?:number;maxPending?:number;maxPendingBytes?:number;maxBatch?:number;
};
@@ -128,7 +128,7 @@ export function createImportRunner(ports:ImportRunnerPorts){
if(new TextEncoder().encode(JSON.stringify(unit)).byteLength>maxBytes)throw fault('import_unit_capacity');
return{unit:freeze(clone(unit)),ids:{...ids}};
}
const checkpoint=async()=>{if(ports.optimisticLocal&&ports.awaitDurabilityBudget)await ports.awaitDurabilityBudget();else await ports.persist();};
const checkpoint=async(stage:'parsed'|'committed'='committed')=>{if(ports.optimisticLocal&&ports.awaitDurabilityBudget)await ports.awaitDurabilityBudget(stage);else await ports.persist();};
const digest=async(unit:ImportUnit)=>[...new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(JSON.stringify(unit))))].map(value=>value.toString(16).padStart(2,'0')).join('');
function completed(job:ImportJob,units:readonly ImportUnit[],pending:readonly ImportLocator[]){
const bindings:Record<Id,ImportBinding>=Object.assign(Object.create(null),job.bindings??{});
@@ -229,7 +229,7 @@ export function createImportRunner(ports:ImportRunnerPorts){
// First ready record displays immediately; subsequent slices amortize publication.
if(costPerUnit===undefined&&staged.pending.some(unit=>unit.dependencies.every(key=>committed.has(key)))||now()-frame>=budget)break;
}
if(staged!==base){const{generation,...patch}=staged;publish(base,patch);if(!ports.optimisticLocal||!staged.pending.some(unit=>unit.dependencies.every(key=>committed.has(key))))await checkpoint();}
if(staged!==base){const{generation,...patch}=staged;publish(base,patch);const ready=staged.pending.some(unit=>unit.dependencies.every(key=>committed.has(key)));if(ports.awaitDurabilityBudget||!ports.optimisticLocal||!ready)await checkpoint(ready?'parsed':'committed');}
}
}