@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/lib/containerRuntime.js
CHANGED
|
@@ -747,6 +747,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
747
747
|
this.rollbackStagedChange(runtimeOp, localOpMetadata);
|
|
748
748
|
});
|
|
749
749
|
this.updateDocumentDirtyState();
|
|
750
|
+
this.updateHasStagedChangesState();
|
|
750
751
|
return batchInfos;
|
|
751
752
|
}, "discard"),
|
|
752
753
|
commitChanges: (options) => {
|
|
@@ -754,10 +755,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
754
755
|
exitStagingMode(() => {
|
|
755
756
|
// Replay all staged batches in typical FIFO order.
|
|
756
757
|
// We'll be out of staging mode so they'll be sent to the service finally.
|
|
757
|
-
|
|
758
|
+
const batchInfos = this.pendingStateManager.replayPendingStates({
|
|
758
759
|
committingStagedBatches: true,
|
|
759
760
|
squash,
|
|
760
761
|
});
|
|
762
|
+
this.updateHasStagedChangesState();
|
|
763
|
+
return batchInfos;
|
|
761
764
|
}, "commit");
|
|
762
765
|
},
|
|
763
766
|
};
|
|
@@ -1219,6 +1222,8 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1219
1222
|
// We haven't emitted dirty/saved yet, but this is the baseline so we know to emit when it changes
|
|
1220
1223
|
this.lastEmittedDirty = this.computeCurrentDirtyState();
|
|
1221
1224
|
context.updateDirtyContainerState(this.lastEmittedDirty);
|
|
1225
|
+
// We haven't emitted hasStagedChangesChanged yet, but this is the baseline so we know to emit when it changes
|
|
1226
|
+
this.lastEmittedHasStagedChanges = this.computeCurrentHasStagedChanges();
|
|
1222
1227
|
// Reference Sequence Number may have just changed, and it must be consistent across a batch,
|
|
1223
1228
|
// so we should flush now to clear the way for the next ops.
|
|
1224
1229
|
// NOTE: This will be redundant whenever CR.process was called for the op (since we flush there too) -
|
|
@@ -1340,8 +1345,18 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1340
1345
|
if (this.isSummarizerClient) {
|
|
1341
1346
|
// We want to dynamically import any thing inside summaryDelayLoadedModule module only when we are the summarizer client,
|
|
1342
1347
|
// so that all non summarizer clients don't have to load the code inside this module.
|
|
1348
|
+
// Import the delay-loaded module by its leaf path rather than through the `./summary/index.js` barrel,
|
|
1349
|
+
// which is also statically imported above for summarization dependencies every client loads
|
|
1350
|
+
// (SummaryManager, SummaryCollection, SummarizerClientElection, etc.). A bundler that traces re-exports
|
|
1351
|
+
// per symbol (e.g. webpack honoring sideEffects + providedExports) already keeps the summarizer out of
|
|
1352
|
+
// the initial chunk even if this dynamic import targets the barrel, because the barrel's static importers
|
|
1353
|
+
// use only non-summarizer symbols. A bundler without that analysis would instead treat the
|
|
1354
|
+
// statically-imported barrel as making the whole summarizer subgraph available and fold it into the
|
|
1355
|
+
// initial chunk. Targeting summaryDelayLoadedModule/index.js directly makes the split deterministic
|
|
1356
|
+
// across bundlers.
|
|
1343
1357
|
const module = await import(
|
|
1344
|
-
|
|
1358
|
+
// eslint-disable-next-line import-x/no-internal-modules -- Needed to import the delay-loaded module directly.
|
|
1359
|
+
/* webpackChunkName: "summarizerDelayLoadedModule" */ "./summary/summaryDelayLoadedModule/index.js");
|
|
1345
1360
|
this._summarizer = new module.Summarizer(this /* ISummarizerRuntime */, () => this.summaryConfiguration, this /* ISummarizerInternalsProvider */, this.handleContext, summaryCollection, async (runtime) => module.RunWhileConnectedCoordinator.create(runtime,
|
|
1346
1361
|
// Summarization runs in summarizer client and needs access to the real (non-proxy) active
|
|
1347
1362
|
// information. The proxy delta manager would always return false for summarizer client.
|
|
@@ -1992,11 +2007,13 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
1992
2007
|
const result = this.duplicateBatchDetector?.processInboundBatch(batchStart);
|
|
1993
2008
|
if (result?.duplicate === true) {
|
|
1994
2009
|
const error = new DataCorruptionError("Duplicate batch - The same batch was sequenced twice", { batchId: batchStart.batchId });
|
|
2010
|
+
const batchIdExplicit = batchStart.batchId !== undefined;
|
|
2011
|
+
const otherBatchIdExplicit = result.otherBatchInfo?.batchIdExplicit ?? false;
|
|
1995
2012
|
this.mc.logger.sendTelemetryEvent({
|
|
1996
2013
|
eventName: "DuplicateBatch",
|
|
1997
2014
|
details: {
|
|
1998
2015
|
batchId: batchStart.batchId,
|
|
1999
|
-
batchIdExplicit
|
|
2016
|
+
batchIdExplicit,
|
|
2000
2017
|
clientId: batchStart.clientId,
|
|
2001
2018
|
batchStartCsn: batchStart.batchStartCsn,
|
|
2002
2019
|
size: inboundResult.length,
|
|
@@ -2007,7 +2024,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2007
2024
|
// loaded from a summary snapshot rather than seen at runtime.
|
|
2008
2025
|
otherClientId: result.otherBatchInfo?.clientId,
|
|
2009
2026
|
otherBatchStartCsn: result.otherBatchInfo?.batchStartCsn,
|
|
2010
|
-
otherBatchIdExplicit
|
|
2027
|
+
otherBatchIdExplicit,
|
|
2011
2028
|
otherFromSnapshot: result.otherBatchInfo === undefined,
|
|
2012
2029
|
...extractSafePropertiesFromMessage(batchStart.keyMessage),
|
|
2013
2030
|
// For grouped batches, `keyMessage` is one of the sub-messages produced by
|
|
@@ -2018,10 +2035,12 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2018
2035
|
messageClientSequenceNumber: batchStart.batchStartCsn,
|
|
2019
2036
|
},
|
|
2020
2037
|
}, error);
|
|
2021
|
-
//
|
|
2022
|
-
//
|
|
2023
|
-
|
|
2024
|
-
|
|
2038
|
+
// Only throw the error if either the current batch or the other batch has an explicit batchId since that indicates a meaningful duplication scenario
|
|
2039
|
+
// coming from our batch readings rather than a server outage scenario.
|
|
2040
|
+
const shouldThrowOnDuplicate = batchIdExplicit || otherBatchIdExplicit;
|
|
2041
|
+
if (shouldThrowOnDuplicate) {
|
|
2042
|
+
throw error;
|
|
2043
|
+
}
|
|
2025
2044
|
}
|
|
2026
2045
|
}
|
|
2027
2046
|
// Reach out to PendingStateManager, either to zip localOpMetadata into the *local* message list,
|
|
@@ -2340,6 +2359,11 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2340
2359
|
this.closeFn(error2);
|
|
2341
2360
|
throw error2;
|
|
2342
2361
|
}
|
|
2362
|
+
// Flushing moves any staged batch from the Outbox into the PendingStateManager. Since
|
|
2363
|
+
// computeCurrentHasStagedChanges() now also considers a non-empty Outbox in Staging Mode, the
|
|
2364
|
+
// externally-visible value shouldn't change here, but re-checking is cheap and keeps this
|
|
2365
|
+
// method robust to future changes in how the two are tracked.
|
|
2366
|
+
this.updateHasStagedChangesState();
|
|
2343
2367
|
}
|
|
2344
2368
|
/**
|
|
2345
2369
|
* {@inheritDoc @fluidframework/runtime-definitions#IContainerRuntimeBase.orderSequentially}
|
|
@@ -2482,6 +2506,34 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
2482
2506
|
this.pendingStateManager.hasPendingUserChanges() ||
|
|
2483
2507
|
this.outbox.containsUserChanges());
|
|
2484
2508
|
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Returns true if there are any staged changes, i.e. changes submitted while in Staging Mode
|
|
2511
|
+
* (see {@link @fluidframework/runtime-definitions#IContainerRuntimeBaseInternal.enterStagingMode})
|
|
2512
|
+
* that have not yet been discarded or committed.
|
|
2513
|
+
*
|
|
2514
|
+
* @remarks This is distinct from {@link ContainerRuntime.isDirty}: a container may be dirty due to
|
|
2515
|
+
* ordinary unacknowledged local changes without having any staged changes.
|
|
2516
|
+
*/
|
|
2517
|
+
get hasStagedChanges() {
|
|
2518
|
+
// Rather than recomputing this in the moment, just regurgitate the last emitted state.
|
|
2519
|
+
return this.lastEmittedHasStagedChanges;
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Returns true if there are currently any staged (not yet discarded or committed) changes pending.
|
|
2523
|
+
*
|
|
2524
|
+
* @remarks While in Staging Mode, newly submitted ops sit in the Outbox until the next flush before
|
|
2525
|
+
* they're moved to the PendingStateManager (where `pendingStateManager.hasStagedChanges()` looks).
|
|
2526
|
+
* So we also check the Outbox here, otherwise there would be a window between submit and flush where
|
|
2527
|
+
* staged changes exist but this would incorrectly report false.
|
|
2528
|
+
*
|
|
2529
|
+
* @remarks We don't care about the type of ops in the Outbox here (unlike dirty state), just whether
|
|
2530
|
+
* there's anything queued at all, for consistency with how `pendingStateManager.hasStagedChanges()`
|
|
2531
|
+
* doesn't discriminate by op type either.
|
|
2532
|
+
*/
|
|
2533
|
+
computeCurrentHasStagedChanges() {
|
|
2534
|
+
return (this.pendingStateManager.hasStagedChanges() ||
|
|
2535
|
+
(this.inStagingMode && !this.outbox.isEmpty));
|
|
2536
|
+
}
|
|
2485
2537
|
/**
|
|
2486
2538
|
* Submits the signal to be sent to other clients.
|
|
2487
2539
|
* @param type - Type of the signal.
|
|
@@ -3086,12 +3138,28 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3086
3138
|
*/
|
|
3087
3139
|
updateDocumentDirtyState() {
|
|
3088
3140
|
const dirty = this.computeCurrentDirtyState();
|
|
3089
|
-
if (this.lastEmittedDirty
|
|
3141
|
+
if (this.lastEmittedDirty !== dirty) {
|
|
3142
|
+
this.lastEmittedDirty = dirty;
|
|
3143
|
+
if (this.emitDirtyDocumentEvent) {
|
|
3144
|
+
this.emit(dirty ? "dirty" : "saved");
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Emit "hasStagedChangesChanged" if the current staged-changes state differs from what was last emitted.
|
|
3150
|
+
* This must be called explicitly at each place staged changes can be added or removed (submit, flush,
|
|
3151
|
+
* discardChanges, commitChanges) -- unlike {@link ContainerRuntime.updateDocumentDirtyState}, it is not
|
|
3152
|
+
* safe to call this unconditionally alongside dirty tracking, since most dirty-state transitions (e.g.
|
|
3153
|
+
* acking ops, reconnecting) can't affect staged changes.
|
|
3154
|
+
*/
|
|
3155
|
+
updateHasStagedChangesState() {
|
|
3156
|
+
const hasStagedChanges = this.computeCurrentHasStagedChanges();
|
|
3157
|
+
if (this.lastEmittedHasStagedChanges === hasStagedChanges) {
|
|
3090
3158
|
return;
|
|
3091
3159
|
}
|
|
3092
|
-
this.
|
|
3160
|
+
this.lastEmittedHasStagedChanges = hasStagedChanges;
|
|
3093
3161
|
if (this.emitDirtyDocumentEvent) {
|
|
3094
|
-
this.emit(
|
|
3162
|
+
this.emit("hasStagedChangesChanged", hasStagedChanges);
|
|
3095
3163
|
}
|
|
3096
3164
|
}
|
|
3097
3165
|
// Keep in sync with IFluidRootParentContextPrivate.submitMessage.
|
|
@@ -3226,6 +3294,7 @@ export class ContainerRuntime extends TypedEventEmitter {
|
|
|
3226
3294
|
throw dpe;
|
|
3227
3295
|
}
|
|
3228
3296
|
this.updateDocumentDirtyState();
|
|
3297
|
+
this.updateHasStagedChangesState();
|
|
3229
3298
|
}
|
|
3230
3299
|
scheduleFlush() {
|
|
3231
3300
|
// During staging mode, suppress automatic flush scheduling until the main batch
|