@fluidframework/container-runtime 2.4.0 → 2.5.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 (95) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/api-report/container-runtime.legacy.alpha.api.md +3 -1
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +3 -3
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +1 -1
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +20 -5
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +183 -119
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/containerRuntime.d.ts +12 -4
  13. package/dist/containerRuntime.d.ts.map +1 -1
  14. package/dist/containerRuntime.js +155 -66
  15. package/dist/containerRuntime.js.map +1 -1
  16. package/dist/dataStoreContext.d.ts +15 -3
  17. package/dist/dataStoreContext.d.ts.map +1 -1
  18. package/dist/dataStoreContext.js +48 -19
  19. package/dist/dataStoreContext.js.map +1 -1
  20. package/dist/gc/garbageCollection.d.ts +5 -6
  21. package/dist/gc/garbageCollection.d.ts.map +1 -1
  22. package/dist/gc/garbageCollection.js +23 -22
  23. package/dist/gc/garbageCollection.js.map +1 -1
  24. package/dist/gc/gcDefinitions.d.ts +2 -2
  25. package/dist/gc/gcDefinitions.d.ts.map +1 -1
  26. package/dist/gc/gcDefinitions.js.map +1 -1
  27. package/dist/opLifecycle/outbox.d.ts +3 -0
  28. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  29. package/dist/opLifecycle/outbox.js +9 -0
  30. package/dist/opLifecycle/outbox.js.map +1 -1
  31. package/dist/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  32. package/dist/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  33. package/dist/opLifecycle/remoteMessageProcessor.js +2 -0
  34. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  35. package/dist/opProperties.js +1 -1
  36. package/dist/opProperties.js.map +1 -1
  37. package/dist/packageVersion.d.ts +1 -1
  38. package/dist/packageVersion.js +1 -1
  39. package/dist/packageVersion.js.map +1 -1
  40. package/dist/summary/documentSchema.d.ts +11 -0
  41. package/dist/summary/documentSchema.d.ts.map +1 -1
  42. package/dist/summary/documentSchema.js +45 -30
  43. package/dist/summary/documentSchema.js.map +1 -1
  44. package/lib/blobManager/blobManager.d.ts +3 -3
  45. package/lib/blobManager/blobManager.d.ts.map +1 -1
  46. package/lib/blobManager/blobManager.js +1 -1
  47. package/lib/blobManager/blobManager.js.map +1 -1
  48. package/lib/channelCollection.d.ts +20 -5
  49. package/lib/channelCollection.d.ts.map +1 -1
  50. package/lib/channelCollection.js +183 -119
  51. package/lib/channelCollection.js.map +1 -1
  52. package/lib/containerRuntime.d.ts +12 -4
  53. package/lib/containerRuntime.d.ts.map +1 -1
  54. package/lib/containerRuntime.js +155 -66
  55. package/lib/containerRuntime.js.map +1 -1
  56. package/lib/dataStoreContext.d.ts +15 -3
  57. package/lib/dataStoreContext.d.ts.map +1 -1
  58. package/lib/dataStoreContext.js +48 -19
  59. package/lib/dataStoreContext.js.map +1 -1
  60. package/lib/gc/garbageCollection.d.ts +5 -6
  61. package/lib/gc/garbageCollection.d.ts.map +1 -1
  62. package/lib/gc/garbageCollection.js +23 -22
  63. package/lib/gc/garbageCollection.js.map +1 -1
  64. package/lib/gc/gcDefinitions.d.ts +2 -2
  65. package/lib/gc/gcDefinitions.d.ts.map +1 -1
  66. package/lib/gc/gcDefinitions.js.map +1 -1
  67. package/lib/opLifecycle/outbox.d.ts +3 -0
  68. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  69. package/lib/opLifecycle/outbox.js +9 -0
  70. package/lib/opLifecycle/outbox.js.map +1 -1
  71. package/lib/opLifecycle/remoteMessageProcessor.d.ts +1 -0
  72. package/lib/opLifecycle/remoteMessageProcessor.d.ts.map +1 -1
  73. package/lib/opLifecycle/remoteMessageProcessor.js +2 -0
  74. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  75. package/lib/opProperties.js +1 -1
  76. package/lib/opProperties.js.map +1 -1
  77. package/lib/packageVersion.d.ts +1 -1
  78. package/lib/packageVersion.js +1 -1
  79. package/lib/packageVersion.js.map +1 -1
  80. package/lib/summary/documentSchema.d.ts +11 -0
  81. package/lib/summary/documentSchema.d.ts.map +1 -1
  82. package/lib/summary/documentSchema.js +45 -30
  83. package/lib/summary/documentSchema.js.map +1 -1
  84. package/package.json +24 -24
  85. package/src/blobManager/blobManager.ts +2 -2
  86. package/src/channelCollection.ts +227 -160
  87. package/src/containerRuntime.ts +197 -80
  88. package/src/dataStoreContext.ts +66 -23
  89. package/src/gc/garbageCollection.ts +32 -32
  90. package/src/gc/gcDefinitions.ts +3 -3
  91. package/src/opLifecycle/outbox.ts +12 -0
  92. package/src/opLifecycle/remoteMessageProcessor.ts +3 -0
  93. package/src/opProperties.ts +1 -1
  94. package/src/packageVersion.ts +1 -1
  95. package/src/summary/documentSchema.ts +58 -39
@@ -39,11 +39,13 @@ import {
39
39
  IFluidDataStoreRegistry,
40
40
  IFluidParentContext,
41
41
  ISummarizeResult,
42
- InboundAttachMessage,
43
42
  NamedFluidDataStoreRegistryEntries,
44
43
  channelsTreeName,
45
44
  IInboundSignalMessage,
46
45
  gcDataBlobKey,
46
+ type IRuntimeMessagesContent,
47
+ type InboundAttachMessage,
48
+ type IRuntimeMessageCollection,
47
49
  } from "@fluidframework/runtime-definitions/internal";
48
50
  import {
49
51
  GCDataBuilder,
@@ -391,115 +393,119 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
391
393
  this.parentContext.makeLocallyVisible();
392
394
  }
393
395
 
394
- private processAttachMessage(message: ISequencedDocumentMessage, local: boolean) {
395
- const attachMessage = message.contents as InboundAttachMessage;
396
-
397
- // We need to process the GC Data for both local and remote attach messages
398
- const foundGCData = processAttachMessageGCData(
399
- attachMessage.snapshot,
400
- (nodeId, toPath) => {
401
- // nodeId is the relative path under the node being attached. Always starts with "/", but no trailing "/" after an id
402
- const fromPath = `/${attachMessage.id}${nodeId === "/" ? "" : nodeId}`;
403
- this.parentContext.addedGCOutboundRoute(fromPath, toPath, message.timestamp);
404
- },
405
- );
406
-
407
- // Only log once per container to avoid noise/cost.
408
- // Allows longitudinal tracking of various state (e.g. foundGCData), and some sampled details
409
- if (this.shouldSendAttachLog) {
410
- this.shouldSendAttachLog = false;
411
- this.mc.logger.sendTelemetryEvent({
412
- eventName: "dataStoreAttachMessage_sampled",
413
- ...tagCodeArtifacts({ id: attachMessage.id, pkg: attachMessage.type }),
414
- details: {
415
- local,
416
- snapshot: !!attachMessage.snapshot,
417
- foundGCData,
396
+ private processAttachMessages(messageCollection: IRuntimeMessageCollection) {
397
+ const { envelope, messagesContent, local } = messageCollection;
398
+ for (const { contents } of messagesContent) {
399
+ const attachMessage = contents as InboundAttachMessage;
400
+ // We need to process the GC Data for both local and remote attach messages
401
+ const foundGCData = processAttachMessageGCData(
402
+ attachMessage.snapshot,
403
+ (nodeId, toPath) => {
404
+ // nodeId is the relative path under the node being attached. Always starts with "/", but no trailing "/" after an id
405
+ const fromPath = `/${attachMessage.id}${nodeId === "/" ? "" : nodeId}`;
406
+ this.parentContext.addedGCOutboundRoute(fromPath, toPath, envelope.timestamp);
418
407
  },
419
- ...extractSafePropertiesFromMessage(message),
420
- });
421
- }
422
-
423
- // The local object has already been attached
424
- if (local) {
425
- assert(
426
- this.pendingAttach.has(attachMessage.id),
427
- 0x15e /* "Local object does not have matching attach message id" */,
428
408
  );
429
- this.contexts.get(attachMessage.id)?.setAttachState(AttachState.Attached);
430
- this.pendingAttach.delete(attachMessage.id);
431
- return;
432
- }
433
409
 
434
- // If a non-local operation then go and create the object, otherwise mark it as officially attached.
435
- if (this.alreadyProcessed(attachMessage.id)) {
436
- // TODO: dataStoreId may require a different tag from PackageData #7488
437
- const error = new DataCorruptionError(
438
- // pre-0.58 error message: duplicateDataStoreCreatedWithExistingId
439
- "Duplicate DataStore created with existing id",
440
- {
441
- ...extractSafePropertiesFromMessage(message),
442
- ...tagCodeArtifacts({ dataStoreId: attachMessage.id }),
443
- },
444
- );
445
- throw error;
446
- }
410
+ // Only log once per container to avoid noise/cost.
411
+ // Allows longitudinal tracking of various state (e.g. foundGCData), and some sampled details
412
+ if (this.shouldSendAttachLog) {
413
+ this.shouldSendAttachLog = false;
414
+ this.mc.logger.sendTelemetryEvent({
415
+ eventName: "dataStoreAttachMessage_sampled",
416
+ ...tagCodeArtifacts({ id: attachMessage.id, pkg: attachMessage.type }),
417
+ details: {
418
+ local,
419
+ snapshot: !!attachMessage.snapshot,
420
+ foundGCData,
421
+ },
422
+ ...extractSafePropertiesFromMessage(envelope),
423
+ });
424
+ }
447
425
 
448
- const flatAttachBlobs = new Map<string, ArrayBufferLike>();
449
- let snapshot: ISnapshotTree | ISnapshot | undefined;
450
- if (attachMessage.snapshot) {
451
- snapshot = buildSnapshotTree(attachMessage.snapshot.entries, flatAttachBlobs);
452
- if (isInstanceOfISnapshot(this.baseSnapshot)) {
453
- snapshot = { ...this.baseSnapshot, snapshotTree: snapshot };
426
+ // The local object has already been attached
427
+ if (local) {
428
+ assert(
429
+ this.pendingAttach.has(attachMessage.id),
430
+ 0x15e /* "Local object does not have matching attach message id" */,
431
+ );
432
+ this.contexts.get(attachMessage.id)?.setAttachState(AttachState.Attached);
433
+ this.pendingAttach.delete(attachMessage.id);
434
+ continue;
454
435
  }
455
- }
456
436
 
457
- // Include the type of attach message which is the pkg of the store to be
458
- // used by RemoteFluidDataStoreContext in case it is not in the snapshot.
459
- const pkg = [attachMessage.type];
460
- const remoteFluidDataStoreContext = new RemoteFluidDataStoreContext({
461
- id: attachMessage.id,
462
- snapshot,
463
- parentContext: this.wrapContextForInnerChannel(attachMessage.id),
464
- storage: new StorageServiceWithAttachBlobs(this.parentContext.storage, flatAttachBlobs),
465
- scope: this.parentContext.scope,
466
- loadingGroupId: attachMessage.snapshot?.groupId,
467
- createSummarizerNodeFn: this.parentContext.getCreateChildSummarizerNodeFn(
468
- attachMessage.id,
469
- {
470
- type: CreateSummarizerNodeSource.FromAttach,
471
- sequenceNumber: message.sequenceNumber,
472
- snapshot: attachMessage.snapshot ?? {
473
- entries: [createAttributesBlob(pkg, true /* isRootDataStore */)],
437
+ // If a non-local operation then go and create the object, otherwise mark it as officially attached.
438
+ if (this.alreadyProcessed(attachMessage.id)) {
439
+ // TODO: dataStoreId may require a different tag from PackageData #7488
440
+ const error = new DataCorruptionError(
441
+ // pre-0.58 error message: duplicateDataStoreCreatedWithExistingId
442
+ "Duplicate DataStore created with existing id",
443
+ {
444
+ ...extractSafePropertiesFromMessage(envelope),
445
+ ...tagCodeArtifacts({ dataStoreId: attachMessage.id }),
474
446
  },
475
- },
476
- ),
477
- pkg,
478
- });
447
+ );
448
+ throw error;
449
+ }
479
450
 
480
- this.contexts.addBoundOrRemoted(remoteFluidDataStoreContext);
481
- }
451
+ const flatAttachBlobs = new Map<string, ArrayBufferLike>();
452
+ let snapshot: ISnapshotTree | ISnapshot | undefined;
453
+ if (attachMessage.snapshot) {
454
+ snapshot = buildSnapshotTree(attachMessage.snapshot.entries, flatAttachBlobs);
455
+ if (isInstanceOfISnapshot(this.baseSnapshot)) {
456
+ snapshot = { ...this.baseSnapshot, snapshotTree: snapshot };
457
+ }
458
+ }
482
459
 
483
- private processAliasMessage(
484
- message: ISequencedDocumentMessage,
485
- localOpMetadata: unknown,
486
- local: boolean,
487
- ): void {
488
- const aliasMessage = message.contents as IDataStoreAliasMessage;
489
- if (!isDataStoreAliasMessage(aliasMessage)) {
490
- throw new DataCorruptionError("malformedDataStoreAliasMessage", {
491
- ...extractSafePropertiesFromMessage(message),
460
+ // Include the type of attach message which is the pkg of the store to be
461
+ // used by RemoteFluidDataStoreContext in case it is not in the snapshot.
462
+ const pkg = [attachMessage.type];
463
+ const remoteFluidDataStoreContext = new RemoteFluidDataStoreContext({
464
+ id: attachMessage.id,
465
+ snapshot,
466
+ parentContext: this.wrapContextForInnerChannel(attachMessage.id),
467
+ storage: new StorageServiceWithAttachBlobs(
468
+ this.parentContext.storage,
469
+ flatAttachBlobs,
470
+ ),
471
+ scope: this.parentContext.scope,
472
+ loadingGroupId: attachMessage.snapshot?.groupId,
473
+ createSummarizerNodeFn: this.parentContext.getCreateChildSummarizerNodeFn(
474
+ attachMessage.id,
475
+ {
476
+ type: CreateSummarizerNodeSource.FromAttach,
477
+ sequenceNumber: envelope.sequenceNumber,
478
+ snapshot: attachMessage.snapshot ?? {
479
+ entries: [createAttributesBlob(pkg, true /* isRootDataStore */)],
480
+ },
481
+ },
482
+ ),
483
+ pkg,
492
484
  });
485
+
486
+ this.contexts.addBoundOrRemoted(remoteFluidDataStoreContext);
493
487
  }
488
+ }
494
489
 
495
- const resolve = localOpMetadata as PendingAliasResolve;
496
- const aliasResult = this.processAliasMessageCore(
497
- aliasMessage.internalId,
498
- aliasMessage.alias,
499
- message.timestamp,
500
- );
501
- if (local) {
502
- resolve(aliasResult);
490
+ private processAliasMessages(messageCollection: IRuntimeMessageCollection): void {
491
+ const { envelope, messagesContent, local } = messageCollection;
492
+ for (const { contents, localOpMetadata } of messagesContent) {
493
+ const aliasMessage = contents as IDataStoreAliasMessage;
494
+ if (!isDataStoreAliasMessage(aliasMessage)) {
495
+ throw new DataCorruptionError("malformedDataStoreAliasMessage", {
496
+ ...extractSafePropertiesFromMessage(envelope),
497
+ });
498
+ }
499
+
500
+ const resolve = localOpMetadata as PendingAliasResolve;
501
+ const aliasResult = this.processAliasMessageCore(
502
+ aliasMessage.internalId,
503
+ aliasMessage.alias,
504
+ envelope.timestamp,
505
+ );
506
+ if (local) {
507
+ resolve(aliasResult);
508
+ }
503
509
  }
504
510
  }
505
511
 
@@ -819,84 +825,145 @@ export class ChannelCollection implements IFluidDataStoreChannel, IDisposable {
819
825
  }
820
826
  }
821
827
 
822
- public process(
823
- message: ISequencedDocumentMessage,
824
- local: boolean,
825
- localMessageMetadata: unknown,
826
- ) {
827
- switch (message.type) {
828
+ /**
829
+ * Process messages for this channel collection. The messages here are contiguous messages in a batch.
830
+ * @param messageCollection - The collection of messages to process.
831
+ */
832
+ public processMessages(messageCollection: IRuntimeMessageCollection): void {
833
+ switch (messageCollection.envelope.type) {
834
+ case ContainerMessageType.FluidDataStoreOp:
835
+ this.processChannelMessages(messageCollection);
836
+ break;
828
837
  case ContainerMessageType.Attach:
829
- this.processAttachMessage(message, local);
830
- return;
838
+ this.processAttachMessages(messageCollection);
839
+ break;
831
840
  case ContainerMessageType.Alias:
832
- this.processAliasMessage(message, localMessageMetadata, local);
833
- return;
834
- case ContainerMessageType.FluidDataStoreOp: {
835
- const envelope = message.contents as IEnvelope;
836
- const innerContents = envelope.contents as FluidDataStoreMessage;
837
- const transformed = {
838
- ...message,
839
- type: innerContents.type,
840
- contents: innerContents.content,
841
- };
842
-
843
- this.processChannelOp(envelope.address, transformed, local, localMessageMetadata);
844
-
845
- // Notify GC of any outbound references that were added by this op.
846
- detectOutboundReferences(
847
- envelope.address,
848
- transformed.contents,
849
- (fromPath: string, toPath: string) =>
850
- this.parentContext.addedGCOutboundRoute(fromPath, toPath, message.timestamp),
851
- );
841
+ this.processAliasMessages(messageCollection);
852
842
  break;
853
- }
854
843
  default:
855
844
  assert(false, 0x8e9 /* unreached */);
856
845
  }
857
846
  }
858
847
 
859
- protected processChannelOp(
860
- address: string,
848
+ /**
849
+ * This is still here for back-compat purposes because channel collection implements
850
+ * IFluidDataStoreChannel. Once it is removed from the interface, this method can be removed.
851
+ * Container runtime calls `processMessages` instead.
852
+ */
853
+ public process(
861
854
  message: ISequencedDocumentMessage,
862
855
  local: boolean,
863
- localMessageMetadata: unknown,
856
+ localOpMetadata: unknown,
864
857
  ) {
865
- const context = this.contexts.get(address);
866
-
867
- // If the data store has been deleted, log an error and ignore this message. This helps prevent document
868
- // corruption in case a deleted data store accidentally submitted an op.
869
- if (this.checkAndLogIfDeleted(address, context, "Changed", "processFluidDataStoreOp")) {
870
- return;
871
- }
872
-
873
- if (context === undefined) {
874
- // Former assert 0x162
875
- throw DataProcessingError.create(
876
- "No context for op",
877
- "processFluidDataStoreOp",
878
- message,
858
+ this.processMessages({
859
+ envelope: message,
860
+ messagesContent: [
879
861
  {
880
- local,
881
- messageDetails: JSON.stringify({
882
- type: message.type,
883
- contentType: typeof message.contents,
884
- }),
885
- ...tagCodeArtifacts({ address }),
862
+ contents: message.contents,
863
+ localOpMetadata,
864
+ clientSequenceNumber: message.clientSequenceNumber,
886
865
  },
866
+ ],
867
+ local,
868
+ });
869
+ }
870
+
871
+ /**
872
+ * Process channel messages. The messages here are contiguous channel type messages in a batch. Bunch
873
+ * of contiguous messages for a data store should be sent to it together.
874
+ * @param messageCollection - The collection of messages to process.
875
+ */
876
+ private processChannelMessages(messageCollection: IRuntimeMessageCollection): void {
877
+ const { messagesContent, local } = messageCollection;
878
+ let currentMessageState: { address: string; type: string } | undefined;
879
+ let currentMessagesContent: IRuntimeMessagesContent[] = [];
880
+
881
+ // Helper that sends the current bunch of messages to the data store. It validates that the data stores exists.
882
+ const sendBunchedMessages = () => {
883
+ // Current message state will be undefined for the first message in the list.
884
+ if (currentMessageState === undefined) {
885
+ return;
886
+ }
887
+ const currentContext = this.contexts.get(currentMessageState.address);
888
+ assert(!!currentContext, 0xa66 /* Context not found */);
889
+
890
+ currentContext.processMessages({
891
+ envelope: { ...messageCollection.envelope, type: currentMessageState.type },
892
+ messagesContent: currentMessagesContent,
893
+ local,
894
+ });
895
+ currentMessagesContent = [];
896
+ };
897
+
898
+ /**
899
+ * Bunch contiguous messages for the same data store and send them together.
900
+ * This is an optimization mainly for DDSes, where it can process a bunch of ops together. DDSes
901
+ * like merge tree or shared tree can process ops more efficiently when they are bunched together.
902
+ */
903
+ for (const { contents, ...restOfMessagesContent } of messagesContent) {
904
+ const contentsEnvelope = contents as IEnvelope;
905
+ const address = contentsEnvelope.address;
906
+ const context = this.contexts.get(address);
907
+
908
+ // If the data store has been deleted, log an error and ignore this message. This helps prevent document
909
+ // corruption in case a deleted data store accidentally submitted an op.
910
+ if (this.checkAndLogIfDeleted(address, context, "Changed", "processFluidDataStoreOp")) {
911
+ continue;
912
+ }
913
+
914
+ if (context === undefined) {
915
+ // Former assert 0x162
916
+ throw DataProcessingError.create(
917
+ "No context for op",
918
+ "processFluidDataStoreOp",
919
+ messageCollection.envelope as ISequencedDocumentMessage,
920
+ {
921
+ local,
922
+ messageDetails: JSON.stringify({
923
+ type: messageCollection.envelope.type,
924
+ contentType: typeof contents,
925
+ }),
926
+ ...tagCodeArtifacts({ address }),
927
+ },
928
+ );
929
+ }
930
+
931
+ const { type: contextType, content: contextContents } =
932
+ contentsEnvelope.contents as FluidDataStoreMessage;
933
+ // If the address or type of the message changes while processing the message, send the current bunch.
934
+ if (
935
+ currentMessageState?.address !== address ||
936
+ currentMessageState?.type !== contextType
937
+ ) {
938
+ sendBunchedMessages();
939
+ }
940
+ currentMessagesContent.push({
941
+ contents: contextContents,
942
+ ...restOfMessagesContent,
943
+ });
944
+ currentMessageState = { address, type: contextType };
945
+
946
+ // Notify that a GC node for the data store changed. This is used to detect if a deleted data store is
947
+ // being used.
948
+ this.gcNodeUpdated({
949
+ node: { type: "DataStore", path: `/${address}` },
950
+ reason: "Changed",
951
+ timestampMs: messageCollection.envelope.timestamp,
952
+ packagePath: context.isLoaded ? context.packagePath : undefined,
953
+ });
954
+
955
+ detectOutboundReferences(address, contextContents, (fromPath: string, toPath: string) =>
956
+ this.parentContext.addedGCOutboundRoute(
957
+ fromPath,
958
+ toPath,
959
+ messageCollection.envelope.timestamp,
960
+ ),
887
961
  );
888
962
  }
889
963
 
890
- context.process(message, local, localMessageMetadata);
891
-
892
- // Notify that a GC node for the data store changed. This is used to detect if a deleted data store is
893
- // being used.
894
- this.gcNodeUpdated({
895
- node: { type: "DataStore", path: `/${address}` },
896
- reason: "Changed",
897
- timestampMs: message.timestamp,
898
- packagePath: context.isLoaded ? context.packagePath : undefined,
899
- });
964
+ // Process the last bunch of messages, if any. Note that there may not be any messages in case all of them are
965
+ // ignored because the data store is deleted.
966
+ sendBunchedMessages();
900
967
  }
901
968
 
902
969
  private async getDataStore(