Deduplicate stored upload files and retain adoption bytes until their Root is durable

This commit is contained in:
2026-11-09 16:31:31 +08:00
parent a626e6c300
commit 902e30a65b
10 changed files with 167 additions and 18 deletions
+5 -5
View File
@@ -79,7 +79,7 @@ export async function openProjectEngine(options: EngineOptions) {
const network = createNetworkGate(2);
const depot=options.assetDepot??(options.storage.assetDepot?createAssetDepot({scope:options.scope,port:options.storage.assetDepot,current:()=>!closing&&!closed&&(!options.isCurrentIdentity||options.isCurrentIdentity()),digest:sourceDigest}):undefined);
if(depot&&(depot.scope.accountId!==options.scope.accountId||depot.scope.workspaceId!==options.scope.workspaceId))throw fault('asset_depot_scope_mismatch');
const adoptionTasks=new Map<AbortController,Promise<Result>>(),adoptedFiles=new Map<Id,Blob>();
const adoptionTasks=new Map<AbortController,Promise<Result>>(),adoptedFiles=new Map<Id,{readonly bytes:Blob;readonly rootRevision:number}>();
let adopting:SharedAsset|undefined;
const runClock: Clock = options.clock ?? { now: () => Date.now(), setTimer: (fn, delay) => setTimeout(fn, delay), clearTimer: handle => clearTimeout(handle as ReturnType<typeof setTimeout>) };
const displays = new Set<() => void>();
@@ -198,7 +198,7 @@ export async function openProjectEngine(options: EngineOptions) {
durableRootRevision = revision; pending.delete(revision);
for(const id of deleteFiles){durablyPurged.add(id);collectionReservations.delete(id);}
for (const file of files) fileWrites.delete(file.id);
for(const id of snapshot.runtime.assetBindings?.keys()??[])adoptedFiles.delete(id);
for(const [id,entry]of adoptedFiles)if(entry.rootRevision<=snapshot.rootRevision&&depot?.writes.some(write=>write.ref.asset.id===id&&write.bytes===entry.bytes))adoptedFiles.delete(id);
if (editor.getSnapshot().runtime.ledger.durableGeneration < snapshot.runtime.ledger.generation) mutate(ledger => ledger.markDurable(snapshot.runtime.ledger.generation));
}
persistenceFailed = false;
@@ -922,9 +922,9 @@ export async function openProjectEngine(options: EngineOptions) {
assertWritable(false);signal.throwIfAborted();if(authorized.ref.asset.url!==options.assets!.sourceURL(id))throw fault('asset_authorization_mismatch');
if(staged.length||adopting)throw fault('reentrant_dispatch');const runtime=editor.getSnapshot().runtime;
if(runtime.resources.has(id)||runtime.orphanFiles?.has(id)||importFiles(runtime.imports).has(id)||fileWrites.has(id)||collectionReservations.has(id))throw fault('resource_identity_conflict',[id]);
const previous=adoptedFiles.get(id);adopting=authorized.ref;adoptedFiles.set(id,authorized.bytes);
const previous=adoptedFiles.get(id);adopting=authorized.ref;adoptedFiles.set(id,{bytes:authorized.bytes,rootRevision:Number.MAX_SAFE_INTEGER});
let committed=false;
try{const result=editor.transaction(tx=>callback(freeze({id,src:authorized.ref.asset.url,asset:authorized.ref.asset}),tx),{label:'Add existing image'});committed=result.status==='applied'&&result.operationIds.length>0;return result;}
try{const result=editor.transaction(tx=>callback(freeze({id,src:authorized.ref.asset.url,asset:authorized.ref.asset}),tx),{label:'Add existing image'});committed=result.status==='applied'&&result.operationIds.length>0;if(committed)adoptedFiles.set(id,{bytes:authorized.bytes,rootRevision:editor.getSnapshot().rootRevision});return result;}
finally{adopting=undefined;if(!committed){if(previous)adoptedFiles.set(id,previous);else adoptedFiles.delete(id);}}
})();
adoptionTasks.set(controller,task);void task.finally(()=>adoptionTasks.delete(controller)).catch(()=>{});return task;
@@ -955,7 +955,7 @@ export async function openProjectEngine(options: EngineOptions) {
const binding=editor.getSnapshot().runtime.assetBindings?.get(id);
const key=JSON.stringify(binding?['shared-asset',id,binding.authority,binding.digest]:retained?['asset',id,retained.operationId,retained.retirement?.purgedAt??null]:orphan?['orphan',id,orphan.digest]:source?['source',id,source.sha256??null]:['unmanaged',id]);
lease=displayCache.acquire(displayOwner,key,async signal=>{
let bytes=adoptedFiles.get(id)??fileWrites.get(id)??await options.storage.readFile(leader,id);
let bytes=adoptedFiles.get(id)?.bytes??fileWrites.get(id)??await options.storage.readFile(leader,id);
if(!bytes&&binding&&depot)bytes=await depot.read(leader,binding,signal);
assertDisplayIdentity();if(signal.aborted)throw fault('display_aborted');
if(orphan&&(!bytes||bytes.size!==orphan.byteLength||bytes.type!==orphan.mimeType||await sourceDigest(bytes,signal)!==orphan.digest))throw fault('file_inventory_changed',[id]);