@fluidframework/container-runtime 0.59.2000-63294 → 0.59.3000-66610
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/.eslintrc.js +0 -1
- package/dist/batchTracker.js +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager.js +8 -8
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.js +8 -8
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerHandleContext.js +1 -1
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +27 -17
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +149 -174
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js +1 -1
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +44 -44
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.d.ts +2 -2
- package/dist/dataStoreContexts.d.ts.map +1 -1
- package/dist/dataStoreContexts.js +8 -8
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStores.d.ts +4 -2
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +45 -33
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +23 -23
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +81 -50
- package/dist/garbageCollection.js.map +1 -1
- package/dist/opTelemetry.js +1 -1
- package/dist/opTelemetry.js.map +1 -1
- package/dist/orderedClientElection.d.ts.map +1 -1
- package/dist/orderedClientElection.js +2 -2
- package/dist/orderedClientElection.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.js +17 -17
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runWhileConnectedCoordinator.js +1 -1
- package/dist/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +7 -6
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +4 -3
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerHeuristics.d.ts +1 -1
- package/dist/summarizerHeuristics.d.ts.map +1 -1
- package/dist/summarizerHeuristics.js +1 -1
- package/dist/summarizerHeuristics.js.map +1 -1
- package/dist/summarizerTypes.d.ts +4 -2
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryCollection.js +2 -2
- package/dist/summaryCollection.js.map +1 -1
- package/dist/summaryFormat.d.ts +37 -11
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +12 -4
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +6 -4
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.js +5 -5
- package/dist/summaryManager.js.map +1 -1
- package/dist/throttler.js +2 -2
- package/dist/throttler.js.map +1 -1
- package/lib/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +27 -17
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +68 -93
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.d.ts +2 -2
- package/lib/dataStoreContexts.d.ts.map +1 -1
- package/lib/dataStoreContexts.js +2 -2
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStores.d.ts +4 -2
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +22 -10
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +23 -23
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +68 -37
- package/lib/garbageCollection.js.map +1 -1
- package/lib/opTelemetry.js.map +1 -1
- package/lib/orderedClientElection.d.ts.map +1 -1
- package/lib/orderedClientElection.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +4 -3
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +1 -0
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerHeuristics.d.ts +1 -1
- package/lib/summarizerHeuristics.d.ts.map +1 -1
- package/lib/summarizerHeuristics.js +1 -1
- package/lib/summarizerHeuristics.js.map +1 -1
- package/lib/summarizerTypes.d.ts +4 -2
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryCollection.js.map +1 -1
- package/lib/summaryFormat.d.ts +37 -11
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +10 -2
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +2 -0
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.js.map +1 -1
- package/lib/throttler.js.map +1 -1
- package/package.json +26 -20
- package/src/blobManager.ts +3 -3
- package/src/containerRuntime.ts +108 -137
- package/src/dataStoreContext.ts +8 -11
- package/src/dataStoreContexts.ts +5 -5
- package/src/dataStores.ts +30 -13
- package/src/garbageCollection.ts +100 -57
- package/src/orderedClientElection.ts +5 -10
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +2 -2
- package/src/runningSummarizer.ts +8 -9
- package/src/summarizer.ts +2 -2
- package/src/summarizerHeuristics.ts +1 -1
- package/src/summarizerTypes.ts +8 -6
- package/src/summaryFormat.ts +38 -11
- package/src/summaryGenerator.ts +7 -5
- package/src/summaryManager.ts +2 -2
- package/src/throttler.ts +1 -1
package/src/dataStores.ts
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
} from "./dataStoreContext";
|
|
51
51
|
import { IContainerRuntimeMetadata, nonDataStorePaths, rootHasIsolatedChannels } from "./summaryFormat";
|
|
52
52
|
import { IDataStoreAliasMessage, isDataStoreAliasMessage } from "./dataStore";
|
|
53
|
+
import { GCNodeType } from "./garbageCollection";
|
|
53
54
|
|
|
54
55
|
type PendingAliasResolve = (success: boolean) => void;
|
|
55
56
|
|
|
@@ -272,8 +273,8 @@ export class DataStores implements IDisposable {
|
|
|
272
273
|
return false;
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
276
|
+
const context = this.contexts.get(aliasMessage.internalId);
|
|
277
|
+
if (context === undefined) {
|
|
277
278
|
this.logger.sendErrorEvent({
|
|
278
279
|
eventName: "AliasFluidDataStoreNotFound",
|
|
279
280
|
fluidDataStoreId: aliasMessage.internalId,
|
|
@@ -281,8 +282,15 @@ export class DataStores implements IDisposable {
|
|
|
281
282
|
return false;
|
|
282
283
|
}
|
|
283
284
|
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
const handle = new FluidObjectHandle(
|
|
286
|
+
context,
|
|
287
|
+
aliasMessage.internalId,
|
|
288
|
+
this.runtime.IFluidHandleContext,
|
|
289
|
+
);
|
|
290
|
+
this.runtime.addedGCOutboundReference(this.containerRuntimeHandle, handle);
|
|
291
|
+
|
|
292
|
+
this.aliasMap.set(aliasMessage.alias, context.id);
|
|
293
|
+
context.setInMemoryRoot();
|
|
286
294
|
return true;
|
|
287
295
|
}
|
|
288
296
|
|
|
@@ -319,8 +327,7 @@ export class DataStores implements IDisposable {
|
|
|
319
327
|
public createDetachedDataStoreCore(
|
|
320
328
|
pkg: Readonly<string[]>,
|
|
321
329
|
isRoot: boolean,
|
|
322
|
-
id = uuid()): IFluidDataStoreContextDetached
|
|
323
|
-
{
|
|
330
|
+
id = uuid()): IFluidDataStoreContextDetached {
|
|
324
331
|
const context = new LocalDetachedFluidDataStoreContext({
|
|
325
332
|
id,
|
|
326
333
|
pkg,
|
|
@@ -363,7 +370,7 @@ export class DataStores implements IDisposable {
|
|
|
363
370
|
return context;
|
|
364
371
|
}
|
|
365
372
|
|
|
366
|
-
public get disposed() {return this.disposeOnce.evaluated;}
|
|
373
|
+
public get disposed() { return this.disposeOnce.evaluated; }
|
|
367
374
|
public readonly dispose = () => this.disposeOnce.value;
|
|
368
375
|
|
|
369
376
|
public resubmitDataStoreOp(content: any, localOpMetadata: unknown) {
|
|
@@ -421,7 +428,10 @@ export class DataStores implements IDisposable {
|
|
|
421
428
|
assert(!local, 0x163 /* "Missing datastore for local signal" */);
|
|
422
429
|
this.logger.sendTelemetryEvent({
|
|
423
430
|
eventName: "SignalFluidDataStoreNotFound",
|
|
424
|
-
fluidDataStoreId:
|
|
431
|
+
fluidDataStoreId: {
|
|
432
|
+
value: address,
|
|
433
|
+
tag: TelemetryDataTag.PackageData,
|
|
434
|
+
},
|
|
425
435
|
});
|
|
426
436
|
return;
|
|
427
437
|
}
|
|
@@ -633,14 +643,21 @@ export class DataStores implements IDisposable {
|
|
|
633
643
|
}
|
|
634
644
|
|
|
635
645
|
/**
|
|
636
|
-
* Called by GC to
|
|
646
|
+
* Called by GC to determine if a node is for a data store or for an object within a data store (for e.g. DDS).
|
|
647
|
+
* @returns the GC node type if the node belongs to a data store or object within data store, undefined otherwise.
|
|
637
648
|
*/
|
|
638
|
-
public
|
|
649
|
+
public getGCNodeType(nodePath: string): GCNodeType | undefined {
|
|
639
650
|
const pathParts = nodePath.split("/");
|
|
640
|
-
if (
|
|
641
|
-
return
|
|
651
|
+
if (!this.contexts.has(pathParts[1])) {
|
|
652
|
+
return undefined;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Data stores paths are of the format "/dataStoreId".
|
|
656
|
+
// Sub data store paths are of the format "/dataStoreId/subPath/...".
|
|
657
|
+
if (pathParts.length === 2) {
|
|
658
|
+
return GCNodeType.DataStore;
|
|
642
659
|
}
|
|
643
|
-
return
|
|
660
|
+
return GCNodeType.SubDataStore;
|
|
644
661
|
}
|
|
645
662
|
}
|
|
646
663
|
|
package/src/garbageCollection.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { ITelemetryLogger, ITelemetryPerformanceEvent } from "@fluidframework/common-definitions";
|
|
7
7
|
import { assert, LazyPromise, Timer } from "@fluidframework/common-utils";
|
|
8
8
|
import { ICriticalContainerError } from "@fluidframework/container-definitions";
|
|
9
|
-
import { ClientSessionExpiredError, DataProcessingError } from "@fluidframework/container-utils";
|
|
9
|
+
import { ClientSessionExpiredError, DataProcessingError, UsageError } from "@fluidframework/container-utils";
|
|
10
10
|
import { IRequestHeader } from "@fluidframework/core-interfaces";
|
|
11
11
|
import {
|
|
12
12
|
cloneGCData,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
metadataBlobName,
|
|
47
47
|
ReadFluidDataStoreAttributes,
|
|
48
48
|
dataStoreAttributesBlobName,
|
|
49
|
+
IGCMetadata,
|
|
49
50
|
} from "./summaryFormat";
|
|
50
51
|
|
|
51
52
|
/** This is the current version of garbage collection. */
|
|
@@ -65,7 +66,9 @@ const runSweepKey = "Fluid.GarbageCollection.RunSweep";
|
|
|
65
66
|
// Feature gate key to write GC data at the root of the summary tree.
|
|
66
67
|
const writeAtRootKey = "Fluid.GarbageCollection.WriteDataAtRoot";
|
|
67
68
|
// Feature gate key to expire a session after a set period of time.
|
|
68
|
-
const
|
|
69
|
+
const runSessionExpiryKey = "Fluid.GarbageCollection.RunSessionExpiry";
|
|
70
|
+
// Feature gate key to disable expiring session after a set period of time, even if expiry value is present
|
|
71
|
+
const disableSessionExpiryKey = "Fluid.GarbageCollection.DisableSessionExpiry";
|
|
69
72
|
// Feature gate key to log error messages if GC reference validation fails.
|
|
70
73
|
const logUnknownOutboundReferencesKey = "Fluid.GarbageCollection.LogUnknownOutboundReferences";
|
|
71
74
|
|
|
@@ -98,9 +101,11 @@ export interface IGCStats {
|
|
|
98
101
|
export const GCNodeType = {
|
|
99
102
|
// Nodes that are for data stores.
|
|
100
103
|
DataStore: "DataStore",
|
|
104
|
+
// Nodes that are within a data store. For example, DDS nodes.
|
|
105
|
+
SubDataStore: "SubDataStore",
|
|
101
106
|
// Nodes that are for attachment blobs, i.e., blobs uploaded via BlobManager.
|
|
102
107
|
Blob: "Blob",
|
|
103
|
-
// Nodes that are neither
|
|
108
|
+
// Nodes that are neither of the above. For example, root node.
|
|
104
109
|
Other: "Other",
|
|
105
110
|
};
|
|
106
111
|
export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
|
|
@@ -138,24 +143,18 @@ export interface IGarbageCollectionRuntime {
|
|
|
138
143
|
export interface IGarbageCollector {
|
|
139
144
|
/** Tells whether GC should run or not. */
|
|
140
145
|
readonly shouldRunGC: boolean;
|
|
141
|
-
/** The time in ms to expire a session for a client for gc. */
|
|
142
|
-
readonly sessionExpiryTimeoutMs: number | undefined;
|
|
143
|
-
/**
|
|
144
|
-
* This tracks two things:
|
|
145
|
-
* 1. Whether GC is enabled - If this is 0, GC is disabled. If this is greater than 0, GC is enabled.
|
|
146
|
-
* 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.
|
|
147
|
-
*/
|
|
148
|
-
readonly gcSummaryFeatureVersion: number;
|
|
149
146
|
/** Tells whether the GC state in summary needs to be reset in the next summary. */
|
|
150
147
|
readonly summaryStateNeedsReset: boolean;
|
|
151
148
|
/** Tells whether GC data should be written to the root of the summary tree. */
|
|
152
149
|
readonly writeDataAtRoot: boolean;
|
|
153
150
|
/** Run garbage collection and update the reference / used state of the system. */
|
|
154
151
|
collectGarbage(
|
|
155
|
-
options: { logger?: ITelemetryLogger
|
|
152
|
+
options: { logger?: ITelemetryLogger; runGC?: boolean; runSweep?: boolean; fullGC?: boolean; },
|
|
156
153
|
): Promise<IGCStats>;
|
|
157
154
|
/** Summarizes the GC data and returns it as a summary tree. */
|
|
158
155
|
summarize(): ISummaryTreeWithStats | undefined;
|
|
156
|
+
/** Returns the garbage collector specific metadata to be written into the summary. */
|
|
157
|
+
getMetadata(): IGCMetadata;
|
|
159
158
|
/** Returns a map of each node id to its base GC details in the base summary. */
|
|
160
159
|
getBaseGCDetails(): Promise<Map<string, IGarbageCollectionDetailsBase>>;
|
|
161
160
|
/** Called when the latest summary of the system has been refreshed. */
|
|
@@ -231,15 +230,15 @@ class UnreferencedStateTracker {
|
|
|
231
230
|
* its state across summaries.
|
|
232
231
|
*
|
|
233
232
|
* Node - represented as nodeId, it's a node on the GC graph
|
|
234
|
-
* Outbound Route - a path from one node to another node, think `nodeA`
|
|
233
|
+
* Outbound Route - a path from one node to another node, think `nodeA` -\> `nodeB`
|
|
235
234
|
* Graph - all nodes with their respective routes
|
|
236
235
|
* GC Graph
|
|
237
236
|
*
|
|
238
237
|
* Node
|
|
239
238
|
* NodeId = "datastore1"
|
|
240
|
-
* /
|
|
239
|
+
* / \\
|
|
241
240
|
* OutboundRoute OutboundRoute
|
|
242
|
-
* /
|
|
241
|
+
* / \\
|
|
243
242
|
* Node Node
|
|
244
243
|
* NodeId = "dds1" NodeId = "dds2"
|
|
245
244
|
*/
|
|
@@ -268,24 +267,10 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
268
267
|
);
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
/**
|
|
272
|
-
* Tells whether GC should be run based on the GC options and local storage flags.
|
|
273
|
-
*/
|
|
274
|
-
public readonly shouldRunGC: boolean;
|
|
275
|
-
|
|
276
270
|
/**
|
|
277
271
|
* The time in ms to expire a session for a client for gc.
|
|
278
272
|
*/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* This tracks two things:
|
|
283
|
-
* 1. Whether GC is enabled - If this is 0, GC is disabled. If this is greater than 0, GC is enabled.
|
|
284
|
-
* 2. If GC is enabled, the version of GC used to generate the GC data written in a summary.
|
|
285
|
-
*/
|
|
286
|
-
public get gcSummaryFeatureVersion(): number {
|
|
287
|
-
return this.gcEnabled ? this.currentGCVersion : 0;
|
|
288
|
-
}
|
|
273
|
+
private readonly sessionExpiryTimeoutMs: number | undefined;
|
|
289
274
|
|
|
290
275
|
/**
|
|
291
276
|
* Tells whether the GC state needs to be reset in the next summary. We need to do this if:
|
|
@@ -305,7 +290,23 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
305
290
|
* throughout its lifetime.
|
|
306
291
|
*/
|
|
307
292
|
private readonly gcEnabled: boolean;
|
|
293
|
+
/**
|
|
294
|
+
* Tracks if sweep phase is enabled for this document. This is specified during document creation and doesn't change
|
|
295
|
+
* throughout its lifetime.
|
|
296
|
+
*/
|
|
297
|
+
private readonly sweepEnabled: boolean;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Tracks if GC should run or not. Even if GC is enabled for a document (see gcEnabled), it can be explicitly
|
|
301
|
+
* disabled via runtime options or feature flags.
|
|
302
|
+
*/
|
|
303
|
+
public readonly shouldRunGC: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Tracks if sweep phase should run or not. Even if the sweep phase is enabled for a document (see sweepEnabled), it
|
|
306
|
+
* can be explicitly disabled via feature flags. It also won't run if session expiry is not enabled.
|
|
307
|
+
*/
|
|
308
308
|
private readonly shouldRunSweep: boolean;
|
|
309
|
+
|
|
309
310
|
private readonly testMode: boolean;
|
|
310
311
|
private readonly mc: MonitoringContext;
|
|
311
312
|
|
|
@@ -377,26 +378,47 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
377
378
|
|
|
378
379
|
let prevSummaryGCVersion: number | undefined;
|
|
379
380
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
381
|
+
/**
|
|
382
|
+
* The following GC state is enabled during container creation and cannot be changed throughout its lifetime:
|
|
383
|
+
* 1. Whether running GC mark phase is allowed or not.
|
|
384
|
+
* 2. Whether running GC sweep phase is allowed or not.
|
|
385
|
+
* 3. Whether GC session expiry is enabled or not.
|
|
386
|
+
* For existing containers, we get this information from the metadata blob of its summary.
|
|
387
|
+
*/
|
|
383
388
|
if (existing) {
|
|
384
389
|
prevSummaryGCVersion = getGCVersion(metadata);
|
|
385
390
|
// Existing documents which did not have metadata blob or had GC disabled have version as 0. For all
|
|
386
391
|
// other existing documents, GC is enabled.
|
|
387
392
|
this.gcEnabled = prevSummaryGCVersion > 0;
|
|
393
|
+
this.sweepEnabled = metadata?.sweepEnabled ?? false;
|
|
388
394
|
this.sessionExpiryTimeoutMs = metadata?.sessionExpiryTimeoutMs;
|
|
389
395
|
} else {
|
|
390
|
-
//
|
|
396
|
+
// Sweep should not be enabled without enabling GC mark phase. We could silently disable sweep in this
|
|
397
|
+
// scenario but explicitly failing makes it clearer and promotes correct usage.
|
|
398
|
+
if (gcOptions.sweepAllowed && !gcOptions.gcAllowed) {
|
|
399
|
+
throw new UsageError("GC sweep phase cannot be enabled without enabling GC mark phase");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// For new documents, GC has to be explicitly enabled via the flags in GC options.
|
|
391
403
|
this.gcEnabled = gcOptions.gcAllowed === true;
|
|
404
|
+
this.sweepEnabled = gcOptions.sweepAllowed === true;
|
|
405
|
+
|
|
392
406
|
// Set the Session Expiry only if the flag is enabled or the test option is set.
|
|
393
|
-
if (this.mc.config.getBoolean(
|
|
407
|
+
if (this.mc.config.getBoolean(runSessionExpiryKey) && this.gcEnabled) {
|
|
394
408
|
this.sessionExpiryTimeoutMs = defaultSessionExpiryDurationMs;
|
|
395
409
|
}
|
|
396
410
|
}
|
|
397
411
|
|
|
398
412
|
// If session expiry is enabled, we need to close the container when the timeout expires
|
|
399
|
-
if (this.sessionExpiryTimeoutMs !== undefined
|
|
413
|
+
if (this.sessionExpiryTimeoutMs !== undefined
|
|
414
|
+
&& this.mc.config.getBoolean(disableSessionExpiryKey) !== true) {
|
|
415
|
+
// If Test Override config is set, override Session Expiry timeout
|
|
416
|
+
const overrideSessionExpiryTimeoutMs =
|
|
417
|
+
this.mc.config.getNumber("Fluid.GarbageCollection.TestOverride.SessionExpiryMs");
|
|
418
|
+
if (overrideSessionExpiryTimeoutMs !== undefined) {
|
|
419
|
+
this.sessionExpiryTimeoutMs = overrideSessionExpiryTimeoutMs;
|
|
420
|
+
}
|
|
421
|
+
|
|
400
422
|
const timeoutMs = this.sessionExpiryTimeoutMs;
|
|
401
423
|
setLongTimeout(timeoutMs,
|
|
402
424
|
() => {
|
|
@@ -411,7 +433,12 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
411
433
|
// latest tracked GC version. For new documents, we will be writing the first summary with the current version.
|
|
412
434
|
this.latestSummaryGCVersion = prevSummaryGCVersion ?? this.currentGCVersion;
|
|
413
435
|
|
|
414
|
-
|
|
436
|
+
/**
|
|
437
|
+
* Whether GC should run or not. The following conditions have to be met to run sweep:
|
|
438
|
+
* 1. GC should be enabled for this container.
|
|
439
|
+
* 2. GC should not be disabled via disableGC GC option.
|
|
440
|
+
* These conditions can be overridden via runGCKey feature flag.
|
|
441
|
+
*/
|
|
415
442
|
this.shouldRunGC = this.mc.config.getBoolean(runGCKey) ?? (
|
|
416
443
|
// GC must be enabled for the document.
|
|
417
444
|
this.gcEnabled
|
|
@@ -419,11 +446,15 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
419
446
|
&& !gcOptions.disableGC
|
|
420
447
|
);
|
|
421
448
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Whether sweep should run or not. The following conditions have to be met to run sweep:
|
|
451
|
+
* 1. Overall GC or mark phase must be enabled (this.shouldRunGC).
|
|
452
|
+
* 2. Session expiry and sweep should be enabled for this container. Without session expiry we cannot safely
|
|
453
|
+
* delete unreferenced objects. This condition (#2) can be overridden via runSweepKey feature flag.
|
|
454
|
+
*/
|
|
455
|
+
this.shouldRunSweep = this.shouldRunGC && (
|
|
456
|
+
this.mc.config.getBoolean(runSweepKey) ?? (this.sessionExpiryTimeoutMs !== undefined && this.sweepEnabled)
|
|
457
|
+
);
|
|
427
458
|
|
|
428
459
|
// Whether we are running in test mode. In this mode, unreferenced nodes are immediately deleted.
|
|
429
460
|
this.testMode = this.mc.config.getBoolean(gcTestModeKey) ?? gcOptions.runGCInTestMode === true;
|
|
@@ -514,7 +545,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
514
545
|
return;
|
|
515
546
|
}
|
|
516
547
|
|
|
517
|
-
const gcNodes: { [ id: string ]: string[] } = {};
|
|
548
|
+
const gcNodes: { [ id: string ]: string[]; } = {};
|
|
518
549
|
for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
|
|
519
550
|
if (nodeData.unreferencedTimestampMs !== undefined) {
|
|
520
551
|
this.unreferencedNodesState.set(
|
|
@@ -539,7 +570,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
539
570
|
return new Map();
|
|
540
571
|
}
|
|
541
572
|
|
|
542
|
-
const gcNodes: { [ id: string ]: string[] } = {};
|
|
573
|
+
const gcNodes: { [ id: string ]: string[]; } = {};
|
|
543
574
|
for (const [nodeId, nodeData] of Object.entries(baseState.gcNodes)) {
|
|
544
575
|
gcNodes[nodeId] = Array.from(nodeData.outboundRoutes);
|
|
545
576
|
}
|
|
@@ -548,7 +579,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
548
579
|
// each node in the summary.
|
|
549
580
|
const usedRoutes = runGarbageCollection(
|
|
550
581
|
gcNodes,
|
|
551
|
-
[
|
|
582
|
+
["/"],
|
|
552
583
|
this.mc.logger,
|
|
553
584
|
).referencedNodeIds;
|
|
554
585
|
|
|
@@ -593,11 +624,11 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
593
624
|
public async collectGarbage(
|
|
594
625
|
options: {
|
|
595
626
|
/** Logger to use for logging GC events */
|
|
596
|
-
logger?: ITelemetryLogger
|
|
627
|
+
logger?: ITelemetryLogger;
|
|
597
628
|
/** True to run GC sweep phase after the mark phase */
|
|
598
|
-
runSweep?: boolean
|
|
629
|
+
runSweep?: boolean;
|
|
599
630
|
/** True to generate full GC data */
|
|
600
|
-
fullGC?: boolean
|
|
631
|
+
fullGC?: boolean;
|
|
601
632
|
},
|
|
602
633
|
): Promise<IGCStats> {
|
|
603
634
|
const {
|
|
@@ -616,14 +647,14 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
616
647
|
const gcData = await this.runtime.getGCData(fullGC);
|
|
617
648
|
const gcResult = runGarbageCollection(
|
|
618
649
|
gcData.gcNodes,
|
|
619
|
-
[
|
|
650
|
+
["/"],
|
|
620
651
|
logger,
|
|
621
652
|
);
|
|
622
|
-
const gcStats = this.generateStatsAndLogEvents(gcResult);
|
|
653
|
+
const gcStats = this.generateStatsAndLogEvents(gcResult, logger);
|
|
623
654
|
|
|
624
655
|
// Update the state since the last GC run. There can be nodes that were referenced between the last and
|
|
625
656
|
// the current run. We need to identify than and update their unreferenced state if needed.
|
|
626
|
-
this.updateStateSinceLastRun(gcData);
|
|
657
|
+
this.updateStateSinceLastRun(gcData, logger);
|
|
627
658
|
|
|
628
659
|
// Update the current state of the system based on the GC run.
|
|
629
660
|
const currentReferenceTimestampMs = this.runtime.getCurrentReferenceTimestampMs();
|
|
@@ -669,6 +700,18 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
669
700
|
return builder.getSummaryTree();
|
|
670
701
|
}
|
|
671
702
|
|
|
703
|
+
public getMetadata(): IGCMetadata {
|
|
704
|
+
return {
|
|
705
|
+
/**
|
|
706
|
+
* If GC is enabled, the GC data is written using the current GC version and that is the gcFeature that goes
|
|
707
|
+
* into the metadata blob. If GC is disabled, the gcFeature is 0.
|
|
708
|
+
*/
|
|
709
|
+
gcFeature: this.gcEnabled ? this.currentGCVersion : 0,
|
|
710
|
+
sessionExpiryTimeoutMs: this.sessionExpiryTimeoutMs,
|
|
711
|
+
sweepEnabled: this.sweepEnabled,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
672
715
|
/**
|
|
673
716
|
* Returns a map of node ids to their base GC details generated from the base summary. This is used by the caller
|
|
674
717
|
* to initialize the GC state of the nodes.
|
|
@@ -840,7 +883,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
840
883
|
* This function identifies nodes that were referenced since last run and removes their unreferenced state, if any.
|
|
841
884
|
* If these nodes are currently unreferenced, they will be assigned new unreferenced state by the current run.
|
|
842
885
|
*/
|
|
843
|
-
private updateStateSinceLastRun(currentGCData: IGarbageCollectionData) {
|
|
886
|
+
private updateStateSinceLastRun(currentGCData: IGarbageCollectionData, logger: ITelemetryLogger) {
|
|
844
887
|
// If we haven't run GC before there is nothing to do.
|
|
845
888
|
if (this.previousGCDataFromLastRun === undefined) {
|
|
846
889
|
return;
|
|
@@ -855,7 +898,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
855
898
|
|
|
856
899
|
// The following log will be enabled once this issue is resolved:
|
|
857
900
|
// https://github.com/microsoft/FluidFramework/issues/8878.
|
|
858
|
-
if(this.mc.config.getBoolean(logUnknownOutboundReferencesKey) === true
|
|
901
|
+
if (this.mc.config.getBoolean(logUnknownOutboundReferencesKey) === true
|
|
859
902
|
&& missingExplicitReferences.length > 0) {
|
|
860
903
|
missingExplicitReferences.forEach((missingExplicitReference) => {
|
|
861
904
|
const event: ITelemetryPerformanceEvent = {
|
|
@@ -863,7 +906,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
863
906
|
gcNodeId: missingExplicitReference[0],
|
|
864
907
|
gcRoutes: JSON.stringify(missingExplicitReference[1]),
|
|
865
908
|
};
|
|
866
|
-
|
|
909
|
+
logger.sendPerformanceEvent(event);
|
|
867
910
|
});
|
|
868
911
|
}
|
|
869
912
|
|
|
@@ -902,7 +945,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
902
945
|
* unreferenced, stop tracking them and remove from unreferenced list.
|
|
903
946
|
* Some of these nodes may be unreferenced now and if so, the current run will add unreferenced state for them.
|
|
904
947
|
*/
|
|
905
|
-
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"],
|
|
948
|
+
const gcResult = runGarbageCollection(gcDataSuperSet.gcNodes, ["/"], logger);
|
|
906
949
|
for (const nodeId of gcResult.referencedNodeIds) {
|
|
907
950
|
const nodeStateTracker = this.unreferencedNodesState.get(nodeId);
|
|
908
951
|
if (nodeStateTracker !== undefined) {
|
|
@@ -973,13 +1016,13 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
973
1016
|
* @param gcResult - The result of a GC run.
|
|
974
1017
|
* @returns the GC stats of the GC run.
|
|
975
1018
|
*/
|
|
976
|
-
private generateStatsAndLogEvents(gcResult: IGCResult): IGCStats {
|
|
1019
|
+
private generateStatsAndLogEvents(gcResult: IGCResult, logger: ITelemetryLogger): IGCStats {
|
|
977
1020
|
// Log pending events for unreferenced nodes after GC has run. We should have the package data available for
|
|
978
1021
|
// them now since the GC run should have loaded these nodes.
|
|
979
1022
|
let event = this.pendingEventsQueue.shift();
|
|
980
1023
|
while (event !== undefined) {
|
|
981
1024
|
const pkg = this.getNodePackagePath(event.id);
|
|
982
|
-
|
|
1025
|
+
logger.sendErrorEvent({
|
|
983
1026
|
...event,
|
|
984
1027
|
pkg: pkg ? { value: `/${pkg.join("/")}`, tag: TelemetryDataTag.PackageData } : undefined,
|
|
985
1028
|
});
|
|
@@ -417,8 +417,7 @@ export class OrderedClientElection
|
|
|
417
417
|
// Note that we allow a summarizer client to supercede an interactive client as elected client.
|
|
418
418
|
if (this._electedClient === undefined || (!electedClientIsSummarizer && newClientIsSummarizer)) {
|
|
419
419
|
this.tryElectingClient(client, sequenceNumber);
|
|
420
|
-
}
|
|
421
|
-
else if (this._electedParent === undefined && !newClientIsSummarizer) {
|
|
420
|
+
} else if (this._electedParent === undefined && !newClientIsSummarizer) {
|
|
422
421
|
// This is an odd case. If the _electedClient is set, the _electedParent should be as well.
|
|
423
422
|
this.tryElectingParent(client, sequenceNumber);
|
|
424
423
|
}
|
|
@@ -443,16 +442,14 @@ export class OrderedClientElection
|
|
|
443
442
|
throw new UsageError("Elected client should be a summarizer client 1");
|
|
444
443
|
}
|
|
445
444
|
this.tryElectingClient(this._electedParent, sequenceNumber);
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
445
|
+
} else {
|
|
448
446
|
// 2. The _electedClient is an interactive client that has left the quorum.
|
|
449
447
|
// Automatically shift to next oldest client.
|
|
450
448
|
const nextClient = this.findFirstEligibleParent(this._electedParent?.youngerClient) ??
|
|
451
449
|
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
452
450
|
this.tryElectingClient(nextClient, sequenceNumber);
|
|
453
451
|
}
|
|
454
|
-
}
|
|
455
|
-
else if (this._electedParent === client) {
|
|
452
|
+
} else if (this._electedParent === client) {
|
|
456
453
|
// Removing the _electedParent (but not _electedClient).
|
|
457
454
|
// Shift to the next oldest parent, but do not replace the _electedClient,
|
|
458
455
|
// which is a summarizer that is still doing work.
|
|
@@ -478,8 +475,7 @@ export class OrderedClientElection
|
|
|
478
475
|
this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
479
476
|
if (this._electedClient === undefined || this._electedClient === this._electedParent) {
|
|
480
477
|
this.tryElectingClient(nextClient, sequenceNumber);
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
478
|
+
} else {
|
|
483
479
|
// The _electedClient is a summarizer and should not be replaced until it leaves the quorum.
|
|
484
480
|
// Changing the _electedParent will stop the summarizer.
|
|
485
481
|
this.tryElectingParent(nextClient, sequenceNumber);
|
|
@@ -493,8 +489,7 @@ export class OrderedClientElection
|
|
|
493
489
|
const firstClient = this.findFirstEligibleParent(this.orderedClientCollection.oldestClient);
|
|
494
490
|
if (this._electedClient === undefined || this._electedClient === this._electedParent) {
|
|
495
491
|
this.tryElectingClient(firstClient, sequenceNumber);
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
492
|
+
} else {
|
|
498
493
|
// The _electedClient is a summarizer and should not be replaced until it leaves the quorum.
|
|
499
494
|
// Changing the _electedParent will stop the summarizer.
|
|
500
495
|
this.tryElectingParent(firstClient, sequenceNumber);
|
package/src/packageVersion.ts
CHANGED
|
@@ -114,7 +114,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
114
114
|
pendingStates: this.pendingStates.toArray().map(
|
|
115
115
|
// delete localOpMetadata since it may not be serializable
|
|
116
116
|
// and will be regenerated by applyStashedOp()
|
|
117
|
-
(state) => state.type === "message" ? {...state, localOpMetadata: undefined } : state),
|
|
117
|
+
(state) => state.type === "message" ? { ...state, localOpMetadata: undefined } : state),
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -288,7 +288,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
288
288
|
assert(message.type === state.messageType, 0x28c /* "different message type" */);
|
|
289
289
|
assert(message.clientSequenceNumber === state.clientSequenceNumber || !isOriginalClientId,
|
|
290
290
|
0x28d /* "client sequence number doesn't match" */);
|
|
291
|
-
switch(message.type) {
|
|
291
|
+
switch (message.type) {
|
|
292
292
|
case ContainerMessageType.Attach:
|
|
293
293
|
assert(message.contents.id === state.content.id, 0x28e /* "datastore ID doesn't match" */);
|
|
294
294
|
break;
|
package/src/runningSummarizer.ts
CHANGED
|
@@ -261,9 +261,10 @@ export class RunningSummarizer implements IDisposable {
|
|
|
261
261
|
this.summaryCollection.unsetPendingAckTimerTimeoutCallback();
|
|
262
262
|
|
|
263
263
|
if (waitStartResult.result === "done" && waitStartResult.value !== undefined) {
|
|
264
|
-
this.heuristicData.
|
|
264
|
+
this.heuristicData.updateWithLastSummaryAckInfo({
|
|
265
265
|
refSequenceNumber: waitStartResult.value.summaryOp.referenceSequenceNumber,
|
|
266
|
-
|
|
266
|
+
// This will be the Summarizer starting point so only use timestamps from client's machine.
|
|
267
|
+
summaryTime: Date.now(),
|
|
267
268
|
summarySequenceNumber: waitStartResult.value.summaryOp.sequenceNumber,
|
|
268
269
|
});
|
|
269
270
|
}
|
|
@@ -277,7 +278,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
277
278
|
* @returns - result of action.
|
|
278
279
|
*/
|
|
279
280
|
private async lockedSummaryAction<T>(action: () => Promise<T>) {
|
|
280
|
-
assert
|
|
281
|
+
assert(this.summarizingLock === undefined, 0x25b /* "Caller is responsible for checking lock" */);
|
|
281
282
|
|
|
282
283
|
const summarizingLock = new Deferred<void>();
|
|
283
284
|
this.summarizingLock = summarizingLock.promise;
|
|
@@ -311,8 +312,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
311
312
|
summarizeProps: ISummarizeTelemetryProperties,
|
|
312
313
|
options: ISummarizeOptions,
|
|
313
314
|
cancellationToken = this.cancellationToken,
|
|
314
|
-
resultsBuilder = new SummarizeResultBuilder()): ISummarizeResults
|
|
315
|
-
{
|
|
315
|
+
resultsBuilder = new SummarizeResultBuilder()): ISummarizeResults {
|
|
316
316
|
this.lockedSummaryAction(async () => {
|
|
317
317
|
const summarizeResult = this.generator.summarize(
|
|
318
318
|
summarizeProps,
|
|
@@ -334,8 +334,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
334
334
|
/** Heuristics summarize attempt. */
|
|
335
335
|
private trySummarize(
|
|
336
336
|
reason: SummarizeReason,
|
|
337
|
-
cancellationToken = this.cancellationToken): void
|
|
338
|
-
{
|
|
337
|
+
cancellationToken = this.cancellationToken): void {
|
|
339
338
|
if (this.summarizingLock !== undefined) {
|
|
340
339
|
// lockedSummaryAction() will retry heuristic-based summary at the end of current attempt
|
|
341
340
|
// if it's still needed
|
|
@@ -344,7 +343,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
344
343
|
}
|
|
345
344
|
|
|
346
345
|
this.lockedSummaryAction(async () => {
|
|
347
|
-
const attempts: (ISummarizeOptions & { delaySeconds?: number })[] = [
|
|
346
|
+
const attempts: (ISummarizeOptions & { delaySeconds?: number; })[] = [
|
|
348
347
|
{ refreshLatestAck: false, fullTree: false },
|
|
349
348
|
{ refreshLatestAck: true, fullTree: false },
|
|
350
349
|
{ refreshLatestAck: true, fullTree: false, delaySeconds: 2 * 60 },
|
|
@@ -379,7 +378,7 @@ export class RunningSummarizer implements IDisposable {
|
|
|
379
378
|
this.logger.sendPerformanceEvent({
|
|
380
379
|
eventName: "SummarizeAttemptDelay",
|
|
381
380
|
duration: delaySeconds,
|
|
382
|
-
|
|
381
|
+
summaryNackDelay: overrideDelaySeconds !== undefined,
|
|
383
382
|
...summarizeProps,
|
|
384
383
|
});
|
|
385
384
|
await delay(delaySeconds * 1000);
|
package/src/summarizer.ts
CHANGED
|
@@ -242,6 +242,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
242
242
|
eventName: "RunningSummarizer",
|
|
243
243
|
onBehalfOf,
|
|
244
244
|
initSummarySeqNumber: this.runtime.deltaManager.initialSequenceNumber,
|
|
245
|
+
config: JSON.stringify(this.configurationGetter()),
|
|
245
246
|
});
|
|
246
247
|
|
|
247
248
|
// Summarizing container ID (with clientType === "summarizer")
|
|
@@ -359,8 +360,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
359
360
|
});
|
|
360
361
|
|
|
361
362
|
return builder.build();
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
363
|
+
} catch (error) {
|
|
364
364
|
throw SummarizingWarning.wrap(error, false /* logged */, this.logger);
|
|
365
365
|
}
|
|
366
366
|
};
|
|
@@ -29,7 +29,7 @@ export class SummarizeHeuristicData implements ISummarizeHeuristicData {
|
|
|
29
29
|
this._lastSuccessfulSummary = { ...attemptBaseline };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
public
|
|
32
|
+
public updateWithLastSummaryAckInfo(lastSummary: Readonly<ISummarizeAttempt>) {
|
|
33
33
|
this._lastAttempt = lastSummary;
|
|
34
34
|
this._lastSuccessfulSummary = { ...lastSummary };
|
|
35
35
|
}
|
package/src/summarizerTypes.ts
CHANGED
|
@@ -103,16 +103,16 @@ export interface ISummarizerRuntime extends IConnectableRuntime {
|
|
|
103
103
|
/** Options affecting summarize behavior. */
|
|
104
104
|
export interface ISummarizeOptions {
|
|
105
105
|
/** True to generate the full tree with no handle reuse optimizations; defaults to false */
|
|
106
|
-
readonly fullTree?: boolean
|
|
106
|
+
readonly fullTree?: boolean;
|
|
107
107
|
/** True to ask the server what the latest summary is first; defaults to false */
|
|
108
|
-
readonly refreshLatestAck?: boolean
|
|
108
|
+
readonly refreshLatestAck?: boolean;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export interface ISubmitSummaryOptions extends ISummarizeOptions {
|
|
112
112
|
/** Logger to use for correlated summary events */
|
|
113
|
-
readonly summaryLogger: ITelemetryLogger
|
|
113
|
+
readonly summaryLogger: ITelemetryLogger;
|
|
114
114
|
/** Tells when summary process should be cancelled */
|
|
115
|
-
readonly cancellationToken: ISummaryCancellationToken
|
|
115
|
+
readonly cancellationToken: ISummaryCancellationToken;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
export interface IOnDemandSummarizeOptions extends ISummarizeOptions {
|
|
@@ -152,6 +152,8 @@ export interface IGeneratedSummaryStats extends ISummaryStats {
|
|
|
152
152
|
readonly opsSizesSinceLastSummary: number;
|
|
153
153
|
/** Number of non-system ops since the last summary @see isSystemMessage */
|
|
154
154
|
readonly nonSystemOpsSinceLastSummary: number;
|
|
155
|
+
/** The summary number for a container's summary. Incremented on summaries throughout its lifetime. */
|
|
156
|
+
readonly summaryNumber: number;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
/** Base results for all submitSummary attempts. */
|
|
@@ -358,10 +360,10 @@ export interface ISummarizeHeuristicData {
|
|
|
358
360
|
readonly lastSuccessfulSummary: Readonly<ISummarizeAttempt>;
|
|
359
361
|
|
|
360
362
|
/**
|
|
361
|
-
*
|
|
363
|
+
* Updates lastAttempt and lastSuccessfulAttempt based on the last summary.
|
|
362
364
|
* @param lastSummary - last ack summary
|
|
363
365
|
*/
|
|
364
|
-
|
|
366
|
+
updateWithLastSummaryAckInfo(lastSummary: ISummarizeAttempt): void;
|
|
365
367
|
|
|
366
368
|
/**
|
|
367
369
|
* Records a summary attempt. If the attempt was successfully sent,
|