@fluidframework/container-runtime 2.1.0-281041 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +4 -4
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/containerRuntime.d.ts.map +1 -1
  5. package/dist/containerRuntime.js +11 -3
  6. package/dist/containerRuntime.js.map +1 -1
  7. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  8. package/dist/opLifecycle/batchManager.js +1 -1
  9. package/dist/opLifecycle/batchManager.js.map +1 -1
  10. package/dist/opLifecycle/index.d.ts +1 -1
  11. package/dist/opLifecycle/index.d.ts.map +1 -1
  12. package/dist/opLifecycle/index.js +2 -1
  13. package/dist/opLifecycle/index.js.map +1 -1
  14. package/dist/opLifecycle/outbox.js +1 -1
  15. package/dist/opLifecycle/outbox.js.map +1 -1
  16. package/dist/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  17. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  18. package/dist/opLifecycle/remoteMessageProcessor.js +22 -10
  19. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.d.ts.map +1 -1
  22. package/dist/packageVersion.js +1 -1
  23. package/dist/packageVersion.js.map +1 -1
  24. package/lib/containerRuntime.d.ts.map +1 -1
  25. package/lib/containerRuntime.js +12 -4
  26. package/lib/containerRuntime.js.map +1 -1
  27. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  28. package/lib/opLifecycle/batchManager.js +1 -1
  29. package/lib/opLifecycle/batchManager.js.map +1 -1
  30. package/lib/opLifecycle/index.d.ts +1 -1
  31. package/lib/opLifecycle/index.d.ts.map +1 -1
  32. package/lib/opLifecycle/index.js +1 -1
  33. package/lib/opLifecycle/index.js.map +1 -1
  34. package/lib/opLifecycle/outbox.js +1 -1
  35. package/lib/opLifecycle/outbox.js.map +1 -1
  36. package/lib/opLifecycle/remoteMessageProcessor.d.ts +4 -2
  37. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  38. package/lib/opLifecycle/remoteMessageProcessor.js +20 -9
  39. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  40. package/lib/packageVersion.d.ts +1 -1
  41. package/lib/packageVersion.d.ts.map +1 -1
  42. package/lib/packageVersion.js +1 -1
  43. package/lib/packageVersion.js.map +1 -1
  44. package/package.json +24 -24
  45. package/src/containerRuntime.ts +15 -3
  46. package/src/opLifecycle/batchManager.ts +4 -1
  47. package/src/opLifecycle/index.ts +5 -1
  48. package/src/opLifecycle/outbox.ts +1 -1
  49. package/src/opLifecycle/remoteMessageProcessor.ts +33 -10
  50. package/src/packageVersion.ts +1 -1
@@ -171,6 +171,7 @@ import { IBatchMetadata, ISavedOpMetadata } from "./metadata.js";
171
171
  import {
172
172
  BatchId,
173
173
  BatchMessage,
174
+ ensureContentsDeserialized,
174
175
  IBatch,
175
176
  IBatchCheckpoint,
176
177
  OpCompressor,
@@ -2616,14 +2617,25 @@ export class ContainerRuntime
2616
2617
  // or something different, like a system message.
2617
2618
  const modernRuntimeMessage = messageArg.type === MessageType.Operation;
2618
2619
 
2620
+ const savedOp = (messageArg.metadata as ISavedOpMetadata)?.savedOp;
2621
+
2622
+ // There is some ancient back-compat code that we'd like to instrument
2623
+ // to understand if/when it is hit.
2624
+ const logLegacyCase = (codePath: string) =>
2625
+ this.logger.sendTelemetryEvent({
2626
+ eventName: "LegacyMessageFormat",
2627
+ details: { codePath, type: messageArg.type },
2628
+ });
2629
+
2619
2630
  // Do shallow copy of message, as the processing flow will modify it.
2620
2631
  // There might be multiple container instances receiving the same message.
2621
2632
  // We do not need to make a deep copy. Each layer will just replace message.contents itself,
2622
2633
  // but will not modify the contents object (likely it will replace it on the message).
2623
2634
  const messageCopy = { ...messageArg };
2624
- const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2635
+ // We expect runtime messages to have JSON contents - deserialize it in place.
2636
+ ensureContentsDeserialized(messageCopy, modernRuntimeMessage, logLegacyCase);
2625
2637
  if (modernRuntimeMessage) {
2626
- const processResult = this.remoteMessageProcessor.process(messageCopy);
2638
+ const processResult = this.remoteMessageProcessor.process(messageCopy, logLegacyCase);
2627
2639
  if (processResult === undefined) {
2628
2640
  // This means the incoming message is an incomplete part of a message or batch
2629
2641
  // and we need to process more messages before the rest of the system can understand it.
@@ -4324,7 +4336,7 @@ export class ContainerRuntime
4324
4336
  fetchSource: FetchSource.noCache,
4325
4337
  });
4326
4338
  const id = snapshot.snapshotTree.id;
4327
- assert(id !== undefined, "id of the fetched snapshot should be defined");
4339
+ assert(id !== undefined, 0x9d0 /* id of the fetched snapshot should be defined */);
4328
4340
  props.snapshotVersion = id;
4329
4341
  snapshotTree = snapshot.snapshotTree;
4330
4342
  } else {
@@ -156,7 +156,10 @@ const addBatchMetadata = (batch: IBatch, batchId?: BatchId): IBatch => {
156
156
 
157
157
  const firstMsg = batch.messages[0];
158
158
  const lastMsg = batch.messages[batchEnd];
159
- assert(firstMsg !== undefined && lastMsg !== undefined, "expected non-empty batch");
159
+ assert(
160
+ firstMsg !== undefined && lastMsg !== undefined,
161
+ 0x9d1 /* expected non-empty batch */,
162
+ );
160
163
 
161
164
  const firstMetadata: Partial<IBatchMetadata> = firstMsg.metadata ?? {};
162
165
  const lastMetadata: Partial<IBatchMetadata> = lastMsg.metadata ?? {};
@@ -16,7 +16,11 @@ export { Outbox, getLongStack } from "./outbox.js";
16
16
  export { OpCompressor } from "./opCompressor.js";
17
17
  export { OpDecompressor } from "./opDecompressor.js";
18
18
  export { OpSplitter, splitOp, isChunkedMessage } from "./opSplitter.js";
19
- export { RemoteMessageProcessor, unpackRuntimeMessage } from "./remoteMessageProcessor.js";
19
+ export {
20
+ ensureContentsDeserialized,
21
+ RemoteMessageProcessor,
22
+ unpackRuntimeMessage,
23
+ } from "./remoteMessageProcessor.js";
20
24
  export {
21
25
  OpGroupingManager,
22
26
  OpGroupingManagerConfig,
@@ -298,7 +298,7 @@ export class Outbox {
298
298
  clientSequenceNumber = this.sendBatch(processedBatch);
299
299
  assert(
300
300
  clientSequenceNumber === undefined || clientSequenceNumber >= 0,
301
- "unexpected negative clientSequenceNumber (empty batch should yield undefined)",
301
+ 0x9d2 /* unexpected negative clientSequenceNumber (empty batch should yield undefined) */,
302
302
  );
303
303
  }
304
304
 
@@ -70,7 +70,10 @@ export class RemoteMessageProcessor {
70
70
  * @returns all the unchunked, decompressed, ungrouped, unpacked InboundSequencedContainerRuntimeMessage from a single batch
71
71
  * or undefined if the batch is not yet complete.
72
72
  */
73
- public process(remoteMessageCopy: ISequencedDocumentMessage):
73
+ public process(
74
+ remoteMessageCopy: ISequencedDocumentMessage,
75
+ logLegacyCase: (codePath: string) => void,
76
+ ):
74
77
  | {
75
78
  messages: InboundSequencedContainerRuntimeMessage[];
76
79
  batchStartCsn: number;
@@ -78,8 +81,6 @@ export class RemoteMessageProcessor {
78
81
  | undefined {
79
82
  let message = remoteMessageCopy;
80
83
 
81
- ensureContentsDeserialized(message);
82
-
83
84
  if (isChunkedMessage(message)) {
84
85
  const chunkProcessingResult = this.opSplitter.processChunk(message);
85
86
  // Only continue further if current chunk is the final chunk
@@ -104,10 +105,13 @@ export class RemoteMessageProcessor {
104
105
 
105
106
  if (isGroupedBatch(message)) {
106
107
  // We should be awaiting a new batch (batchStartCsn undefined)
107
- assert(this.batchStartCsn === undefined, "Grouped batch interrupting another batch");
108
+ assert(
109
+ this.batchStartCsn === undefined,
110
+ 0x9d3 /* Grouped batch interrupting another batch */,
111
+ );
108
112
  assert(
109
113
  this.processorBatch.length === 0,
110
- "Processor batch should be empty on grouped batch",
114
+ 0x9d4 /* Processor batch should be empty on grouped batch */,
111
115
  );
112
116
  return {
113
117
  messages: this.opGroupingManager.ungroupOp(message).map(unpack),
@@ -118,7 +122,7 @@ export class RemoteMessageProcessor {
118
122
  const batchStartCsn = this.getAndUpdateBatchStartCsn(message);
119
123
 
120
124
  // Do a final unpack of runtime messages in case the message was not grouped, compressed, or chunked
121
- unpackRuntimeMessage(message);
125
+ unpackRuntimeMessage(message, logLegacyCase);
122
126
  this.processorBatch.push(message as InboundSequencedContainerRuntimeMessage);
123
127
 
124
128
  // this.batchStartCsn is undefined only if we have processed all messages in the batch.
@@ -146,7 +150,7 @@ export class RemoteMessageProcessor {
146
150
  const batchMetadataFlag = asBatchMetadata(message.metadata)?.batch;
147
151
  if (this.batchStartCsn === undefined) {
148
152
  // We are waiting for a new batch
149
- assert(batchMetadataFlag !== false, "Unexpected batch end marker");
153
+ assert(batchMetadataFlag !== false, 0x9d5 /* Unexpected batch end marker */);
150
154
 
151
155
  // Start of a new multi-message batch
152
156
  if (batchMetadataFlag === true) {
@@ -162,7 +166,7 @@ export class RemoteMessageProcessor {
162
166
  // We are in the middle or end of an existing multi-message batch. Return the current batchStartCsn
163
167
  const batchStartCsn = this.batchStartCsn;
164
168
 
165
- assert(batchMetadataFlag !== true, "Unexpected batch start marker");
169
+ assert(batchMetadataFlag !== true, 0x9d6 /* Unexpected batch start marker */);
166
170
  if (batchMetadataFlag === false) {
167
171
  // Batch end? Then get ready for the next batch to start
168
172
  this.batchStartCsn = undefined;
@@ -173,12 +177,27 @@ export class RemoteMessageProcessor {
173
177
  }
174
178
 
175
179
  /** Takes an incoming message and if the contents is a string, JSON.parse's it in place */
176
- function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {
180
+ export function ensureContentsDeserialized(
181
+ mutableMessage: ISequencedDocumentMessage,
182
+ modernRuntimeMessage: boolean,
183
+ logLegacyCase: (codePath: string) => void,
184
+ ): void {
177
185
  // back-compat: ADO #1385: eventually should become unconditional, but only for runtime messages!
178
186
  // System message may have no contents, or in some cases (mostly for back-compat) they may have actual objects.
179
187
  // Old ops may contain empty string (I assume noops).
188
+ let parsedJsonContents: boolean;
180
189
  if (typeof mutableMessage.contents === "string" && mutableMessage.contents !== "") {
181
190
  mutableMessage.contents = JSON.parse(mutableMessage.contents);
191
+ parsedJsonContents = true;
192
+ } else {
193
+ parsedJsonContents = false;
194
+ }
195
+
196
+ // We expect Modern Runtime Messages to have JSON serialized contents,
197
+ // and all other messages not to (system messages and legacy runtime messages without outer "op" type envelope)
198
+ // Let's observe if we are wrong about this to learn about these cases.
199
+ if (modernRuntimeMessage !== parsedJsonContents) {
200
+ logLegacyCase("ensureContentsDeserialized_unexpectedContentsType");
182
201
  }
183
202
  }
184
203
 
@@ -214,7 +233,10 @@ function unpack(message: ISequencedDocumentMessage): InboundSequencedContainerRu
214
233
  *
215
234
  * @internal
216
235
  */
217
- export function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolean {
236
+ export function unpackRuntimeMessage(
237
+ message: ISequencedDocumentMessage,
238
+ logLegacyCase: (codePath: string) => void = () => {},
239
+ ): boolean {
218
240
  if (message.type !== MessageType.Operation) {
219
241
  // Legacy format, but it's already "unpacked",
220
242
  // i.e. message.type is actually ContainerMessageType.
@@ -230,6 +252,7 @@ export function unpackRuntimeMessage(message: ISequencedDocumentMessage): boolea
230
252
  (message.contents as { type?: unknown }).type === undefined
231
253
  ) {
232
254
  message.type = ContainerMessageType.FluidDataStoreOp;
255
+ logLegacyCase("unpackRuntimeMessage_contentsWithAddress");
233
256
  } else {
234
257
  // new format
235
258
  unpack(message);
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.1.0-281041";
9
+ export const pkgVersion = "2.1.0";