@fluidframework/container-runtime 2.110.0 → 2.112.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/.dependency-cruiser.cjs +41 -0
- package/CHANGELOG.md +31 -0
- package/container-runtime.test-files.tar +0 -0
- package/dist/blobManager/blobManager.d.ts +6 -3
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +19 -5
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/containerRuntime.d.ts +31 -0
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +80 -11
- package/dist/containerRuntime.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 +7 -0
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +15 -0
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runtimeLayerCompatState.d.ts +2 -2
- package/lib/blobManager/blobManager.d.ts +6 -3
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +19 -5
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/containerRuntime.d.ts +31 -0
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +80 -11
- package/lib/containerRuntime.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 +7 -0
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +15 -0
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/runtimeLayerCompatState.d.ts +2 -2
- package/package.json +21 -19
- package/src/blobManager/blobManager.ts +20 -4
- package/src/containerRuntime.ts +89 -12
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +16 -0
package/src/containerRuntime.ts
CHANGED
|
@@ -1523,6 +1523,7 @@ export class ContainerRuntime
|
|
|
1523
1523
|
|
|
1524
1524
|
private lastEmittedDirty: boolean;
|
|
1525
1525
|
private emitDirtyDocumentEvent = true;
|
|
1526
|
+
private lastEmittedHasStagedChanges: boolean;
|
|
1526
1527
|
private readonly useDeltaManagerOpsProxy: boolean;
|
|
1527
1528
|
private readonly closeSummarizerDelayMs: number;
|
|
1528
1529
|
|
|
@@ -2196,6 +2197,9 @@ export class ContainerRuntime
|
|
|
2196
2197
|
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
2197
2198
|
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
2198
2199
|
|
|
2200
|
+
// We haven't emitted hasStagedChangesChanged yet, but this is the baseline so we know to emit when it changes
|
|
2201
|
+
this.lastEmittedHasStagedChanges = this.computeCurrentHasStagedChanges();
|
|
2202
|
+
|
|
2199
2203
|
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
2200
2204
|
// so we should flush now to clear the way for the next ops.
|
|
2201
2205
|
// NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
|
|
@@ -2360,8 +2364,18 @@ export class ContainerRuntime
|
|
|
2360
2364
|
if (this.isSummarizerClient) {
|
|
2361
2365
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
2362
2366
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
2367
|
+
// Import the delay-loaded module by its leaf path rather than through the `./summary/index.js` barrel,
|
|
2368
|
+
// which is also statically imported above for summarization dependencies every client loads
|
|
2369
|
+
// (SummaryManager, SummaryCollection, SummarizerClientElection, etc.). A bundler that traces re-exports
|
|
2370
|
+
// per symbol (e.g. webpack honoring sideEffects + providedExports) already keeps the summarizer out of
|
|
2371
|
+
// the initial chunk even if this dynamic import targets the barrel, because the barrel's static importers
|
|
2372
|
+
// use only non-summarizer symbols. A bundler without that analysis would instead treat the
|
|
2373
|
+
// statically-imported barrel as making the whole summarizer subgraph available and fold it into the
|
|
2374
|
+
// initial chunk. Targeting summaryDelayLoadedModule/index.js directly makes the split deterministic
|
|
2375
|
+
// across bundlers.
|
|
2363
2376
|
const module = await import(
|
|
2364
|
-
|
|
2377
|
+
// eslint-disable-next-line import-x/no-internal-modules -- Needed to import the delay-loaded module directly.
|
|
2378
|
+
/* webpackChunkName: "summarizerDelayLoadedModule" */ "./summary/summaryDelayLoadedModule/index.js"
|
|
2365
2379
|
);
|
|
2366
2380
|
this._summarizer = new module.Summarizer(
|
|
2367
2381
|
this /* ISummarizerRuntime */,
|
|
@@ -3234,13 +3248,14 @@ export class ContainerRuntime
|
|
|
3234
3248
|
"Duplicate batch - The same batch was sequenced twice",
|
|
3235
3249
|
{ batchId: batchStart.batchId },
|
|
3236
3250
|
);
|
|
3237
|
-
|
|
3251
|
+
const batchIdExplicit = batchStart.batchId !== undefined;
|
|
3252
|
+
const otherBatchIdExplicit = result.otherBatchInfo?.batchIdExplicit ?? false;
|
|
3238
3253
|
this.mc.logger.sendTelemetryEvent(
|
|
3239
3254
|
{
|
|
3240
3255
|
eventName: "DuplicateBatch",
|
|
3241
3256
|
details: {
|
|
3242
3257
|
batchId: batchStart.batchId,
|
|
3243
|
-
batchIdExplicit
|
|
3258
|
+
batchIdExplicit,
|
|
3244
3259
|
clientId: batchStart.clientId,
|
|
3245
3260
|
batchStartCsn: batchStart.batchStartCsn,
|
|
3246
3261
|
size: inboundResult.length,
|
|
@@ -3251,7 +3266,7 @@ export class ContainerRuntime
|
|
|
3251
3266
|
// loaded from a summary snapshot rather than seen at runtime.
|
|
3252
3267
|
otherClientId: result.otherBatchInfo?.clientId,
|
|
3253
3268
|
otherBatchStartCsn: result.otherBatchInfo?.batchStartCsn,
|
|
3254
|
-
otherBatchIdExplicit
|
|
3269
|
+
otherBatchIdExplicit,
|
|
3255
3270
|
otherFromSnapshot: result.otherBatchInfo === undefined,
|
|
3256
3271
|
...extractSafePropertiesFromMessage(batchStart.keyMessage),
|
|
3257
3272
|
// For grouped batches, `keyMessage` is one of the sub-messages produced by
|
|
@@ -3264,10 +3279,12 @@ export class ContainerRuntime
|
|
|
3264
3279
|
},
|
|
3265
3280
|
error,
|
|
3266
3281
|
);
|
|
3267
|
-
//
|
|
3268
|
-
//
|
|
3269
|
-
|
|
3270
|
-
|
|
3282
|
+
// Only throw the error if either the current batch or the other batch has an explicit batchId since that indicates a meaningful duplication scenario
|
|
3283
|
+
// coming from our batch readings rather than a server outage scenario.
|
|
3284
|
+
const shouldThrowOnDuplicate = batchIdExplicit || otherBatchIdExplicit;
|
|
3285
|
+
if (shouldThrowOnDuplicate) {
|
|
3286
|
+
throw error;
|
|
3287
|
+
}
|
|
3271
3288
|
}
|
|
3272
3289
|
}
|
|
3273
3290
|
|
|
@@ -3707,6 +3724,12 @@ export class ContainerRuntime
|
|
|
3707
3724
|
this.closeFn(error2);
|
|
3708
3725
|
throw error2;
|
|
3709
3726
|
}
|
|
3727
|
+
|
|
3728
|
+
// Flushing moves any staged batch from the Outbox into the PendingStateManager. Since
|
|
3729
|
+
// computeCurrentHasStagedChanges() now also considers a non-empty Outbox in Staging Mode, the
|
|
3730
|
+
// externally-visible value shouldn't change here, but re-checking is cheap and keeps this
|
|
3731
|
+
// method robust to future changes in how the two are tracked.
|
|
3732
|
+
this.updateHasStagedChangesState();
|
|
3710
3733
|
}
|
|
3711
3734
|
|
|
3712
3735
|
/**
|
|
@@ -3889,6 +3912,7 @@ export class ContainerRuntime
|
|
|
3889
3912
|
},
|
|
3890
3913
|
);
|
|
3891
3914
|
this.updateDocumentDirtyState();
|
|
3915
|
+
this.updateHasStagedChangesState();
|
|
3892
3916
|
return batchInfos;
|
|
3893
3917
|
}, "discard"),
|
|
3894
3918
|
commitChanges: (options) => {
|
|
@@ -3896,10 +3920,12 @@ export class ContainerRuntime
|
|
|
3896
3920
|
exitStagingMode(() => {
|
|
3897
3921
|
// Replay all staged batches in typical FIFO order.
|
|
3898
3922
|
// We'll be out of staging mode so they'll be sent to the service finally.
|
|
3899
|
-
|
|
3923
|
+
const batchInfos = this.pendingStateManager.replayPendingStates({
|
|
3900
3924
|
committingStagedBatches: true,
|
|
3901
3925
|
squash,
|
|
3902
3926
|
});
|
|
3927
|
+
this.updateHasStagedChangesState();
|
|
3928
|
+
return batchInfos;
|
|
3903
3929
|
}, "commit");
|
|
3904
3930
|
},
|
|
3905
3931
|
};
|
|
@@ -4026,6 +4052,38 @@ export class ContainerRuntime
|
|
|
4026
4052
|
);
|
|
4027
4053
|
}
|
|
4028
4054
|
|
|
4055
|
+
/**
|
|
4056
|
+
* Returns true if there are any staged changes, i.e. changes submitted while in Staging Mode
|
|
4057
|
+
* (see {@link @fluidframework/runtime-definitions#IContainerRuntimeBaseInternal.enterStagingMode})
|
|
4058
|
+
* that have not yet been discarded or committed.
|
|
4059
|
+
*
|
|
4060
|
+
* @remarks This is distinct from {@link ContainerRuntime.isDirty}: a container may be dirty due to
|
|
4061
|
+
* ordinary unacknowledged local changes without having any staged changes.
|
|
4062
|
+
*/
|
|
4063
|
+
public get hasStagedChanges(): boolean {
|
|
4064
|
+
// Rather than recomputing this in the moment, just regurgitate the last emitted state.
|
|
4065
|
+
return this.lastEmittedHasStagedChanges;
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
/**
|
|
4069
|
+
* Returns true if there are currently any staged (not yet discarded or committed) changes pending.
|
|
4070
|
+
*
|
|
4071
|
+
* @remarks While in Staging Mode, newly submitted ops sit in the Outbox until the next flush before
|
|
4072
|
+
* they're moved to the PendingStateManager (where `pendingStateManager.hasStagedChanges()` looks).
|
|
4073
|
+
* So we also check the Outbox here, otherwise there would be a window between submit and flush where
|
|
4074
|
+
* staged changes exist but this would incorrectly report false.
|
|
4075
|
+
*
|
|
4076
|
+
* @remarks We don't care about the type of ops in the Outbox here (unlike dirty state), just whether
|
|
4077
|
+
* there's anything queued at all, for consistency with how `pendingStateManager.hasStagedChanges()`
|
|
4078
|
+
* doesn't discriminate by op type either.
|
|
4079
|
+
*/
|
|
4080
|
+
private computeCurrentHasStagedChanges(): boolean {
|
|
4081
|
+
return (
|
|
4082
|
+
this.pendingStateManager.hasStagedChanges() ||
|
|
4083
|
+
(this.inStagingMode && !this.outbox.isEmpty)
|
|
4084
|
+
);
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4029
4087
|
/**
|
|
4030
4088
|
* Submits the signal to be sent to other clients.
|
|
4031
4089
|
* @param type - Type of the signal.
|
|
@@ -4861,13 +4919,31 @@ export class ContainerRuntime
|
|
|
4861
4919
|
private updateDocumentDirtyState(): void {
|
|
4862
4920
|
const dirty: boolean = this.computeCurrentDirtyState();
|
|
4863
4921
|
|
|
4864
|
-
if (this.lastEmittedDirty
|
|
4922
|
+
if (this.lastEmittedDirty !== dirty) {
|
|
4923
|
+
this.lastEmittedDirty = dirty;
|
|
4924
|
+
if (this.emitDirtyDocumentEvent) {
|
|
4925
|
+
this.emit(dirty ? "dirty" : "saved");
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
/**
|
|
4931
|
+
* Emit "hasStagedChangesChanged" if the current staged-changes state differs from what was last emitted.
|
|
4932
|
+
* This must be called explicitly at each place staged changes can be added or removed (submit, flush,
|
|
4933
|
+
* discardChanges, commitChanges) -- unlike {@link ContainerRuntime.updateDocumentDirtyState}, it is not
|
|
4934
|
+
* safe to call this unconditionally alongside dirty tracking, since most dirty-state transitions (e.g.
|
|
4935
|
+
* acking ops, reconnecting) can't affect staged changes.
|
|
4936
|
+
*/
|
|
4937
|
+
private updateHasStagedChangesState(): void {
|
|
4938
|
+
const hasStagedChanges: boolean = this.computeCurrentHasStagedChanges();
|
|
4939
|
+
|
|
4940
|
+
if (this.lastEmittedHasStagedChanges === hasStagedChanges) {
|
|
4865
4941
|
return;
|
|
4866
4942
|
}
|
|
4867
4943
|
|
|
4868
|
-
this.
|
|
4944
|
+
this.lastEmittedHasStagedChanges = hasStagedChanges;
|
|
4869
4945
|
if (this.emitDirtyDocumentEvent) {
|
|
4870
|
-
this.emit(
|
|
4946
|
+
this.emit("hasStagedChangesChanged", hasStagedChanges);
|
|
4871
4947
|
}
|
|
4872
4948
|
}
|
|
4873
4949
|
|
|
@@ -5046,6 +5122,7 @@ export class ContainerRuntime
|
|
|
5046
5122
|
}
|
|
5047
5123
|
|
|
5048
5124
|
this.updateDocumentDirtyState();
|
|
5125
|
+
this.updateHasStagedChangesState();
|
|
5049
5126
|
}
|
|
5050
5127
|
|
|
5051
5128
|
private scheduleFlush(): void {
|
package/src/packageVersion.ts
CHANGED
|
@@ -333,6 +333,22 @@ export class PendingStateManager implements IDisposable {
|
|
|
333
333
|
return this.initialMessages.length > 0;
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Checks the pending messages to see if any of them are staged (submitted while in Staging Mode).
|
|
338
|
+
* Unlike {@link PendingStateManager.hasPendingUserChanges}, this does not filter by "dirtyable" messages:
|
|
339
|
+
* anything submitted while in Staging Mode counts as a staged change that must be committed or discarded,
|
|
340
|
+
* regardless of whether it would otherwise count towards dirty tracking.
|
|
341
|
+
*/
|
|
342
|
+
public hasStagedChanges(): boolean {
|
|
343
|
+
for (let i = 0; i < this.pendingMessages.length; i++) {
|
|
344
|
+
const element = this.pendingMessages.get(i);
|
|
345
|
+
if (element?.batchInfo.staged === true) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
336
352
|
/**
|
|
337
353
|
* The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.
|
|
338
354
|
*
|