@fluidframework/container-runtime 2.4.0-299707 → 2.4.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 (62) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/blobManager/blobManager.d.ts.map +1 -1
  4. package/dist/blobManager/blobManager.js +10 -4
  5. package/dist/blobManager/blobManager.js.map +1 -1
  6. package/dist/containerRuntime.d.ts +6 -0
  7. package/dist/containerRuntime.d.ts.map +1 -1
  8. package/dist/containerRuntime.js +22 -10
  9. package/dist/containerRuntime.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.d.ts +6 -0
  15. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  16. package/dist/opLifecycle/outbox.js +9 -1
  17. package/dist/opLifecycle/outbox.js.map +1 -1
  18. package/dist/opLifecycle/remoteMessageProcessor.d.ts +4 -4
  19. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  20. package/dist/opLifecycle/remoteMessageProcessor.js +6 -18
  21. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  22. package/dist/packageVersion.d.ts +1 -1
  23. package/dist/packageVersion.d.ts.map +1 -1
  24. package/dist/packageVersion.js +1 -1
  25. package/dist/packageVersion.js.map +1 -1
  26. package/dist/summary/summaryCollection.d.ts.map +1 -1
  27. package/dist/summary/summaryCollection.js +3 -4
  28. package/dist/summary/summaryCollection.js.map +1 -1
  29. package/lib/blobManager/blobManager.d.ts.map +1 -1
  30. package/lib/blobManager/blobManager.js +10 -4
  31. package/lib/blobManager/blobManager.js.map +1 -1
  32. package/lib/containerRuntime.d.ts +6 -0
  33. package/lib/containerRuntime.d.ts.map +1 -1
  34. package/lib/containerRuntime.js +20 -9
  35. package/lib/containerRuntime.js.map +1 -1
  36. package/lib/opLifecycle/index.d.ts +1 -1
  37. package/lib/opLifecycle/index.d.ts.map +1 -1
  38. package/lib/opLifecycle/index.js +1 -1
  39. package/lib/opLifecycle/index.js.map +1 -1
  40. package/lib/opLifecycle/outbox.d.ts +6 -0
  41. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  42. package/lib/opLifecycle/outbox.js +7 -0
  43. package/lib/opLifecycle/outbox.js.map +1 -1
  44. package/lib/opLifecycle/remoteMessageProcessor.d.ts +4 -4
  45. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  46. package/lib/opLifecycle/remoteMessageProcessor.js +6 -18
  47. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  48. package/lib/packageVersion.d.ts +1 -1
  49. package/lib/packageVersion.d.ts.map +1 -1
  50. package/lib/packageVersion.js +1 -1
  51. package/lib/packageVersion.js.map +1 -1
  52. package/lib/summary/summaryCollection.d.ts.map +1 -1
  53. package/lib/summary/summaryCollection.js +3 -4
  54. package/lib/summary/summaryCollection.js.map +1 -1
  55. package/package.json +17 -17
  56. package/src/blobManager/blobManager.ts +10 -4
  57. package/src/containerRuntime.ts +24 -8
  58. package/src/opLifecycle/index.ts +1 -1
  59. package/src/opLifecycle/outbox.ts +11 -0
  60. package/src/opLifecycle/remoteMessageProcessor.ts +8 -22
  61. package/src/packageVersion.ts +1 -1
  62. package/src/summary/summaryCollection.ts +3 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.4.0-299707",
3
+ "version": "2.4.0",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -129,18 +129,18 @@
129
129
  "temp-directory": "nyc/.nyc_output"
130
130
  },
131
131
  "dependencies": {
132
- "@fluid-internal/client-utils": "2.4.0-299707",
133
- "@fluidframework/container-definitions": "2.4.0-299707",
134
- "@fluidframework/container-runtime-definitions": "2.4.0-299707",
135
- "@fluidframework/core-interfaces": "2.4.0-299707",
136
- "@fluidframework/core-utils": "2.4.0-299707",
137
- "@fluidframework/datastore": "2.4.0-299707",
138
- "@fluidframework/driver-definitions": "2.4.0-299707",
139
- "@fluidframework/driver-utils": "2.4.0-299707",
140
- "@fluidframework/id-compressor": "2.4.0-299707",
141
- "@fluidframework/runtime-definitions": "2.4.0-299707",
142
- "@fluidframework/runtime-utils": "2.4.0-299707",
143
- "@fluidframework/telemetry-utils": "2.4.0-299707",
132
+ "@fluid-internal/client-utils": "~2.4.0",
133
+ "@fluidframework/container-definitions": "~2.4.0",
134
+ "@fluidframework/container-runtime-definitions": "~2.4.0",
135
+ "@fluidframework/core-interfaces": "~2.4.0",
136
+ "@fluidframework/core-utils": "~2.4.0",
137
+ "@fluidframework/datastore": "~2.4.0",
138
+ "@fluidframework/driver-definitions": "~2.4.0",
139
+ "@fluidframework/driver-utils": "~2.4.0",
140
+ "@fluidframework/id-compressor": "~2.4.0",
141
+ "@fluidframework/runtime-definitions": "~2.4.0",
142
+ "@fluidframework/runtime-utils": "~2.4.0",
143
+ "@fluidframework/telemetry-utils": "~2.4.0",
144
144
  "@tylerbu/sorted-btree-es6": "^1.8.0",
145
145
  "double-ended-queue": "^2.1.0-0",
146
146
  "lz4js": "^0.2.0",
@@ -149,16 +149,16 @@
149
149
  "devDependencies": {
150
150
  "@arethetypeswrong/cli": "^0.16.4",
151
151
  "@biomejs/biome": "~1.8.3",
152
- "@fluid-internal/mocha-test-setup": "2.4.0-299707",
153
- "@fluid-private/stochastic-test-utils": "2.4.0-299707",
154
- "@fluid-private/test-pairwise-generator": "2.4.0-299707",
152
+ "@fluid-internal/mocha-test-setup": "~2.4.0",
153
+ "@fluid-private/stochastic-test-utils": "~2.4.0",
154
+ "@fluid-private/test-pairwise-generator": "~2.4.0",
155
155
  "@fluid-tools/benchmark": "^0.50.0",
156
156
  "@fluid-tools/build-cli": "^0.48.0",
157
157
  "@fluidframework/build-common": "^2.0.3",
158
158
  "@fluidframework/build-tools": "^0.48.0",
159
159
  "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@~2.3.0",
160
160
  "@fluidframework/eslint-config-fluid": "^5.4.0",
161
- "@fluidframework/test-runtime-utils": "2.4.0-299707",
161
+ "@fluidframework/test-runtime-utils": "~2.4.0",
162
162
  "@microsoft/api-extractor": "7.47.8",
163
163
  "@types/double-ended-queue": "^2.1.0",
164
164
  "@types/mocha": "^9.1.1",
@@ -811,6 +811,13 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
811
811
  for (const [id, entry] of this.pendingBlobs) {
812
812
  if (!localBlobs.has(entry)) {
813
813
  localBlobs.add(entry);
814
+ // In order to follow natural blob creation flow we need to:
815
+ // 1 send the blob attach op
816
+ // 2 resolve the blob handle
817
+ // 3 wait for op referencing the blob
818
+ if (!entry.opsent) {
819
+ this.sendBlobAttachOp(id, entry.storageId);
820
+ }
814
821
  // Resolving the blob handle to let hosts continue with their operations (it will resolve
815
822
  // original createBlob call) and let them attach the blob. This is a lie we told since the upload
816
823
  // hasn't finished yet, but it's fine since we will retry on rehydration.
@@ -843,7 +850,9 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
843
850
  }
844
851
  // Wait for all blobs to be attached. This is important, otherwise serialized container
845
852
  // could send the blobAttach op without any op that references the blob, making it useless.
846
- await Promise.allSettled(attachBlobsP).catch(() => {});
853
+ await Promise.allSettled(attachBlobsP).catch(() => {
854
+ return undefined;
855
+ });
847
856
  }
848
857
 
849
858
  for (const [id, entry] of this.pendingBlobs) {
@@ -855,9 +864,6 @@ export class BlobManager extends TypedEventEmitter<IBlobManagerEvents> {
855
864
  continue;
856
865
  }
857
866
  assert(entry.attached === true, 0x790 /* stashed blob should be attached */);
858
- if (!entry.opsent) {
859
- this.sendBlobAttachOp(id, entry.storageId);
860
- }
861
867
  blobs[id] = {
862
868
  blob: bufferToString(entry.blob, "base64"),
863
869
  storageId: entry.storageId,
@@ -185,6 +185,7 @@ import {
185
185
  OpSplitter,
186
186
  Outbox,
187
187
  RemoteMessageProcessor,
188
+ serializeOpContents,
188
189
  } from "./opLifecycle/index.js";
189
190
  import { pkgVersion } from "./packageVersion.js";
190
191
  import {
@@ -762,7 +763,7 @@ function lastMessageFromMetadata(metadata: IContainerRuntimeMetadata | undefined
762
763
  * to understand if/when it is hit.
763
764
  * We only want to log this once, to avoid spamming telemetry if we are wrong and these cases are hit commonly.
764
765
  */
765
- let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: string) => {
766
+ export let getSingleUseLegacyLogCallback = (logger: ITelemetryLoggerExt, type: string) => {
766
767
  return (codePath: string) => {
767
768
  logger.sendTelemetryEvent({
768
769
  eventName: "LegacyMessageFormat",
@@ -2710,8 +2711,13 @@ export class ContainerRuntime
2710
2711
  const savedOp = (messageCopy.metadata as ISavedOpMetadata)?.savedOp;
2711
2712
  const logLegacyCase = getSingleUseLegacyLogCallback(this.logger, messageCopy.type);
2712
2713
 
2713
- // We expect runtime messages to have JSON contents - deserialize it in place.
2714
- ensureContentsDeserialized(messageCopy, hasModernRuntimeMessageEnvelope, logLegacyCase);
2714
+ let runtimeBatch: boolean =
2715
+ hasModernRuntimeMessageEnvelope || isUnpackedRuntimeMessage(messageCopy);
2716
+ if (runtimeBatch) {
2717
+ // We expect runtime messages to have JSON contents - deserialize it in place.
2718
+ ensureContentsDeserialized(messageCopy);
2719
+ }
2720
+
2715
2721
  if (hasModernRuntimeMessageEnvelope) {
2716
2722
  // If the message has the modern message envelope, then process it here.
2717
2723
  // Here we unpack the message (decompress, unchunk, and/or ungroup) into a batch of messages with ContainerMessageType
@@ -2749,7 +2755,6 @@ export class ContainerRuntime
2749
2755
  }
2750
2756
  }
2751
2757
 
2752
- let runtimeBatch: boolean = true;
2753
2758
  // Reach out to PendingStateManager, either to zip localOpMetadata into the *local* message list,
2754
2759
  // or to check to ensure the *remote* messages don't match the batchId of a pending local batch.
2755
2760
  // This latter case would indicate that the container has forked - two copies are trying to persist the same local changes.
@@ -2807,12 +2812,23 @@ export class ContainerRuntime
2807
2812
  runtimeBatch,
2808
2813
  );
2809
2814
  } else {
2815
+ if (!runtimeBatch) {
2816
+ // The DeltaManager used to do this, but doesn't anymore as of Loader v2.4
2817
+ // Anyone listening to our "op" event would expect the contents to be parsed per this same logic
2818
+ if (
2819
+ typeof messageCopy.contents === "string" &&
2820
+ messageCopy.contents !== "" &&
2821
+ messageCopy.type !== MessageType.ClientLeave
2822
+ ) {
2823
+ messageCopy.contents = JSON.parse(messageCopy.contents);
2824
+ }
2825
+ }
2810
2826
  this.processInboundMessages(
2811
2827
  [{ message: messageCopy, localOpMetadata: undefined }],
2812
2828
  { batchStart: true, batchEnd: true }, // Single message
2813
2829
  local,
2814
2830
  savedOp,
2815
- isUnpackedRuntimeMessage(messageCopy) /* runtimeBatch */,
2831
+ runtimeBatch,
2816
2832
  );
2817
2833
  }
2818
2834
 
@@ -4212,7 +4228,7 @@ export class ContainerRuntime
4212
4228
  contents: idRange,
4213
4229
  };
4214
4230
  const idAllocationBatchMessage: BatchMessage = {
4215
- contents: JSON.stringify(idAllocationMessage),
4231
+ contents: serializeOpContents(idAllocationMessage),
4216
4232
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4217
4233
  };
4218
4234
  this.outbox.submitIdAllocation(idAllocationBatchMessage);
@@ -4275,13 +4291,13 @@ export class ContainerRuntime
4275
4291
  contents: schemaChangeMessage,
4276
4292
  };
4277
4293
  this.outbox.submit({
4278
- contents: JSON.stringify(msg),
4294
+ contents: serializeOpContents(msg),
4279
4295
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
4280
4296
  });
4281
4297
  }
4282
4298
 
4283
4299
  const message: BatchMessage = {
4284
- contents: JSON.stringify(containerRuntimeMessage) /* serialized content */,
4300
+ contents: serializeOpContents(containerRuntimeMessage),
4285
4301
  metadata,
4286
4302
  localOpMetadata,
4287
4303
  referenceSequenceNumber: this.deltaManager.lastSequenceNumber,
@@ -14,7 +14,7 @@ export {
14
14
  } from "./batchManager.js";
15
15
  export { BatchMessage, IBatch, IBatchCheckpoint, IChunkedOp } from "./definitions.js";
16
16
  export { DuplicateBatchDetector } from "./duplicateBatchDetector.js";
17
- export { Outbox, getLongStack } from "./outbox.js";
17
+ export { Outbox, getLongStack, serializeOpContents } from "./outbox.js";
18
18
  export { OpCompressor } from "./opCompressor.js";
19
19
  export { OpDecompressor } from "./opDecompressor.js";
20
20
  export { OpSplitter, splitOp, isChunkedMessage } from "./opSplitter.js";
@@ -15,6 +15,7 @@ import {
15
15
  } from "@fluidframework/telemetry-utils/internal";
16
16
 
17
17
  import { ICompressionRuntimeOptions } from "../containerRuntime.js";
18
+ import { OutboundContainerRuntimeMessage } from "../messageTypes.js";
18
19
  import { PendingMessageResubmitData, PendingStateManager } from "../pendingStateManager.js";
19
20
 
20
21
  import {
@@ -28,6 +29,8 @@ import { BatchMessage, IBatch, IBatchCheckpoint } from "./definitions.js";
28
29
  import { OpCompressor } from "./opCompressor.js";
29
30
  import { OpGroupingManager } from "./opGroupingManager.js";
30
31
  import { OpSplitter } from "./opSplitter.js";
32
+ // eslint-disable-next-line unused-imports/no-unused-imports -- Used by "@link" comment annotation below
33
+ import { ensureContentsDeserialized } from "./remoteMessageProcessor.js";
31
34
 
32
35
  export interface IOutboxConfig {
33
36
  readonly compressionOptions: ICompressionRuntimeOptions;
@@ -54,6 +57,14 @@ export interface IOutboxParameters {
54
57
  readonly closeContainer: (error?: ICriticalContainerError) => void;
55
58
  }
56
59
 
60
+ /**
61
+ * Before submitting an op to the Outbox, its contents must be serialized using this function.
62
+ * @remarks - The deserialization on process happens via the function {@link ensureContentsDeserialized}.
63
+ */
64
+ export function serializeOpContents(contents: OutboundContainerRuntimeMessage): string {
65
+ return JSON.stringify(contents);
66
+ }
67
+
57
68
  /**
58
69
  * Temporarily increase the stack limit while executing the provided action.
59
70
  * If a negative value is provided for `length`, no stack frames will be collected.
@@ -20,6 +20,8 @@ import { asBatchMetadata } from "../metadata.js";
20
20
  import { OpDecompressor } from "./opDecompressor.js";
21
21
  import { OpGroupingManager, isGroupedBatch } from "./opGroupingManager.js";
22
22
  import { OpSplitter, isChunkedMessage } from "./opSplitter.js";
23
+ // eslint-disable-next-line unused-imports/no-unused-imports -- Used by "@link" comment annotation below
24
+ import { serializeOpContents } from "./outbox.js";
23
25
 
24
26
  /** Info about the batch we learn when we process the first message */
25
27
  export interface BatchStartInfo {
@@ -236,32 +238,16 @@ export class RemoteMessageProcessor {
236
238
  }
237
239
 
238
240
  /**
239
- * Takes an incoming message and if the contents is a string, JSON.parse's it in place
241
+ * Takes an incoming runtime message JSON.parse's its contents in place, if needed (old Loader does this for us).
242
+ * Only to be used for runtine messages.
243
+ * @remarks - Serialization during submit happens via {@link serializeOpContents}
240
244
  * @param mutableMessage - op message received
241
- * @param hasModernRuntimeMessageEnvelope - false if the message does not contain the modern op envelop where message.type is MessageType.Operation
242
- * @param logLegacyCase - callback to log when legacy op is encountered
243
245
  */
244
- export function ensureContentsDeserialized(
245
- mutableMessage: ISequencedDocumentMessage,
246
- hasModernRuntimeMessageEnvelope: boolean,
247
- logLegacyCase: (codePath: string) => void,
248
- ): void {
249
- // This should become unconditional once (Loader LTS) DeltaManager.processInboundMessage() stops parsing content (ADO #12052)
250
- // Note: Until that change is made in the loader, this case will never be hit.
251
- // Then there will be a long time of needing both cases, until LTS catches up to the change.
252
- let didParseJsonContents: boolean;
246
+ export function ensureContentsDeserialized(mutableMessage: ISequencedDocumentMessage): void {
247
+ // This should become unconditional once Loader LTS reaches 2.4 or later.
248
+ // There will be a long time of needing both cases, until LTS advances to that point.
253
249
  if (typeof mutableMessage.contents === "string" && mutableMessage.contents !== "") {
254
250
  mutableMessage.contents = JSON.parse(mutableMessage.contents);
255
- didParseJsonContents = true;
256
- } else {
257
- didParseJsonContents = false;
258
- }
259
-
260
- // The DeltaManager parses the contents of the message as JSON if it is a string,
261
- // so we should never end up parsing it here.
262
- // Let's observe if we are wrong about this to learn about these cases.
263
- if (didParseJsonContents) {
264
- logLegacyCase("ensureContentsDeserialized_foundJsonContents");
265
251
  }
266
252
  }
267
253
 
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.4.0-299707";
9
+ export const pkgVersion = "2.4.0";
@@ -353,11 +353,10 @@ export class SummaryCollection extends TypedEventEmitter<ISummaryCollectionOpEve
353
353
  }
354
354
 
355
355
  private parseContent(op: ISequencedDocumentMessage) {
356
- // This should become unconditional once (Loader LTS) DeltaManager.processInboundMessage() stops parsing content (ADO #12052)
357
- // Note: Until that change is made in the loader, this case will never be hit.
358
- // Then there will be a long time of needing both cases, until LTS catches up to the change.
356
+ // This should become unconditional once (Loader LTS) reaches 2.4 or later
357
+ // There will be a long time of needing both cases, until LTS catches up to the change.
359
358
  // That said, we may instead move to listen for "op" events from ContainerRuntime,
360
- // and parsing may not be required at all if ContainerRuntime.process() would parse it for all types of ops.
359
+ // and parsing may not be required at all if ContainerRuntime.process() continues to parse it for all types of ops.
361
360
  if (typeof op.contents === "string") {
362
361
  op.contents = JSON.parse(op.contents);
363
362
  }