@fluidframework/datastore 2.5.0-302463 → 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 (55) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/api-report/datastore.legacy.alpha.api.md +1 -1
  3. package/dist/channelContext.d.ts +7 -3
  4. package/dist/channelContext.d.ts.map +1 -1
  5. package/dist/channelContext.js.map +1 -1
  6. package/dist/channelDeltaConnection.d.ts +2 -2
  7. package/dist/channelDeltaConnection.d.ts.map +1 -1
  8. package/dist/channelDeltaConnection.js +39 -4
  9. package/dist/channelDeltaConnection.js.map +1 -1
  10. package/dist/dataStoreRuntime.d.ts +18 -2
  11. package/dist/dataStoreRuntime.d.ts.map +1 -1
  12. package/dist/dataStoreRuntime.js +118 -44
  13. package/dist/dataStoreRuntime.js.map +1 -1
  14. package/dist/localChannelContext.d.ts +9 -4
  15. package/dist/localChannelContext.d.ts.map +1 -1
  16. package/dist/localChannelContext.js +19 -7
  17. package/dist/localChannelContext.js.map +1 -1
  18. package/dist/packageVersion.d.ts +1 -1
  19. package/dist/packageVersion.d.ts.map +1 -1
  20. package/dist/packageVersion.js +1 -1
  21. package/dist/packageVersion.js.map +1 -1
  22. package/dist/remoteChannelContext.d.ts +9 -4
  23. package/dist/remoteChannelContext.d.ts.map +1 -1
  24. package/dist/remoteChannelContext.js +25 -13
  25. package/dist/remoteChannelContext.js.map +1 -1
  26. package/lib/channelContext.d.ts +7 -3
  27. package/lib/channelContext.d.ts.map +1 -1
  28. package/lib/channelContext.js.map +1 -1
  29. package/lib/channelDeltaConnection.d.ts +2 -2
  30. package/lib/channelDeltaConnection.d.ts.map +1 -1
  31. package/lib/channelDeltaConnection.js +39 -4
  32. package/lib/channelDeltaConnection.js.map +1 -1
  33. package/lib/dataStoreRuntime.d.ts +18 -2
  34. package/lib/dataStoreRuntime.d.ts.map +1 -1
  35. package/lib/dataStoreRuntime.js +118 -44
  36. package/lib/dataStoreRuntime.js.map +1 -1
  37. package/lib/localChannelContext.d.ts +9 -4
  38. package/lib/localChannelContext.d.ts.map +1 -1
  39. package/lib/localChannelContext.js +19 -7
  40. package/lib/localChannelContext.js.map +1 -1
  41. package/lib/packageVersion.d.ts +1 -1
  42. package/lib/packageVersion.d.ts.map +1 -1
  43. package/lib/packageVersion.js +1 -1
  44. package/lib/packageVersion.js.map +1 -1
  45. package/lib/remoteChannelContext.d.ts +9 -4
  46. package/lib/remoteChannelContext.d.ts.map +1 -1
  47. package/lib/remoteChannelContext.js +25 -13
  48. package/lib/remoteChannelContext.js.map +1 -1
  49. package/package.json +19 -15
  50. package/src/channelContext.ts +6 -6
  51. package/src/channelDeltaConnection.ts +46 -15
  52. package/src/dataStoreRuntime.ts +146 -68
  53. package/src/localChannelContext.ts +21 -16
  54. package/src/packageVersion.ts +1 -1
  55. package/src/remoteChannelContext.ts +39 -18
@@ -54,6 +54,8 @@ import {
54
54
  VisibilityState,
55
55
  gcDataBlobKey,
56
56
  IInboundSignalMessage,
57
+ type IRuntimeMessageCollection,
58
+ type IRuntimeMessagesContent,
57
59
  } from "@fluidframework/runtime-definitions/internal";
58
60
  import {
59
61
  GCDataBuilder,
@@ -356,6 +358,18 @@ export class FluidDataStoreRuntime
356
358
  public async request(request: IRequest): Promise<IResponse> {
357
359
  try {
358
360
  const parser = RequestParser.create(request);
361
+ // If there are not path parts, and the request is via a handle
362
+ // then we should return the entrypoint object for this runtime.
363
+ // This allows the entrypoint handle to be resolved without the need
364
+ // for the entrypoint object to know anything about requests or handles.
365
+ //
366
+ // This works because the entrypoint handle is an object handle,
367
+ // which always has a real reference to the object itself.
368
+ // Those get serialized and then deserialized into a plain handle, which really just has a path,
369
+ // resolution walks to the runtime, which calls this, and get the true object off the internal object handle
370
+ if (parser.pathParts.length === 0 && request.headers?.viaHandle === true) {
371
+ return { mimeType: "fluid/object", status: 200, value: await this.entryPoint.get() };
372
+ }
359
373
  const id = parser.pathParts[0];
360
374
 
361
375
  if (id === "_channels" || id === "_custom") {
@@ -650,66 +664,151 @@ export class FluidDataStoreRuntime
650
664
  );
651
665
  }
652
666
 
653
- public process(
654
- message: ISequencedDocumentMessage,
655
- local: boolean,
656
- localOpMetadata: unknown,
657
- ) {
667
+ /**
668
+ * Process channel messages. The messages here are contiguous channel types messages in a batch for this data
669
+ * store.
670
+ * @param messageCollection - The collection of messages to process.
671
+ */
672
+ private processChannelMessages(messageCollection: IRuntimeMessageCollection) {
658
673
  this.verifyNotClosed();
659
674
 
660
- try {
661
- // catches as data processing error whether or not they come from async pending queues
662
- switch (message.type) {
663
- case DataStoreMessageType.Attach: {
664
- const attachMessage = message.contents as IAttachMessage;
665
- const id = attachMessage.id;
666
-
667
- // We need to process the GC Data for both local and remote attach messages
668
- processAttachMessageGCData(attachMessage.snapshot, (nodeId, toPath) => {
669
- // Note: nodeId will be "/" unless and until we support sub-DDS GC Nodes
670
- const fromPath = `/${this.id}/${id}${nodeId === "/" ? "" : nodeId}`;
671
- this.dataStoreContext.addedGCOutboundRoute(fromPath, toPath, message.timestamp);
672
- });
673
-
674
- // If a non-local operation then go and create the object
675
- // Otherwise mark it as officially attached.
676
- if (local) {
677
- assert(
678
- this.pendingAttach.delete(id),
679
- 0x17c /* "Unexpected attach (local) channel OP" */,
680
- );
681
- } else {
682
- assert(!this.contexts.has(id), 0x17d /* "Unexpected attach channel OP" */);
683
-
684
- const summarizerNodeParams = {
685
- type: CreateSummarizerNodeSource.FromAttach,
686
- sequenceNumber: message.sequenceNumber,
687
- snapshot: attachMessage.snapshot,
688
- };
689
-
690
- const remoteChannelContext = this.createRemoteChannelContext(
691
- attachMessage,
692
- summarizerNodeParams,
693
- );
694
- this.contexts.set(id, remoteChannelContext);
695
- }
696
- break;
697
- }
675
+ /*
676
+ * Bunch contiguous messages for the same channel and send them together.
677
+ * This is an optimization where DDSes can process a bunch of ops together. DDSes
678
+ * like merge tree or shared tree can process ops more efficiently when they are bunched together.
679
+ */
680
+ let currentAddress: string | undefined;
681
+ let currentMessagesContent: IRuntimeMessagesContent[] = [];
682
+ const { messagesContent, local } = messageCollection;
683
+
684
+ const sendBunchedMessages = () => {
685
+ // Current address will be undefined for the first message in the list.
686
+ if (currentAddress === undefined) {
687
+ return;
688
+ }
689
+
690
+ // process the last set of channel ops
691
+ const channelContext = this.contexts.get(currentAddress);
692
+ assert(!!channelContext, 0xa6b /* Channel context not found */);
693
+
694
+ channelContext.processMessages({
695
+ envelope: messageCollection.envelope,
696
+ messagesContent: currentMessagesContent,
697
+ local,
698
+ });
699
+
700
+ currentMessagesContent = [];
701
+ };
702
+
703
+ for (const { contents, ...restOfMessagesContent } of messagesContent) {
704
+ const contentsEnvelope = contents as IEnvelope;
705
+
706
+ // If the address of the message changes while processing the batch, send the current bunch.
707
+ if (currentAddress !== contentsEnvelope.address) {
708
+ sendBunchedMessages();
709
+ }
710
+
711
+ currentMessagesContent.push({
712
+ contents: contentsEnvelope.contents,
713
+ ...restOfMessagesContent,
714
+ });
715
+ currentAddress = contentsEnvelope.address;
716
+ }
717
+
718
+ // Process the last bunch of messages.
719
+ sendBunchedMessages();
720
+ }
721
+
722
+ private processAttachMessages(messageCollection: IRuntimeMessageCollection) {
723
+ const { envelope, messagesContent, local } = messageCollection;
724
+ for (const { contents } of messagesContent) {
725
+ const attachMessage = contents as IAttachMessage;
726
+ const id = attachMessage.id;
727
+
728
+ // We need to process the GC Data for both local and remote attach messages
729
+ processAttachMessageGCData(attachMessage.snapshot, (nodeId, toPath) => {
730
+ // Note: nodeId will be "/" unless and until we support sub-DDS GC Nodes
731
+ const fromPath = `/${this.id}/${id}${nodeId === "/" ? "" : nodeId}`;
732
+ this.dataStoreContext.addedGCOutboundRoute(fromPath, toPath, envelope.timestamp);
733
+ });
734
+
735
+ // If a non-local operation then go and create the object
736
+ // Otherwise mark it as officially attached.
737
+ if (local) {
738
+ assert(
739
+ this.pendingAttach.delete(id),
740
+ 0x17c /* "Unexpected attach (local) channel OP" */,
741
+ );
742
+ } else {
743
+ assert(!this.contexts.has(id), 0x17d /* "Unexpected attach channel OP" */);
744
+
745
+ const summarizerNodeParams = {
746
+ type: CreateSummarizerNodeSource.FromAttach,
747
+ sequenceNumber: envelope.sequenceNumber,
748
+ snapshot: attachMessage.snapshot,
749
+ };
750
+
751
+ const remoteChannelContext = this.createRemoteChannelContext(
752
+ attachMessage,
753
+ summarizerNodeParams,
754
+ );
755
+ this.contexts.set(id, remoteChannelContext);
756
+ }
757
+ }
758
+ }
759
+
760
+ /**
761
+ * Process messages for this data store. The messages here are contiguous messages in a batch.
762
+ * @param messageCollection - The collection of messages to process.
763
+ */
764
+ public processMessages(messageCollection: IRuntimeMessageCollection): void {
765
+ this.verifyNotClosed();
698
766
 
767
+ const { envelope, messagesContent } = messageCollection;
768
+ try {
769
+ switch (envelope.type) {
699
770
  case DataStoreMessageType.ChannelOp:
700
- this.processChannelOp(message, local, localOpMetadata);
771
+ this.processChannelMessages(messageCollection);
772
+ break;
773
+ case DataStoreMessageType.Attach:
774
+ this.processAttachMessages(messageCollection);
701
775
  break;
702
776
  default:
703
777
  }
704
-
705
- this.emit("op", message);
706
778
  } catch (error) {
707
779
  throw DataProcessingError.wrapIfUnrecognized(
708
780
  error,
709
781
  "fluidDataStoreRuntimeFailedToProcessMessage",
710
- message,
782
+ envelope,
711
783
  );
712
784
  }
785
+
786
+ for (const { contents, clientSequenceNumber } of messagesContent) {
787
+ this.emit("op", { ...envelope, contents, clientSequenceNumber });
788
+ }
789
+ }
790
+
791
+ /**
792
+ * back-compat ADO 21575.
793
+ * This is still here for back-compat purposes because it exists on IFluidDataStoreChannel. Once it is removed from
794
+ * the interface, this method can be removed.
795
+ */
796
+ public process(
797
+ message: ISequencedDocumentMessage,
798
+ local: boolean,
799
+ localOpMetadata: unknown,
800
+ ) {
801
+ this.processMessages({
802
+ envelope: message,
803
+ messagesContent: [
804
+ {
805
+ contents: message.contents,
806
+ localOpMetadata,
807
+ clientSequenceNumber: message.clientSequenceNumber,
808
+ },
809
+ ],
810
+ local,
811
+ });
713
812
  }
714
813
 
715
814
  public processSignal(message: IInboundSignalMessage, local: boolean) {
@@ -1126,27 +1225,6 @@ export class FluidDataStoreRuntime
1126
1225
  this.dataStoreContext.setChannelDirty(address);
1127
1226
  }
1128
1227
 
1129
- private processChannelOp(
1130
- message: ISequencedDocumentMessage,
1131
- local: boolean,
1132
- localOpMetadata: unknown,
1133
- ) {
1134
- this.verifyNotClosed();
1135
-
1136
- const envelope = message.contents as IEnvelope;
1137
-
1138
- const transformed: ISequencedDocumentMessage = {
1139
- ...message,
1140
- contents: envelope.contents,
1141
- };
1142
-
1143
- const channelContext = this.contexts.get(envelope.address);
1144
- assert(!!channelContext, 0x185 /* "Channel not found" */);
1145
- channelContext.processOp(transformed, local, localOpMetadata);
1146
-
1147
- return channelContext;
1148
- }
1149
-
1150
1228
  private attachListener() {
1151
1229
  this.setMaxListeners(Number.MAX_SAFE_INTEGER);
1152
1230
 
@@ -12,13 +12,14 @@ import {
12
12
  import {
13
13
  IDocumentStorageService,
14
14
  ISnapshotTree,
15
- ISequencedDocumentMessage,
16
15
  } from "@fluidframework/driver-definitions/internal";
17
16
  import {
18
17
  ITelemetryContext,
19
18
  IFluidDataStoreContext,
20
19
  IGarbageCollectionData,
21
20
  ISummarizeResult,
21
+ type IPendingMessagesState,
22
+ type IRuntimeMessageCollection,
22
23
  } from "@fluidframework/runtime-definitions/internal";
23
24
  import {
24
25
  ITelemetryLoggerExt,
@@ -41,7 +42,11 @@ import { ISharedObjectRegistry } from "./dataStoreRuntime.js";
41
42
  */
42
43
  export abstract class LocalChannelContextBase implements IChannelContext {
43
44
  private globallyVisible = false;
44
- protected readonly pending: ISequencedDocumentMessage[] = [];
45
+ /** Tracks the messages for this channel that are sent while it's not loaded */
46
+ protected pendingMessagesState: IPendingMessagesState = {
47
+ messageCollections: [],
48
+ pendingCount: 0,
49
+ };
45
50
  constructor(
46
51
  protected readonly id: string,
47
52
  protected readonly runtime: IFluidDataStoreRuntime,
@@ -74,11 +79,11 @@ export abstract class LocalChannelContextBase implements IChannelContext {
74
79
  }
75
80
  }
76
81
 
77
- public processOp(
78
- message: ISequencedDocumentMessage,
79
- local: boolean,
80
- localOpMetadata: unknown,
81
- ): void {
82
+ /**
83
+ * Process messages for this channel context. The messages here are contiguous messages for this context in a batch.
84
+ * @param messageCollection - The collection of messages to process.
85
+ */
86
+ processMessages(messageCollection: IRuntimeMessageCollection): void {
82
87
  assert(
83
88
  this.globallyVisible,
84
89
  0x2d3 /* "Local channel must be globally visible when processing op" */,
@@ -88,13 +93,17 @@ export abstract class LocalChannelContextBase implements IChannelContext {
88
93
  // delay loading. So after the container is attached and some other client joins which start generating
89
94
  // ops for this channel. So not loaded local channel can still receive ops and we store them to process later.
90
95
  if (this.isLoaded) {
91
- this.services.value.deltaConnection.process(message, local, localOpMetadata);
96
+ this.services.value.deltaConnection.processMessages(messageCollection);
92
97
  } else {
93
98
  assert(
94
- local === false,
99
+ !messageCollection.local,
95
100
  0x189 /* "Should always be remote because a local dds shouldn't generate ops before loading" */,
96
101
  );
97
- this.pending.push(message);
102
+ const propsCopy = {
103
+ ...messageCollection,
104
+ messagesContent: Array.from(messageCollection.messagesContent),
105
+ };
106
+ this.pendingMessagesState.messageCollections.push(propsCopy);
98
107
  }
99
108
  }
100
109
 
@@ -254,12 +263,8 @@ export class RehydratedLocalChannelContext extends LocalChannelContextBase {
254
263
  this.id,
255
264
  );
256
265
  // Send all pending messages to the channel
257
- for (const message of this.pending) {
258
- this.services.value.deltaConnection.process(
259
- message,
260
- false,
261
- undefined /* localOpMetadata */,
262
- );
266
+ for (const messageCollection of this.pendingMessagesState.messageCollections) {
267
+ this.services.value.deltaConnection.processMessages(messageCollection);
263
268
  }
264
269
  return channel;
265
270
  } catch (err) {
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/datastore";
9
- export const pkgVersion = "2.5.0-302463";
9
+ export const pkgVersion = "2.5.0";
@@ -12,7 +12,6 @@ import {
12
12
  import {
13
13
  IDocumentStorageService,
14
14
  ISnapshotTree,
15
- ISequencedDocumentMessage,
16
15
  } from "@fluidframework/driver-definitions/internal";
17
16
  import {
18
17
  IExperimentalIncrementalSummaryContext,
@@ -23,6 +22,8 @@ import {
23
22
  ISummarizeInternalResult,
24
23
  ISummarizeResult,
25
24
  ISummarizerNodeWithGC,
25
+ type IPendingMessagesState,
26
+ type IRuntimeMessageCollection,
26
27
  } from "@fluidframework/runtime-definitions/internal";
27
28
  import {
28
29
  ITelemetryLoggerExt,
@@ -42,7 +43,11 @@ import { ISharedObjectRegistry } from "./dataStoreRuntime.js";
42
43
 
43
44
  export class RemoteChannelContext implements IChannelContext {
44
45
  private isLoaded = false;
45
- private pending: ISequencedDocumentMessage[] | undefined = [];
46
+ /** Tracks the messages for this channel that are sent while it's not loaded */
47
+ private pendingMessagesState: IPendingMessagesState | undefined = {
48
+ messageCollections: [],
49
+ pendingCount: 0,
50
+ };
46
51
  private readonly channelP: Promise<IChannel>;
47
52
  private channel: IChannel | undefined;
48
53
  private readonly services: ChannelServiceEndpoints;
@@ -100,16 +105,21 @@ export class RemoteChannelContext implements IChannelContext {
100
105
  this.id,
101
106
  );
102
107
 
103
- // Send all pending messages to the channel
104
- assert(this.pending !== undefined, 0x23f /* "pending undefined" */);
105
- for (const message of this.pending) {
106
- this.services.deltaConnection.process(message, false, undefined /* localOpMetadata */);
108
+ assert(
109
+ this.pendingMessagesState !== undefined,
110
+ 0xa6c /* pending messages state is undefined */,
111
+ );
112
+ for (const messageCollection of this.pendingMessagesState.messageCollections) {
113
+ this.services.deltaConnection.processMessages(messageCollection);
107
114
  }
108
- this.thresholdOpsCounter.send("ProcessPendingOps", this.pending.length);
115
+ this.thresholdOpsCounter.send(
116
+ "ProcessPendingOps",
117
+ this.pendingMessagesState.pendingCount,
118
+ );
109
119
 
110
120
  // Commit changes.
111
121
  this.channel = channel;
112
- this.pending = undefined;
122
+ this.pendingMessagesState = undefined;
113
123
  this.isLoaded = true;
114
124
 
115
125
  // Because have some await between we created the service and here, the connection state might have changed
@@ -161,20 +171,31 @@ export class RemoteChannelContext implements IChannelContext {
161
171
  return this.services.deltaConnection.applyStashedOp(content);
162
172
  }
163
173
 
164
- public processOp(
165
- message: ISequencedDocumentMessage,
166
- local: boolean,
167
- localOpMetadata: unknown,
168
- ): void {
169
- this.summarizerNode.invalidate(message.sequenceNumber);
174
+ /**
175
+ * Process messages for this channel context. The messages here are contiguous messages for this context in a batch.
176
+ * @param messageCollection - The collection of messages to process.
177
+ */
178
+ public processMessages(messageCollection: IRuntimeMessageCollection): void {
179
+ const { envelope, messagesContent, local } = messageCollection;
180
+ this.summarizerNode.invalidate(envelope.sequenceNumber);
170
181
 
171
182
  if (this.isLoaded) {
172
- this.services.deltaConnection.process(message, local, localOpMetadata);
183
+ this.services.deltaConnection.processMessages(messageCollection);
173
184
  } else {
174
185
  assert(!local, 0x195 /* "Remote channel must not be local when processing op" */);
175
- assert(this.pending !== undefined, 0x23e /* "pending is undefined" */);
176
- this.pending.push(message);
177
- this.thresholdOpsCounter.sendIfMultiple("StorePendingOps", this.pending.length);
186
+ assert(
187
+ this.pendingMessagesState !== undefined,
188
+ 0xa6d /* pending messages queue is undefined */,
189
+ );
190
+ this.pendingMessagesState.messageCollections.push({
191
+ ...messageCollection,
192
+ messagesContent: Array.from(messagesContent),
193
+ });
194
+ this.pendingMessagesState.pendingCount += messagesContent.length;
195
+ this.thresholdOpsCounter.sendIfMultiple(
196
+ "StorePendingOps",
197
+ this.pendingMessagesState.pendingCount,
198
+ );
178
199
  }
179
200
  }
180
201