@fluidframework/container-loader 1.2.2 → 2.0.0-internal.1.0.0.81589

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 (105) hide show
  1. package/dist/audience.d.ts +2 -2
  2. package/dist/audience.d.ts.map +1 -1
  3. package/dist/audience.js.map +1 -1
  4. package/dist/catchUpMonitor.d.ts +40 -0
  5. package/dist/catchUpMonitor.d.ts.map +1 -0
  6. package/dist/catchUpMonitor.js +74 -0
  7. package/dist/catchUpMonitor.js.map +1 -0
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +0 -1
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionState.d.ts +0 -5
  12. package/dist/connectionState.d.ts.map +1 -1
  13. package/dist/connectionState.js +0 -5
  14. package/dist/connectionState.js.map +1 -1
  15. package/dist/connectionStateHandler.d.ts +12 -4
  16. package/dist/connectionStateHandler.d.ts.map +1 -1
  17. package/dist/connectionStateHandler.js +47 -15
  18. package/dist/connectionStateHandler.js.map +1 -1
  19. package/dist/container.d.ts +8 -6
  20. package/dist/container.d.ts.map +1 -1
  21. package/dist/container.js +82 -46
  22. package/dist/container.js.map +1 -1
  23. package/dist/deltaManager.d.ts.map +1 -1
  24. package/dist/deltaManager.js +6 -6
  25. package/dist/deltaManager.js.map +1 -1
  26. package/dist/deltaManagerProxy.d.ts +4 -1
  27. package/dist/deltaManagerProxy.d.ts.map +1 -1
  28. package/dist/deltaQueue.d.ts +9 -2
  29. package/dist/deltaQueue.d.ts.map +1 -1
  30. package/dist/deltaQueue.js +31 -26
  31. package/dist/deltaQueue.js.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/loader.d.ts +7 -0
  36. package/dist/loader.d.ts.map +1 -1
  37. package/dist/loader.js +4 -3
  38. package/dist/loader.js.map +1 -1
  39. package/dist/packageVersion.d.ts +1 -1
  40. package/dist/packageVersion.d.ts.map +1 -1
  41. package/dist/packageVersion.js +1 -1
  42. package/dist/packageVersion.js.map +1 -1
  43. package/dist/protocol.d.ts +22 -0
  44. package/dist/protocol.d.ts.map +1 -0
  45. package/dist/protocol.js +52 -0
  46. package/dist/protocol.js.map +1 -0
  47. package/lib/audience.d.ts +2 -2
  48. package/lib/audience.d.ts.map +1 -1
  49. package/lib/audience.js.map +1 -1
  50. package/lib/catchUpMonitor.d.ts +40 -0
  51. package/lib/catchUpMonitor.d.ts.map +1 -0
  52. package/lib/catchUpMonitor.js +69 -0
  53. package/lib/catchUpMonitor.js.map +1 -0
  54. package/lib/connectionManager.d.ts.map +1 -1
  55. package/lib/connectionManager.js +0 -1
  56. package/lib/connectionManager.js.map +1 -1
  57. package/lib/connectionState.d.ts +0 -5
  58. package/lib/connectionState.d.ts.map +1 -1
  59. package/lib/connectionState.js +0 -5
  60. package/lib/connectionState.js.map +1 -1
  61. package/lib/connectionStateHandler.d.ts +12 -4
  62. package/lib/connectionStateHandler.d.ts.map +1 -1
  63. package/lib/connectionStateHandler.js +47 -15
  64. package/lib/connectionStateHandler.js.map +1 -1
  65. package/lib/container.d.ts +8 -6
  66. package/lib/container.d.ts.map +1 -1
  67. package/lib/container.js +82 -46
  68. package/lib/container.js.map +1 -1
  69. package/lib/deltaManager.d.ts.map +1 -1
  70. package/lib/deltaManager.js +6 -6
  71. package/lib/deltaManager.js.map +1 -1
  72. package/lib/deltaManagerProxy.d.ts +4 -1
  73. package/lib/deltaManagerProxy.d.ts.map +1 -1
  74. package/lib/deltaQueue.d.ts +9 -2
  75. package/lib/deltaQueue.d.ts.map +1 -1
  76. package/lib/deltaQueue.js +32 -27
  77. package/lib/deltaQueue.js.map +1 -1
  78. package/lib/index.d.ts +1 -0
  79. package/lib/index.d.ts.map +1 -1
  80. package/lib/index.js.map +1 -1
  81. package/lib/loader.d.ts +7 -0
  82. package/lib/loader.d.ts.map +1 -1
  83. package/lib/loader.js +4 -3
  84. package/lib/loader.js.map +1 -1
  85. package/lib/packageVersion.d.ts +1 -1
  86. package/lib/packageVersion.d.ts.map +1 -1
  87. package/lib/packageVersion.js +1 -1
  88. package/lib/packageVersion.js.map +1 -1
  89. package/lib/protocol.d.ts +22 -0
  90. package/lib/protocol.d.ts.map +1 -0
  91. package/lib/protocol.js +48 -0
  92. package/lib/protocol.js.map +1 -0
  93. package/package.json +23 -15
  94. package/src/audience.ts +2 -2
  95. package/src/catchUpMonitor.ts +99 -0
  96. package/src/connectionManager.ts +0 -1
  97. package/src/connectionState.ts +0 -6
  98. package/src/connectionStateHandler.ts +55 -15
  99. package/src/container.ts +115 -63
  100. package/src/deltaManager.ts +6 -4
  101. package/src/deltaQueue.ts +34 -28
  102. package/src/index.ts +4 -0
  103. package/src/loader.ts +13 -2
  104. package/src/packageVersion.ts +1 -1
  105. package/src/protocol.ts +96 -0
package/src/deltaQueue.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IDeltaQueue, IDeltaQueueEvents } from "@fluidframework/container-definitions";
7
- import { assert, performance, Deferred, TypedEventEmitter } from "@fluidframework/common-utils";
7
+ import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
8
8
  import Deque from "double-ended-queue";
9
9
 
10
10
  export interface IDeltaQueueWriter<T> {
@@ -30,7 +30,7 @@ export class DeltaQueue<T>
30
30
  * When processing is ongoing, holds a deferred that will resolve once processing stops.
31
31
  * Undefined when not processing.
32
32
  */
33
- private processingDeferred: Deferred<void> | undefined;
33
+ private processingPromise: Promise<{ count: number; duration: number; }> | undefined;
34
34
 
35
35
  public get disposed(): boolean {
36
36
  return this.isDisposed;
@@ -48,13 +48,11 @@ export class DeltaQueue<T>
48
48
  }
49
49
 
50
50
  public get idle(): boolean {
51
- return this.processingDeferred === undefined && this.q.length === 0;
51
+ return this.processingPromise === undefined && this.q.length === 0;
52
52
  }
53
53
 
54
- public async waitTillProcessingDone(): Promise<void> {
55
- if (this.processingDeferred !== undefined) {
56
- return this.processingDeferred.promise;
57
- }
54
+ public async waitTillProcessingDone() {
55
+ return this.processingPromise ?? { count: 0, duration: 0 };
58
56
  }
59
57
 
60
58
  /**
@@ -98,7 +96,7 @@ export class DeltaQueue<T>
98
96
  this.pauseCount++;
99
97
  // If called from within the processing loop, we are in the middle of processing an op. Return a promise
100
98
  // that will resolve when processing has actually stopped.
101
- return this.waitTillProcessingDone();
99
+ await this.waitTillProcessingDone();
102
100
  }
103
101
 
104
102
  public resume(): void {
@@ -113,20 +111,31 @@ export class DeltaQueue<T>
113
111
  * not already started.
114
112
  */
115
113
  private ensureProcessing() {
116
- if (!this.paused && this.processingDeferred === undefined) {
117
- this.processingDeferred = new Deferred<void>();
114
+ if (this.anythingToProcess() && this.processingPromise === undefined) {
118
115
  // Use a resolved promise to start the processing on a separate stack.
119
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
120
- Promise.resolve().then(() => {
121
- this.processDeltas();
122
- if (this.processingDeferred !== undefined) {
123
- this.processingDeferred.resolve();
124
- this.processingDeferred = undefined;
125
- }
116
+ this.processingPromise = Promise.resolve().then(() => {
117
+ assert(this.processingPromise !== undefined, "reentrancy?");
118
+ const result = this.processDeltas();
119
+ assert(this.processingPromise !== undefined, "reentrancy?");
120
+ // WARNING: Do not move next line to .finally() clause!
121
+ // It runs async and creates a race condition where incoming ensureProcessing() call observes
122
+ // from previous run while previous run is over (but finally clause was not scheduled yet)
123
+ this.processingPromise = undefined;
124
+ return result;
125
+ }).catch((error) => {
126
+ this.error = error;
127
+ this.processingPromise = undefined;
128
+ this.emit("error", error);
129
+ return { count: 0, duration: 0 };
126
130
  });
131
+ assert(this.processingPromise !== undefined, "processDeltas() should run async");
127
132
  }
128
133
  }
129
134
 
135
+ private anythingToProcess() {
136
+ return this.q.length !== 0 && !this.paused && this.error === undefined;
137
+ }
138
+
130
139
  /**
131
140
  * Executes the delta processing loop until a stop condition is reached.
132
141
  */
@@ -136,24 +145,21 @@ export class DeltaQueue<T>
136
145
 
137
146
  // For grouping to work we must process all local messages immediately and in the single turn.
138
147
  // So loop over them until no messages to process, we have become paused, or hit an error.
139
- while (!(this.q.length === 0 || this.paused || this.error !== undefined)) {
148
+ while (this.anythingToProcess()) {
140
149
  // Get the next message in the queue
141
150
  const next = this.q.shift();
142
151
  count++;
143
152
  // Process the message.
144
- try {
145
- // We know next is defined since we did a length check just prior to shifting.
146
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
147
- this.worker(next!);
148
- this.emit("op", next);
149
- } catch (error) {
150
- this.error = error;
151
- this.emit("error", error);
152
- }
153
+ // We know next is defined since we did a length check just prior to shifting.
154
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155
+ this.worker(next!);
156
+ this.emit("op", next);
153
157
  }
154
158
 
159
+ const duration = performance.now() - start;
155
160
  if (this.q.length === 0) {
156
- this.emit("idle", count, performance.now() - start);
161
+ this.emit("idle", count, duration);
157
162
  }
163
+ return { count, duration };
158
164
  }
159
165
  }
package/src/index.ts CHANGED
@@ -21,3 +21,7 @@ export {
21
21
  Loader,
22
22
  RelativeLoader,
23
23
  } from "./loader";
24
+ export {
25
+ IProtocolHandler,
26
+ ProtocolHandlerBuilder,
27
+ } from "./protocol";
package/src/loader.ts CHANGED
@@ -48,6 +48,7 @@ import {
48
48
  import { Container, IPendingContainerState } from "./container";
49
49
  import { IParsedUrl, parseUrl } from "./utils";
50
50
  import { pkgVersion } from "./packageVersion";
51
+ import { ProtocolHandlerBuilder } from "./protocol";
51
52
 
52
53
  function canUseCache(request: IRequest): boolean {
53
54
  if (request.headers === undefined) {
@@ -211,7 +212,13 @@ export interface ILoaderProps {
211
212
  /**
212
213
  * The configuration provider which may be used to control features.
213
214
  */
214
- readonly configProvider?: IConfigProviderBase;
215
+ readonly configProvider?: IConfigProviderBase;
216
+
217
+ /**
218
+ * Optional property for allowing the container to use a custom
219
+ * protocol implementation for handling the quorum and/or the audience.
220
+ */
221
+ readonly protocolHandlerBuilder?: ProtocolHandlerBuilder;
215
222
  }
216
223
 
217
224
  /**
@@ -278,6 +285,7 @@ export class Loader implements IHostLoader {
278
285
  private readonly containers = new Map<string, Promise<Container>>();
279
286
  public readonly services: ILoaderServices;
280
287
  private readonly mc: MonitoringContext;
288
+ private readonly protocolHandlerBuilder: ProtocolHandlerBuilder | undefined;
281
289
 
282
290
  constructor(loaderProps: ILoaderProps) {
283
291
  const scope: FluidObject<ILoader> = { ...loaderProps.scope };
@@ -306,6 +314,7 @@ export class Loader implements IHostLoader {
306
314
  };
307
315
  this.mc = loggerToMonitoringContext(
308
316
  ChildLogger.create(this.services.subLogger, "Loader"));
317
+ this.protocolHandlerBuilder = loaderProps.protocolHandlerBuilder;
309
318
  }
310
319
 
311
320
  public get IFluidRouter(): IFluidRouter { return this; }
@@ -314,6 +323,7 @@ export class Loader implements IHostLoader {
314
323
  const container = await Container.createDetached(
315
324
  this,
316
325
  codeDetails,
326
+ this.protocolHandlerBuilder,
317
327
  );
318
328
 
319
329
  if (this.cachingEnabled) {
@@ -330,7 +340,7 @@ export class Loader implements IHostLoader {
330
340
  }
331
341
 
332
342
  public async rehydrateDetachedContainerFromSnapshot(snapshot: string): Promise<IContainer> {
333
- return Container.rehydrateDetachedFromSnapshot(this, snapshot);
343
+ return Container.rehydrateDetachedFromSnapshot(this, snapshot, this.protocolHandlerBuilder);
334
344
  }
335
345
 
336
346
  public async resolve(request: IRequest, pendingLocalState?: string): Promise<IContainer> {
@@ -482,6 +492,7 @@ export class Loader implements IHostLoader {
482
492
  loadMode: request.headers?.[LoaderHeader.loadMode],
483
493
  },
484
494
  pendingLocalState,
495
+ this.protocolHandlerBuilder,
485
496
  );
486
497
  }
487
498
  }
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "1.2.2";
9
+ export const pkgVersion = "2.0.0-internal.1.0.0.81589";
@@ -0,0 +1,96 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IAudienceOwner } from "@fluidframework/container-definitions";
7
+ import {
8
+ ILocalSequencedClient,
9
+ IProtocolHandler as IBaseProtocolHandler,
10
+ IQuorumSnapshot,
11
+ ProtocolOpHandler,
12
+ } from "@fluidframework/protocol-base";
13
+ import {
14
+ IDocumentAttributes,
15
+ IProcessMessageResult,
16
+ ISequencedDocumentMessage,
17
+ ISignalClient,
18
+ ISignalMessage,
19
+ MessageType,
20
+ } from "@fluidframework/protocol-definitions";
21
+
22
+ /**
23
+ * Function to be used for creating a protocol handler.
24
+ */
25
+ export type ProtocolHandlerBuilder = (
26
+ attributes: IDocumentAttributes,
27
+ snapshot: IQuorumSnapshot,
28
+ sendProposal: (key: string, value: any) => number,
29
+ initialClients: ISignalClient[],
30
+ ) => IProtocolHandler;
31
+
32
+ export interface IProtocolHandler extends IBaseProtocolHandler {
33
+ readonly audience: IAudienceOwner;
34
+ processSignal(message: ISignalMessage);
35
+ }
36
+
37
+ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
38
+ constructor(
39
+ attributes: IDocumentAttributes,
40
+ quorumSnapshot: IQuorumSnapshot,
41
+ sendProposal: (key: string, value: any) => number,
42
+ initialClients: ISignalClient[],
43
+ readonly audience: IAudienceOwner,
44
+ ) {
45
+ super(
46
+ attributes.minimumSequenceNumber,
47
+ attributes.sequenceNumber,
48
+ attributes.term,
49
+ quorumSnapshot.members,
50
+ quorumSnapshot.proposals,
51
+ quorumSnapshot.values,
52
+ sendProposal,
53
+ );
54
+
55
+ for (const initialClient of initialClients) {
56
+ this.audience.addMember(initialClient.clientId, initialClient.client);
57
+ }
58
+ }
59
+
60
+ public processMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult {
61
+ const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
62
+
63
+ // Check and report if we're getting messages from a clientId that we previously
64
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
65
+ if (message.clientId != null) {
66
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
67
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
68
+ throw new Error("Remote message's clientId is missing from the quorum");
69
+ }
70
+
71
+ if (client?.shouldHaveLeft === true && message.type !== MessageType.NoOp) {
72
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
73
+ throw new Error("Remote message's clientId already should have left");
74
+ }
75
+ }
76
+
77
+ return super.processMessage(message, local);
78
+ }
79
+
80
+ public processSignal(message: ISignalMessage) {
81
+ const innerContent = message.content as { content: any; type: string; };
82
+ switch (innerContent.type) {
83
+ case MessageType.ClientJoin: {
84
+ const newClient = innerContent.content as ISignalClient;
85
+ this.audience.addMember(newClient.clientId, newClient.client);
86
+ break;
87
+ }
88
+ case MessageType.ClientLeave: {
89
+ const leftClientId = innerContent.content as string;
90
+ this.audience.removeMember(leftClientId);
91
+ break;
92
+ }
93
+ default: break;
94
+ }
95
+ }
96
+ }