diff --git a/packages/runtime/container-runtime/src/containerRuntime.ts b/packages/runtime/container-runtime/src/containerRuntime.ts index 1c9ffe034292..1cbe4747f363 100644 --- a/packages/runtime/container-runtime/src/containerRuntime.ts +++ b/packages/runtime/container-runtime/src/containerRuntime.ts @@ -150,7 +150,7 @@ import { type OutboundContainerRuntimeMessage, type UnknownContainerRuntimeMessage, } from "./messageTypes.js"; -import { IBatchMetadata, IIdAllocationMetadata } from "./metadata.js"; +import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js"; import { BatchMessage, IBatch, @@ -671,11 +671,13 @@ type MessageWithContext = message: InboundSequencedContainerRuntimeMessage; modernRuntimeMessage: true; local: boolean; + savedOp?: boolean; } | { message: InboundSequencedContainerRuntimeMessageOrSystemMessage; modernRuntimeMessage: false; local: boolean; + savedOp?: boolean; }; const summarizerRequestUrl = "_summarizer"; @@ -1128,7 +1130,7 @@ export class ContainerRuntime // Id Compressor serializes final state (see getPendingLocalState()). As result, it needs to skip all ops that preceeded that state // (such ops will be marked by Loader layer as savedOp === true) // That said, in "delayed" mode it's possible that Id Compressor was never initialized before getPendingLocalState() is called. - // In such case we have to process all ops, including those marked with saveOp === true. + // In such case we have to process all ops, including those marked with savedOp === true. private readonly skipSavedCompressorOps: boolean; public get idCompressorMode() { @@ -2540,21 +2542,28 @@ export class ContainerRuntime // We do not need to make a deep copy. Each layer will just replace message.contents itself, // but will not modify the contents object (likely it will replace it on the message). const messageCopy = { ...messageArg }; + const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp; for (const message of this.remoteMessageProcessor.process(messageCopy)) { - if (modernRuntimeMessage) { - this.processCore({ - // Cast it since we expect it to be this based on modernRuntimeMessage computation above. - // There is nothing really ensuring that anytime original message.type is Operation that - // the result messages will be so. In the end modern bool being true only directs to - // throw error if ultimately unrecognized without compat details saying otherwise. - message: message as InboundSequencedContainerRuntimeMessage, - local, - modernRuntimeMessage, - }); - } else { - // Unrecognized message will be ignored. - this.processCore({ message, local, modernRuntimeMessage }); - } + const msg: MessageWithContext = modernRuntimeMessage + ? { + // Cast it since we expect it to be this based on modernRuntimeMessage computation above. + // There is nothing really ensuring that anytime original message.type is Operation that + // the result messages will be so. In the end modern bool being true only directs to + // throw error if ultimately unrecognized without compat details saying otherwise. + message: message as InboundSequencedContainerRuntimeMessage, + local, + modernRuntimeMessage, + } + : // Unrecognized message will be ignored. + { + message, + local, + modernRuntimeMessage, + }; + msg.savedOp = savedOp; + + // ensure that we observe any re-entrancy, and if needed, rebase ops + this.ensureNoDataModelChanges(() => this.processCore(msg)); } } @@ -2642,13 +2651,7 @@ export class ContainerRuntime // stashed ops flow. The compressor is stashed with these ops already processed. // That said, in idCompressorMode === "delayed", we might not serialize ID compressor, and // thus we need to process all the ops. - if ( - !( - this.skipSavedCompressorOps && - (messageWithContext.message.metadata as IIdAllocationMetadata)?.savedOp === - true - ) - ) { + if (!(this.skipSavedCompressorOps && messageWithContext.savedOp === true)) { const range = messageWithContext.message.contents; // Some other client turned on the id compressor. If we have not turned it on, // put it in a pending queue and delay finalization. diff --git a/packages/runtime/container-runtime/src/metadata.ts b/packages/runtime/container-runtime/src/metadata.ts index e81c576773ed..7e7fca339463 100644 --- a/packages/runtime/container-runtime/src/metadata.ts +++ b/packages/runtime/container-runtime/src/metadata.ts @@ -19,8 +19,8 @@ export interface IBlobMetadata { } /** - * The IdCompressor needs to know if this is a replayed savedOp as those need to be skipped in stashed ops scenarios. + * ContainerRuntime needs to know if this is a replayed savedOp as those need to be skipped in stashed ops scenarios. */ -export interface IIdAllocationMetadata { +export interface ISavedOpMetadata { savedOp?: boolean; }