@fluidframework/container-runtime 2.33.2 → 2.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/api-report/container-runtime.legacy.alpha.api.md +4 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +31 -8
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +90 -17
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/channelCollection.d.ts +26 -10
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +42 -11
- package/dist/channelCollection.js.map +1 -1
- package/dist/compatUtils.d.ts +19 -10
- package/dist/compatUtils.d.ts.map +1 -1
- package/dist/compatUtils.js +39 -32
- package/dist/compatUtils.js.map +1 -1
- package/dist/containerRuntime.d.ts +29 -13
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +139 -149
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStoreContext.d.ts +15 -16
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +37 -19
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -9
- package/dist/index.js.map +1 -1
- package/dist/legacy.d.ts +1 -0
- package/dist/opLifecycle/index.d.ts +1 -1
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts +20 -7
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +16 -20
- package/dist/opLifecycle/outbox.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 +22 -8
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +11 -16
- package/dist/pendingStateManager.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +31 -8
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +91 -18
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/channelCollection.d.ts +26 -10
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +43 -12
- package/lib/channelCollection.js.map +1 -1
- package/lib/compatUtils.d.ts +19 -10
- package/lib/compatUtils.d.ts.map +1 -1
- package/lib/compatUtils.js +36 -29
- package/lib/compatUtils.js.map +1 -1
- package/lib/containerRuntime.d.ts +29 -13
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +60 -70
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStoreContext.d.ts +15 -16
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +38 -20
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/index.d.ts +1 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -3
- package/lib/index.js.map +1 -1
- package/lib/legacy.d.ts +1 -0
- package/lib/opLifecycle/index.d.ts +1 -1
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts +20 -7
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +16 -20
- package/lib/opLifecycle/outbox.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 +22 -8
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +11 -16
- package/lib/pendingStateManager.js.map +1 -1
- package/package.json +18 -18
- package/src/blobManager/blobManager.ts +141 -33
- package/src/channelCollection.ts +77 -19
- package/src/compatUtils.ts +53 -30
- package/src/containerRuntime.ts +102 -81
- package/src/dataStoreContext.ts +48 -38
- package/src/index.ts +1 -13
- package/src/opLifecycle/index.ts +1 -0
- package/src/opLifecycle/outbox.ts +42 -33
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +37 -20
package/src/dataStoreContext.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
FluidObject,
|
|
16
16
|
IDisposable,
|
|
17
17
|
ITelemetryBaseProperties,
|
|
18
|
-
type IErrorBase,
|
|
19
18
|
type IEvent,
|
|
20
19
|
} from "@fluidframework/core-interfaces";
|
|
21
20
|
import {
|
|
@@ -52,7 +51,6 @@ import {
|
|
|
52
51
|
IFluidDataStoreContext,
|
|
53
52
|
IFluidDataStoreContextDetached,
|
|
54
53
|
IFluidDataStoreRegistry,
|
|
55
|
-
IFluidParentContext,
|
|
56
54
|
IGarbageCollectionDetailsBase,
|
|
57
55
|
IProvideFluidDataStoreFactory,
|
|
58
56
|
ISummarizeInternalResult,
|
|
@@ -64,6 +62,7 @@ import {
|
|
|
64
62
|
type IPendingMessagesState,
|
|
65
63
|
type IRuntimeMessageCollection,
|
|
66
64
|
type IFluidDataStoreFactory,
|
|
65
|
+
type IFluidParentContext,
|
|
67
66
|
} from "@fluidframework/runtime-definitions/internal";
|
|
68
67
|
import {
|
|
69
68
|
addBlobToSummary,
|
|
@@ -81,6 +80,7 @@ import {
|
|
|
81
80
|
tagCodeArtifacts,
|
|
82
81
|
} from "@fluidframework/telemetry-utils/internal";
|
|
83
82
|
|
|
83
|
+
import type { IFluidParentContextPrivate } from "./channelCollection.js";
|
|
84
84
|
import { BaseDeltaManagerProxy } from "./deltaManagerProxies.js";
|
|
85
85
|
import {
|
|
86
86
|
runtimeCompatDetailsForDataStore,
|
|
@@ -94,7 +94,6 @@ import {
|
|
|
94
94
|
getAttributesFormatVersion,
|
|
95
95
|
getFluidDataStoreAttributes,
|
|
96
96
|
hasIsolatedChannels,
|
|
97
|
-
summarizerClientType,
|
|
98
97
|
wrapSummaryInChannelsTree,
|
|
99
98
|
} from "./summary/index.js";
|
|
100
99
|
|
|
@@ -117,9 +116,6 @@ export function createAttributesBlob(
|
|
|
117
116
|
return new BlobTreeEntry(dataStoreAttributesBlobName, JSON.stringify(attributes));
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
/**
|
|
121
|
-
* @internal
|
|
122
|
-
*/
|
|
123
119
|
export interface ISnapshotDetails {
|
|
124
120
|
pkg: readonly string[];
|
|
125
121
|
isRootDataStore: boolean;
|
|
@@ -131,7 +127,6 @@ export interface ISnapshotDetails {
|
|
|
131
127
|
* This is interface that every context should implement.
|
|
132
128
|
* This interface is used for context's parent - ChannelCollection.
|
|
133
129
|
* It should not be exposed to any other users of context.
|
|
134
|
-
* @internal
|
|
135
130
|
*/
|
|
136
131
|
export interface IFluidDataStoreContextInternal extends IFluidDataStoreContext {
|
|
137
132
|
getAttachSummary(telemetryContext?: ITelemetryContext): ISummaryTreeWithStats;
|
|
@@ -147,11 +142,10 @@ export interface IFluidDataStoreContextInternal extends IFluidDataStoreContext {
|
|
|
147
142
|
|
|
148
143
|
/**
|
|
149
144
|
* Properties necessary for creating a FluidDataStoreContext
|
|
150
|
-
* @internal
|
|
151
145
|
*/
|
|
152
146
|
export interface IFluidDataStoreContextProps {
|
|
153
147
|
readonly id: string;
|
|
154
|
-
readonly parentContext:
|
|
148
|
+
readonly parentContext: IFluidParentContextPrivate;
|
|
155
149
|
readonly storage: IDocumentStorageService;
|
|
156
150
|
readonly scope: FluidObject;
|
|
157
151
|
readonly createSummarizerNodeFn: CreateChildSummarizerNodeFn;
|
|
@@ -161,7 +155,6 @@ export interface IFluidDataStoreContextProps {
|
|
|
161
155
|
|
|
162
156
|
/**
|
|
163
157
|
* Properties necessary for creating a local FluidDataStoreContext
|
|
164
|
-
* @internal
|
|
165
158
|
*/
|
|
166
159
|
export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContextProps {
|
|
167
160
|
readonly pkg: Readonly<string[]> | undefined;
|
|
@@ -171,7 +164,6 @@ export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContext
|
|
|
171
164
|
|
|
172
165
|
/**
|
|
173
166
|
* Properties necessary for creating a local FluidDataStoreContext
|
|
174
|
-
* @internal
|
|
175
167
|
*/
|
|
176
168
|
export interface ILocalDetachedFluidDataStoreContextProps
|
|
177
169
|
extends ILocalFluidDataStoreContextProps {
|
|
@@ -180,7 +172,6 @@ export interface ILocalDetachedFluidDataStoreContextProps
|
|
|
180
172
|
|
|
181
173
|
/**
|
|
182
174
|
* Properties necessary for creating a remote FluidDataStoreContext
|
|
183
|
-
* @internal
|
|
184
175
|
*/
|
|
185
176
|
export interface IRemoteFluidDataStoreContextProps extends IFluidDataStoreContextProps {
|
|
186
177
|
readonly snapshot: ISnapshotTree | ISnapshot | undefined;
|
|
@@ -189,7 +180,6 @@ export interface IRemoteFluidDataStoreContextProps extends IFluidDataStoreContex
|
|
|
189
180
|
// back-compat: To be removed in the future.
|
|
190
181
|
// Added in "2.0.0-rc.2.0.0" timeframe (to support older builds).
|
|
191
182
|
/**
|
|
192
|
-
* @internal
|
|
193
183
|
*/
|
|
194
184
|
export interface IFluidDataStoreContextEvents extends IEvent {
|
|
195
185
|
(event: "attaching" | "attached", listener: () => void);
|
|
@@ -233,23 +223,19 @@ class ContextDeltaManagerProxy extends BaseDeltaManagerProxy {
|
|
|
233
223
|
}
|
|
234
224
|
|
|
235
225
|
/**
|
|
236
|
-
* Called by the owning datastore context to
|
|
237
|
-
*
|
|
226
|
+
* Called by the owning datastore context to emit the readonly
|
|
227
|
+
* event on the delta manger that is projected down to the datastore
|
|
238
228
|
* runtime. This state may not align with that of the true delta
|
|
239
229
|
* manager if the context wishes to control the read only state
|
|
240
230
|
* differently than the delta manager itself.
|
|
241
231
|
*/
|
|
242
|
-
public
|
|
243
|
-
readonly
|
|
244
|
-
readonlyConnectionReason?: { reason: string; error?: IErrorBase },
|
|
245
|
-
): void {
|
|
246
|
-
this.emit("readonly", readonly, readonlyConnectionReason);
|
|
232
|
+
public emitReadonly(): void {
|
|
233
|
+
this.emit("readonly", this.isReadOnly());
|
|
247
234
|
}
|
|
248
235
|
}
|
|
249
236
|
|
|
250
237
|
/**
|
|
251
238
|
* Represents the context for the store. This context is passed to the store runtime.
|
|
252
|
-
* @internal
|
|
253
239
|
*/
|
|
254
240
|
export abstract class FluidDataStoreContext
|
|
255
241
|
extends TypedEventEmitter<IFluidDataStoreContextEvents>
|
|
@@ -281,7 +267,10 @@ export abstract class FluidDataStoreContext
|
|
|
281
267
|
return this._contextDeltaManagerProxy;
|
|
282
268
|
}
|
|
283
269
|
|
|
284
|
-
|
|
270
|
+
private isStagingMode: boolean = false;
|
|
271
|
+
public isReadOnly = (): boolean =>
|
|
272
|
+
(this.isStagingMode && this.channel?.policies?.readonlyInStagingMode !== false) ||
|
|
273
|
+
this.parentContext.isReadOnly();
|
|
285
274
|
|
|
286
275
|
public get connected(): boolean {
|
|
287
276
|
return this.parentContext.connected;
|
|
@@ -424,7 +413,7 @@ export abstract class FluidDataStoreContext
|
|
|
424
413
|
|
|
425
414
|
public readonly id: string;
|
|
426
415
|
private readonly _containerRuntime: IContainerRuntimeBase;
|
|
427
|
-
private readonly parentContext:
|
|
416
|
+
private readonly parentContext: IFluidParentContextPrivate;
|
|
428
417
|
public readonly storage: IDocumentStorageService;
|
|
429
418
|
public readonly scope: FluidObject;
|
|
430
419
|
// Represents the group to which the data store belongs too.
|
|
@@ -688,11 +677,28 @@ export abstract class FluidDataStoreContext
|
|
|
688
677
|
this.channel!.setConnectionState(connected, clientId);
|
|
689
678
|
}
|
|
690
679
|
|
|
691
|
-
public notifyReadOnlyState(
|
|
680
|
+
public notifyReadOnlyState(): void {
|
|
692
681
|
this.verifyNotClosed("notifyReadOnlyState", false /* checkTombstone */);
|
|
693
682
|
|
|
694
|
-
|
|
695
|
-
this.
|
|
683
|
+
// These two calls achieve the same purpose, and are both needed for a time for back compat
|
|
684
|
+
this.channel?.notifyReadOnlyState?.(this.isReadOnly());
|
|
685
|
+
this._contextDeltaManagerProxy.emitReadonly();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Updates the readonly state of the data store based on the staging mode.
|
|
690
|
+
*
|
|
691
|
+
* @param staging - A boolean indicating whether the container is in staging mode.
|
|
692
|
+
* If true, the data store is set to readonly unless explicitly allowed by its policies.
|
|
693
|
+
*/
|
|
694
|
+
public notifyStagingMode(staging: boolean): void {
|
|
695
|
+
// If the `readonlyInStagingMode` policy is not explicitly set to `false`,
|
|
696
|
+
// the data store is treated as readonly in staging mode.
|
|
697
|
+
const oldReadOnlyState = this.isReadOnly();
|
|
698
|
+
this.isStagingMode = staging;
|
|
699
|
+
if (this.isReadOnly() !== oldReadOnlyState) {
|
|
700
|
+
this.notifyReadOnlyState();
|
|
701
|
+
}
|
|
696
702
|
}
|
|
697
703
|
|
|
698
704
|
/**
|
|
@@ -877,8 +883,8 @@ export abstract class FluidDataStoreContext
|
|
|
877
883
|
public submitMessage(type: string, content: unknown, localOpMetadata: unknown): void {
|
|
878
884
|
this.verifyNotClosed("submitMessage");
|
|
879
885
|
assert(!!this.channel, 0x146 /* "Channel must exist when submitting message" */);
|
|
880
|
-
//
|
|
881
|
-
this.
|
|
886
|
+
// Readonly clients should not submit messages.
|
|
887
|
+
this.identifyLocalChangeIfReadonly("DataStoreMessageWhileReadonly", type);
|
|
882
888
|
|
|
883
889
|
this.parentContext.submitMessage(type, content, localOpMetadata);
|
|
884
890
|
}
|
|
@@ -1036,9 +1042,14 @@ export abstract class FluidDataStoreContext
|
|
|
1036
1042
|
return {};
|
|
1037
1043
|
}
|
|
1038
1044
|
|
|
1039
|
-
public reSubmit(
|
|
1045
|
+
public reSubmit(
|
|
1046
|
+
type: string,
|
|
1047
|
+
contents: unknown,
|
|
1048
|
+
localOpMetadata: unknown,
|
|
1049
|
+
squash: boolean,
|
|
1050
|
+
): void {
|
|
1040
1051
|
assert(!!this.channel, 0x14b /* "Channel must exist when resubmitting ops" */);
|
|
1041
|
-
this.channel.reSubmit(type, contents, localOpMetadata);
|
|
1052
|
+
this.channel.reSubmit(type, contents, localOpMetadata, squash);
|
|
1042
1053
|
}
|
|
1043
1054
|
|
|
1044
1055
|
public rollback(type: string, contents: unknown, localOpMetadata: unknown): void {
|
|
@@ -1109,19 +1120,16 @@ export abstract class FluidDataStoreContext
|
|
|
1109
1120
|
}
|
|
1110
1121
|
|
|
1111
1122
|
/**
|
|
1112
|
-
*
|
|
1123
|
+
* Readonly client, including summarizer, should not have local changes. These changes can become part of the summary and can break
|
|
1113
1124
|
* eventual consistency. For example, the next summary (say at ref seq# 100) may contain these changes whereas
|
|
1114
1125
|
* other clients that are up-to-date till seq# 100 may not have them yet.
|
|
1115
1126
|
*/
|
|
1116
|
-
protected
|
|
1117
|
-
if (
|
|
1118
|
-
this.clientDetails.type !== summarizerClientType ||
|
|
1119
|
-
this.localChangesTelemetryCount <= 0
|
|
1120
|
-
) {
|
|
1127
|
+
protected identifyLocalChangeIfReadonly(eventName: string, type?: string): void {
|
|
1128
|
+
if (!this.isReadOnly() || this.localChangesTelemetryCount <= 0) {
|
|
1121
1129
|
return;
|
|
1122
1130
|
}
|
|
1123
1131
|
|
|
1124
|
-
// Log a telemetry if there are local changes in
|
|
1132
|
+
// Log a telemetry if there are local changes in readonly. This will give us data on how often
|
|
1125
1133
|
// this is happening and which data stores do this. The eventual goal is to disallow local changes
|
|
1126
1134
|
// in the summarizer and the data will help us plan this.
|
|
1127
1135
|
this.mc.logger.sendTelemetryEvent({
|
|
@@ -1129,6 +1137,8 @@ export abstract class FluidDataStoreContext
|
|
|
1129
1137
|
type,
|
|
1130
1138
|
isSummaryInProgress: this.summarizerNode.isSummaryInProgress?.(),
|
|
1131
1139
|
stack: generateStack(30),
|
|
1140
|
+
readonly: this.isReadOnly(),
|
|
1141
|
+
isStagingMode: this.isStagingMode,
|
|
1132
1142
|
});
|
|
1133
1143
|
this.localChangesTelemetryCount--;
|
|
1134
1144
|
}
|
|
@@ -1330,7 +1340,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
|
|
|
1330
1340
|
);
|
|
1331
1341
|
|
|
1332
1342
|
// Summarizer client should not create local data stores.
|
|
1333
|
-
this.
|
|
1343
|
+
this.identifyLocalChangeIfReadonly("DataStoreCreatedWhileReadonly");
|
|
1334
1344
|
|
|
1335
1345
|
this.snapshotTree = props.snapshotTree;
|
|
1336
1346
|
}
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ export {
|
|
|
31
31
|
ChannelCollectionFactory,
|
|
32
32
|
AllowTombstoneRequestHeaderKey,
|
|
33
33
|
} from "./channelCollection.js";
|
|
34
|
+
export type { MinimumVersionForCollab } from "./compatUtils.js";
|
|
34
35
|
export {
|
|
35
36
|
GCNodeType,
|
|
36
37
|
IGCMetadata,
|
|
@@ -109,16 +110,3 @@ export {
|
|
|
109
110
|
DefaultSummaryConfiguration,
|
|
110
111
|
} from "./summary/index.js";
|
|
111
112
|
export { IChunkedOp, unpackRuntimeMessage } from "./opLifecycle/index.js";
|
|
112
|
-
export { ChannelCollection } from "./channelCollection.js";
|
|
113
|
-
export {
|
|
114
|
-
IFluidDataStoreContextInternal,
|
|
115
|
-
ISnapshotDetails,
|
|
116
|
-
LocalFluidDataStoreContext,
|
|
117
|
-
LocalFluidDataStoreContextBase,
|
|
118
|
-
FluidDataStoreContext,
|
|
119
|
-
IFluidDataStoreContextProps,
|
|
120
|
-
ILocalFluidDataStoreContextProps,
|
|
121
|
-
ILocalDetachedFluidDataStoreContextProps,
|
|
122
|
-
IFluidDataStoreContextEvents,
|
|
123
|
-
} from "./dataStoreContext.js";
|
|
124
|
-
export { DataStoreContexts } from "./dataStoreContexts.js";
|
package/src/opLifecycle/index.ts
CHANGED
|
@@ -66,10 +66,27 @@ export interface IOutboxParameters {
|
|
|
66
66
|
readonly logger: ITelemetryBaseLogger;
|
|
67
67
|
readonly groupingManager: OpGroupingManager;
|
|
68
68
|
readonly getCurrentSequenceNumbers: () => BatchSequenceNumbers;
|
|
69
|
-
readonly reSubmit: (message: PendingMessageResubmitData) => void;
|
|
69
|
+
readonly reSubmit: (message: PendingMessageResubmitData, squash: boolean) => void;
|
|
70
70
|
readonly opReentrancy: () => boolean;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Info needed to correctly resubmit a batch
|
|
75
|
+
*/
|
|
76
|
+
export interface BatchResubmitInfo {
|
|
77
|
+
/**
|
|
78
|
+
* If defined, indicates the Batch ID of the batch being resubmitted.
|
|
79
|
+
* This must be preserved on the new batch about to be submitted so they can be correlated/deduped in case both are sent.
|
|
80
|
+
*/
|
|
81
|
+
batchId?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Indicates whether or not this batch is "staged", meaning it should not be sent to the ordering service yet
|
|
84
|
+
* This is important on resubmit because we may be in Staging Mode for new changes,
|
|
85
|
+
* but resubmitting a non-staged change from before entering Staging Mode
|
|
86
|
+
*/
|
|
87
|
+
staged: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
73
90
|
/**
|
|
74
91
|
* Temporarily increase the stack limit while executing the provided action.
|
|
75
92
|
* If a negative value is provided for `length`, no stack frames will be collected.
|
|
@@ -335,37 +352,33 @@ export class Outbox {
|
|
|
335
352
|
* This method is expected to be called at the end of a batch.
|
|
336
353
|
*
|
|
337
354
|
* @throws If called from a reentrant context, or if the batch being flushed is too large.
|
|
338
|
-
* @param
|
|
339
|
-
* with the given Batch ID, which must be preserved
|
|
340
|
-
* @param resubmittingStagedBatch - If defined, indicates this is a resubmission of a batch that is staged,
|
|
341
|
-
* meaning it should not be sent to the ordering service yet.
|
|
355
|
+
* @param resubmitInfo - Key information when flushing a resubmitted batch. Undefined means this is not resubmit.
|
|
342
356
|
*/
|
|
343
|
-
public flush(
|
|
357
|
+
public flush(resubmitInfo?: BatchResubmitInfo): void {
|
|
344
358
|
assert(
|
|
345
359
|
!this.isContextReentrant(),
|
|
346
360
|
0xb7b /* Flushing must not happen while incoming changes are being processed */,
|
|
347
361
|
);
|
|
348
|
-
|
|
349
|
-
this.flushAll(resubmittingBatchId, resubmittingStagedBatch);
|
|
362
|
+
this.flushAll(resubmitInfo);
|
|
350
363
|
}
|
|
351
364
|
|
|
352
|
-
private flushAll(
|
|
365
|
+
private flushAll(resubmitInfo?: BatchResubmitInfo): void {
|
|
353
366
|
const allBatchesEmpty =
|
|
354
367
|
this.idAllocationBatch.empty && this.blobAttachBatch.empty && this.mainBatch.empty;
|
|
355
368
|
if (allBatchesEmpty) {
|
|
356
|
-
// If we're resubmitting and all batches are empty, we need to flush an empty batch.
|
|
357
|
-
// Note that we currently resubmit one batch at a time, so on resubmit,
|
|
369
|
+
// If we're resubmitting with a batchId and all batches are empty, we need to flush an empty batch.
|
|
370
|
+
// Note that we currently resubmit one batch at a time, so on resubmit, 1 of the 2 batches will *always* be empty.
|
|
358
371
|
// It's theoretically possible that we don't *need* to resubmit this empty batch, and in those cases, it'll safely be ignored
|
|
359
372
|
// by the rest of the system, including remote clients.
|
|
360
373
|
// In some cases we *must* resubmit the empty batch (to match up with a non-empty version tracked locally by a container fork), so we do it always.
|
|
361
|
-
if (
|
|
362
|
-
this.flushEmptyBatch(
|
|
374
|
+
if (resubmitInfo?.batchId !== undefined) {
|
|
375
|
+
this.flushEmptyBatch(resubmitInfo.batchId, resubmitInfo.staged);
|
|
363
376
|
}
|
|
364
377
|
return;
|
|
365
378
|
}
|
|
366
379
|
|
|
367
380
|
// Don't use resubmittingBatchId for idAllocationBatch.
|
|
368
|
-
// ID Allocation messages are not directly resubmitted so
|
|
381
|
+
// ID Allocation messages are not directly resubmitted so don't pass the resubmitInfo
|
|
369
382
|
this.flushInternal({
|
|
370
383
|
batchManager: this.idAllocationBatch,
|
|
371
384
|
// Note: For now, we will never stage ID Allocation messages.
|
|
@@ -374,13 +387,11 @@ export class Outbox {
|
|
|
374
387
|
this.flushInternal({
|
|
375
388
|
batchManager: this.blobAttachBatch,
|
|
376
389
|
disableGroupedBatching: true,
|
|
377
|
-
|
|
378
|
-
resubmittingStagedBatch,
|
|
390
|
+
resubmitInfo,
|
|
379
391
|
});
|
|
380
392
|
this.flushInternal({
|
|
381
393
|
batchManager: this.mainBatch,
|
|
382
|
-
|
|
383
|
-
resubmittingStagedBatch,
|
|
394
|
+
resubmitInfo,
|
|
384
395
|
});
|
|
385
396
|
}
|
|
386
397
|
|
|
@@ -416,25 +427,19 @@ export class Outbox {
|
|
|
416
427
|
private flushInternal(params: {
|
|
417
428
|
batchManager: BatchManager;
|
|
418
429
|
disableGroupedBatching?: boolean;
|
|
419
|
-
|
|
420
|
-
resubmittingStagedBatch?: boolean; // undefined if not resubmitting
|
|
430
|
+
resubmitInfo?: BatchResubmitInfo; // undefined if not resubmitting
|
|
421
431
|
}): void {
|
|
422
|
-
const {
|
|
423
|
-
batchManager,
|
|
424
|
-
disableGroupedBatching = false,
|
|
425
|
-
resubmittingBatchId,
|
|
426
|
-
resubmittingStagedBatch,
|
|
427
|
-
} = params;
|
|
432
|
+
const { batchManager, disableGroupedBatching = false, resubmitInfo } = params;
|
|
428
433
|
if (batchManager.empty) {
|
|
429
434
|
return;
|
|
430
435
|
}
|
|
431
436
|
|
|
432
|
-
const rawBatch = batchManager.popBatch(
|
|
437
|
+
const rawBatch = batchManager.popBatch(resubmitInfo?.batchId);
|
|
433
438
|
|
|
434
439
|
// When resubmitting, we respect the staged state of the original batch.
|
|
435
440
|
// In this case rawBatch.staged will match the state of inStagingMode when
|
|
436
441
|
// the resubmit occurred, which is not relevant.
|
|
437
|
-
const staged =
|
|
442
|
+
const staged = resubmitInfo?.staged ?? rawBatch.staged === true;
|
|
438
443
|
|
|
439
444
|
const groupingEnabled =
|
|
440
445
|
!disableGroupedBatching && this.params.groupingManager.groupedBatchingEnabled();
|
|
@@ -490,12 +495,16 @@ export class Outbox {
|
|
|
490
495
|
assert(batchManager.options.canRebase, 0x9a7 /* BatchManager does not support rebase */);
|
|
491
496
|
|
|
492
497
|
this.rebasing = true;
|
|
498
|
+
const squash = false;
|
|
493
499
|
for (const message of rawBatch.messages) {
|
|
494
|
-
this.params.reSubmit(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
500
|
+
this.params.reSubmit(
|
|
501
|
+
{
|
|
502
|
+
runtimeOp: message.runtimeOp,
|
|
503
|
+
localOpMetadata: message.localOpMetadata,
|
|
504
|
+
opMetadata: message.metadata,
|
|
505
|
+
},
|
|
506
|
+
squash,
|
|
507
|
+
);
|
|
499
508
|
}
|
|
500
509
|
|
|
501
510
|
if (this.batchRebasesToReport > 0) {
|
package/src/packageVersion.ts
CHANGED
|
@@ -22,13 +22,13 @@ import {
|
|
|
22
22
|
} from "./messageTypes.js";
|
|
23
23
|
import { asBatchMetadata, asEmptyBatchLocalOpMetadata } from "./metadata.js";
|
|
24
24
|
import {
|
|
25
|
-
BatchId,
|
|
26
25
|
LocalBatchMessage,
|
|
27
26
|
getEffectiveBatchId,
|
|
28
27
|
BatchStartInfo,
|
|
29
28
|
InboundMessageResult,
|
|
30
29
|
serializeOp,
|
|
31
30
|
type LocalEmptyBatchPlaceholder,
|
|
31
|
+
type BatchResubmitInfo,
|
|
32
32
|
} from "./opLifecycle/index.js";
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -114,11 +114,21 @@ export type PendingMessageResubmitData = Pick<
|
|
|
114
114
|
runtimeOp: LocalContainerRuntimeMessage;
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
export interface PendingBatchResubmitMetadata extends BatchResubmitInfo {
|
|
118
|
+
/**
|
|
119
|
+
* Whether changes in this batch should be squashed when resubmitting.
|
|
120
|
+
*/
|
|
121
|
+
squash: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
export interface IRuntimeStateHandler {
|
|
118
125
|
connected(): boolean;
|
|
119
126
|
clientId(): string | undefined;
|
|
120
127
|
applyStashedOp(serializedOp: string): Promise<unknown>;
|
|
121
|
-
reSubmitBatch(
|
|
128
|
+
reSubmitBatch(
|
|
129
|
+
batch: PendingMessageResubmitData[],
|
|
130
|
+
metadata: PendingBatchResubmitMetadata,
|
|
131
|
+
): void;
|
|
122
132
|
isActiveConnection: () => boolean;
|
|
123
133
|
isAttached: () => boolean;
|
|
124
134
|
}
|
|
@@ -211,6 +221,24 @@ function toSerializableForm(
|
|
|
211
221
|
};
|
|
212
222
|
}
|
|
213
223
|
|
|
224
|
+
interface ReplayPendingStateOptions {
|
|
225
|
+
/**
|
|
226
|
+
* If true, only replay staged batches. This is used when we are exiting staging mode and want to rebase and submit the staged batches.
|
|
227
|
+
* Default: false
|
|
228
|
+
*/
|
|
229
|
+
onlyStagedBatches: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* @param squash - If true, edits should be squashed when resubmitting.
|
|
232
|
+
* Default: false
|
|
233
|
+
*/
|
|
234
|
+
squash: boolean;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const defaultReplayPendingStatesOptions: ReplayPendingStateOptions = {
|
|
238
|
+
onlyStagedBatches: false,
|
|
239
|
+
squash: false,
|
|
240
|
+
};
|
|
241
|
+
|
|
214
242
|
/**
|
|
215
243
|
* PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been
|
|
216
244
|
* acknowledged by the server. It also maintains the batch information for both automatically and manually flushed
|
|
@@ -683,11 +711,12 @@ export class PendingStateManager implements IDisposable {
|
|
|
683
711
|
* Called when the Container's connection state changes. If the Container gets connected, it replays all the pending
|
|
684
712
|
* states in its queue. This includes triggering resubmission of unacked ops.
|
|
685
713
|
* ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)
|
|
686
|
-
* @param onlyStagedBatches - If true, only replay staged batches. This is used when we are exiting staging mode and want to rebase and submit the staged batches.
|
|
687
714
|
*/
|
|
688
|
-
public replayPendingStates(
|
|
715
|
+
public replayPendingStates(optionsParam?: ReplayPendingStateOptions): void {
|
|
716
|
+
const options = { ...defaultReplayPendingStatesOptions, ...optionsParam };
|
|
717
|
+
const { onlyStagedBatches, squash } = options;
|
|
689
718
|
assert(
|
|
690
|
-
this.stateHandler.connected(),
|
|
719
|
+
this.stateHandler.connected() || onlyStagedBatches === true,
|
|
691
720
|
0x172 /* "The connection state is not consistent with the runtime" */,
|
|
692
721
|
);
|
|
693
722
|
|
|
@@ -741,7 +770,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
741
770
|
|
|
742
771
|
if (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {
|
|
743
772
|
// Resubmit no messages, with the batchId. Will result in another empty batch marker.
|
|
744
|
-
this.stateHandler.reSubmitBatch([], batchId, staged);
|
|
773
|
+
this.stateHandler.reSubmitBatch([], { batchId, staged, squash });
|
|
745
774
|
continue;
|
|
746
775
|
}
|
|
747
776
|
|
|
@@ -766,8 +795,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
766
795
|
opMetadata: pendingMessage.opMetadata,
|
|
767
796
|
},
|
|
768
797
|
],
|
|
769
|
-
batchId,
|
|
770
|
-
staged,
|
|
798
|
+
{ batchId, staged, squash },
|
|
771
799
|
);
|
|
772
800
|
continue;
|
|
773
801
|
}
|
|
@@ -807,7 +835,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
807
835
|
);
|
|
808
836
|
}
|
|
809
837
|
|
|
810
|
-
this.stateHandler.reSubmitBatch(batch, batchId, staged);
|
|
838
|
+
this.stateHandler.reSubmitBatch(batch, { batchId, staged, squash });
|
|
811
839
|
}
|
|
812
840
|
|
|
813
841
|
// pending ops should no longer depend on previous sequenced local ops after resubmit
|
|
@@ -825,17 +853,6 @@ export class PendingStateManager implements IDisposable {
|
|
|
825
853
|
}
|
|
826
854
|
}
|
|
827
855
|
|
|
828
|
-
/**
|
|
829
|
-
* Clears the 'staged' flag off all pending messages.
|
|
830
|
-
*/
|
|
831
|
-
public clearStagingFlags(): void {
|
|
832
|
-
for (const message of this.pendingMessages.toArray()) {
|
|
833
|
-
if (message.batchInfo.staged) {
|
|
834
|
-
message.batchInfo.staged = false;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
856
|
/**
|
|
840
857
|
* Pops all staged batches, invoking the callback on each one in order (LIFO)
|
|
841
858
|
*/
|