@fluidframework/container-runtime 0.58.1000 → 0.58.2000
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/batchTracker.d.ts +1 -1
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +9 -4
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts +17 -14
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +41 -31
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStores.d.ts.map +1 -1
- package/dist/dataStores.js +4 -2
- package/dist/dataStores.js.map +1 -1
- package/dist/garbageCollection.d.ts +1 -1
- package/dist/garbageCollection.d.ts.map +1 -1
- package/dist/garbageCollection.js +3 -4
- package/dist/garbageCollection.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.d.ts +15 -5
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +78 -72
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runningSummarizer.d.ts.map +1 -1
- package/dist/runningSummarizer.js +1 -1
- package/dist/runningSummarizer.js.map +1 -1
- package/dist/summarizerClientElection.d.ts.map +1 -1
- package/dist/summarizerClientElection.js +1 -0
- package/dist/summarizerClientElection.js.map +1 -1
- package/dist/summarizerTypes.d.ts +13 -1
- package/dist/summarizerTypes.d.ts.map +1 -1
- package/dist/summarizerTypes.js.map +1 -1
- package/dist/summaryGenerator.d.ts +3 -3
- package/dist/summaryGenerator.d.ts.map +1 -1
- package/dist/summaryGenerator.js +1 -0
- package/dist/summaryGenerator.js.map +1 -1
- package/dist/summaryManager.d.ts.map +1 -1
- package/dist/summaryManager.js.map +1 -1
- package/lib/batchTracker.d.ts +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +9 -4
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts +17 -14
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +41 -31
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStores.d.ts.map +1 -1
- package/lib/dataStores.js +4 -2
- package/lib/dataStores.js.map +1 -1
- package/lib/garbageCollection.d.ts +1 -1
- package/lib/garbageCollection.d.ts.map +1 -1
- package/lib/garbageCollection.js +1 -2
- package/lib/garbageCollection.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.d.ts +15 -5
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +78 -72
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runningSummarizer.d.ts.map +1 -1
- package/lib/runningSummarizer.js +1 -1
- package/lib/runningSummarizer.js.map +1 -1
- package/lib/summarizerClientElection.d.ts.map +1 -1
- package/lib/summarizerClientElection.js +1 -0
- package/lib/summarizerClientElection.js.map +1 -1
- package/lib/summarizerTypes.d.ts +13 -1
- package/lib/summarizerTypes.d.ts.map +1 -1
- package/lib/summarizerTypes.js.map +1 -1
- package/lib/summaryGenerator.d.ts +3 -3
- package/lib/summaryGenerator.d.ts.map +1 -1
- package/lib/summaryGenerator.js +1 -0
- package/lib/summaryGenerator.js.map +1 -1
- package/lib/summaryManager.d.ts.map +1 -1
- package/lib/summaryManager.js.map +1 -1
- package/package.json +17 -13
- package/src/batchTracker.ts +2 -2
- package/src/containerRuntime.ts +5 -1
- package/src/dataStore.ts +1 -1
- package/src/dataStoreContext.ts +38 -36
- package/src/dataStores.ts +2 -1
- package/src/garbageCollection.ts +5 -6
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +102 -86
- package/src/runningSummarizer.ts +5 -4
- package/src/summarizerClientElection.ts +1 -0
- package/src/summarizerTypes.ts +18 -0
- package/src/summaryGenerator.ts +41 -5
- package/src/summaryManager.ts +0 -1
package/src/batchTracker.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Licensed under the MIT License.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import EventEmitter from "events";
|
|
6
7
|
import { ITelemetryLogger } from "@fluidframework/common-definitions";
|
|
7
8
|
import { assert, performance } from "@fluidframework/common-utils";
|
|
8
9
|
import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
|
|
9
10
|
import { ChildLogger } from "@fluidframework/telemetry-utils";
|
|
10
|
-
import EventEmitter from "events";
|
|
11
11
|
|
|
12
12
|
export class BatchTracker {
|
|
13
13
|
private readonly logger: ITelemetryLogger;
|
|
@@ -77,4 +77,4 @@ export const BindBatchTracker = (
|
|
|
77
77
|
logger: ITelemetryLogger,
|
|
78
78
|
batchLengthThreshold: number = 128,
|
|
79
79
|
batchCountSamplingRate: number = 1000,
|
|
80
|
-
) => new BatchTracker(batchEventEmitter, logger, batchLengthThreshold, batchCountSamplingRate)
|
|
80
|
+
) => new BatchTracker(batchEventEmitter, logger, batchLengthThreshold, batchCountSamplingRate);
|
package/src/containerRuntime.ts
CHANGED
|
@@ -780,6 +780,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
780
780
|
if (loadSequenceNumberVerification !== "bypass" && runtimeSequenceNumber !== protocolSequenceNumber) {
|
|
781
781
|
// "Load from summary, runtime metadata sequenceNumber !== initialSequenceNumber"
|
|
782
782
|
const error = new DataCorruptionError(
|
|
783
|
+
// pre-0.58 error message: SummaryMetadataMismatch
|
|
783
784
|
"Summary metadata mismatch",
|
|
784
785
|
{ runtimeSequenceNumber, protocolSequenceNumber },
|
|
785
786
|
);
|
|
@@ -1128,6 +1129,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1128
1129
|
this.pendingStateManager = new PendingStateManager(
|
|
1129
1130
|
this,
|
|
1130
1131
|
async (type, content) => this.applyStashedOp(type, content),
|
|
1132
|
+
this._flushMode,
|
|
1131
1133
|
context.pendingLocalState as IPendingLocalState);
|
|
1132
1134
|
|
|
1133
1135
|
this.context.quorum.on("removeMember", (clientId: string) => {
|
|
@@ -1579,6 +1581,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1579
1581
|
this.context.pendingLocalState = undefined;
|
|
1580
1582
|
if (!this.shouldContinueReconnecting()) {
|
|
1581
1583
|
this.closeFn(new GenericError(
|
|
1584
|
+
// pre-0.58 error message: MaxReconnectsWithNoProgress
|
|
1582
1585
|
"Runtime detected too many reconnects with no progress syncing local ops",
|
|
1583
1586
|
undefined, // error
|
|
1584
1587
|
{ attempts: this.consecutiveReconnects }));
|
|
@@ -1762,6 +1765,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
1762
1765
|
this._orderSequentiallyCalls++;
|
|
1763
1766
|
callback();
|
|
1764
1767
|
} catch (error) {
|
|
1768
|
+
// pre-0.58 error message: orderSequentiallyCallbackException
|
|
1765
1769
|
this.closeFn(new GenericError("orderSequentially callback exception", error));
|
|
1766
1770
|
throw error; // throw the original error for the consumer of the runtime
|
|
1767
1771
|
} finally {
|
|
@@ -2147,7 +2151,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
|
|
|
2147
2151
|
if (summaryRefSeqNum !== this.deltaManager.lastMessage?.sequenceNumber) {
|
|
2148
2152
|
summaryLogger.sendErrorEvent({
|
|
2149
2153
|
eventName: "LastSequenceMismatch",
|
|
2150
|
-
message,
|
|
2154
|
+
error: message,
|
|
2151
2155
|
});
|
|
2152
2156
|
}
|
|
2153
2157
|
|
package/src/dataStore.ts
CHANGED
|
@@ -123,7 +123,7 @@ class DataStore implements IDataStore {
|
|
|
123
123
|
private readonly fluidDataStoreChannel: IFluidDataStoreChannel,
|
|
124
124
|
private readonly internalId: string,
|
|
125
125
|
private readonly runtime: ContainerRuntime,
|
|
126
|
-
private datastores: DataStores,
|
|
126
|
+
private readonly datastores: DataStores,
|
|
127
127
|
private readonly logger: ITelemetryLogger,
|
|
128
128
|
) { }
|
|
129
129
|
public get IFluidRouter() { return this.fluidDataStoreChannel; }
|
package/src/dataStoreContext.ts
CHANGED
|
@@ -106,6 +106,7 @@ export function createAttributesBlob(
|
|
|
106
106
|
|
|
107
107
|
interface ISnapshotDetails {
|
|
108
108
|
pkg: readonly string[];
|
|
109
|
+
isRootDataStore: boolean;
|
|
109
110
|
snapshot?: ISnapshotTree;
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -206,11 +207,25 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
206
207
|
return this.registry;
|
|
207
208
|
}
|
|
208
209
|
|
|
210
|
+
/**
|
|
211
|
+
* A datastore is considered as root if it
|
|
212
|
+
* 1. is root in memory - see isInMemoryRoot
|
|
213
|
+
* 2. is root as part of the base snapshot that the datastore loaded from
|
|
214
|
+
* @returns whether a datastore is root
|
|
215
|
+
*/
|
|
209
216
|
public async isRoot(): Promise<boolean> {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
217
|
+
return this.isInMemoryRoot() || (await this.getInitialSnapshotDetails()).isRootDataStore;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* There are 3 states where isInMemoryRoot needs to be true
|
|
222
|
+
* 1. when a datastore becomes aliased. This can happen for both remote and local datastores
|
|
223
|
+
* 2. when a datastore is created locally as root
|
|
224
|
+
* 3. when a datastore is created locally as root and is rehydrated
|
|
225
|
+
* @returns whether a datastore is root in memory
|
|
226
|
+
*/
|
|
227
|
+
protected isInMemoryRoot(): boolean {
|
|
228
|
+
return this._isInMemoryRoot;
|
|
214
229
|
}
|
|
215
230
|
|
|
216
231
|
protected registry: IFluidDataStoreRegistry | undefined;
|
|
@@ -223,7 +238,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
223
238
|
protected channelDeferred: Deferred<IFluidDataStoreChannel> | undefined;
|
|
224
239
|
private _baseSnapshot: ISnapshotTree | undefined;
|
|
225
240
|
protected _attachState: AttachState;
|
|
226
|
-
|
|
241
|
+
private _isInMemoryRoot: boolean = false;
|
|
227
242
|
protected readonly summarizerNode: ISummarizerNodeWithGC;
|
|
228
243
|
private readonly subLogger: ITelemetryLogger;
|
|
229
244
|
private readonly thresholdOpsCounter: ThresholdCounter;
|
|
@@ -450,7 +465,8 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
450
465
|
|
|
451
466
|
// Add data store's attributes to the summary.
|
|
452
467
|
const { pkg } = await this.getInitialSnapshotDetails();
|
|
453
|
-
const
|
|
468
|
+
const isRoot = await this.isRoot();
|
|
469
|
+
const attributes = createAttributes(pkg, isRoot, this.disableIsolatedChannels);
|
|
454
470
|
addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
|
|
455
471
|
|
|
456
472
|
// Add GC data to the summary if it's not written at the root.
|
|
@@ -676,7 +692,9 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
|
|
|
676
692
|
* This method should not be used outside of the aliasing context.
|
|
677
693
|
* It will be removed, as the source of truth for this flag will be the aliasing blob.
|
|
678
694
|
*/
|
|
679
|
-
public
|
|
695
|
+
public setInMemoryRoot(): void {
|
|
696
|
+
this._isInMemoryRoot = true;
|
|
697
|
+
}
|
|
680
698
|
|
|
681
699
|
/**
|
|
682
700
|
* @deprecated - Renamed to getBaseGCDetails().
|
|
@@ -793,7 +811,7 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
793
811
|
* data stores in older documents are not garbage collected incorrectly. This may lead to additional
|
|
794
812
|
* roots in the document but they won't break.
|
|
795
813
|
*/
|
|
796
|
-
isRootDataStore =
|
|
814
|
+
isRootDataStore = attributes.isRootDataStore ?? true;
|
|
797
815
|
|
|
798
816
|
if (hasIsolatedChannels(attributes)) {
|
|
799
817
|
tree = tree.trees[channelsTreeName];
|
|
@@ -802,11 +820,10 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
802
820
|
}
|
|
803
821
|
}
|
|
804
822
|
|
|
805
|
-
this.isRootDataStore = isRootDataStore;
|
|
806
|
-
|
|
807
823
|
return {
|
|
808
824
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
809
825
|
pkg: this.pkg!,
|
|
826
|
+
isRootDataStore,
|
|
810
827
|
snapshot: tree,
|
|
811
828
|
};
|
|
812
829
|
});
|
|
@@ -829,15 +846,6 @@ export class RemoteFluidDataStoreContext extends FluidDataStoreContext {
|
|
|
829
846
|
public generateAttachMessage(): IAttachMessage {
|
|
830
847
|
throw new Error("Cannot attach remote store");
|
|
831
848
|
}
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* @deprecated - Sets the datastore as root, for aliasing purposes: #7948
|
|
835
|
-
* This method should not be used outside of the aliasing context.
|
|
836
|
-
* It will be removed, as the source of truth for this flag will be the aliasing blob.
|
|
837
|
-
*/
|
|
838
|
-
public setRoot(): void {
|
|
839
|
-
this.isRootDataStore = true;
|
|
840
|
-
}
|
|
841
849
|
}
|
|
842
850
|
|
|
843
851
|
/**
|
|
@@ -860,7 +868,9 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
860
868
|
);
|
|
861
869
|
|
|
862
870
|
this.snapshotTree = props.snapshotTree;
|
|
863
|
-
|
|
871
|
+
if (props.isRootDataStore === true) {
|
|
872
|
+
this.setInMemoryRoot();
|
|
873
|
+
}
|
|
864
874
|
this.createProps = props.createProps;
|
|
865
875
|
this.attachListeners();
|
|
866
876
|
}
|
|
@@ -879,8 +889,6 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
879
889
|
public generateAttachMessage(): IAttachMessage {
|
|
880
890
|
assert(this.channel !== undefined, 0x14f /* "There should be a channel when generating attach message" */);
|
|
881
891
|
assert(this.pkg !== undefined, 0x150 /* "pkg should be available in local data store context" */);
|
|
882
|
-
assert(this.isRootDataStore !== undefined,
|
|
883
|
-
0x151 /* "isRootDataStore should be available in local data store context" */);
|
|
884
892
|
|
|
885
893
|
const summarizeResult = this.channel.getAttachSummary();
|
|
886
894
|
|
|
@@ -892,7 +900,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
892
900
|
// Add data store's attributes to the summary.
|
|
893
901
|
const attributes = createAttributes(
|
|
894
902
|
this.pkg,
|
|
895
|
-
this.
|
|
903
|
+
this.isInMemoryRoot(),
|
|
896
904
|
this.disableIsolatedChannels,
|
|
897
905
|
);
|
|
898
906
|
addBlobToSummary(summarizeResult, dataStoreAttributesBlobName, JSON.stringify(attributes));
|
|
@@ -912,6 +920,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
912
920
|
protected async getInitialSnapshotDetails(): Promise<ISnapshotDetails> {
|
|
913
921
|
let snapshot = this.snapshotTree;
|
|
914
922
|
let attributes: ReadFluidDataStoreAttributes;
|
|
923
|
+
let isRootDataStore = false;
|
|
915
924
|
if (snapshot !== undefined) {
|
|
916
925
|
// Get the dataStore attributes.
|
|
917
926
|
// Note: storage can be undefined in special case while detached.
|
|
@@ -926,15 +935,17 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
926
935
|
// If there is no isRootDataStore in the attributes blob, set it to true. This ensures that data
|
|
927
936
|
// stores in older documents are not garbage collected incorrectly. This may lead to additional
|
|
928
937
|
// roots in the document but they won't break.
|
|
929
|
-
|
|
938
|
+
if (attributes.isRootDataStore ?? true) {
|
|
939
|
+
isRootDataStore = true;
|
|
940
|
+
this.setInMemoryRoot();
|
|
941
|
+
}
|
|
930
942
|
}
|
|
931
943
|
}
|
|
932
944
|
assert(this.pkg !== undefined, 0x152 /* "pkg should be available in local data store" */);
|
|
933
|
-
assert(this.isRootDataStore !== undefined,
|
|
934
|
-
0x153 /* "isRootDataStore should be available in local data store" */);
|
|
935
945
|
|
|
936
946
|
return {
|
|
937
947
|
pkg: this.pkg,
|
|
948
|
+
isRootDataStore,
|
|
938
949
|
snapshot,
|
|
939
950
|
};
|
|
940
951
|
}
|
|
@@ -951,15 +962,6 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
951
962
|
// Local data store does not have initial summary.
|
|
952
963
|
return {};
|
|
953
964
|
}
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* @deprecated - Sets the datastore as root, for aliasing purposes: #7948
|
|
957
|
-
* This method should not be used outside of the aliasing context.
|
|
958
|
-
* It will be removed, as the source of truth for this flag will be the aliasing blob.
|
|
959
|
-
*/
|
|
960
|
-
public setRoot(): void {
|
|
961
|
-
this.isRootDataStore = true;
|
|
962
|
-
}
|
|
963
965
|
}
|
|
964
966
|
|
|
965
967
|
/**
|
|
@@ -1009,7 +1011,7 @@ export class LocalDetachedFluidDataStoreContext
|
|
|
1009
1011
|
|
|
1010
1012
|
super.bindRuntime(dataStoreRuntime);
|
|
1011
1013
|
|
|
1012
|
-
if (this.
|
|
1014
|
+
if (await this.isRoot()) {
|
|
1013
1015
|
dataStoreRuntime.bindToContext();
|
|
1014
1016
|
}
|
|
1015
1017
|
}
|
package/src/dataStores.ts
CHANGED
|
@@ -194,6 +194,7 @@ export class DataStores implements IDisposable {
|
|
|
194
194
|
if (this.alreadyProcessed(attachMessage.id)) {
|
|
195
195
|
// TODO: dataStoreId may require a different tag from PackageData #7488
|
|
196
196
|
const error = new DataCorruptionError(
|
|
197
|
+
// pre-0.58 error message: duplicateDataStoreCreatedWithExistingId
|
|
197
198
|
"Duplicate DataStore created with existing id",
|
|
198
199
|
{
|
|
199
200
|
...extractSafePropertiesFromMessage(message),
|
|
@@ -282,7 +283,7 @@ export class DataStores implements IDisposable {
|
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
this.aliasMap.set(aliasMessage.alias, currentContext.id);
|
|
285
|
-
currentContext.
|
|
286
|
+
currentContext.setInMemoryRoot();
|
|
286
287
|
return true;
|
|
287
288
|
}
|
|
288
289
|
|
package/src/garbageCollection.ts
CHANGED
|
@@ -35,10 +35,9 @@ import {
|
|
|
35
35
|
MonitoringContext,
|
|
36
36
|
PerformanceEvent,
|
|
37
37
|
TelemetryDataTag,
|
|
38
|
-
|
|
39
|
-
import { RuntimeHeaders } from ".";
|
|
38
|
+
} from "@fluidframework/telemetry-utils";
|
|
40
39
|
|
|
41
|
-
import { IGCRuntimeOptions } from "./containerRuntime";
|
|
40
|
+
import { IGCRuntimeOptions, RuntimeHeaders } from "./containerRuntime";
|
|
42
41
|
import { getSummaryForDatastores } from "./dataStores";
|
|
43
42
|
import {
|
|
44
43
|
getGCVersion,
|
|
@@ -96,7 +95,7 @@ interface IUnreferencedEvent {
|
|
|
96
95
|
lastSummaryTime?: number;
|
|
97
96
|
externalRequest?: boolean;
|
|
98
97
|
viaHandle?: boolean;
|
|
99
|
-
}
|
|
98
|
+
}
|
|
100
99
|
|
|
101
100
|
/** Defines the APIs for the runtime object to be passed to the garbage collector. */
|
|
102
101
|
export interface IGarbageCollectionRuntime {
|
|
@@ -186,7 +185,7 @@ class UnreferencedStateTracker {
|
|
|
186
185
|
}
|
|
187
186
|
|
|
188
187
|
// The node isn't inactive yet. Restart a timer for the duration remaining for it to become inactive.
|
|
189
|
-
const remainingDurationMs = this.inactiveTimeoutMs - unreferencedDurationMs;
|
|
188
|
+
const remainingDurationMs = this.inactiveTimeoutMs - unreferencedDurationMs;
|
|
190
189
|
if (this.timer === undefined) {
|
|
191
190
|
this.timer = new Timer(remainingDurationMs, () => { this._inactive = true; });
|
|
192
191
|
}
|
|
@@ -322,7 +321,7 @@ export class GarbageCollector implements IGarbageCollector {
|
|
|
322
321
|
// per event per node.
|
|
323
322
|
private readonly loggedUnreferencedEvents: Set<string> = new Set();
|
|
324
323
|
// Queue for unreferenced events that should be logged the next time GC runs.
|
|
325
|
-
private pendingEventsQueue: IUnreferencedEvent[] = [];
|
|
324
|
+
private readonly pendingEventsQueue: IUnreferencedEvent[] = [];
|
|
326
325
|
|
|
327
326
|
protected constructor(
|
|
328
327
|
private readonly provider: IGarbageCollectionRuntime,
|
package/src/packageVersion.ts
CHANGED
|
@@ -37,8 +37,8 @@ export interface IPendingFlushMode {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* This represents
|
|
41
|
-
* flush
|
|
40
|
+
* This represents an explicit flush call and is added to the pending queue when flush is called on the ContainerRuntime
|
|
41
|
+
* to flush pending messages.
|
|
42
42
|
*/
|
|
43
43
|
export interface IPendingFlush {
|
|
44
44
|
type: "flush";
|
|
@@ -86,6 +86,13 @@ export class PendingStateManager implements IDisposable {
|
|
|
86
86
|
// the correct batch metadata.
|
|
87
87
|
private pendingBatchBeginMessage: ISequencedDocumentMessage | undefined;
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* This tracks the flush mode for the next message in the pending state queue. When replaying messages, we need to
|
|
91
|
+
* first set the flush mode to this value and then send ops. It is important to do this info because the flush
|
|
92
|
+
* mode could have been updated.
|
|
93
|
+
*/
|
|
94
|
+
private flushModeForNextMessage: FlushMode;
|
|
95
|
+
|
|
89
96
|
private clientId: string | undefined;
|
|
90
97
|
|
|
91
98
|
private get connected(): boolean {
|
|
@@ -115,19 +122,23 @@ export class PendingStateManager implements IDisposable {
|
|
|
115
122
|
constructor(
|
|
116
123
|
private readonly containerRuntime: ContainerRuntime,
|
|
117
124
|
private readonly applyStashedOp: (type, content) => Promise<unknown>,
|
|
118
|
-
|
|
125
|
+
initialFlushMode: FlushMode,
|
|
126
|
+
initialLocalState: IPendingLocalState | undefined,
|
|
119
127
|
) {
|
|
120
|
-
this.initialStates = new Deque<IPendingState>(
|
|
128
|
+
this.initialStates = new Deque<IPendingState>(initialLocalState?.pendingStates ?? []);
|
|
121
129
|
|
|
122
|
-
if (
|
|
123
|
-
if (
|
|
124
|
-
this.previousClientIds.add(
|
|
130
|
+
if (initialLocalState) {
|
|
131
|
+
if (initialLocalState?.clientId) {
|
|
132
|
+
this.previousClientIds.add(initialLocalState.clientId);
|
|
125
133
|
}
|
|
126
134
|
// get stashed op count and client sequence number of first op
|
|
127
|
-
const messages =
|
|
135
|
+
const messages = initialLocalState.pendingStates
|
|
128
136
|
.filter((state) => state.type === "message") as IPendingMessage[];
|
|
129
137
|
this.firstStashedCSN = messages[0].clientSequenceNumber;
|
|
130
138
|
}
|
|
139
|
+
|
|
140
|
+
this.flushModeForNextMessage = initialFlushMode;
|
|
141
|
+
this.onFlushModeUpdated(initialFlushMode);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
public get disposed() { return this.disposeOnce.evaluated; }
|
|
@@ -169,54 +180,27 @@ export class PendingStateManager implements IDisposable {
|
|
|
169
180
|
* @param flushMode - The flushMode that was updated.
|
|
170
181
|
*/
|
|
171
182
|
public onFlushModeUpdated(flushMode: FlushMode) {
|
|
172
|
-
|
|
173
|
-
const previousState = this.pendingStates.peekBack();
|
|
174
|
-
|
|
175
|
-
// We don't have to track a previous "flush" state because FlushMode.Immediate flushes the messages. So,
|
|
176
|
-
// just tracking this FlushMode.Immediate is enough.
|
|
177
|
-
if (previousState?.type === "flush") {
|
|
178
|
-
this.pendingStates.removeBack();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// If no messages were sent between FlushMode.TurnBased and FlushMode.Immediate,
|
|
182
|
-
// then we do not have to track both these states.
|
|
183
|
-
// Remove FlushMode.TurnBased from the pending queue and return.
|
|
184
|
-
if (previousState?.type === "flushMode" && previousState.flushMode === FlushMode.TurnBased) {
|
|
185
|
-
this.pendingStates.removeBack();
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const pendingFlushMode: IPendingFlushMode = {
|
|
191
|
-
type: "flushMode",
|
|
192
|
-
flushMode,
|
|
193
|
-
};
|
|
194
|
-
this.pendingStates.push(pendingFlushMode);
|
|
183
|
+
this.pendingStates.push({ type: "flushMode", flushMode });
|
|
195
184
|
}
|
|
196
185
|
|
|
197
186
|
/**
|
|
198
187
|
* Called when flush() is called on the ContainerRuntime to manually flush messages.
|
|
199
188
|
*/
|
|
200
189
|
public onFlush() {
|
|
201
|
-
// If the FlushMode is Immediate, we
|
|
202
|
-
// is
|
|
190
|
+
// If the FlushMode is Immediate, we don't need to track an explicit flush call because every message is
|
|
191
|
+
// automatically flushed. So, flush is a no-op.
|
|
203
192
|
if (this.containerRuntime.flushMode === FlushMode.Immediate) {
|
|
204
193
|
return;
|
|
205
194
|
}
|
|
206
195
|
|
|
207
|
-
// If the previous state is not a message,
|
|
196
|
+
// If the previous state is not a message, flush is a no-op.
|
|
208
197
|
const previousState = this.pendingStates.peekBack();
|
|
209
198
|
if (previousState?.type !== "message") {
|
|
210
199
|
return;
|
|
211
200
|
}
|
|
212
201
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
// new one.
|
|
216
|
-
const pendingFlush: IPendingFlush = {
|
|
217
|
-
type: "flush",
|
|
218
|
-
};
|
|
219
|
-
this.pendingStates.push(pendingFlush);
|
|
202
|
+
// An explicit flush is interesting and is tracked only if there are messages sent in TurnBased mode.
|
|
203
|
+
this.pendingStates.push({ type: "flush" });
|
|
220
204
|
}
|
|
221
205
|
|
|
222
206
|
/**
|
|
@@ -249,7 +233,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
249
233
|
* Processes a local message once it's ack'd by the server to verify that there was no data corruption and that
|
|
250
234
|
* the batch information was preserved for batch messages. Also process remote messages that might have been
|
|
251
235
|
* sent from a previous container.
|
|
252
|
-
* @param message - The
|
|
236
|
+
* @param message - The message that got ack'd and needs to be processed.
|
|
253
237
|
*/
|
|
254
238
|
public processMessage(message: ISequencedDocumentMessage, local: boolean) {
|
|
255
239
|
// Do not process chunked ops until all pieces are available.
|
|
@@ -324,7 +308,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
324
308
|
/**
|
|
325
309
|
* Processes a local message once its ack'd by the server. It verifies that there was no data corruption and that
|
|
326
310
|
* the batch information was preserved for batch messages.
|
|
327
|
-
* @param message - The
|
|
311
|
+
* @param message - The message that got ack'd and needs to be processed.
|
|
328
312
|
*/
|
|
329
313
|
private processPendingLocalMessage(message: ISequencedDocumentMessage): unknown {
|
|
330
314
|
// Pre-processing part - This may be the start of a batch.
|
|
@@ -353,9 +337,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
353
337
|
this.pendingMessagesCount--;
|
|
354
338
|
|
|
355
339
|
// Post-processing part - If we are processing a batch then this could be the last message in the batch.
|
|
356
|
-
|
|
357
|
-
this.maybeProcessBatchEnd(message);
|
|
358
|
-
}
|
|
340
|
+
this.maybeProcessBatchEnd(message);
|
|
359
341
|
|
|
360
342
|
return pendingState.localOpMetadata;
|
|
361
343
|
}
|
|
@@ -365,46 +347,82 @@ export class PendingStateManager implements IDisposable {
|
|
|
365
347
|
* @param message - The message that is being processed.
|
|
366
348
|
*/
|
|
367
349
|
private maybeProcessBatchBegin(message: ISequencedDocumentMessage) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
350
|
+
// Tracks the last FlushMode that was set before this message was sent.
|
|
351
|
+
let pendingFlushMode: FlushMode | undefined;
|
|
352
|
+
// Tracks whether a flush was called before this message was sent.
|
|
353
|
+
let pendingFlush: boolean = false;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* We are checking if the next message is the start of a batch. It can happen in the following scenarios:
|
|
357
|
+
* 1. The FlushMode was set to TurnBased before this message was sent.
|
|
358
|
+
* 2. The FlushMode was already TurnBased and a flush was called before this message was sent. This essentially
|
|
359
|
+
* means that the flush marked the end of a previous batch and beginning of a new batch.
|
|
360
|
+
*
|
|
361
|
+
* Keep reading pending states from the queue until we encounter a message. It's possible that the FlushMode was
|
|
362
|
+
* updated a bunch of times without sending any messages.
|
|
363
|
+
*/
|
|
364
|
+
let nextPendingState = this.peekNextPendingState();
|
|
365
|
+
while (nextPendingState.type !== "message") {
|
|
366
|
+
if (nextPendingState.type === "flushMode") {
|
|
367
|
+
pendingFlushMode = nextPendingState.flushMode;
|
|
368
|
+
}
|
|
369
|
+
if (nextPendingState.type === "flush") {
|
|
370
|
+
pendingFlush = true;
|
|
371
|
+
}
|
|
372
|
+
this.pendingStates.shift();
|
|
373
|
+
nextPendingState = this.peekNextPendingState();
|
|
371
374
|
}
|
|
372
375
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (pendingState.type === "flushMode") {
|
|
376
|
-
assert(pendingState.flushMode === FlushMode.TurnBased,
|
|
377
|
-
0x16a /* "Flush mode should be manual when processing batch begin" */);
|
|
376
|
+
if (pendingFlushMode !== undefined) {
|
|
377
|
+
this.flushModeForNextMessage = pendingFlushMode;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
this.pendingBatchBeginMessage = message;
|
|
386
|
-
this.isProcessingBatch = true;
|
|
380
|
+
// If the FlushMode was set to Immediate before this message was sent, this message won't be a batch message
|
|
381
|
+
// because in Immediate mode, every message is flushed individually.
|
|
382
|
+
if (pendingFlushMode === FlushMode.Immediate) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
387
385
|
|
|
388
|
-
|
|
389
|
-
|
|
386
|
+
/**
|
|
387
|
+
* This message is the first in a batch if before it was sent either the FlushMode was set to TurnBased or there
|
|
388
|
+
* was an explicit flush call. Note that a flush call is tracked only in TurnBased mode and it indicates the end
|
|
389
|
+
* of one batch and beginning of another.
|
|
390
|
+
*/
|
|
391
|
+
if (pendingFlushMode === FlushMode.TurnBased || pendingFlush) {
|
|
392
|
+
// We should not already be processing a batch and there should be no pending batch begin message.
|
|
393
|
+
assert(!this.isProcessingBatch && this.pendingBatchBeginMessage === undefined,
|
|
394
|
+
0x16b /* "The pending batch state indicates we are already processing a batch" */);
|
|
395
|
+
|
|
396
|
+
// Set the pending batch state indicating we have started processing a batch.
|
|
397
|
+
this.pendingBatchBeginMessage = message;
|
|
398
|
+
this.isProcessingBatch = true;
|
|
399
|
+
}
|
|
390
400
|
}
|
|
391
401
|
|
|
402
|
+
/**
|
|
403
|
+
* This message could be the last message in batch. If so, clear batch state since the batch is complete.
|
|
404
|
+
* @param message - The message that is being processed.
|
|
405
|
+
*/
|
|
392
406
|
private maybeProcessBatchEnd(message: ISequencedDocumentMessage) {
|
|
393
|
-
|
|
394
|
-
if (nextPendingState.type !== "flush" && nextPendingState.type !== "flushMode") {
|
|
407
|
+
if (!this.isProcessingBatch) {
|
|
395
408
|
return;
|
|
396
409
|
}
|
|
397
410
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// beginning of a new one. So, it will removed when the next batch begin is processed.
|
|
402
|
-
if (nextPendingState.type === "flushMode") {
|
|
403
|
-
assert(nextPendingState.flushMode === FlushMode.Immediate,
|
|
404
|
-
0x16c /* "Flush mode is set to TurnBased in the middle of processing a batch" */);
|
|
405
|
-
this.pendingStates.shift();
|
|
411
|
+
const nextPendingState = this.peekNextPendingState();
|
|
412
|
+
if (nextPendingState.type === "message") {
|
|
413
|
+
return;
|
|
406
414
|
}
|
|
407
415
|
|
|
416
|
+
/**
|
|
417
|
+
* We are in the middle of processing a batch. The batch ends when we see an explicit flush. We should never see
|
|
418
|
+
* a FlushMode before flush. This is true because we track batches only when FlushMode is TurnBased and in this
|
|
419
|
+
* mode, a batch ends either by calling flush or by changing the mode to Immediate which also triggers a flush.
|
|
420
|
+
*/
|
|
421
|
+
assert(
|
|
422
|
+
nextPendingState.type !== "flushMode",
|
|
423
|
+
0x2bd /* "We should not see a pending FlushMode until we see a flush when processing a batch" */,
|
|
424
|
+
);
|
|
425
|
+
|
|
408
426
|
// There should be a pending batch begin message.
|
|
409
427
|
assert(this.pendingBatchBeginMessage !== undefined, 0x16d /* "There is no pending batch begin message" */);
|
|
410
428
|
|
|
@@ -462,6 +480,10 @@ export class PendingStateManager implements IDisposable {
|
|
|
462
480
|
// Save the current FlushMode so that we can revert it back after replaying the states.
|
|
463
481
|
const savedFlushMode = this.containerRuntime.flushMode;
|
|
464
482
|
|
|
483
|
+
// Set the flush mode for the next message. This step is important because the flush mode may have been changed
|
|
484
|
+
// after the next pending message was sent.
|
|
485
|
+
this.containerRuntime.setFlushMode(this.flushModeForNextMessage);
|
|
486
|
+
|
|
465
487
|
// Process exactly `pendingStatesCount` items in the queue as it represents the number of states that were
|
|
466
488
|
// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue
|
|
467
489
|
// which must not be replayed.
|
|
@@ -470,23 +492,17 @@ export class PendingStateManager implements IDisposable {
|
|
|
470
492
|
const pendingState = this.pendingStates.shift()!;
|
|
471
493
|
switch (pendingState.type) {
|
|
472
494
|
case "message":
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
pendingState.opMetadata);
|
|
479
|
-
}
|
|
495
|
+
this.containerRuntime.reSubmitFn(
|
|
496
|
+
pendingState.messageType,
|
|
497
|
+
pendingState.content,
|
|
498
|
+
pendingState.localOpMetadata,
|
|
499
|
+
pendingState.opMetadata);
|
|
480
500
|
break;
|
|
481
501
|
case "flushMode":
|
|
482
|
-
|
|
483
|
-
this.containerRuntime.setFlushMode(pendingState.flushMode);
|
|
484
|
-
}
|
|
502
|
+
this.containerRuntime.setFlushMode(pendingState.flushMode);
|
|
485
503
|
break;
|
|
486
504
|
case "flush":
|
|
487
|
-
|
|
488
|
-
this.containerRuntime.flush();
|
|
489
|
-
}
|
|
505
|
+
this.containerRuntime.flush();
|
|
490
506
|
break;
|
|
491
507
|
default:
|
|
492
508
|
break;
|