@fluidframework/container-runtime 2.81.1 → 2.83.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 (44) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/container-runtime.test-files.tar +0 -0
  3. package/dist/containerRuntime.d.ts.map +1 -1
  4. package/dist/containerRuntime.js +3 -1
  5. package/dist/containerRuntime.js.map +1 -1
  6. package/dist/packageVersion.d.ts +1 -1
  7. package/dist/packageVersion.js +1 -1
  8. package/dist/packageVersion.js.map +1 -1
  9. package/dist/runtimeLayerCompatState.d.ts +1 -1
  10. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  11. package/dist/runtimeLayerCompatState.js +11 -6
  12. package/dist/runtimeLayerCompatState.js.map +1 -1
  13. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  14. package/dist/summary/summaryDelayLoadedModule/summarizer.js +39 -3
  15. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  16. package/dist/summary/summaryManager.d.ts +2 -0
  17. package/dist/summary/summaryManager.d.ts.map +1 -1
  18. package/dist/summary/summaryManager.js +34 -7
  19. package/dist/summary/summaryManager.js.map +1 -1
  20. package/lib/containerRuntime.d.ts.map +1 -1
  21. package/lib/containerRuntime.js +3 -1
  22. package/lib/containerRuntime.js.map +1 -1
  23. package/lib/packageVersion.d.ts +1 -1
  24. package/lib/packageVersion.js +1 -1
  25. package/lib/packageVersion.js.map +1 -1
  26. package/lib/runtimeLayerCompatState.d.ts +1 -1
  27. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  28. package/lib/runtimeLayerCompatState.js +12 -7
  29. package/lib/runtimeLayerCompatState.js.map +1 -1
  30. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  31. package/lib/summary/summaryDelayLoadedModule/summarizer.js +40 -4
  32. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  33. package/lib/summary/summaryManager.d.ts +2 -0
  34. package/lib/summary/summaryManager.d.ts.map +1 -1
  35. package/lib/summary/summaryManager.js +34 -7
  36. package/lib/summary/summaryManager.js.map +1 -1
  37. package/package.json +21 -21
  38. package/src/containerRuntime.ts +1 -0
  39. package/src/packageVersion.ts +1 -1
  40. package/src/runtimeLayerCompatState.ts +18 -6
  41. package/src/summary/summaryDelayLoadedModule/summarizer.ts +48 -5
  42. package/src/summary/summaryManager.ts +39 -8
  43. package/api-extractor-lint.json +0 -4
  44. package/biome.jsonc +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "2.81.1",
3
+ "version": "2.83.0",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -119,19 +119,19 @@
119
119
  "temp-directory": "nyc/.nyc_output"
120
120
  },
121
121
  "dependencies": {
122
- "@fluid-internal/client-utils": "~2.81.1",
123
- "@fluidframework/container-definitions": "~2.81.1",
124
- "@fluidframework/container-runtime-definitions": "~2.81.1",
125
- "@fluidframework/core-interfaces": "~2.81.1",
126
- "@fluidframework/core-utils": "~2.81.1",
127
- "@fluidframework/datastore": "~2.81.1",
128
- "@fluidframework/driver-definitions": "~2.81.1",
129
- "@fluidframework/driver-utils": "~2.81.1",
130
- "@fluidframework/id-compressor": "~2.81.1",
131
- "@fluidframework/runtime-definitions": "~2.81.1",
132
- "@fluidframework/runtime-utils": "~2.81.1",
133
- "@fluidframework/telemetry-utils": "~2.81.1",
134
- "@tylerbu/sorted-btree-es6": "^1.8.0",
122
+ "@fluid-internal/client-utils": "~2.83.0",
123
+ "@fluidframework/container-definitions": "~2.83.0",
124
+ "@fluidframework/container-runtime-definitions": "~2.83.0",
125
+ "@fluidframework/core-interfaces": "~2.83.0",
126
+ "@fluidframework/core-utils": "~2.83.0",
127
+ "@fluidframework/datastore": "~2.83.0",
128
+ "@fluidframework/driver-definitions": "~2.83.0",
129
+ "@fluidframework/driver-utils": "~2.83.0",
130
+ "@fluidframework/id-compressor": "~2.83.0",
131
+ "@fluidframework/runtime-definitions": "~2.83.0",
132
+ "@fluidframework/runtime-utils": "~2.83.0",
133
+ "@fluidframework/telemetry-utils": "~2.83.0",
134
+ "@tylerbu/sorted-btree-es6": "^2.1.1",
135
135
  "double-ended-queue": "^2.1.0-0",
136
136
  "lz4js": "^0.2.0",
137
137
  "semver-ts": "^1.0.3",
@@ -140,21 +140,21 @@
140
140
  "devDependencies": {
141
141
  "@arethetypeswrong/cli": "^0.18.2",
142
142
  "@biomejs/biome": "~1.9.3",
143
- "@fluid-internal/mocha-test-setup": "~2.81.1",
144
- "@fluid-private/stochastic-test-utils": "~2.81.1",
145
- "@fluid-private/test-pairwise-generator": "~2.81.1",
143
+ "@fluid-internal/mocha-test-setup": "~2.83.0",
144
+ "@fluid-private/stochastic-test-utils": "~2.83.0",
145
+ "@fluid-private/test-pairwise-generator": "~2.83.0",
146
146
  "@fluid-tools/benchmark": "^0.52.0",
147
147
  "@fluid-tools/build-cli": "^0.63.0",
148
148
  "@fluidframework/build-common": "^2.0.3",
149
149
  "@fluidframework/build-tools": "^0.63.0",
150
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.81.0",
151
- "@fluidframework/eslint-config-fluid": "~2.81.1",
152
- "@fluidframework/test-runtime-utils": "~2.81.1",
150
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@2.82.0",
151
+ "@fluidframework/eslint-config-fluid": "~2.83.0",
152
+ "@fluidframework/test-runtime-utils": "~2.83.0",
153
153
  "@microsoft/api-extractor": "7.52.11",
154
154
  "@types/double-ended-queue": "^2.1.0",
155
155
  "@types/lz4js": "^0.2.0",
156
156
  "@types/mocha": "^10.0.10",
157
- "@types/node": "^18.19.0",
157
+ "@types/node": "~20.19.30",
158
158
  "@types/sinon": "^17.0.3",
159
159
  "c8": "^10.1.3",
160
160
  "concurrently": "^9.2.1",
@@ -2872,6 +2872,7 @@ export class ContainerRuntime
2872
2872
  }
2873
2873
  case ConnectionState.CatchingUp: {
2874
2874
  assert(
2875
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- TODO: ADO#58523 Code owners should verify if this code change is safe and make it if so or update this comment otherwise
2875
2876
  this.getConnectionState !== undefined &&
2876
2877
  this.getConnectionState() === ConnectionState.CatchingUp,
2877
2878
  0xc8d /* connection state mismatch between getConnectionState and setConnectionStatus notification */,
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-runtime";
9
- export const pkgVersion = "2.81.1";
9
+ export const pkgVersion = "2.83.0";
@@ -5,6 +5,7 @@
5
5
 
6
6
  import {
7
7
  generation,
8
+ LayerCompatibilityPolicyWindowMonths,
8
9
  type ILayerCompatDetails,
9
10
  type ILayerCompatSupportRequirements,
10
11
  } from "@fluid-internal/client-utils";
@@ -59,10 +60,15 @@ export const runtimeCompatDetailsForLoader: ILayerCompatDetails = {
59
60
  */
60
61
  export const loaderSupportRequirementsForRuntime: ILayerCompatSupportRequirements = {
61
62
  /**
62
- * Minimum generation that Loader must be at to be compatible with Runtime. Note that 0 is used here so
63
- * that Loader layers before the introduction of the layer compatibility enforcement are compatible.
63
+ * Minimum generation that Loader must be at to be compatible with this Runtime. This is calculated
64
+ * based on the LayerCompatibilityPolicyWindowMonths.RuntimeLoader value which defines how many months old can
65
+ * the Loader layer be compared to the Runtime layer for them to still be considered compatible.
66
+ * The minimum valid generation value is 0.
64
67
  */
65
- minSupportedGeneration: 0,
68
+ minSupportedGeneration: Math.max(
69
+ 0,
70
+ runtimeCoreCompatDetails.generation - LayerCompatibilityPolicyWindowMonths.RuntimeLoader,
71
+ ),
66
72
  /**
67
73
  * The features that the Loader must support to be compatible with Runtime.
68
74
  */
@@ -87,10 +93,16 @@ export const runtimeCompatDetailsForDataStore: ILayerCompatDetails = {
87
93
  */
88
94
  export const dataStoreSupportRequirementsForRuntime: ILayerCompatSupportRequirements = {
89
95
  /**
90
- * Minimum generation that DataStore must be at to be compatible with Runtime. Note that 0 is used here so
91
- * that DataStore layers before the introduction of the layer compatibility enforcement are compatible.
96
+ * Minimum generation that DataStore must be at to be compatible with this Runtime. This is calculated
97
+ * based on the LayerCompatibilityPolicyWindowMonths.RuntimeDataStore value which defines how many months old can
98
+ * the DataStore layer be compared to the Runtime layer for them to still be considered compatible.
99
+ * The minimum valid generation value is 0.
92
100
  */
93
- minSupportedGeneration: 0,
101
+ minSupportedGeneration: Math.max(
102
+ 0,
103
+ runtimeCoreCompatDetails.generation -
104
+ LayerCompatibilityPolicyWindowMonths.RuntimeDataStore,
105
+ ),
94
106
  /**
95
107
  * The features that the DataStore must support to be compatible with Runtime.
96
108
  */
@@ -9,7 +9,7 @@ import type {
9
9
  SummarizerStopReason,
10
10
  } from "@fluidframework/container-runtime-definitions/internal";
11
11
  import type { IFluidHandleContext } from "@fluidframework/core-interfaces/internal";
12
- import { Deferred } from "@fluidframework/core-utils/internal";
12
+ import { assert, Deferred } from "@fluidframework/core-utils/internal";
13
13
  import {
14
14
  type IFluidErrorBase,
15
15
  type ITelemetryLoggerExt,
@@ -165,9 +165,40 @@ export class Summarizer extends TypedEventEmitter<ISummarizerEvents> implements
165
165
  }
166
166
 
167
167
  private async runCore(onBehalfOf: string): Promise<SummarizerStopReason> {
168
- const runCoordinator: ICancellableSummarizerController = await this.runCoordinatorCreateFn(
169
- this.runtime,
168
+ // Race coordinator creation against a timeout to ensure we don't hang indefinitely
169
+ // if the summarizer container fails to connect.
170
+ let coordinatorTimedOut = false;
171
+ const coordinatorTimeoutMs = 2 * 60 * 1000; // 2 minutes
172
+ const coordinatorResult = await Promise.race([
173
+ this.runCoordinatorCreateFn(this.runtime).then((coordinator) => {
174
+ if (coordinatorTimedOut) {
175
+ coordinator.stop("summarizerClientDisconnected");
176
+ }
177
+ return coordinator;
178
+ }),
179
+ new Promise<undefined>((resolve) =>
180
+ setTimeout(() => {
181
+ coordinatorTimedOut = true;
182
+ resolve(undefined);
183
+ }, coordinatorTimeoutMs),
184
+ ),
185
+ ]);
186
+
187
+ // If we timed out before coordinator was created, exit early
188
+ if (coordinatorTimedOut) {
189
+ this.logger.sendTelemetryEvent({
190
+ eventName: "CreateRunCoordinatorTimeout",
191
+ onBehalfOf,
192
+ timeoutMs: coordinatorTimeoutMs,
193
+ });
194
+ return "summarizerClientDisconnected";
195
+ }
196
+
197
+ assert(
198
+ coordinatorResult !== undefined,
199
+ 0xcd6 /* Expect coordinatorResult to be defined */,
170
200
  );
201
+ const runCoordinator = coordinatorResult;
171
202
 
172
203
  // Wait for either external signal to cancel, or loss of connectivity.
173
204
  const stopP = Promise.race([runCoordinator.waitCancelled, this.stopDeferred.promise]);
@@ -216,10 +247,22 @@ export class Summarizer extends TypedEventEmitter<ISummarizerEvents> implements
216
247
  // summarizer client to not be created until current summarizer fully moves to exit, and that would reduce
217
248
  // cons of #2 substantially.
218
249
 
219
- // Cleanup after running
220
- await runningSummarizer.waitStop(
250
+ // Cleanup after running with a timeout to prevent hanging
251
+ const waitStopPromise = runningSummarizer.waitStop(
221
252
  !runCoordinator.cancelled && Summarizer.stopReasonCanRunLastSummary(stopReason),
222
253
  );
254
+ const summarizerStopTimeoutMs = 2 * 60 * 1000; // 2 minutes
255
+ const timeoutPromise = new Promise<"timeout">((resolve) =>
256
+ setTimeout(() => resolve("timeout"), summarizerStopTimeoutMs),
257
+ );
258
+ const waitStopResult = await Promise.race([waitStopPromise, timeoutPromise]);
259
+ if (waitStopResult === "timeout") {
260
+ this.logger.sendTelemetryEvent({
261
+ eventName: "SummarizerStopTimeout",
262
+ onBehalfOf,
263
+ timeoutMs: summarizerStopTimeoutMs,
264
+ });
265
+ }
223
266
 
224
267
  // Propagate reason and ensure that if someone is waiting for cancellation token, they are moving to exit
225
268
  runCoordinator.stop(stopReason);
@@ -103,6 +103,7 @@ export class SummaryManager
103
103
  private state = SummaryManagerState.Off;
104
104
  private summarizer?: ISummarizer;
105
105
  private _disposed = false;
106
+ private summarizerStopTimeout?: ReturnType<typeof setTimeout>;
106
107
 
107
108
  public get disposed(): boolean {
108
109
  return this._disposed;
@@ -347,16 +348,26 @@ export class SummaryManager
347
348
  })
348
349
  .finally(() => {
349
350
  assert(this.state !== SummaryManagerState.Off, 0x264 /* "Expected: Not Off" */);
350
- this.state = SummaryManagerState.Off;
351
+ this.cleanupAfterSummarizerStop();
352
+ });
353
+ }
351
354
 
352
- this.cleanupForwardedEvents();
353
- this.summarizer?.close();
354
- this.summarizer = undefined;
355
+ private cleanupAfterSummarizerStop(): void {
356
+ this.state = SummaryManagerState.Off;
355
357
 
356
- if (this.getShouldSummarizeState().shouldSummarize) {
357
- this.startSummarization();
358
- }
359
- });
358
+ // Clear any pending stop timeout to avoid it firing for a different summarizer
359
+ if (this.summarizerStopTimeout !== undefined) {
360
+ clearTimeout(this.summarizerStopTimeout);
361
+ this.summarizerStopTimeout = undefined;
362
+ }
363
+
364
+ this.cleanupForwardedEvents();
365
+ this.summarizer?.close();
366
+ this.summarizer = undefined;
367
+
368
+ if (this.getShouldSummarizeState().shouldSummarize) {
369
+ this.startSummarization();
370
+ }
360
371
  }
361
372
 
362
373
  private stop(reason: SummarizerStopReason): void {
@@ -368,6 +379,23 @@ export class SummaryManager
368
379
  // Stopping the running summarizer client should trigger a change
369
380
  // in states when the running summarizer closes
370
381
  this.summarizer?.stop(reason);
382
+
383
+ const summarizerCloseTimeoutMs = 2 * 60 * 1000; // 2 minutes
384
+ // Clear any existing timeout before setting a new one
385
+ if (this.summarizerStopTimeout !== undefined) {
386
+ clearTimeout(this.summarizerStopTimeout);
387
+ }
388
+ // Set a timeout to force cleanup if the summarizer doesn't close in time
389
+ this.summarizerStopTimeout = setTimeout(() => {
390
+ if (this.state === SummaryManagerState.Stopping && this.summarizer !== undefined) {
391
+ this.logger.sendTelemetryEvent({
392
+ eventName: "SummarizerStopTimeout",
393
+ timeoutMs: summarizerCloseTimeoutMs,
394
+ stopReason: reason,
395
+ });
396
+ this.cleanupAfterSummarizerStop();
397
+ }
398
+ }, summarizerCloseTimeoutMs);
371
399
  }
372
400
 
373
401
  /**
@@ -453,6 +481,9 @@ export class SummaryManager
453
481
  this.connectedState.off("connected", this.handleConnected);
454
482
  this.connectedState.off("disconnected", this.handleDisconnected);
455
483
  this.cleanupForwardedEvents();
484
+ if (this.summarizerStopTimeout !== undefined) {
485
+ clearTimeout(this.summarizerStopTimeout);
486
+ }
456
487
  this._disposed = true;
457
488
  }
458
489
 
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3
- "extends": "../../../common/build/build-common/api-extractor-lint.esm.primary.json"
4
- }
package/biome.jsonc DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3
- "extends": ["../../../biome.jsonc"]
4
- }