@fluidframework/container-runtime 2.74.0-370705 → 2.80.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.
- package/CHANGELOG.md +8 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +0 -2
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +4 -5
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +3 -3
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +20 -2
- package/dist/channelCollection.js.map +1 -1
- package/dist/containerRuntime.d.ts +5 -0
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +13 -7
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +1 -1
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts +56 -9
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +56 -9
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/runtimeLayerCompatState.d.ts +10 -6
- package/dist/runtimeLayerCompatState.d.ts.map +1 -1
- package/dist/runtimeLayerCompatState.js +16 -6
- package/dist/runtimeLayerCompatState.js.map +1 -1
- package/dist/summary/summarizerClientElection.d.ts.map +1 -1
- package/dist/summary/summarizerClientElection.js +1 -0
- package/dist/summary/summarizerClientElection.js.map +1 -1
- package/dist/summary/summarizerTypes.d.ts +5 -0
- package/dist/summary/summarizerTypes.d.ts.map +1 -1
- package/dist/summary/summarizerTypes.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +3 -1
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js +39 -18
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizer.js +13 -11
- package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +4 -1
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js +30 -9
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +1 -1
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +10 -9
- package/dist/summary/summaryManager.js.map +1 -1
- package/eslint.config.mts +31 -0
- package/lib/blobManager/blobManager.d.ts +0 -2
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +4 -5
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +3 -3
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +20 -2
- package/lib/channelCollection.js.map +1 -1
- package/lib/containerRuntime.d.ts +5 -0
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +13 -7
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts +56 -9
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +56 -9
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/runtimeLayerCompatState.d.ts +10 -6
- package/lib/runtimeLayerCompatState.d.ts.map +1 -1
- package/lib/runtimeLayerCompatState.js +15 -5
- package/lib/runtimeLayerCompatState.js.map +1 -1
- package/lib/summary/summarizerClientElection.d.ts.map +1 -1
- package/lib/summary/summarizerClientElection.js +1 -0
- package/lib/summary/summarizerClientElection.js.map +1 -1
- package/lib/summary/summarizerTypes.d.ts +5 -0
- package/lib/summary/summarizerTypes.d.ts.map +1 -1
- package/lib/summary/summarizerTypes.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts +3 -1
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js +39 -18
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizer.js +13 -11
- package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts +4 -1
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.d.ts.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js +26 -5
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +1 -1
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +10 -9
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +27 -26
- package/src/blobManager/blobManager.ts +3 -7
- package/src/channelCollection.ts +29 -5
- package/src/containerRuntime.ts +19 -6
- package/src/dataStoreContext.ts +2 -2
- package/src/dataStoreContexts.ts +56 -9
- package/src/packageVersion.ts +1 -1
- package/src/runtimeLayerCompatState.ts +25 -9
- package/src/summary/summarizerClientElection.ts +1 -0
- package/src/summary/summarizerTypes.ts +5 -0
- package/src/summary/summaryDelayLoadedModule/runningSummarizer.ts +54 -26
- package/src/summary/summaryDelayLoadedModule/summarizer.ts +13 -11
- package/src/summary/summaryDelayLoadedModule/summaryGenerator.ts +35 -5
- package/src/summary/summaryManager.ts +11 -10
|
@@ -270,7 +270,6 @@ export class BlobManager {
|
|
|
270
270
|
// blobPath's format - `/<basePath>/<localId>`.
|
|
271
271
|
private readonly isBlobDeleted: (blobPath: string) => boolean;
|
|
272
272
|
private readonly runtime: IBlobManagerRuntime;
|
|
273
|
-
private readonly localIdGenerator: () => string;
|
|
274
273
|
|
|
275
274
|
private readonly createBlobPayloadPending: boolean;
|
|
276
275
|
|
|
@@ -298,7 +297,6 @@ export class BlobManager {
|
|
|
298
297
|
readonly isBlobDeleted: (blobPath: string) => boolean;
|
|
299
298
|
readonly runtime: IBlobManagerRuntime;
|
|
300
299
|
pendingBlobs: IPendingBlobs | undefined;
|
|
301
|
-
readonly localIdGenerator?: (() => string) | undefined;
|
|
302
300
|
readonly createBlobPayloadPending: boolean;
|
|
303
301
|
}) {
|
|
304
302
|
const {
|
|
@@ -310,7 +308,6 @@ export class BlobManager {
|
|
|
310
308
|
isBlobDeleted,
|
|
311
309
|
runtime,
|
|
312
310
|
pendingBlobs,
|
|
313
|
-
localIdGenerator,
|
|
314
311
|
createBlobPayloadPending,
|
|
315
312
|
} = props;
|
|
316
313
|
this.routeContext = routeContext;
|
|
@@ -319,7 +316,6 @@ export class BlobManager {
|
|
|
319
316
|
this.blobRequested = blobRequested;
|
|
320
317
|
this.isBlobDeleted = isBlobDeleted;
|
|
321
318
|
this.runtime = runtime;
|
|
322
|
-
this.localIdGenerator = localIdGenerator ?? uuid;
|
|
323
319
|
this.createBlobPayloadPending = createBlobPayloadPending;
|
|
324
320
|
|
|
325
321
|
this.mc = createChildMonitoringContext({
|
|
@@ -480,7 +476,7 @@ export class BlobManager {
|
|
|
480
476
|
if (signal?.aborted === true) {
|
|
481
477
|
throw createAbortError();
|
|
482
478
|
}
|
|
483
|
-
const localId =
|
|
479
|
+
const localId = uuid();
|
|
484
480
|
this.localBlobCache.set(localId, { state: "uploading", blob });
|
|
485
481
|
// Blobs created while the container is detached are stored in IDetachedBlobStorage.
|
|
486
482
|
// The 'IContainerStorageService.createBlob()' call below will respond with a pseudo storage ID.
|
|
@@ -497,7 +493,7 @@ export class BlobManager {
|
|
|
497
493
|
blob: ArrayBufferLike,
|
|
498
494
|
signal?: AbortSignal,
|
|
499
495
|
): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>> {
|
|
500
|
-
const localId =
|
|
496
|
+
const localId = uuid();
|
|
501
497
|
this.localBlobCache.set(localId, { state: "localOnly", blob });
|
|
502
498
|
await this.uploadAndAttach(localId, signal);
|
|
503
499
|
return this.getNonPayloadPendingBlobHandle(localId);
|
|
@@ -507,7 +503,7 @@ export class BlobManager {
|
|
|
507
503
|
blob: ArrayBufferLike,
|
|
508
504
|
signal?: AbortSignal,
|
|
509
505
|
): IFluidHandleInternalPayloadPending<ArrayBufferLike> {
|
|
510
|
-
const localId =
|
|
506
|
+
const localId = uuid();
|
|
511
507
|
this.localBlobCache.set(localId, { state: "localOnly", blob });
|
|
512
508
|
|
|
513
509
|
const blobHandle = new BlobHandle(
|
package/src/channelCollection.ts
CHANGED
|
@@ -492,9 +492,33 @@ export class ChannelCollection
|
|
|
492
492
|
continue;
|
|
493
493
|
}
|
|
494
494
|
|
|
495
|
-
//
|
|
495
|
+
// Check for collision with local (not yet live / known to other clients) DataStore
|
|
496
|
+
// This is not a DataCorruption case if we crash the container before the DataStore becomes visible to others (it's a DataProcessingError instead)
|
|
497
|
+
//
|
|
498
|
+
// POSSIBLE CAUSES:
|
|
499
|
+
// - Something with ID creation, e.g. a bug in shortID logic, or somehow a generated ID matches an existing alias.
|
|
500
|
+
// - An invalid operation by the application or service where an existing container is returned to a new container attach call,
|
|
501
|
+
// resulting in duplicate accounting for objects that were supposed to be local-only. e.g. if the application patches in custom
|
|
502
|
+
// logic not supported by Fluid's API.
|
|
503
|
+
if (this.contexts.getUnbound(attachMessage.id) !== undefined) {
|
|
504
|
+
const error = DataProcessingError.create(
|
|
505
|
+
"Local DataStore matches remote DataStore id",
|
|
506
|
+
"DataStoreAttach",
|
|
507
|
+
envelope,
|
|
508
|
+
{ ...tagCodeArtifacts({ dataStoreId: attachMessage.id }) },
|
|
509
|
+
);
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Check for collision with already processed (attaching/attached or aliased) DataStore
|
|
514
|
+
// This is presumed to indicate a corrupted op stream, where we'd expect all future sessions to fail here too.
|
|
515
|
+
//
|
|
516
|
+
// POSSIBLE CAUSES:
|
|
517
|
+
// - A bug in the service or driver that results in ops being duplicated
|
|
518
|
+
// - Similar to above, an existing container being returned to a new container attach call,
|
|
519
|
+
// where the DataStore in question was already made locally visible before container attach.
|
|
520
|
+
// (Perhaps future sessions would not fail in this case, but it's hypothetical and hard to differentiate)
|
|
496
521
|
if (this.alreadyProcessed(attachMessage.id)) {
|
|
497
|
-
// TODO: dataStoreId may require a different tag from PackageData #7488
|
|
498
522
|
const error = new DataCorruptionError(
|
|
499
523
|
// pre-0.58 error message: duplicateDataStoreCreatedWithExistingId
|
|
500
524
|
"Duplicate DataStore created with existing id",
|
|
@@ -769,7 +793,7 @@ export class ChannelCollection
|
|
|
769
793
|
| OutboundContainerRuntimeAttachMessage
|
|
770
794
|
| ContainerRuntimeAliasMessage,
|
|
771
795
|
localOpMetadata: unknown,
|
|
772
|
-
squash: boolean
|
|
796
|
+
squash: boolean,
|
|
773
797
|
): void => {
|
|
774
798
|
switch (message.type) {
|
|
775
799
|
case ContainerMessageType.Attach:
|
|
@@ -789,7 +813,7 @@ export class ChannelCollection
|
|
|
789
813
|
protected readonly resubmitDataStoreOp = (
|
|
790
814
|
envelope: IEnvelope<FluidDataStoreMessage>,
|
|
791
815
|
localOpMetadata: unknown,
|
|
792
|
-
squash: boolean
|
|
816
|
+
squash: boolean,
|
|
793
817
|
): void => {
|
|
794
818
|
const context = this.contexts.get(envelope.address);
|
|
795
819
|
// If the data store has been deleted, log an error and throw an error. If there are local changes for a
|
|
@@ -1796,7 +1820,7 @@ export class ComposableChannelCollection
|
|
|
1796
1820
|
type: string,
|
|
1797
1821
|
content: unknown,
|
|
1798
1822
|
localOpMetadata: unknown,
|
|
1799
|
-
squash
|
|
1823
|
+
squash: boolean,
|
|
1800
1824
|
): void {
|
|
1801
1825
|
// If the cast is incorrect and type is not one of the three supported,
|
|
1802
1826
|
// reSubmitContainerMessage will assert.
|
package/src/containerRuntime.ts
CHANGED
|
@@ -1622,10 +1622,11 @@ export class ContainerRuntime
|
|
|
1622
1622
|
|
|
1623
1623
|
// Validate that the Loader is compatible with this Runtime.
|
|
1624
1624
|
const maybeLoaderCompatDetailsForRuntime = context as FluidObject<ILayerCompatDetails>;
|
|
1625
|
+
|
|
1625
1626
|
validateLoaderCompatibility(
|
|
1626
1627
|
maybeLoaderCompatDetailsForRuntime.ILayerCompatDetails,
|
|
1627
1628
|
this.disposeFn,
|
|
1628
|
-
this.mc
|
|
1629
|
+
this.mc,
|
|
1629
1630
|
);
|
|
1630
1631
|
|
|
1631
1632
|
// If we support multiple algorithms in the future, then we would need to manage it here carefully.
|
|
@@ -1828,7 +1829,9 @@ export class ContainerRuntime
|
|
|
1828
1829
|
validateSummaryHeuristicConfiguration(this.summaryConfiguration);
|
|
1829
1830
|
}
|
|
1830
1831
|
|
|
1831
|
-
this.summariesDisabled =
|
|
1832
|
+
this.summariesDisabled =
|
|
1833
|
+
isSummariesDisabled(this.summaryConfiguration) ||
|
|
1834
|
+
this.mc.config.getBoolean("Fluid.ContainerRuntime.Test.DisableSummaries") === true;
|
|
1832
1835
|
|
|
1833
1836
|
this.maxConsecutiveReconnects =
|
|
1834
1837
|
this.mc.config.getNumber(maxConsecutiveReconnectsKey) ?? defaultMaxConsecutiveReconnects;
|
|
@@ -2294,7 +2297,10 @@ export class ContainerRuntime
|
|
|
2294
2297
|
|
|
2295
2298
|
const defaultAction = (): void => {
|
|
2296
2299
|
if (summaryCollection.opsSinceLastAck > maxOpsSinceLastSummary) {
|
|
2297
|
-
this.mc.logger.sendTelemetryEvent({
|
|
2300
|
+
this.mc.logger.sendTelemetryEvent({
|
|
2301
|
+
eventName: "SummaryStatus:Behind",
|
|
2302
|
+
opsWithoutSummary: summaryCollection.opsSinceLastAck,
|
|
2303
|
+
});
|
|
2298
2304
|
// unregister default to no log on every op after falling behind
|
|
2299
2305
|
// and register summary ack handler to re-register this handler
|
|
2300
2306
|
// after successful summary
|
|
@@ -2336,8 +2342,9 @@ export class ContainerRuntime
|
|
|
2336
2342
|
"summarizerStop",
|
|
2337
2343
|
"summarizerStart",
|
|
2338
2344
|
"summarizerStartupFailed",
|
|
2339
|
-
|
|
2340
|
-
|
|
2345
|
+
"summarizeTimeout",
|
|
2346
|
+
] as const) {
|
|
2347
|
+
this.summaryManager.on(eventName, (...args: unknown[]) => {
|
|
2341
2348
|
this.emit(eventName, ...args);
|
|
2342
2349
|
});
|
|
2343
2350
|
}
|
|
@@ -3930,6 +3937,10 @@ export class ContainerRuntime
|
|
|
3930
3937
|
* True to run GC sweep phase after the mark phase
|
|
3931
3938
|
*/
|
|
3932
3939
|
runSweep?: boolean;
|
|
3940
|
+
/**
|
|
3941
|
+
* Telemetry context to populate during summarization.
|
|
3942
|
+
*/
|
|
3943
|
+
telemetryContext?: TelemetryContext;
|
|
3933
3944
|
}): Promise<ISummaryTreeWithStats> {
|
|
3934
3945
|
this.verifyNotClosed();
|
|
3935
3946
|
|
|
@@ -3940,9 +3951,9 @@ export class ContainerRuntime
|
|
|
3940
3951
|
runGC = this.garbageCollector.shouldRunGC,
|
|
3941
3952
|
runSweep,
|
|
3942
3953
|
fullGC,
|
|
3954
|
+
telemetryContext = new TelemetryContext(),
|
|
3943
3955
|
} = options;
|
|
3944
3956
|
|
|
3945
|
-
const telemetryContext = new TelemetryContext();
|
|
3946
3957
|
// Add the options that are used to generate this summary to the telemetry context.
|
|
3947
3958
|
telemetryContext.setMultiple("fluid_Summarize", "Options", {
|
|
3948
3959
|
fullTree,
|
|
@@ -4183,6 +4194,7 @@ export class ContainerRuntime
|
|
|
4183
4194
|
finalAttempt = false,
|
|
4184
4195
|
summaryLogger,
|
|
4185
4196
|
latestSummaryRefSeqNum,
|
|
4197
|
+
telemetryContext = new TelemetryContext(),
|
|
4186
4198
|
} = options;
|
|
4187
4199
|
// The summary number for this summary. This will be updated during the summary process, so get it now and
|
|
4188
4200
|
// use it for all events logged during this summary.
|
|
@@ -4372,6 +4384,7 @@ export class ContainerRuntime
|
|
|
4372
4384
|
trackState: true,
|
|
4373
4385
|
summaryLogger: summaryNumberLogger,
|
|
4374
4386
|
runGC: this.garbageCollector.shouldRunGC,
|
|
4387
|
+
telemetryContext,
|
|
4375
4388
|
});
|
|
4376
4389
|
} catch (error) {
|
|
4377
4390
|
return {
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -1003,7 +1003,7 @@ export abstract class FluidDataStoreContext
|
|
|
1003
1003
|
validateDatastoreCompatibility(
|
|
1004
1004
|
maybeDataStoreCompatDetails.ILayerCompatDetails,
|
|
1005
1005
|
this.dispose.bind(this),
|
|
1006
|
-
this.mc
|
|
1006
|
+
this.mc,
|
|
1007
1007
|
);
|
|
1008
1008
|
|
|
1009
1009
|
// And now mark the runtime active
|
|
@@ -1096,7 +1096,7 @@ export abstract class FluidDataStoreContext
|
|
|
1096
1096
|
public reSubmit(
|
|
1097
1097
|
message: FluidDataStoreMessage,
|
|
1098
1098
|
localOpMetadata: unknown,
|
|
1099
|
-
squash
|
|
1099
|
+
squash: boolean,
|
|
1100
1100
|
): void {
|
|
1101
1101
|
assert(!!this.channel, 0x14b /* "Channel must exist when resubmitting ops" */);
|
|
1102
1102
|
this.channel.reSubmit(message.type, message.content, localOpMetadata, squash);
|
package/src/dataStoreContexts.ts
CHANGED
|
@@ -13,27 +13,41 @@ import {
|
|
|
13
13
|
import type { FluidDataStoreContext, LocalFluidDataStoreContext } from "./dataStoreContext.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
+
* Manages the collection of data store contexts, tracking their bound/unbound state.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* A context is "unbound" when it's created locally but not yet made visible (reachable from root).
|
|
20
|
+
* A context is "bound" once it's made locally visible, regardless of the Container's attach state.
|
|
21
|
+
* In attached containers, binding a context immediately sends an attach op and transitions it to Attaching state.
|
|
22
|
+
*
|
|
16
23
|
* @internal
|
|
17
24
|
*/
|
|
18
25
|
export class DataStoreContexts
|
|
19
26
|
implements Iterable<[string, FluidDataStoreContext]>, IDisposable
|
|
20
27
|
{
|
|
28
|
+
/**
|
|
29
|
+
* Set of IDs for contexts that are unbound (not yet made locally visible).
|
|
30
|
+
* These contexts exist locally but aren't known to other clients (even in an attached container).
|
|
31
|
+
*/
|
|
21
32
|
private readonly notBoundContexts = new Set<string>();
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
|
-
*
|
|
35
|
+
* Map of all data store contexts (both bound and unbound).
|
|
25
36
|
*/
|
|
26
37
|
private readonly _contexts = new Map<string, FluidDataStoreContext>();
|
|
27
38
|
|
|
28
39
|
/**
|
|
29
40
|
* List of pending context waiting either to be bound or to arrive from another client.
|
|
30
41
|
* This covers the case where a local context has been created but not yet bound,
|
|
31
|
-
* or the case where a client knows a store will exist and is waiting on its creation,
|
|
42
|
+
* or the case where a client knows a store will exist (e.g. by alias) and is waiting on its creation,
|
|
32
43
|
* so that a caller may await the deferred's promise until such a time as the context is fully ready.
|
|
33
44
|
* This is a superset of _contexts, since contexts remain here once the Deferred resolves.
|
|
34
45
|
*/
|
|
35
46
|
private readonly deferredContexts = new Map<string, Deferred<FluidDataStoreContext>>();
|
|
36
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Lazy disposal logic that disposes all contexts when called.
|
|
50
|
+
*/
|
|
37
51
|
// eslint-disable-next-line unicorn/consistent-function-scoping -- Property is defined once; no need to extract inner lambda
|
|
38
52
|
private readonly disposeOnce = new Lazy<void>(() => {
|
|
39
53
|
// close/stop all store contexts
|
|
@@ -73,22 +87,39 @@ export class DataStoreContexts
|
|
|
73
87
|
}
|
|
74
88
|
public readonly dispose = (): void => this.disposeOnce.value;
|
|
75
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Returns the count of unbound contexts (i.e. local-only on this client)
|
|
92
|
+
*/
|
|
76
93
|
public notBoundLength(): number {
|
|
77
94
|
return this.notBoundContexts.size;
|
|
78
95
|
}
|
|
79
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Returns true if the given ID corresponds to an unbound context. (i.e. local-only on this client)
|
|
99
|
+
*/
|
|
80
100
|
public isNotBound(id: string): boolean {
|
|
81
101
|
return this.notBoundContexts.has(id);
|
|
82
102
|
}
|
|
83
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Returns true if a context with the given ID exists (bound or unbound).
|
|
106
|
+
*/
|
|
84
107
|
public has(id: string): boolean {
|
|
85
108
|
return this._contexts.has(id);
|
|
86
109
|
}
|
|
87
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Returns the context with the given ID, or undefined if not found.
|
|
113
|
+
* This returns both bound and unbound contexts.
|
|
114
|
+
*/
|
|
88
115
|
public get(id: string): FluidDataStoreContext | undefined {
|
|
89
116
|
return this._contexts.get(id);
|
|
90
117
|
}
|
|
91
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Deletes the context with the given ID from all internal maps.
|
|
121
|
+
* @returns True if the context was found and deleted, false otherwise.
|
|
122
|
+
*/
|
|
92
123
|
public delete(id: string): boolean {
|
|
93
124
|
this.deferredContexts.delete(id);
|
|
94
125
|
this.notBoundContexts.delete(id);
|
|
@@ -100,16 +131,24 @@ export class DataStoreContexts
|
|
|
100
131
|
return this._contexts.delete(id);
|
|
101
132
|
}
|
|
102
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Map of recently deleted contexts for diagnostic purposes for GC.
|
|
136
|
+
* Allows retrieval of context information even after deletion for logging/telemetry.
|
|
137
|
+
*/
|
|
103
138
|
private readonly _recentlyDeletedContexts: Map<string, FluidDataStoreContext | undefined> =
|
|
104
139
|
new Map();
|
|
105
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Returns a recently deleted context by ID, or undefined if not found.
|
|
143
|
+
* Used for diagnostic logging for GC, when a deleted context is referenced.
|
|
144
|
+
*/
|
|
106
145
|
public getRecentlyDeletedContext(id: string): FluidDataStoreContext | undefined {
|
|
107
146
|
return this._recentlyDeletedContexts.get(id);
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
/**
|
|
111
|
-
*
|
|
112
|
-
* or undefined if
|
|
150
|
+
* Returns the unbound local context with the given ID.
|
|
151
|
+
* @returns The unbound context, or undefined if not found or not unbound.
|
|
113
152
|
*/
|
|
114
153
|
public getUnbound(id: string): LocalFluidDataStoreContext | undefined {
|
|
115
154
|
const context = this._contexts.get(id);
|
|
@@ -121,7 +160,8 @@ export class DataStoreContexts
|
|
|
121
160
|
}
|
|
122
161
|
|
|
123
162
|
/**
|
|
124
|
-
*
|
|
163
|
+
* Adds the given context to the collection, marking it as unbound (not yet locally visible).
|
|
164
|
+
* Asserts that no context with this ID already exists.
|
|
125
165
|
*/
|
|
126
166
|
public addUnbound(context: LocalFluidDataStoreContext): void {
|
|
127
167
|
const id = context.id;
|
|
@@ -152,6 +192,10 @@ export class DataStoreContexts
|
|
|
152
192
|
return deferredContext.promise;
|
|
153
193
|
}
|
|
154
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Gets or creates a deferred promise for the given context ID.
|
|
197
|
+
* Used to allow waiting for contexts that don't exist yet.
|
|
198
|
+
*/
|
|
155
199
|
private ensureDeferred(id: string): Deferred<FluidDataStoreContext> {
|
|
156
200
|
const deferred = this.deferredContexts.get(id);
|
|
157
201
|
if (deferred) {
|
|
@@ -164,7 +208,8 @@ export class DataStoreContexts
|
|
|
164
208
|
}
|
|
165
209
|
|
|
166
210
|
/**
|
|
167
|
-
*
|
|
211
|
+
* Marks the context with the given ID as bound (locally visible).
|
|
212
|
+
* Removes it from the unbound set and resolves its deferred promise.
|
|
168
213
|
*/
|
|
169
214
|
public bind(id: string): void {
|
|
170
215
|
const removed: boolean = this.notBoundContexts.delete(id);
|
|
@@ -191,9 +236,11 @@ export class DataStoreContexts
|
|
|
191
236
|
}
|
|
192
237
|
|
|
193
238
|
/**
|
|
194
|
-
*
|
|
195
|
-
* This
|
|
196
|
-
*
|
|
239
|
+
* Adds the given context to the collection as already bound or from a remote client.
|
|
240
|
+
* This is used when:
|
|
241
|
+
* - Adding a local context that's already been bound via the bind() method, OR
|
|
242
|
+
* - Adding a remote context that was created by another client.
|
|
243
|
+
* The context's deferred promise is resolved immediately.
|
|
197
244
|
*/
|
|
198
245
|
public addBoundOrRemoted(context: FluidDataStoreContext): void {
|
|
199
246
|
const id = context.id;
|
package/src/packageVersion.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import {
|
|
7
|
+
generation,
|
|
8
|
+
type ILayerCompatDetails,
|
|
9
|
+
type ILayerCompatSupportRequirements,
|
|
9
10
|
} from "@fluid-internal/client-utils";
|
|
10
11
|
import type { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
11
12
|
import {
|
|
@@ -14,11 +15,17 @@ import {
|
|
|
14
15
|
} from "@fluidframework/runtime-definitions/internal";
|
|
15
16
|
import {
|
|
16
17
|
validateLayerCompatibility,
|
|
17
|
-
type
|
|
18
|
+
type MonitoringContext,
|
|
18
19
|
} from "@fluidframework/telemetry-utils/internal";
|
|
19
20
|
|
|
20
21
|
import { pkgVersion } from "./packageVersion.js";
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* The config key to disable strict loader layer compatibility check.
|
|
25
|
+
*/
|
|
26
|
+
export const disableStrictLoaderLayerCompatibilityCheckKey =
|
|
27
|
+
"Fluid.ContainerRuntime.DisableStrictLoaderLayerCompatibilityCheck";
|
|
28
|
+
|
|
22
29
|
/**
|
|
23
30
|
* The core compatibility details of the Runtime layer that is the same across all layer boundaries.
|
|
24
31
|
* @internal
|
|
@@ -31,7 +38,7 @@ export const runtimeCoreCompatDetails = {
|
|
|
31
38
|
/**
|
|
32
39
|
* The current generation of the Runtime layer.
|
|
33
40
|
*/
|
|
34
|
-
generation
|
|
41
|
+
generation,
|
|
35
42
|
} as const;
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -97,8 +104,16 @@ export const dataStoreSupportRequirementsForRuntime: ILayerCompatSupportRequirem
|
|
|
97
104
|
export function validateLoaderCompatibility(
|
|
98
105
|
maybeLoaderCompatDetailsForRuntime: ILayerCompatDetails | undefined,
|
|
99
106
|
disposeFn: (error?: ICriticalContainerError) => void,
|
|
100
|
-
|
|
107
|
+
mc: MonitoringContext,
|
|
101
108
|
): void {
|
|
109
|
+
// By default, use strictCompatibilityCheck here - If the Loader doesn't provide compatibility details,
|
|
110
|
+
// assume it's a very old version and should be considered incompatible,
|
|
111
|
+
// since Loader can drift far from the Runtime causing issues.
|
|
112
|
+
// Can be disabled via config `disableStrictLoaderLayerCompatibilityCheckKey`.
|
|
113
|
+
const disableStrictLoaderLayerCompatibilityCheck = mc.config.getBoolean(
|
|
114
|
+
disableStrictLoaderLayerCompatibilityCheckKey,
|
|
115
|
+
);
|
|
116
|
+
|
|
102
117
|
validateLayerCompatibility(
|
|
103
118
|
"runtime",
|
|
104
119
|
"loader",
|
|
@@ -106,7 +121,8 @@ export function validateLoaderCompatibility(
|
|
|
106
121
|
loaderSupportRequirementsForRuntime,
|
|
107
122
|
maybeLoaderCompatDetailsForRuntime,
|
|
108
123
|
disposeFn,
|
|
109
|
-
|
|
124
|
+
mc,
|
|
125
|
+
disableStrictLoaderLayerCompatibilityCheck !== true /* strictCompatibilityCheck */,
|
|
110
126
|
);
|
|
111
127
|
}
|
|
112
128
|
|
|
@@ -117,7 +133,7 @@ export function validateLoaderCompatibility(
|
|
|
117
133
|
export function validateDatastoreCompatibility(
|
|
118
134
|
maybeDataStoreCompatDetailsForRuntime: ILayerCompatDetails | undefined,
|
|
119
135
|
disposeFn: () => void,
|
|
120
|
-
|
|
136
|
+
mc: MonitoringContext,
|
|
121
137
|
): void {
|
|
122
138
|
validateLayerCompatibility(
|
|
123
139
|
"runtime",
|
|
@@ -126,6 +142,6 @@ export function validateDatastoreCompatibility(
|
|
|
126
142
|
dataStoreSupportRequirementsForRuntime,
|
|
127
143
|
maybeDataStoreCompatDetailsForRuntime,
|
|
128
144
|
disposeFn,
|
|
129
|
-
|
|
145
|
+
mc,
|
|
130
146
|
);
|
|
131
147
|
}
|
|
@@ -91,6 +91,7 @@ export class SummarizerClientElection
|
|
|
91
91
|
lastSummaryAckSeqForClient: this.lastSummaryAckSeqForClient,
|
|
92
92
|
electionSequenceNumber,
|
|
93
93
|
nextElectedClientId: this.clientElection.peekNextElectedClient()?.clientId,
|
|
94
|
+
opsWithoutSummary,
|
|
94
95
|
});
|
|
95
96
|
this.lastReportedSeq = sequenceNumber;
|
|
96
97
|
}
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ISequencedDocumentMessage,
|
|
23
23
|
} from "@fluidframework/driver-definitions/internal";
|
|
24
24
|
import type { ISummaryStats } from "@fluidframework/runtime-definitions/internal";
|
|
25
|
+
import type { TelemetryContext } from "@fluidframework/runtime-utils/internal";
|
|
25
26
|
import type {
|
|
26
27
|
ITelemetryLoggerExt,
|
|
27
28
|
ITelemetryLoggerPropertyBag,
|
|
@@ -167,6 +168,10 @@ export interface ISubmitSummaryOptions extends ISummarizeOptions {
|
|
|
167
168
|
* The sequence number of the latest summary used to validate if summary state is correct before summarizing
|
|
168
169
|
*/
|
|
169
170
|
readonly latestSummaryRefSeqNum: number;
|
|
171
|
+
/**
|
|
172
|
+
* Shared telemetry context for the current summarize attempt.
|
|
173
|
+
*/
|
|
174
|
+
telemetryContext?: TelemetryContext;
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
/**
|
|
@@ -177,11 +177,6 @@ export class RunningSummarizer
|
|
|
177
177
|
private totalSuccessfulAttempts = 0;
|
|
178
178
|
private initialized = false;
|
|
179
179
|
|
|
180
|
-
private readonly runtimeListener: (
|
|
181
|
-
op: ISequencedDocumentMessage,
|
|
182
|
-
runtimeMessage?: boolean,
|
|
183
|
-
) => void;
|
|
184
|
-
|
|
185
180
|
/**
|
|
186
181
|
* The maximum number of summary attempts to do when submit summary fails.
|
|
187
182
|
*/
|
|
@@ -295,12 +290,6 @@ export class RunningSummarizer
|
|
|
295
290
|
this.mc.logger,
|
|
296
291
|
);
|
|
297
292
|
|
|
298
|
-
// Listen to runtime for ops
|
|
299
|
-
this.runtimeListener = (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => {
|
|
300
|
-
this.handleOp(op, runtimeMessage === true);
|
|
301
|
-
};
|
|
302
|
-
this.runtime.on("op", this.runtimeListener);
|
|
303
|
-
|
|
304
293
|
// The max attempts for submit failures can be overridden via a feature flag. This allows us to
|
|
305
294
|
// tweak this as per telemetry data until we arrive at a stable number.
|
|
306
295
|
// If its set to a number higher than `defaultMaxAttemptsForSubmitFailures`, it will be ignored.
|
|
@@ -312,6 +301,8 @@ export class RunningSummarizer
|
|
|
312
301
|
overrideMaxAttempts < defaultMaxAttemptsForSubmitFailures
|
|
313
302
|
? overrideMaxAttempts
|
|
314
303
|
: defaultMaxAttemptsForSubmitFailures;
|
|
304
|
+
|
|
305
|
+
this.setupEventListeners();
|
|
315
306
|
}
|
|
316
307
|
|
|
317
308
|
private async handleSummaryAck(ack: IAckedSummary): Promise<void> {
|
|
@@ -397,7 +388,7 @@ export class RunningSummarizer
|
|
|
397
388
|
}
|
|
398
389
|
|
|
399
390
|
public dispose(): void {
|
|
400
|
-
this.
|
|
391
|
+
this.cleanupEventListeners();
|
|
401
392
|
this.summaryWatcher.dispose();
|
|
402
393
|
this.heuristicRunner?.dispose();
|
|
403
394
|
this.heuristicRunner = undefined;
|
|
@@ -408,6 +399,33 @@ export class RunningSummarizer
|
|
|
408
399
|
this.stopping = true;
|
|
409
400
|
}
|
|
410
401
|
|
|
402
|
+
private readonly eventsCleanup: (() => void)[] = [];
|
|
403
|
+
|
|
404
|
+
private setupEventListeners(): void {
|
|
405
|
+
const runtimeListener: (op: ISequencedDocumentMessage, runtimeMessage?: boolean) => void =
|
|
406
|
+
(op: ISequencedDocumentMessage, runtimeMessage?: boolean) => {
|
|
407
|
+
this.handleOp(op, runtimeMessage === true);
|
|
408
|
+
};
|
|
409
|
+
this.runtime.on("op", runtimeListener);
|
|
410
|
+
this.eventsCleanup.push(() => this.runtime.off("op", runtimeListener));
|
|
411
|
+
|
|
412
|
+
// Forward events
|
|
413
|
+
for (const event of ["summarizeTimeout"] as const) {
|
|
414
|
+
const listener = (...args: unknown[]): void => {
|
|
415
|
+
this.emit(event, ...args);
|
|
416
|
+
};
|
|
417
|
+
this.generator.on(event, listener);
|
|
418
|
+
this.eventsCleanup.push(() => this.generator.off(event, listener));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private cleanupEventListeners(): void {
|
|
423
|
+
for (const cleanup of this.eventsCleanup) {
|
|
424
|
+
cleanup();
|
|
425
|
+
}
|
|
426
|
+
this.eventsCleanup.length = 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
411
429
|
/**
|
|
412
430
|
* RunningSummarizer's logger includes the sequenced index of the current summary on each event.
|
|
413
431
|
* If some other Summarizer code wants that event on their logs they can get it here,
|
|
@@ -655,11 +673,12 @@ export class RunningSummarizer
|
|
|
655
673
|
numUnsummarizedNonRuntimeOps: this.heuristicData.numNonRuntimeOps,
|
|
656
674
|
isLastSummary,
|
|
657
675
|
});
|
|
658
|
-
|
|
676
|
+
summaryLogger.sendErrorEvent(
|
|
659
677
|
{
|
|
660
678
|
eventName: "SummarizeFailed",
|
|
661
679
|
maxAttempts: 1,
|
|
662
680
|
summaryAttempts: 1,
|
|
681
|
+
isLastSummary,
|
|
663
682
|
},
|
|
664
683
|
result.error,
|
|
665
684
|
);
|
|
@@ -708,7 +727,10 @@ export class RunningSummarizer
|
|
|
708
727
|
this.afterSummaryAction();
|
|
709
728
|
},
|
|
710
729
|
).catch((error) => {
|
|
711
|
-
this.mc.logger.sendErrorEvent(
|
|
730
|
+
this.mc.logger.sendErrorEvent(
|
|
731
|
+
{ eventName: "UnexpectedSummarizeError", summarizeReason: reason },
|
|
732
|
+
error,
|
|
733
|
+
);
|
|
712
734
|
});
|
|
713
735
|
}
|
|
714
736
|
|
|
@@ -718,6 +740,7 @@ export class RunningSummarizer
|
|
|
718
740
|
*/
|
|
719
741
|
private async trySummarizeWithRetries(
|
|
720
742
|
reason: SummarizeReason,
|
|
743
|
+
summarizeOptions: ISummarizeOptions = {},
|
|
721
744
|
): Promise<ISummarizeResults | undefined> {
|
|
722
745
|
// Helper to set summarize options, telemetry properties and call summarize.
|
|
723
746
|
const attemptSummarize = (
|
|
@@ -727,13 +750,13 @@ export class RunningSummarizer
|
|
|
727
750
|
summarizeProps: ISummarizeTelemetryProperties;
|
|
728
751
|
summarizeResult: ISummarizeResults;
|
|
729
752
|
} => {
|
|
730
|
-
const
|
|
731
|
-
fullTree: false,
|
|
753
|
+
const attemptSummarizeOptions: ISummarizeOptions = {
|
|
754
|
+
fullTree: summarizeOptions.fullTree ?? false,
|
|
732
755
|
};
|
|
733
756
|
const summarizeProps: ISummarizeTelemetryProperties = {
|
|
734
757
|
summarizeReason: reason,
|
|
735
758
|
summaryAttempts: attemptNumber,
|
|
736
|
-
...
|
|
759
|
+
...attemptSummarizeOptions,
|
|
737
760
|
finalAttempt,
|
|
738
761
|
};
|
|
739
762
|
const summaryLogger = createChildLogger({
|
|
@@ -742,7 +765,7 @@ export class RunningSummarizer
|
|
|
742
765
|
});
|
|
743
766
|
|
|
744
767
|
const summaryOptions: ISubmitSummaryOptions = {
|
|
745
|
-
...
|
|
768
|
+
...attemptSummarizeOptions,
|
|
746
769
|
summaryLogger,
|
|
747
770
|
cancellationToken: this.cancellationToken,
|
|
748
771
|
finalAttempt,
|
|
@@ -872,6 +895,8 @@ export class RunningSummarizer
|
|
|
872
895
|
eventName: "SummarizeFailed",
|
|
873
896
|
maxAttempts,
|
|
874
897
|
summaryAttempts: currentAttempt,
|
|
898
|
+
summarizeReason: reason,
|
|
899
|
+
isLastSummary: reason === "lastSummary",
|
|
875
900
|
},
|
|
876
901
|
error,
|
|
877
902
|
);
|
|
@@ -893,8 +918,9 @@ export class RunningSummarizer
|
|
|
893
918
|
private async summarizeOnDemandWithRetries(
|
|
894
919
|
reason: SummarizeReason,
|
|
895
920
|
resultsBuilder: SummarizeResultBuilder,
|
|
921
|
+
summarizeOptions: ISummarizeOptions = {},
|
|
896
922
|
): Promise<ISummarizeResults> {
|
|
897
|
-
const results = await this.trySummarizeWithRetries(reason);
|
|
923
|
+
const results = await this.trySummarizeWithRetries(reason, summarizeOptions);
|
|
898
924
|
if (results === undefined) {
|
|
899
925
|
resultsBuilder.fail(
|
|
900
926
|
"Summarization was canceled",
|
|
@@ -932,13 +958,15 @@ export class RunningSummarizer
|
|
|
932
958
|
throw new UsageError("Attempted to run an already-running summarizer on demand");
|
|
933
959
|
}
|
|
934
960
|
|
|
935
|
-
const { reason, ...summarizeOptions } = options;
|
|
936
|
-
if (
|
|
937
|
-
this.summarizeOnDemandWithRetries(
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
)
|
|
961
|
+
const { reason, retryOnFailure, ...summarizeOptions } = options;
|
|
962
|
+
if (retryOnFailure === true) {
|
|
963
|
+
this.summarizeOnDemandWithRetries(
|
|
964
|
+
`onDemand;${reason}`,
|
|
965
|
+
resultsBuilder,
|
|
966
|
+
summarizeOptions,
|
|
967
|
+
).catch((error: IRetriableFailureError) => {
|
|
968
|
+
resultsBuilder.fail("summarize failed", error);
|
|
969
|
+
});
|
|
942
970
|
} else {
|
|
943
971
|
this.trySummarizeOnce(
|
|
944
972
|
{ summarizeReason: `onDemand/${reason}` },
|