@fluidframework/container-runtime 0.52.0 → 0.54.0-47413
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/dist/containerHandleContext.d.ts +0 -1
- package/dist/containerHandleContext.d.ts.map +1 -1
- package/dist/containerHandleContext.js +0 -1
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +43 -19
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +201 -111
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +33 -4
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +45 -17
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts +14 -10
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +73 -41
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +82 -15
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +359 -26
- package/dist/garbageCollection.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.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/pendingStateManager.d.ts +0 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +0 -36
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts +3 -2
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +6 -6
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizer.d.ts +23 -3
- package/dist/summarizer.d.ts.map +1 -1
- package/dist/summarizer.js +135 -45
- package/dist/summarizer.js.map +1 -1
- package/dist/summarizerTypes.d.ts +3 -10
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryFormat.d.ts +10 -1
- package/dist/summaryFormat.d.ts.map +1 -1
- package/dist/summaryFormat.js +2 -1
- package/dist/summaryFormat.js.map +1 -1
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -3
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts +0 -15
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js +1 -35
- package/dist/summaryManager.js.map +1 -1
- package/lib/containerHandleContext.d.ts +0 -1
- package/lib/containerHandleContext.d.ts.map +1 -1
- package/lib/containerHandleContext.js +0 -1
- package/lib/containerHandleContext.js.map +1 -1
- package/lib/containerRuntime.d.ts +43 -19
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +206 -117
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +33 -4
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +45 -17
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts +14 -10
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +76 -44
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +82 -15
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +361 -28
- package/lib/garbageCollection.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/index.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/pendingStateManager.d.ts +0 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +0 -36
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts +3 -2
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +6 -6
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizer.d.ts +23 -3
- package/lib/summarizer.d.ts.map +1 -1
- package/lib/summarizer.js +135 -45
- package/lib/summarizer.js.map +1 -1
- package/lib/summarizerTypes.d.ts +3 -10
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryFormat.d.ts +10 -1
- package/lib/summaryFormat.d.ts.map +1 -1
- package/lib/summaryFormat.js +1 -0
- package/lib/summaryFormat.js.map +1 -1
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -3
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts +0 -15
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js +1 -34
- package/lib/summaryManager.js.map +1 -1
- package/package.json +14 -14
- package/src/containerHandleContext.ts +0 -1
- package/src/containerRuntime.ts +280 -140
- package/src/dataStoreContext.ts +59 -20
- package/src/dataStores.ts +116 -54
- package/src/garbageCollection.ts +492 -29
- package/src/index.ts +20 -2
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +0 -43
- package/src/runningSummarizer.ts +12 -10
- package/src/summarizer.ts +154 -53
- package/src/summarizerTypes.ts +3 -11
- package/src/summaryFormat.ts +11 -1
- package/src/summaryGenerator.ts +2 -3
- package/src/summaryManager.ts +2 -49
- package/dist/localStorageFeatureGates.d.ts +0 -13
- package/dist/localStorageFeatureGates.d.ts.map +0 -1
- package/dist/localStorageFeatureGates.js +0 -31
- package/dist/localStorageFeatureGates.js.map +0 -1
- package/lib/localStorageFeatureGates.d.ts +0 -13
- package/lib/localStorageFeatureGates.d.ts.map +0 -1
- package/lib/localStorageFeatureGates.js +0 -27
- package/lib/localStorageFeatureGates.js.map +0 -1
- package/src/localStorageFeatureGates.ts +0 -27
package/src/index.ts
CHANGED
|
@@ -3,10 +3,28 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export {
|
|
7
|
+
ContainerMessageType,
|
|
8
|
+
IChunkedOp,
|
|
9
|
+
ContainerRuntimeMessage,
|
|
10
|
+
IGCRuntimeOptions,
|
|
11
|
+
ISummaryRuntimeOptions,
|
|
12
|
+
IContainerRuntimeOptions,
|
|
13
|
+
isRuntimeMessage,
|
|
14
|
+
unpackRuntimeMessage,
|
|
15
|
+
ScheduleManager,
|
|
16
|
+
agentSchedulerId,
|
|
17
|
+
ContainerRuntime,
|
|
18
|
+
} from "./containerRuntime";
|
|
7
19
|
export * from "./deltaScheduler";
|
|
8
20
|
export * from "./dataStoreRegistry";
|
|
9
|
-
export {
|
|
21
|
+
export {
|
|
22
|
+
gcBlobPrefix,
|
|
23
|
+
gcTreeKey,
|
|
24
|
+
IGarbageCollectionRuntime,
|
|
25
|
+
IGCStats,
|
|
26
|
+
IUsedStateStats,
|
|
27
|
+
} from "./garbageCollection";
|
|
10
28
|
export * from "./pendingStateManager";
|
|
11
29
|
export * from "./summarizer";
|
|
12
30
|
export * from "./summarizerTypes";
|
package/src/packageVersion.ts
CHANGED
|
@@ -71,7 +71,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
71
71
|
private readonly initialStates: Deque<IPendingState>;
|
|
72
72
|
private readonly previousClientIds = new Set<string>();
|
|
73
73
|
private readonly firstStashedCSN: number = -1;
|
|
74
|
-
private stashedCount = 0;
|
|
75
74
|
private readonly disposeOnce = new Lazy<void>(() => {
|
|
76
75
|
this.initialStates.clear();
|
|
77
76
|
this.pendingStates.clear();
|
|
@@ -127,7 +126,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
127
126
|
// get stashed op count and client sequence number of first op
|
|
128
127
|
const messages = initialState.pendingStates
|
|
129
128
|
.filter((state) => state.type === "message") as IPendingMessage[];
|
|
130
|
-
this.stashedCount = messages.length;
|
|
131
129
|
this.firstStashedCSN = messages[0].clientSequenceNumber;
|
|
132
130
|
}
|
|
133
131
|
}
|
|
@@ -290,7 +288,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
290
288
|
// if it's not a message just drop it and keep looking
|
|
291
289
|
if (nextState.type === "message") {
|
|
292
290
|
this.assertOpMatch(nextState, message, isOriginalClientId);
|
|
293
|
-
--this.stashedCount;
|
|
294
291
|
return { localAck: true, localOpMetadata: nextState.localOpMetadata };
|
|
295
292
|
}
|
|
296
293
|
}
|
|
@@ -454,7 +451,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
454
451
|
// This assert suggests we are about to send same ops twice, which will result in data loss.
|
|
455
452
|
assert(this.clientId !== this.containerRuntime.clientId,
|
|
456
453
|
0x173 /* "replayPendingStates called twice for same clientId!" */);
|
|
457
|
-
const prevClientId = this.clientId;
|
|
458
454
|
this.clientId = this.containerRuntime.clientId;
|
|
459
455
|
|
|
460
456
|
assert(this.initialStates.isEmpty(), 0x174 /* "initial states should be empty before replaying pending" */);
|
|
@@ -464,45 +460,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
464
460
|
return;
|
|
465
461
|
}
|
|
466
462
|
|
|
467
|
-
if (!prevClientId && this.stashedCount > 0) {
|
|
468
|
-
// this is first connect, verify we are about to "resubmit" only stashed ops
|
|
469
|
-
assert(this.pendingStates.toArray().filter((s) => s.type === "message").length === this.stashedCount,
|
|
470
|
-
0x290 /* "unexpected message queued before first connect" */);
|
|
471
|
-
|
|
472
|
-
Array.from(this.previousClientIds).map((id) =>
|
|
473
|
-
assert(this.containerRuntime.getQuorum().getMember(id) === undefined,
|
|
474
|
-
0x291 /* "client with stashed ops already connected" */));
|
|
475
|
-
|
|
476
|
-
// send rejoin op with stashed client ID if we have it
|
|
477
|
-
if (this.previousClientIds.size > 0) {
|
|
478
|
-
const clientId = Array.from(this.previousClientIds)[0];
|
|
479
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
480
|
-
this.pendingStates.unshift({
|
|
481
|
-
type: "message",
|
|
482
|
-
messageType: ContainerMessageType.Rejoin,
|
|
483
|
-
content: { clientId },
|
|
484
|
-
} as IPendingMessage);
|
|
485
|
-
++pendingStatesCount;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (prevClientId) {
|
|
490
|
-
// add a rejoin op so future clients provided with our stashed pending ops can recognize them
|
|
491
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
492
|
-
const firstState = this.pendingStates.peekFront()!;
|
|
493
|
-
if (firstState.type !== "message" || firstState.messageType !== ContainerMessageType.Rejoin) {
|
|
494
|
-
// if there is already a rejoin op in the queue, just resubmit same op under new client ID
|
|
495
|
-
// otherwise, add one to the queue
|
|
496
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
497
|
-
this.pendingStates.unshift({
|
|
498
|
-
type: "message",
|
|
499
|
-
messageType: ContainerMessageType.Rejoin,
|
|
500
|
-
content: { clientId: prevClientId },
|
|
501
|
-
} as IPendingMessage);
|
|
502
|
-
++pendingStatesCount;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
463
|
// Reset the pending message count because all these messages will be removed from the queue.
|
|
507
464
|
this.pendingMessagesCount = 0;
|
|
508
465
|
|
package/src/runningSummarizer.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { IDisposable, ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
|
|
7
7
|
import { assert, delay, Deferred, PromiseTimer } from "@fluidframework/common-utils";
|
|
8
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
8
9
|
import {
|
|
9
10
|
ISequencedDocumentMessage,
|
|
10
11
|
ISummaryConfiguration,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
ISummarizeHeuristicData,
|
|
19
20
|
ISummarizeHeuristicRunner,
|
|
20
21
|
ISummarizerOptions,
|
|
21
|
-
OnDemandSummarizeResult,
|
|
22
22
|
IOnDemandSummarizeOptions,
|
|
23
23
|
EnqueueSummarizeResult,
|
|
24
24
|
SummarizerStopReason,
|
|
@@ -408,25 +408,27 @@ export class RunningSummarizer implements IDisposable {
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
/** {@inheritdoc (ISummarizer:interface).summarizeOnDemand} */
|
|
411
|
-
public summarizeOnDemand(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
411
|
+
public summarizeOnDemand(
|
|
412
|
+
resultsBuilder: SummarizeResultBuilder = new SummarizeResultBuilder(),
|
|
413
|
+
{
|
|
414
|
+
reason,
|
|
415
|
+
...options
|
|
416
|
+
}: IOnDemandSummarizeOptions): ISummarizeResults {
|
|
415
417
|
if (this.stopping) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return failBuilder.build();
|
|
418
|
+
resultsBuilder.fail("RunningSummarizer stopped or disposed", undefined);
|
|
419
|
+
return resultsBuilder.build();
|
|
419
420
|
}
|
|
420
421
|
// Check for concurrent summary attempts. If one is found,
|
|
421
422
|
// return a promise that caller can await before trying again.
|
|
422
423
|
if (this.summarizingLock !== undefined) {
|
|
423
424
|
// The heuristics are blocking concurrent summarize attempts.
|
|
424
|
-
|
|
425
|
+
throw new UsageError("Attempted to run an already-running summarizer on demand");
|
|
425
426
|
}
|
|
426
427
|
const result = this.trySummarizeOnce(
|
|
427
428
|
{ summarizeReason: `onDemand/${reason}` },
|
|
428
429
|
options,
|
|
429
|
-
this.cancellationToken
|
|
430
|
+
this.cancellationToken,
|
|
431
|
+
resultsBuilder);
|
|
430
432
|
return result;
|
|
431
433
|
}
|
|
432
434
|
|
package/src/summarizer.ts
CHANGED
|
@@ -6,19 +6,23 @@
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { Deferred } from "@fluidframework/common-utils";
|
|
8
8
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
9
|
+
import { ILoader, LoaderHeader } from "@fluidframework/container-definitions";
|
|
10
|
+
import { UsageError } from "@fluidframework/container-utils";
|
|
11
|
+
import { DriverHeader } from "@fluidframework/driver-definitions";
|
|
12
|
+
import { requestFluidObject } from "@fluidframework/runtime-utils";
|
|
9
13
|
import { ChildLogger, IFluidErrorBase, LoggingError, wrapErrorAndLog } from "@fluidframework/telemetry-utils";
|
|
10
14
|
import {
|
|
11
|
-
|
|
12
|
-
IResponse,
|
|
15
|
+
FluidObject,
|
|
13
16
|
IFluidHandleContext,
|
|
14
17
|
IFluidHandle,
|
|
18
|
+
IRequest,
|
|
15
19
|
} from "@fluidframework/core-interfaces";
|
|
16
20
|
import {
|
|
17
21
|
ISequencedDocumentMessage,
|
|
18
22
|
ISummaryConfiguration,
|
|
19
23
|
} from "@fluidframework/protocol-definitions";
|
|
20
|
-
import { create404Response } from "@fluidframework/runtime-utils";
|
|
21
24
|
import { ICancellableSummarizerController } from "./runWhileConnectedCoordinator";
|
|
25
|
+
import { summarizerClientType } from "./summarizerClientElection";
|
|
22
26
|
import { SummaryCollection } from "./summaryCollection";
|
|
23
27
|
import { SummarizerHandle } from "./summarizerHandle";
|
|
24
28
|
import { RunningSummarizer } from "./runningSummarizer";
|
|
@@ -31,6 +35,7 @@ import {
|
|
|
31
35
|
SummarizerStopReason,
|
|
32
36
|
} from "./summarizerTypes";
|
|
33
37
|
import { SummarizeHeuristicData } from "./summarizerHeuristics";
|
|
38
|
+
import { SummarizeResultBuilder } from "./summaryGenerator";
|
|
34
39
|
import { IConnectableRuntime } from ".";
|
|
35
40
|
|
|
36
41
|
const summarizingError = "summarizingError";
|
|
@@ -63,7 +68,6 @@ export const createSummarizingWarning =
|
|
|
63
68
|
*/
|
|
64
69
|
export class Summarizer extends EventEmitter implements ISummarizer {
|
|
65
70
|
public get IFluidLoadable() { return this; }
|
|
66
|
-
public get IFluidRouter() { return this; }
|
|
67
71
|
public get ISummarizer() { return this; }
|
|
68
72
|
|
|
69
73
|
private readonly logger: ITelemetryLogger;
|
|
@@ -71,6 +75,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
71
75
|
private systemOpListener?: (op: ISequencedDocumentMessage) => void;
|
|
72
76
|
private opListener?: (error: any, op: ISequencedDocumentMessage) => void;
|
|
73
77
|
private _disposed: boolean = false;
|
|
78
|
+
private starting: boolean = false;
|
|
74
79
|
|
|
75
80
|
private readonly innerHandle: IFluidHandle<this>;
|
|
76
81
|
|
|
@@ -98,6 +103,40 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
98
103
|
this.innerHandle = new SummarizerHandle(this, url, handleContext);
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Creates a Summarizer and its underlying client.
|
|
108
|
+
* Note that different implementations of ILoader will handle the URL differently.
|
|
109
|
+
* ILoader provided by a ContainerRuntime is a RelativeLoader, which will treat URL's
|
|
110
|
+
* starting with "/" as relative to the Container. The general ILoader
|
|
111
|
+
* interface will expect an absolute URL and will not handle "/".
|
|
112
|
+
* @param loader - the loader that resolves the request
|
|
113
|
+
* @param url - the URL used to resolve the container
|
|
114
|
+
*/
|
|
115
|
+
public static async create(
|
|
116
|
+
loader: ILoader,
|
|
117
|
+
url: string): Promise<ISummarizer> {
|
|
118
|
+
const request: IRequest = {
|
|
119
|
+
headers: {
|
|
120
|
+
[LoaderHeader.cache]: false,
|
|
121
|
+
[LoaderHeader.clientDetails]: {
|
|
122
|
+
capabilities: { interactive: false },
|
|
123
|
+
type: summarizerClientType,
|
|
124
|
+
},
|
|
125
|
+
[DriverHeader.summarizingClient]: true,
|
|
126
|
+
[LoaderHeader.reconnect]: false,
|
|
127
|
+
},
|
|
128
|
+
url,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const resolvedContainer = await loader.resolve(request);
|
|
132
|
+
const fluidObject =
|
|
133
|
+
await requestFluidObject<FluidObject<ISummarizer>>(resolvedContainer, { url: "_summarizer" });
|
|
134
|
+
if (fluidObject.ISummarizer === undefined) {
|
|
135
|
+
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
136
|
+
}
|
|
137
|
+
return fluidObject.ISummarizer;
|
|
138
|
+
}
|
|
139
|
+
|
|
101
140
|
public async run(
|
|
102
141
|
onBehalfOf: string,
|
|
103
142
|
options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason> {
|
|
@@ -121,27 +160,9 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
121
160
|
this.stopDeferred.resolve(reason);
|
|
122
161
|
}
|
|
123
162
|
|
|
124
|
-
public async request(request: IRequest): Promise<IResponse> {
|
|
125
|
-
if (request.url === "/" || request.url === "") {
|
|
126
|
-
return {
|
|
127
|
-
mimeType: "fluid/object",
|
|
128
|
-
status: 200,
|
|
129
|
-
value: this,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return create404Response(request);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
163
|
private async runCore(
|
|
136
164
|
onBehalfOf: string,
|
|
137
165
|
options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason> {
|
|
138
|
-
// Initialize values and first ack (time is not exact)
|
|
139
|
-
this.logger.sendTelemetryEvent({
|
|
140
|
-
eventName: "RunningSummarizer",
|
|
141
|
-
onBehalfOf,
|
|
142
|
-
initSummarySeqNumber: this.runtime.deltaManager.initialSequenceNumber,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
166
|
const runCoordinator: ICancellableSummarizerController = await this.runCoordinatorCreateFn(this.runtime);
|
|
146
167
|
|
|
147
168
|
// Wait for either external signal to cancel, or loss of connectivity.
|
|
@@ -158,10 +179,70 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
158
179
|
return runCoordinator.waitCancelled;
|
|
159
180
|
}
|
|
160
181
|
|
|
182
|
+
const runningSummarizer = await this.start(onBehalfOf, runCoordinator, options);
|
|
183
|
+
|
|
184
|
+
// Wait for either external signal to cancel, or loss of connectivity.
|
|
185
|
+
const stopReason = await stopP;
|
|
186
|
+
|
|
187
|
+
// There are two possible approaches here:
|
|
188
|
+
// 1. Propagate cancellation from this.stopDeferred to runCoordinator. This will ensure that we move to the exit
|
|
189
|
+
// faster, including breaking out of the RunningSummarizer.trySummarize() faster.
|
|
190
|
+
// We could create new coordinator and pass it to waitStop() -> trySummarizeOnce("lastSummary") flow.
|
|
191
|
+
// The con of this approach is that we might cancel active summary, and lastSummary will fail because it
|
|
192
|
+
// did not wait for ack/nack from previous summary. Plus we disregard any 429 kind of info from service
|
|
193
|
+
// that way (i.e. trySummarize() loop might have been waiting for 5 min because storage told us so).
|
|
194
|
+
// In general, it's more wasted resources.
|
|
195
|
+
// 2. We can not do it and make waitStop() do last summary only if there was no active summary. This ensures
|
|
196
|
+
// that client behaves properly (from server POV) and we do not waste resources. But, it may mean we wait
|
|
197
|
+
// substantially longer for trySummarize() retries to play out and thus this summary loop may run into
|
|
198
|
+
// conflict with new summarizer client starting on different client.
|
|
199
|
+
// As of now, #2 is implemented. It's more forward looking, as issue #7279 suggests changing design for new
|
|
200
|
+
// summarizer client to not be created until current summarizer fully moves to exit, and that would reduce
|
|
201
|
+
// cons of #2 substantially.
|
|
202
|
+
|
|
203
|
+
// Cleanup after running
|
|
204
|
+
await runningSummarizer.waitStop(!runCoordinator.cancelled /* allowLastSummary */);
|
|
205
|
+
|
|
206
|
+
// Propagate reason and ensure that if someone is waiting for cancellation token, they are moving to exit
|
|
207
|
+
runCoordinator.stop(stopReason);
|
|
208
|
+
|
|
209
|
+
return stopReason;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Put the summarizer in a started state, including creating and initializing the RunningSummarizer.
|
|
214
|
+
* The start request can come either from the SummaryManager (in the auto-summarize case) or from the user
|
|
215
|
+
* (in the on-demand case).
|
|
216
|
+
* @param onBehalfOf - ID of the client that requested that the summarizer start
|
|
217
|
+
* @param runCoordinator - cancellation token
|
|
218
|
+
* @param options - options to forward to the RunningSummarizer
|
|
219
|
+
* @returns - Promise that is fulfilled when the RunningSummarizer is ready
|
|
220
|
+
*/
|
|
221
|
+
private async start(
|
|
222
|
+
onBehalfOf: string,
|
|
223
|
+
runCoordinator: ICancellableSummarizerController,
|
|
224
|
+
options?: Readonly<Partial<ISummarizerOptions>>): Promise<RunningSummarizer> {
|
|
225
|
+
if (this.runningSummarizer) {
|
|
226
|
+
if (this.runningSummarizer.disposed) {
|
|
227
|
+
throw new UsageError("Starting a disposed summarizer");
|
|
228
|
+
}
|
|
229
|
+
return this.runningSummarizer;
|
|
230
|
+
}
|
|
231
|
+
if (this.starting) {
|
|
232
|
+
throw new UsageError("Attempting to start a summarizer that is already starting");
|
|
233
|
+
}
|
|
234
|
+
this.starting = true;
|
|
235
|
+
// Initialize values and first ack (time is not exact)
|
|
236
|
+
this.logger.sendTelemetryEvent({
|
|
237
|
+
eventName: "RunningSummarizer",
|
|
238
|
+
onBehalfOf,
|
|
239
|
+
initSummarySeqNumber: this.runtime.deltaManager.initialSequenceNumber,
|
|
240
|
+
});
|
|
241
|
+
|
|
161
242
|
// Summarizing container ID (with clientType === "summarizer")
|
|
162
243
|
const clientId = this.runtime.clientId;
|
|
163
244
|
if (clientId === undefined) {
|
|
164
|
-
throw
|
|
245
|
+
throw new UsageError("clientId should be defined if connected.");
|
|
165
246
|
}
|
|
166
247
|
|
|
167
248
|
const runningSummarizer = await RunningSummarizer.start(
|
|
@@ -187,6 +268,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
187
268
|
options,
|
|
188
269
|
);
|
|
189
270
|
this.runningSummarizer = runningSummarizer;
|
|
271
|
+
this.starting = false;
|
|
190
272
|
|
|
191
273
|
// Handle summary acks
|
|
192
274
|
// Note: no exceptions are thrown from handleSummaryAcks handler as it handles all exceptions
|
|
@@ -201,32 +283,7 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
201
283
|
this.opListener = (error: any, op: ISequencedDocumentMessage) => runningSummarizer.handleOp(error, op);
|
|
202
284
|
this.runtime.on("batchEnd", this.opListener);
|
|
203
285
|
|
|
204
|
-
|
|
205
|
-
const stopReason = await stopP;
|
|
206
|
-
|
|
207
|
-
// There are two possible approaches here:
|
|
208
|
-
// 1. Propagate cancellation from this.stopDeferred to runCoordinator. This will ensure that we move to the exit
|
|
209
|
-
// faster, including breaking out of the RunningSummarizer.trySummarize() faster.
|
|
210
|
-
// We could create new coordinator and pass it to waitStop() -> trySummarizeOnce("lastSummary") flow.
|
|
211
|
-
// The con of this approach is that we might cancel active summary, and lastSummary will fail because it
|
|
212
|
-
// did not wait for ack/nack from previous summary. Plus we disregard any 429 kind of info from service
|
|
213
|
-
// that way (i.e. trySummarize() loop might have been waiting for 5 min because storage told us so).
|
|
214
|
-
// In general, it's more wasted resources.
|
|
215
|
-
// 2. We can not do it and make waitStop() do last summary only if there was no active summary. This ensures
|
|
216
|
-
// that client behaves properly (from server POV) and we do not waste resources. But, it may mean we wait
|
|
217
|
-
// substantially longer for trySummarize() retries to play out and thus this summary loop may run into
|
|
218
|
-
// conflict with new summarizer client starting on different client.
|
|
219
|
-
// As of now, #2 is implemented. It's more forward looking, as issue #7279 suggests changing design for new
|
|
220
|
-
// summarizer client to not be created until current summarizer fully moves to exit, and that would reduce
|
|
221
|
-
// cons of #2 substantially.
|
|
222
|
-
|
|
223
|
-
// Cleanup after running
|
|
224
|
-
await runningSummarizer.waitStop(!runCoordinator.cancelled /* allowLastSummary */);
|
|
225
|
-
|
|
226
|
-
// Propagate reason and ensure that if someone is waiting for cancellation token, they are moving to exit
|
|
227
|
-
runCoordinator.stop(stopReason);
|
|
228
|
-
|
|
229
|
-
return stopReason;
|
|
286
|
+
return runningSummarizer;
|
|
230
287
|
}
|
|
231
288
|
|
|
232
289
|
/**
|
|
@@ -253,15 +310,59 @@ export class Summarizer extends EventEmitter implements ISummarizer {
|
|
|
253
310
|
}
|
|
254
311
|
|
|
255
312
|
public readonly summarizeOnDemand: ISummarizer["summarizeOnDemand"] = (...args) => {
|
|
256
|
-
|
|
257
|
-
|
|
313
|
+
try {
|
|
314
|
+
if (this._disposed || this.runningSummarizer?.disposed) {
|
|
315
|
+
throw new UsageError("Summarizer is already disposed.");
|
|
316
|
+
}
|
|
317
|
+
if (this.runtime.summarizerClientId !== undefined &&
|
|
318
|
+
this.runtime.summarizerClientId !== this.runtime.clientId) {
|
|
319
|
+
// If there is an elected summarizer, and it's not this one, don't allow on-demand summary.
|
|
320
|
+
// This is to prevent the on-demand summary and heuristic-based summary from stepping on
|
|
321
|
+
// each other.
|
|
322
|
+
throw new UsageError("On-demand summary attempted while an elected summarizer is present");
|
|
323
|
+
}
|
|
324
|
+
const builder = new SummarizeResultBuilder();
|
|
325
|
+
if (this.runningSummarizer) {
|
|
326
|
+
// Summarizer is already running. Go ahead and start.
|
|
327
|
+
return this.runningSummarizer.summarizeOnDemand(builder, ...args);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Summarizer isn't running, so we need to start it, which is an async operation.
|
|
331
|
+
// Manage the promise related to creating the cancellation token here.
|
|
332
|
+
// The promises related to starting, summarizing,
|
|
333
|
+
// and submitting are communicated to the caller through the results builder.
|
|
334
|
+
const coordinatorCreateP = this.runCoordinatorCreateFn(this.runtime);
|
|
335
|
+
|
|
336
|
+
coordinatorCreateP.then((runCoordinator) => {
|
|
337
|
+
// Successully created the cancellation token. Start the summarizer.
|
|
338
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
339
|
+
const startP = this.start(this.runtime.clientId!, runCoordinator, { disableHeuristics: true });
|
|
340
|
+
startP.then(async (runningSummarizer) => {
|
|
341
|
+
// Successfully started the summarizer. Run it.
|
|
342
|
+
runningSummarizer.summarizeOnDemand(builder, ...args);
|
|
343
|
+
// Wait for a command to stop or loss of connectivity before tearing down the summarizer and client.
|
|
344
|
+
const stopReason = await Promise.race([this.stopDeferred.promise, runCoordinator.waitCancelled]);
|
|
345
|
+
await runningSummarizer.waitStop(false);
|
|
346
|
+
runCoordinator.stop(stopReason);
|
|
347
|
+
this.dispose();
|
|
348
|
+
this.runtime.closeFn();
|
|
349
|
+
}).catch((reason) => {
|
|
350
|
+
builder.fail("Failed to start summarizer", reason);
|
|
351
|
+
});
|
|
352
|
+
}).catch((reason) => {
|
|
353
|
+
builder.fail("Failed to create cancellation token", reason);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return builder.build();
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
throw SummarizingWarning.wrap(error, "summarizerRun", false /* logged */, this.logger);
|
|
258
360
|
}
|
|
259
|
-
return this.runningSummarizer.summarizeOnDemand(...args);
|
|
260
361
|
};
|
|
261
362
|
|
|
262
363
|
public readonly enqueueSummarize: ISummarizer["enqueueSummarize"] = (...args) => {
|
|
263
364
|
if (this._disposed || this.runningSummarizer === undefined || this.runningSummarizer.disposed) {
|
|
264
|
-
throw
|
|
365
|
+
throw new UsageError("Summarizer is not running or already disposed.");
|
|
265
366
|
}
|
|
266
367
|
return this.runningSummarizer.enqueueSummarize(...args);
|
|
267
368
|
};
|
package/src/summarizerTypes.ts
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
ITelemetryLogger,
|
|
10
10
|
} from "@fluidframework/common-definitions";
|
|
11
11
|
import {
|
|
12
|
-
IFluidRouter,
|
|
13
12
|
IFluidLoadable,
|
|
14
13
|
} from "@fluidframework/core-interfaces";
|
|
15
14
|
import { ContainerWarning, IDeltaManager } from "@fluidframework/container-definitions";
|
|
@@ -239,14 +238,6 @@ export interface ISummarizeResults {
|
|
|
239
238
|
readonly receivedSummaryAckOrNack: Promise<SummarizeResultPart<IAckSummaryResult, INackSummaryResult>>;
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
export type OnDemandSummarizeResult = (ISummarizeResults & {
|
|
243
|
-
/** Indicates that an already running summarize attempt does not exist. */
|
|
244
|
-
readonly alreadyRunning?: undefined;
|
|
245
|
-
}) | {
|
|
246
|
-
/** Resolves when an already running summarize attempt completes. */
|
|
247
|
-
readonly alreadyRunning: Promise<void>;
|
|
248
|
-
};
|
|
249
|
-
|
|
250
241
|
export type EnqueueSummarizeResult = (ISummarizeResults & {
|
|
251
242
|
/**
|
|
252
243
|
* Indicates that another summarize attempt is not already enqueued,
|
|
@@ -296,8 +287,9 @@ export interface ISummarizerEvents extends IEvent {
|
|
|
296
287
|
}
|
|
297
288
|
|
|
298
289
|
export interface ISummarizer extends
|
|
299
|
-
IEventProvider<ISummarizerEvents>,
|
|
290
|
+
IEventProvider<ISummarizerEvents>, IFluidLoadable, Partial<IProvideSummarizer>{
|
|
300
291
|
stop(reason: SummarizerStopReason): void;
|
|
292
|
+
|
|
301
293
|
run(onBehalfOf: string, options?: Readonly<Partial<ISummarizerOptions>>): Promise<SummarizerStopReason>;
|
|
302
294
|
|
|
303
295
|
/**
|
|
@@ -309,7 +301,7 @@ export interface ISummarizer extends
|
|
|
309
301
|
* that resolve as the summarize attempt progresses. They will resolve with success
|
|
310
302
|
* false if a failure is encountered.
|
|
311
303
|
*/
|
|
312
|
-
summarizeOnDemand(options: IOnDemandSummarizeOptions):
|
|
304
|
+
summarizeOnDemand(options: IOnDemandSummarizeOptions): ISummarizeResults;
|
|
313
305
|
/**
|
|
314
306
|
* Enqueue an attempt to summarize after the specified sequence number.
|
|
315
307
|
* If afterSequenceNumber is provided, the summarize attempt is "enqueued"
|
package/src/summaryFormat.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function hasIsolatedChannels(attributes: ReadFluidDataStoreAttributes): b
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
export type GCVersion = number;
|
|
78
|
-
export interface IContainerRuntimeMetadata {
|
|
78
|
+
export interface IContainerRuntimeMetadata extends ICreateContainerMetadata {
|
|
79
79
|
readonly summaryFormatVersion: 1;
|
|
80
80
|
/** The last message processed at the time of summary. Only primitive propertiy types are added to the summary. */
|
|
81
81
|
readonly message: ISummaryMetadataMessage | undefined;
|
|
@@ -83,6 +83,15 @@ export interface IContainerRuntimeMetadata {
|
|
|
83
83
|
readonly disableIsolatedChannels?: true;
|
|
84
84
|
/** 0 to disable GC, > 0 to enable GC, undefined defaults to disabled. */
|
|
85
85
|
readonly gcFeature?: GCVersion;
|
|
86
|
+
/** Counter of the last summary happened, increments every time we summarize */
|
|
87
|
+
readonly summaryCount?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface ICreateContainerMetadata {
|
|
91
|
+
/** Runtime version of the container when it was first created */
|
|
92
|
+
createContainerRuntimeVersion?: string;
|
|
93
|
+
/** Timestamp of the container when it was first created */
|
|
94
|
+
createContainerTimestamp?: number;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
/** The properties of an ISequencedDocumentMessage to be stored in the metadata blob in summary. */
|
|
@@ -126,6 +135,7 @@ export function getMetadataFormatVersion(metadata?: IContainerRuntimeMetadata):
|
|
|
126
135
|
return metadata?.summaryFormatVersion ?? 0;
|
|
127
136
|
}
|
|
128
137
|
|
|
138
|
+
export const aliasBlobName = ".aliases";
|
|
129
139
|
export const metadataBlobName = ".metadata";
|
|
130
140
|
export const chunksBlobName = ".chunks";
|
|
131
141
|
export const electedSummarizerBlobName = ".electedSummarizer";
|
package/src/summaryGenerator.ts
CHANGED
|
@@ -346,10 +346,9 @@ export class SummaryGenerator {
|
|
|
346
346
|
}});
|
|
347
347
|
} else {
|
|
348
348
|
// Check for retryDelay in summaryNack response.
|
|
349
|
-
// back-compat: cast needed until dep on protocol-definitions version bump
|
|
350
349
|
assert(ackNackOp.type === MessageType.SummaryNack, 0x274 /* "type check" */);
|
|
351
|
-
const summaryNack = ackNackOp.contents
|
|
352
|
-
const message = summaryNack
|
|
350
|
+
const summaryNack = ackNackOp.contents;
|
|
351
|
+
const message = summaryNack?.message;
|
|
353
352
|
const retryAfterSeconds = summaryNack?.retryAfter;
|
|
354
353
|
|
|
355
354
|
const error = new LoggingError(`summaryNack: ${message}`, { retryAfterSeconds });
|
package/src/summaryManager.ts
CHANGED
|
@@ -6,12 +6,9 @@
|
|
|
6
6
|
import { IDisposable, IEvent, IEventProvider, ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
7
|
import { TypedEventEmitter, assert } from "@fluidframework/common-utils";
|
|
8
8
|
import { ChildLogger, PerformanceEvent } from "@fluidframework/telemetry-utils";
|
|
9
|
-
import {
|
|
10
|
-
import { LoaderHeader } from "@fluidframework/container-definitions";
|
|
11
|
-
import { DriverHeader, DriverErrorType } from "@fluidframework/driver-definitions";
|
|
12
|
-
import { requestFluidObject } from "@fluidframework/runtime-utils";
|
|
9
|
+
import { DriverErrorType } from "@fluidframework/driver-definitions";
|
|
13
10
|
import { createSummarizingWarning } from "./summarizer";
|
|
14
|
-
import { ISummarizerClientElection
|
|
11
|
+
import { ISummarizerClientElection } from "./summarizerClientElection";
|
|
15
12
|
import { IThrottler } from "./throttler";
|
|
16
13
|
import {
|
|
17
14
|
ISummarizer,
|
|
@@ -371,47 +368,3 @@ export class SummaryManager extends TypedEventEmitter<ISummaryManagerEvents> imp
|
|
|
371
368
|
this._disposed = true;
|
|
372
369
|
}
|
|
373
370
|
}
|
|
374
|
-
|
|
375
|
-
export interface ISummarizerRequestOptions {
|
|
376
|
-
cache: boolean,
|
|
377
|
-
reconnect: boolean,
|
|
378
|
-
summarizingClient: boolean,
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Forms a function that will request a Summarizer.
|
|
383
|
-
* @param loaderRouter - the loader acting as an IFluidRouter
|
|
384
|
-
* @param lastSequenceNumber - the last sequence number (e.g., from DeltaManager)
|
|
385
|
-
* @param cache - use cache to retrieve summarizer
|
|
386
|
-
* @param summarizingClient - is summarizer client
|
|
387
|
-
* @param reconnect - can reconnect on connection loss
|
|
388
|
-
*/
|
|
389
|
-
export const formRequestSummarizerFn = (
|
|
390
|
-
loaderRouter: IFluidRouter,
|
|
391
|
-
lastSequenceNumber: number,
|
|
392
|
-
{ cache, reconnect, summarizingClient }: ISummarizerRequestOptions,
|
|
393
|
-
) => async () => {
|
|
394
|
-
// TODO eventually we may wish to spawn an execution context from which to run this
|
|
395
|
-
const request: IRequest = {
|
|
396
|
-
headers: {
|
|
397
|
-
[LoaderHeader.cache]: cache,
|
|
398
|
-
[LoaderHeader.clientDetails]: {
|
|
399
|
-
capabilities: { interactive: false },
|
|
400
|
-
type: summarizerClientType,
|
|
401
|
-
},
|
|
402
|
-
[DriverHeader.summarizingClient]: summarizingClient,
|
|
403
|
-
[LoaderHeader.reconnect]: reconnect,
|
|
404
|
-
[LoaderHeader.sequenceNumber]: lastSequenceNumber,
|
|
405
|
-
},
|
|
406
|
-
url: "/_summarizer",
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const fluidObject = await requestFluidObject<FluidObject<ISummarizer>>(loaderRouter, request);
|
|
410
|
-
const summarizer = fluidObject.ISummarizer;
|
|
411
|
-
|
|
412
|
-
if (!summarizer) {
|
|
413
|
-
return Promise.reject(new Error("Fluid object does not implement ISummarizer"));
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return summarizer;
|
|
417
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Helper to check if the given feature key is set in local storage.
|
|
7
|
-
* @returns the following:
|
|
8
|
-
* - true, if the key is set and the value is "1".
|
|
9
|
-
* - false, if the key is set and the value is "0".
|
|
10
|
-
* - undefined, if local storage is not available or the key is not set.
|
|
11
|
-
*/
|
|
12
|
-
export declare function getLocalStorageFeatureGate(key: string): boolean | undefined;
|
|
13
|
-
//# sourceMappingURL=localStorageFeatureGates.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"localStorageFeatureGates.d.ts","sourceRoot":"","sources":["../src/localStorageFeatureGates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAc3E"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/*!
|
|
3
|
-
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
-
* Licensed under the MIT License.
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.getLocalStorageFeatureGate = void 0;
|
|
8
|
-
/**
|
|
9
|
-
* Helper to check if the given feature key is set in local storage.
|
|
10
|
-
* @returns the following:
|
|
11
|
-
* - true, if the key is set and the value is "1".
|
|
12
|
-
* - false, if the key is set and the value is "0".
|
|
13
|
-
* - undefined, if local storage is not available or the key is not set.
|
|
14
|
-
*/
|
|
15
|
-
function getLocalStorageFeatureGate(key) {
|
|
16
|
-
try {
|
|
17
|
-
if (typeof localStorage === "object" && localStorage !== null) {
|
|
18
|
-
const itemValue = localStorage.getItem(key);
|
|
19
|
-
if (itemValue === "1") {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
if (itemValue === "0") {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
catch (e) { }
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
exports.getLocalStorageFeatureGate = getLocalStorageFeatureGate;
|
|
31
|
-
//# sourceMappingURL=localStorageFeatureGates.js.map
|