Synchronize certified canvas scopes while preserving the original loading snapshot
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {loadingQueryCoverage,assertLoadingChanges,loadingCommand,validateLoadingRuntime,validatePrefixExtension,validateLoadingCompletion,type LoadingState} from './loading.ts';
|
||||
import {loadingCanSend,mergeLoadingRead,loadingQueryCoverage,assertLoadingChanges,loadingCommand,validateLoadingRuntime,validatePrefixExtension,validateLoadingCompletion,type LoadingState} from './loading.ts';
|
||||
import {validImageDimensions,type ImageDecoderPort} from './image-decoder.ts';
|
||||
import {unitScale} from '../canvas-core/units.ts';
|
||||
import { validateCreationSizeEffect } from '../canvas-core/creation-size-plan.ts';
|
||||
@@ -98,14 +98,14 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
let recovery: { controller: AbortController; release(): void; cancel():Promise<void> } | undefined;
|
||||
const admissionPins=(snapshot:EngineSnapshot)=>{const pins=new Map<Id,readonly Id[]>();if(copyPreparations.size)pins.set('copy-admission',[...snapshot.runtime.resources.values()].filter(resource=>resource.retirement?.purgedAt===undefined).map(resource=>resource.id));if(imageTasks.size)pins.set('image-decode',[...imageTasks.keys()]);return pins;};
|
||||
const assertWritable = (complete=true) => { if(options.isCurrentIdentity&&!options.isCurrentIdentity())throw fault('identity_changed');if(complete&&editor.getSnapshot().runtime.loading)throw fault('model_loading');if(closing||closed)throw fault('engine_closing');if(recovery)throw fault('recovery_in_progress'); };
|
||||
const initial: RuntimeState = recovered?.runtime ?? Object.freeze({ imports:readMap(new Map()), recoveries:readMap(new Map()), runs: readMap(new Map()), acknowledged: createDocument(options.document.projectId, [...options.document.canvases.values()], [...options.document.elements.values()], options.document.tombstones), versions: openingVersions, ...(openingLoading?{loading:openingLoading}:{}), ledger: createLedger().getSnapshot(), receipts: readMap(new Map()), conflicts: readMap(new Map()), resources: readMap(new Map()), resourcePins: readMap(new Map()) });
|
||||
const initial: RuntimeState = recovered?.runtime ?? Object.freeze({ imports:readMap(new Map()), recoveries:readMap(new Map()), runs: readMap(new Map()), acknowledged: createDocument(options.document.projectId, [...options.document.canvases.values()], [...options.document.elements.values()], options.document.tombstones), versions: openingVersions, ...(openingLoading?{loading:openingLoading,...(openingLoading.version===3?{loadingRead:Object.freeze({document:createDocument(options.document.projectId,[...options.document.canvases.values()],[...options.document.elements.values()],options.document.tombstones),versions:openingVersions})}:{})}:{}), ledger: createLedger().getSnapshot(), receipts: readMap(new Map()), conflicts: readMap(new Map()), resources: readMap(new Map()), resourcePins: readMap(new Map()) });
|
||||
validateLoadingRuntime(initial,options.registry);
|
||||
const editor = createEditor({ registry: options.registry, document: options.document, recovery: recovered, runtime: initial, createId: options.createId, historyLimit: options.historyLimit,queryBudget:options.queryBudget,queryCoverage:loadingQueryCoverage,
|
||||
policy(command, document) { if(options.isCurrentIdentity&&!options.isCurrentIdentity())return{enabled:false,code:'identity_changed',targets:[]};if (closing || closed || recovery) return { enabled: false, code: closing||closed?'engine_closing':'recovery_in_progress', targets: [] }; if(editor.getSnapshot().runtime.loading){const allowed=loadingCommand(command,document,options.registry,editor.getSnapshot().runtime.loading,editor.getSnapshot().runtime.acknowledged);if(!allowed.enabled)return allowed;}return options.policy?.(command, document) ?? { enabled: true }; },
|
||||
traversalPolicy(entry, direction) { if(options.isCurrentIdentity&&!options.isCurrentIdentity())return{enabled:false,code:'identity_changed',targets:[]};if(closing||closed||recovery)return{enabled:false,code:closing||closed?'engine_closing':'recovery_in_progress',targets:[]};if(editor.getSnapshot().runtime.loading){try{assertLoadingChanges(entry.changes,editor.getSnapshot().runtime.acknowledged,options.registry,editor.getSnapshot().runtime.loading);}catch(error){return{enabled:false,code:(error as Error).message,targets:[]};}}return options.traversalPolicy?.(entry,direction)??{enabled:true}; },
|
||||
onListenerError: report,
|
||||
prepareRuntime:(previous,next)=>{
|
||||
if(!acceptingLoading&&!recovery&&(previous.runtime.loading||next.runtime.loading)&&(!equal(previous.runtime.loading,next.runtime.loading)||previous.runtime.acknowledged!==next.runtime.acknowledged||!equal(Object.fromEntries(previous.runtime.versions),Object.fromEntries(next.runtime.versions))))throw fault('loading_state_owned');
|
||||
if(!acceptingLoading&&!recovery&&(previous.runtime.loading||next.runtime.loading||previous.runtime.loadingRead||next.runtime.loadingRead)&&(!equal(previous.runtime.loading,next.runtime.loading)||previous.runtime.loadingRead!==next.runtime.loadingRead||previous.runtime.acknowledged!==next.runtime.acknowledged||!equal(Object.fromEntries(previous.runtime.versions),Object.fromEntries(next.runtime.versions))))throw fault('loading_state_owned');
|
||||
validateLoadingRuntime(next.runtime,options.registry);
|
||||
const sizing=prepareDefaultImageSizing(previous,next);
|
||||
const runtime=prepareResourceRetention({...next,runtime:sizing},options.registry,displayPins,collectionReservations,admissionPins(next));
|
||||
@@ -171,7 +171,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
}
|
||||
persistenceFailed = false;
|
||||
})();
|
||||
writing = task.catch(error => { persistenceFailed = true; throw error; }).finally(() => { writing = undefined;if(!persistenceFailed&&loadingRelease&&loadingCompletionRevision!==undefined&&durableRootRevision>=loadingCompletionRevision&&!editor.getSnapshot().runtime.loading){const release=loadingRelease;loadingRelease=undefined;loadingCompletionRevision=undefined;release();} });
|
||||
writing = task.catch(error => { persistenceFailed = true; throw error; }).finally(() => { writing = undefined;if(!persistenceFailed){if(loadingCompletionRevision!==undefined&&durableRootRevision>=loadingCompletionRevision&&!editor.getSnapshot().runtime.loading){const release=loadingRelease;loadingRelease=undefined;loadingCompletionRevision=undefined;release?.();}if(!closing&&!closed)scheduler.refreshAdmission();} });
|
||||
return writing;
|
||||
}
|
||||
function collectResources(settings:{retentionMs:number;maxFiles?:number}) {
|
||||
@@ -383,7 +383,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
}
|
||||
if(action){const run=runs.get(action.runId);if(!run)throw fault('run_identity_mismatch');runs.set(run.id,freeze({...run,controlEpoch:(run.controlEpoch??0)+1,appliedOutputIds:[...new Set([...(run.appliedOutputIds??[]),action.outputId!])],hasMore:true,blocked:false,error:undefined,pendingPage:undefined,nextPollAt:runClock.now()}));}
|
||||
const runtime: RuntimeState = Object.freeze({ ...previous, runs: readMap(runs), ledger: ledger.getSnapshot(), acknowledged, versions: readMap(proposal.versions), receipts: readMap(receipts), conflicts: readMap(conflicts) });
|
||||
const result = editor.reconcile(document, runtime, root.rootRevision);
|
||||
const oldAccepting=acceptingLoading;acceptingLoading=oldAccepting||previous.loading?.version===3;let result:Result;try{result=editor.reconcile(document, runtime, root.rootRevision);}finally{acceptingLoading=oldAccepting;}
|
||||
if (result.status === 'rejected') throw fault(result.code, result.targets);
|
||||
if(action)queueMicrotask(()=>runTracker.notify([action.runId]));
|
||||
} catch (error) {
|
||||
@@ -395,7 +395,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
report(error); return { blocked: true };
|
||||
}
|
||||
}
|
||||
const scheduler = createScheduler({ getLedger: () => editor.getSnapshot().runtime.ledger, mutate, persist,
|
||||
const scheduler = createScheduler({ admit:intent=>{const root=editor.getSnapshot();if(root.runtime.loading&&root.runtime.loading.version!==3)return true;if(closing||closed||loadingCompletionRevision!==undefined&&durableRootRevision<loadingCompletionRevision)return false;return loadingCanSend(root.runtime,operationCanvases(intent,root.document))&&(!root.runtime.loading||durableRootRevision>=root.rootRevision);},getLedger: () => editor.getSnapshot().runtime.ledger, mutate, persist,
|
||||
createId: options.createId ?? (() => crypto.randomUUID()),
|
||||
participants: intent => { if(intent.historyEffect)validateCreationSizeEffect(intent.historyEffect);return operationCanvases(intent, editor.getSnapshot().document); },
|
||||
compile(intent, id) {
|
||||
@@ -424,14 +424,16 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
query: (request, signal) => network.run(signal, () => ['run.start','run.cancel','run.apply'].includes(request.kind??'') ? options.runs?.query(request,signal) ?? Promise.resolve({ outcome: 'unresolved', blocked: true, error: 'run_interpreter_unavailable' }) : request.kind === 'asset.upload' ? options.assets?.query(request, signal) ?? Promise.resolve({ outcome: 'unresolved', blocked: true, error: 'asset_interpreter_unavailable' }) : options.persistence.query(request, signal)),
|
||||
supportsRequest: request => ['run.start','run.cancel','run.apply'].includes(request.kind??'') ? !!options.runs && request.protocolVersion === 2 : request.kind === 'asset.upload' ? !!options.assets && request.protocolVersion === 2 : options.persistence.supportsRequest?.(request) ?? request.protocolVersion === 1, complete, onError: report, clock: options.clock,
|
||||
});
|
||||
if(initial.loading)loadingRelease=scheduler.hold();
|
||||
if(initial.loading&&initial.loading.version!==3)loadingRelease=scheduler.hold();
|
||||
function completeLoading(document:Document,versions:ReadonlyMap<Id,number>,settings:{fresh?:boolean;sourceToken?:string}={}):Result{
|
||||
try{assertWritable(false);const root=editor.getSnapshot(),previous=root.runtime;if(!previous.loading)throw fault('loading_not_active');
|
||||
validateLoadingCompletion(previous.acknowledged,previous.versions,previous.loading,document,versions,options.registry,settings.fresh,settings.sourceToken);
|
||||
const acknowledged=createDocument(document.projectId,[...document.canvases.values()],[...document.elements.values()],document.tombstones),working=replayPending(acknowledged,previous.ledger,options.registry),conflicts=new Map(previous.conflicts);conflicts.delete('loading');conflicts.delete('refresh');
|
||||
acceptingLoading=true;const result=editor.reconcile(working,Object.freeze({...previous,loading:undefined,acknowledged,versions:readMap(versions),conflicts:readMap(conflicts)}),root.rootRevision);
|
||||
if(settings.fresh&&previous.loading.version===3&&[...previous.ledger.requests.values()].some(request=>request.phase!=='resolved'))throw fault('loading_waiting_for_receipts');
|
||||
const source=!settings.fresh&&previous.loading.version===3?previous.loadingRead:undefined;validateLoadingCompletion(source?.document??previous.acknowledged,source?.versions??previous.versions,previous.loading,document,versions,options.registry,settings.fresh,settings.sourceToken);
|
||||
const proposal=!settings.fresh?mergeLoadingRead(previous,{document,versions},options.registry):{document,versions};
|
||||
const acknowledged=createDocument(document.projectId,[...proposal.document.canvases.values()],[...proposal.document.elements.values()],proposal.document.tombstones),working=replayPending(acknowledged,previous.ledger,options.registry),conflicts=new Map(previous.conflicts);conflicts.delete('loading');conflicts.delete('refresh');
|
||||
acceptingLoading=true;const result=editor.reconcile(working,Object.freeze({...previous,loading:undefined,loadingRead:undefined,acknowledged,versions:readMap(proposal.versions),conflicts:readMap(conflicts)}),root.rootRevision);
|
||||
if(result.status==='applied')loadingCompletionRevision=editor.getSnapshot().rootRevision;return result;
|
||||
}catch(error){const result=reject(error);if(editor.getSnapshot().runtime.loading&&!['engine_closing','identity_changed','recovery_in_progress','loading_not_active'].includes(result.code))editor.updateRuntime(previous=>{const conflicts=new Map(previous.conflicts);conflicts.set('loading',result.code);return Object.freeze({...previous,conflicts:readMap(conflicts)});});return result;}finally{acceptingLoading=false;}
|
||||
}catch(error){const result=reject(error);if(editor.getSnapshot().runtime.loading&&!['engine_closing','identity_changed','recovery_in_progress','loading_not_active','loading_waiting_for_receipts'].includes(result.code))editor.updateRuntime(previous=>{const conflicts=new Map(previous.conflicts);conflicts.set('loading',result.code);return Object.freeze({...previous,conflicts:readMap(conflicts)});});return result;}finally{acceptingLoading=false;}
|
||||
}
|
||||
function updateRun(id: Id, reduce: (run: RunRecord) => RunRecord) {
|
||||
const result = editor.updateRuntime(previous => { const current = previous.runs.get(id);if(!current)throw fault('run_missing');const runs = new Map(previous.runs);runs.set(id,freeze(reduce(current)));return Object.freeze({...previous,runs:readMap(runs)}); });
|
||||
@@ -707,12 +709,15 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
loading:Object.freeze({getState:()=>editor.getSnapshot().runtime.loading,
|
||||
resume():Promise<Result>{
|
||||
if(loadingRead)return loadingRead;
|
||||
const task=(async()=>{assertWritable(false);if(!editor.getSnapshot().runtime.loading)return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:[]};if(!options.persistence.read)throw fault('canonical_reader_unavailable');const controller=new AbortController();loadingReadController=controller;
|
||||
try{const read=await network.run(controller.signal,()=>options.persistence.read!(controller.signal));assertWritable(false);controller.signal.throwIfAborted();if(!editor.getSnapshot().runtime.loading)return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:[]};const result=completeLoading(read.document,read.versions,{fresh:true});if(result.status==='applied')await persist();return result;}finally{if(loadingReadController===controller)loadingReadController=undefined;}
|
||||
const task=(async()=>{assertWritable(false);if(!editor.getSnapshot().runtime.loading)return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:[]};if(!options.persistence.read)throw fault('canonical_reader_unavailable');const controller=new AbortController();loadingReadController=controller;let releaseRead:(()=>void)|undefined;
|
||||
try{if(editor.getSnapshot().runtime.loading?.version===3)await new Promise<void>((resolve,reject)=>{
|
||||
let unsubscribe=()=>{};const finish=(error?:unknown)=>{unsubscribe();controller.signal.removeEventListener('abort',abort);error===undefined?resolve():reject(error);},abort=()=>finish(controller.signal.reason),check=()=>{if(![...editor.getSnapshot().runtime.ledger.requests.values()].some(request=>request.phase!=='resolved')){releaseRead=scheduler.hold();finish();}};
|
||||
unsubscribe=editor.subscribe(check);controller.signal.addEventListener('abort',abort,{once:true});if(controller.signal.aborted)abort();else check();
|
||||
});const read=await network.run(controller.signal,()=>options.persistence.read!(controller.signal));assertWritable(false);controller.signal.throwIfAborted();if(!editor.getSnapshot().runtime.loading)return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:[]};const result=completeLoading(read.document,read.versions,{fresh:true});if(result.status==='applied')await persist();return result;}finally{releaseRead?.();if(loadingReadController===controller)loadingReadController=undefined;}
|
||||
})();loadingRead=task.finally(()=>{if(loadingRead===wrapped)loadingRead=undefined;});const wrapped=loadingRead;return wrapped;
|
||||
},
|
||||
accept(document:Document,versions:ReadonlyMap<Id,number>,state:LoadingState):Result{
|
||||
try{assertWritable(false);const root=editor.getSnapshot(),previous=root.runtime;if(!previous.loading)throw fault('loading_not_active');validatePrefixExtension(previous.acknowledged,previous.versions,previous.loading,document,versions,state,options.registry);const acknowledged=createDocument(document.projectId,[...document.canvases.values()],[...document.elements.values()],document.tombstones),working=replayPending(acknowledged,previous.ledger,options.registry);acceptingLoading=true;return editor.reconcile(working,Object.freeze({...previous,loading:freeze(clone(state)),acknowledged}),root.rootRevision);}catch(error){return reject(error);}finally{acceptingLoading=false;}
|
||||
try{assertWritable(false);const root=editor.getSnapshot(),previous=root.runtime;if(!previous.loading)throw fault('loading_not_active');const source=previous.loadingRead;validatePrefixExtension(source?.document??previous.acknowledged,source?.versions??previous.versions,previous.loading,document,versions,state,options.registry);const nextRead=Object.freeze({document:createDocument(document.projectId,[...document.canvases.values()],[...document.elements.values()],document.tombstones),versions:readMap(versions)}),proposal=mergeLoadingRead(previous,nextRead,options.registry),acknowledged=proposal.document,working=replayPending(acknowledged,previous.ledger,options.registry);acceptingLoading=true;return editor.reconcile(working,Object.freeze({...previous,loading:freeze(clone(state)),...(state.version===3?{loadingRead:nextRead}:{}),versions:readMap(proposal.versions),acknowledged}),root.rootRevision);}catch(error){return reject(error);}finally{acceptingLoading=false;}
|
||||
},
|
||||
async complete(document:Document,versions:ReadonlyMap<Id,number>,settings:{fresh?:boolean;sourceToken?:string}={}):Promise<Result>{const result=completeLoading(document,versions,settings);if(result.status==='applied')await persist();return result;},
|
||||
}),
|
||||
@@ -743,7 +748,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
try{working=replayPending(acknowledged,previous.ledger,options.registry);}catch(error){const rejected=reject(error);conflict=freeze({code:rejected.code,targets:rejected.targets});}
|
||||
let committed:ConflictPlan|'refresh'|undefined,committedIds:readonly Id[]=[];
|
||||
const plans=new WeakSet<ConflictPlan>();
|
||||
const finish=async()=>{if(loadingRelease&&!editor.getSnapshot().runtime.loading)loadingCompletionRevision=editor.getSnapshot().rootRevision;await persist();assertCurrent();await scheduler.resume();assertCurrent();scheduler.notify(committedIds);runTracker.notify(staged.runIds);active.release();return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:committedIds};};
|
||||
const finish=async()=>{if(previous.loading&&!editor.getSnapshot().runtime.loading)loadingCompletionRevision=editor.getSnapshot().rootRevision;await persist();assertCurrent();await scheduler.resume();assertCurrent();scheduler.notify(committedIds);runTracker.notify(staged.runIds);active.release();return{status:'applied' as const,revision:editor.getSnapshot().revision,operationIds:committedIds};};
|
||||
active.cancel=async()=>{if(recovery!==active)return;if(committed){await finish();return;}active.release();};
|
||||
return Object.freeze({baseRootRevision:root.rootRevision,canonical,acknowledgedRequestIds:staged.requestIds,
|
||||
pendingOperationIds:Object.freeze([...previous.ledger.operations.values()].filter(record=>record.phase!=='resolved').map(record=>record.intent.id)),
|
||||
@@ -760,7 +765,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
if(intent.operationType==='run.apply'){const run=previous.runs.get(intent.action.runId);if(!run||run.blocked||run.status!=='conflict'||!runContainsTarget(run,intent.action.target))return{status:'rejected',code:'output_not_applicable',targets:[intent.action.target.id]};}}
|
||||
const recoveries=new Map(previous.recoveries),resourcePins=new Map(previous.resourcePins),conflicts=new Map(previous.conflicts);recoveries.set(plan.id,plan.audit);resourcePins.set(`recovery:${plan.id}`,plan.resourceIds);conflicts.delete('refresh');
|
||||
for(const decision of plan.audit.decisions){const requestId=previous.ledger.operations.get(decision.operationId)?.requestId;if(requestId)conflicts.delete(requestId);}
|
||||
const result=editor.reconcile(plan.document,Object.freeze({...previous,ledger:plan.ledger,loading:undefined,acknowledged,versions:canonical.versions,recoveries:readMap(recoveries),resourcePins:readMap(resourcePins),conflicts:readMap(conflicts)}),root.rootRevision,plan);
|
||||
const result=editor.reconcile(plan.document,Object.freeze({...previous,ledger:plan.ledger,loading:undefined,loadingRead:undefined,acknowledged,versions:canonical.versions,recoveries:readMap(recoveries),resourcePins:readMap(resourcePins),conflicts:readMap(conflicts)}),root.rootRevision,plan);
|
||||
if(result.status==='rejected')return result;committed=plan;committedIds=plan.operationIds;
|
||||
}
|
||||
return finish();
|
||||
@@ -771,7 +776,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
if(conflict)return{status:'rejected',...conflict};
|
||||
if(!committed){
|
||||
const conflicts=new Map(previous.conflicts);conflicts.delete('refresh');
|
||||
const result=editor.reconcile(working!,Object.freeze({...previous,loading:undefined,acknowledged,versions:canonical.versions,conflicts:readMap(conflicts)}),root.rootRevision);
|
||||
const result=editor.reconcile(working!,Object.freeze({...previous,loading:undefined,loadingRead:undefined,acknowledged,versions:canonical.versions,conflicts:readMap(conflicts)}),root.rootRevision);
|
||||
if(result.status==='rejected')return result;
|
||||
committed='refresh';
|
||||
}
|
||||
@@ -917,7 +922,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
cancelRecovery:async()=>{await recovery?.cancel();},
|
||||
getDurability: () => Object.freeze({ rootRevision: editor.getSnapshot().rootRevision, durableRootRevision }),
|
||||
retry: async () => { if(recovery)await recovery.cancel();assertWritable(false);await persist();retryDefaultSizes();const runtime=editor.getSnapshot().runtime,ids=[...runtime.resources.values()].filter(resource=>!resource.digest&&runtime.ledger.operations.get(resource.operationId)?.blockers.some(code=>digestFailures.has(code))).map(resource=>resource.operationId);if(ids.length){mutate(ledger=>{for(const id of ids)ledger.block(id,['waiting_digest']);});prepareDigests();}if(options.images){editor.updateRuntime(previous=>{const resources=new Map(previous.resources);let changed=false;for(const [id,resource]of resources)if(resource.image?.version===1&&resource.image.status==='failed'){resources.set(id,freeze({...resource,image:{version:1 as const,status:'pending' as const}}));changed=true;}return changed?Object.freeze({...previous,resources:readMap(resources)}):previous;});prepareImages();}await scheduler.resume();await runTracker.retry(); },
|
||||
async settle() { await Promise.resolve();await Promise.allSettled([...copyPreparations.values()]);while(importLoads.size)await Promise.all([...importLoads.values()]);await Promise.all([...importRunners.values()].map(runner=>runner.settle())); while (digestTasks.size) await Promise.all([...digestTasks.values()]);while(imageTasks.size)await Promise.all([...imageTasks.values()]);while(sizingTask)await sizingTask; await scheduler.settle(); await runTracker.settle(); await persist(); },
|
||||
async settle() { await Promise.resolve();await Promise.allSettled([...copyPreparations.values()]);while(importLoads.size)await Promise.all([...importLoads.values()]);await Promise.all([...importRunners.values()].map(runner=>runner.settle())); while (digestTasks.size) await Promise.all([...digestTasks.values()]);while(imageTasks.size)await Promise.all([...imageTasks.values()]);while(sizingTask)await sizingTask; await scheduler.settle(); await runTracker.settle(); await persist();await scheduler.settle();await persist(); },
|
||||
async close() {
|
||||
if (closed) return;
|
||||
closing = true;loadingReadController?.abort();await loadingRead?.catch(()=>{});unsubscribeSizing();orphanController?.abort();await Promise.allSettled([collection,sourceCollection,orphanCollection].filter(Boolean));digestLifecycle.abort();for(const controller of preparationControllers.values())controller.abort();for(const controller of copyPreparations.keys())controller.abort();await Promise.allSettled([...copyPreparations.values()]); sessions.dispose(); scheduler.stop(); runTracker.stop();
|
||||
|
||||
Reference in New Issue
Block a user