@fluidframework/container-runtime 2.118.1 → 3.0.1
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 +68 -4
- package/README.md +53 -50
- package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -1
- package/api-report/container-runtime.legacy.alpha.api.md +3 -1
- package/api-report/container-runtime.legacy.beta.api.md +3 -1
- package/container-runtime.test-files.tar +0 -0
- package/dist/batchTracker.d.ts.map +1 -1
- package/dist/batchTracker.js +5 -1
- package/dist/batchTracker.js.map +1 -1
- package/dist/blobManager/blobManager.d.ts +1 -1
- package/dist/blobManager/blobManager.d.ts.map +1 -1
- package/dist/blobManager/blobManager.js +264 -231
- package/dist/blobManager/blobManager.js.map +1 -1
- package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
- package/dist/channelCollection.d.ts.map +1 -1
- package/dist/channelCollection.js +70 -58
- package/dist/channelCollection.js.map +1 -1
- package/dist/connectionTelemetry.js +38 -20
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/containerCompatibility.d.ts +3 -3
- package/dist/containerCompatibility.d.ts.map +1 -1
- package/dist/containerCompatibility.js +36 -45
- package/dist/containerCompatibility.js.map +1 -1
- package/dist/containerHandleContext.d.ts.map +1 -1
- package/dist/containerHandleContext.js +4 -0
- package/dist/containerHandleContext.js.map +1 -1
- package/dist/containerRuntime.d.ts +4 -4
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +346 -223
- package/dist/containerRuntime.js.map +1 -1
- package/dist/dataStore.d.ts.map +1 -1
- package/dist/dataStore.js +9 -1
- package/dist/dataStore.js.map +1 -1
- package/dist/dataStoreContext.d.ts.map +1 -1
- package/dist/dataStoreContext.js +184 -127
- package/dist/dataStoreContext.js.map +1 -1
- package/dist/dataStoreContexts.js +43 -42
- package/dist/dataStoreContexts.js.map +1 -1
- package/dist/dataStoreRegistry.js +1 -0
- package/dist/dataStoreRegistry.js.map +1 -1
- package/dist/deltaManagerProxies.js +5 -0
- package/dist/deltaManagerProxies.js.map +1 -1
- package/dist/deltaScheduler.js +104 -99
- package/dist/deltaScheduler.js.map +1 -1
- package/dist/error.js +2 -1
- package/dist/error.js.map +1 -1
- package/dist/gc/garbageCollection.js +85 -55
- package/dist/gc/garbageCollection.js.map +1 -1
- package/dist/gc/gcConfigs.js +2 -3
- package/dist/gc/gcConfigs.js.map +1 -1
- package/dist/gc/gcHelpers.js +12 -13
- package/dist/gc/gcHelpers.js.map +1 -1
- package/dist/gc/gcReferenceGraphAlgorithm.js +1 -2
- package/dist/gc/gcReferenceGraphAlgorithm.js.map +1 -1
- package/dist/gc/gcSummaryStateTracker.js +8 -3
- package/dist/gc/gcSummaryStateTracker.js.map +1 -1
- package/dist/gc/gcTelemetry.js +12 -5
- package/dist/gc/gcTelemetry.js.map +1 -1
- package/dist/gc/gcUnreferencedStateTracker.js +18 -1
- package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
- package/dist/inboundBatchAggregator.js +122 -117
- package/dist/inboundBatchAggregator.js.map +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +3 -3
- package/dist/metadata.js.map +1 -1
- package/dist/opLifecycle/batchManager.d.ts.map +1 -1
- package/dist/opLifecycle/batchManager.js +11 -5
- package/dist/opLifecycle/batchManager.js.map +1 -1
- package/dist/opLifecycle/duplicateBatchDetector.js +18 -18
- package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -1
- package/dist/opLifecycle/index.d.ts +2 -2
- package/dist/opLifecycle/index.d.ts.map +1 -1
- package/dist/opLifecycle/index.js +2 -1
- package/dist/opLifecycle/index.js.map +1 -1
- package/dist/opLifecycle/opCompressor.js +2 -1
- package/dist/opLifecycle/opCompressor.js.map +1 -1
- package/dist/opLifecycle/opDecompressor.js +10 -6
- package/dist/opLifecycle/opDecompressor.js.map +1 -1
- package/dist/opLifecycle/opGroupingManager.js +5 -3
- package/dist/opLifecycle/opGroupingManager.js.map +1 -1
- package/dist/opLifecycle/opSerialization.d.ts +11 -1
- package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
- package/dist/opLifecycle/opSerialization.js +22 -6
- package/dist/opLifecycle/opSerialization.js.map +1 -1
- package/dist/opLifecycle/opSplitter.d.ts +17 -1
- package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
- package/dist/opLifecycle/opSplitter.js +46 -9
- package/dist/opLifecycle/opSplitter.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +17 -13
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/opLifecycle/remoteMessageProcessor.js +6 -3
- package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/dist/opProperties.d.ts.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.js +54 -47
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/runCounter.js +10 -30
- package/dist/runCounter.js.map +1 -1
- package/dist/runtimeLayerCompatState.d.ts +1 -1
- package/dist/runtimeLayerCompatState.d.ts.map +1 -1
- package/dist/runtimeLayerCompatState.js +31 -11
- package/dist/runtimeLayerCompatState.js.map +1 -1
- package/dist/signalTelemetryProcessing.js +15 -17
- package/dist/signalTelemetryProcessing.js.map +1 -1
- package/dist/storageServiceWithAttachBlobs.js +2 -0
- package/dist/storageServiceWithAttachBlobs.js.map +1 -1
- package/dist/summary/documentSchema.d.ts.map +1 -1
- package/dist/summary/documentSchema.js +51 -13
- package/dist/summary/documentSchema.js.map +1 -1
- package/dist/summary/orderedClientElection.js +25 -17
- package/dist/summary/orderedClientElection.js.map +1 -1
- package/dist/summary/summarizerClientElection.d.ts.map +1 -1
- package/dist/summary/summarizerClientElection.js +18 -7
- package/dist/summary/summarizerClientElection.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNode.js +22 -13
- package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
- package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/dist/summary/summarizerUtils.d.ts.map +1 -1
- package/dist/summary/summarizerUtils.js +4 -3
- package/dist/summary/summarizerUtils.js.map +1 -1
- package/dist/summary/summaryCollection.js +23 -11
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
- package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
- package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizer.js +15 -6
- package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
- package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
- package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
- package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
- package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
- package/dist/summary/summaryFormat.d.ts.map +1 -1
- package/dist/summary/summaryFormat.js +7 -7
- package/dist/summary/summaryFormat.js.map +1 -1
- package/dist/summary/summaryHelpers.js +5 -5
- package/dist/summary/summaryHelpers.js.map +1 -1
- package/dist/summary/summaryManager.js +66 -54
- package/dist/summary/summaryManager.js.map +1 -1
- package/dist/throttler.d.ts.map +1 -1
- package/dist/throttler.js +4 -1
- package/dist/throttler.js.map +1 -1
- package/dist/versionMarks/inboundBatch.d.ts +8 -6
- package/dist/versionMarks/inboundBatch.d.ts.map +1 -1
- package/dist/versionMarks/inboundBatch.js +11 -7
- package/dist/versionMarks/inboundBatch.js.map +1 -1
- package/dist/versionMarks/versionMarkResolver.d.ts +30 -23
- package/dist/versionMarks/versionMarkResolver.d.ts.map +1 -1
- package/dist/versionMarks/versionMarkResolver.js +112 -63
- package/dist/versionMarks/versionMarkResolver.js.map +1 -1
- package/lib/batchTracker.d.ts.map +1 -1
- package/lib/batchTracker.js +5 -1
- package/lib/batchTracker.js.map +1 -1
- package/lib/blobManager/blobManager.d.ts +1 -1
- package/lib/blobManager/blobManager.d.ts.map +1 -1
- package/lib/blobManager/blobManager.js +264 -231
- package/lib/blobManager/blobManager.js.map +1 -1
- package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
- package/lib/channelCollection.d.ts.map +1 -1
- package/lib/channelCollection.js +65 -53
- package/lib/channelCollection.js.map +1 -1
- package/lib/connectionTelemetry.js +36 -18
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerCompatibility.d.ts +3 -3
- package/lib/containerCompatibility.d.ts.map +1 -1
- package/lib/containerCompatibility.js +33 -42
- package/lib/containerCompatibility.js.map +1 -1
- package/lib/containerHandleContext.d.ts.map +1 -1
- package/lib/containerHandleContext.js +4 -0
- package/lib/containerHandleContext.js.map +1 -1
- package/lib/containerRuntime.d.ts +4 -4
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +341 -218
- package/lib/containerRuntime.js.map +1 -1
- package/lib/dataStore.d.ts.map +1 -1
- package/lib/dataStore.js +9 -1
- package/lib/dataStore.js.map +1 -1
- package/lib/dataStoreContext.d.ts.map +1 -1
- package/lib/dataStoreContext.js +182 -125
- package/lib/dataStoreContext.js.map +1 -1
- package/lib/dataStoreContexts.js +43 -42
- package/lib/dataStoreContexts.js.map +1 -1
- package/lib/dataStoreRegistry.js +1 -0
- package/lib/dataStoreRegistry.js.map +1 -1
- package/lib/deltaManagerProxies.js +5 -0
- package/lib/deltaManagerProxies.js.map +1 -1
- package/lib/deltaScheduler.js +104 -99
- package/lib/deltaScheduler.js.map +1 -1
- package/lib/error.js +2 -1
- package/lib/error.js.map +1 -1
- package/lib/gc/garbageCollection.js +85 -55
- package/lib/gc/garbageCollection.js.map +1 -1
- package/lib/gc/gcSummaryStateTracker.js +8 -3
- package/lib/gc/gcSummaryStateTracker.js.map +1 -1
- package/lib/gc/gcTelemetry.js +12 -5
- package/lib/gc/gcTelemetry.js.map +1 -1
- package/lib/gc/gcUnreferencedStateTracker.js +18 -1
- package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
- package/lib/inboundBatchAggregator.js +122 -117
- package/lib/inboundBatchAggregator.js.map +1 -1
- package/lib/legacy.d.ts +1 -1
- package/lib/legacyAlpha.d.ts +1 -1
- package/lib/metadata.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.d.ts.map +1 -1
- package/lib/opLifecycle/batchManager.js +8 -2
- package/lib/opLifecycle/batchManager.js.map +1 -1
- package/lib/opLifecycle/duplicateBatchDetector.js +18 -18
- package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -1
- package/lib/opLifecycle/index.d.ts +2 -2
- package/lib/opLifecycle/index.d.ts.map +1 -1
- package/lib/opLifecycle/index.js +2 -2
- package/lib/opLifecycle/index.js.map +1 -1
- package/lib/opLifecycle/opCompressor.js +3 -2
- package/lib/opLifecycle/opCompressor.js.map +1 -1
- package/lib/opLifecycle/opDecompressor.js +10 -6
- package/lib/opLifecycle/opDecompressor.js.map +1 -1
- package/lib/opLifecycle/opGroupingManager.js +3 -1
- package/lib/opLifecycle/opGroupingManager.js.map +1 -1
- package/lib/opLifecycle/opSerialization.d.ts +11 -1
- package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
- package/lib/opLifecycle/opSerialization.js +16 -0
- package/lib/opLifecycle/opSerialization.js.map +1 -1
- package/lib/opLifecycle/opSplitter.d.ts +17 -1
- package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
- package/lib/opLifecycle/opSplitter.js +44 -7
- package/lib/opLifecycle/opSplitter.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +14 -10
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/opLifecycle/remoteMessageProcessor.js +4 -1
- package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
- package/lib/opProperties.d.ts.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.js +52 -45
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/public.d.ts +1 -1
- package/lib/runCounter.js +10 -30
- package/lib/runCounter.js.map +1 -1
- package/lib/runtimeLayerCompatState.d.ts +1 -1
- package/lib/runtimeLayerCompatState.d.ts.map +1 -1
- package/lib/runtimeLayerCompatState.js +28 -8
- package/lib/runtimeLayerCompatState.js.map +1 -1
- package/lib/signalTelemetryProcessing.js +15 -17
- package/lib/signalTelemetryProcessing.js.map +1 -1
- package/lib/storageServiceWithAttachBlobs.js +2 -0
- package/lib/storageServiceWithAttachBlobs.js.map +1 -1
- package/lib/summary/documentSchema.d.ts.map +1 -1
- package/lib/summary/documentSchema.js +51 -13
- package/lib/summary/documentSchema.js.map +1 -1
- package/lib/summary/orderedClientElection.js +25 -17
- package/lib/summary/orderedClientElection.js.map +1 -1
- package/lib/summary/summarizerClientElection.d.ts.map +1 -1
- package/lib/summary/summarizerClientElection.js +18 -7
- package/lib/summary/summarizerClientElection.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNode.js +22 -13
- package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
- package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
- package/lib/summary/summarizerUtils.d.ts.map +1 -1
- package/lib/summary/summarizerUtils.js +1 -0
- package/lib/summary/summarizerUtils.js.map +1 -1
- package/lib/summary/summaryCollection.js +23 -11
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
- package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
- package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizer.js +15 -6
- package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
- package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
- package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
- package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
- package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
- package/lib/summary/summaryFormat.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +66 -54
- package/lib/summary/summaryManager.js.map +1 -1
- package/lib/throttler.d.ts.map +1 -1
- package/lib/throttler.js +4 -1
- package/lib/throttler.js.map +1 -1
- package/lib/versionMarks/inboundBatch.d.ts +8 -6
- package/lib/versionMarks/inboundBatch.d.ts.map +1 -1
- package/lib/versionMarks/inboundBatch.js +10 -5
- package/lib/versionMarks/inboundBatch.js.map +1 -1
- package/lib/versionMarks/versionMarkResolver.d.ts +30 -23
- package/lib/versionMarks/versionMarkResolver.d.ts.map +1 -1
- package/lib/versionMarks/versionMarkResolver.js +112 -63
- package/lib/versionMarks/versionMarkResolver.js.map +1 -1
- package/package.json +60 -102
- package/src/containerCompatibility.ts +33 -42
- package/src/containerRuntime.ts +15 -12
- package/src/opLifecycle/index.ts +7 -1
- package/src/opLifecycle/opCompressor.ts +2 -2
- package/src/opLifecycle/opSerialization.ts +22 -1
- package/src/opLifecycle/opSplitter.ts +63 -6
- package/src/packageVersion.ts +1 -1
- package/src/runtimeLayerCompatState.ts +28 -21
- package/src/summary/documentSchema.ts +31 -6
- package/src/versionMarks/DEV.md +595 -157
- package/src/versionMarks/inboundBatch.ts +18 -8
- package/src/versionMarks/versionMarkResolver.ts +144 -75
- package/tsconfig.json +1 -1
- package/internal.d.ts +0 -11
- package/legacy/alpha.d.ts +0 -11
- package/legacy.d.ts +0 -11
- /package/api-extractor/{api-extractor.current.json → api-extractor-report.current.json} +0 -0
- /package/api-extractor/{api-extractor.legacy.json → api-extractor-report.legacy.json} +0 -0
|
@@ -21,18 +21,34 @@ const blobManagerSnapSum_js_1 = require("./blobManagerSnapSum.js");
|
|
|
21
21
|
* and loads blob.
|
|
22
22
|
*/
|
|
23
23
|
class BlobHandle extends internal_3.FluidHandleBase {
|
|
24
|
+
path;
|
|
25
|
+
routeContext;
|
|
26
|
+
get;
|
|
27
|
+
payloadPending;
|
|
28
|
+
sharePayloadCallback;
|
|
29
|
+
attachGraphCallback;
|
|
30
|
+
attached = false;
|
|
31
|
+
payloadSharingStarted = false;
|
|
24
32
|
get isAttached() {
|
|
25
33
|
return this.routeContext.isAttached && this.attached;
|
|
26
34
|
}
|
|
35
|
+
_events;
|
|
27
36
|
get events() {
|
|
28
37
|
return (this._events ??= (0, client_utils_1.createEmitter)());
|
|
29
38
|
}
|
|
39
|
+
_payloadState;
|
|
30
40
|
get payloadState() {
|
|
31
41
|
return this._payloadState;
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* The error property starts undefined, signalling that there has been no error yet.
|
|
45
|
+
* If an error occurs, the property will contain the error.
|
|
46
|
+
*/
|
|
47
|
+
_payloadShareError;
|
|
33
48
|
get payloadShareError() {
|
|
34
49
|
return this._payloadShareError;
|
|
35
50
|
}
|
|
51
|
+
absolutePath;
|
|
36
52
|
constructor(path, routeContext,
|
|
37
53
|
// TODO: just take the blob rather than a get function?
|
|
38
54
|
get, payloadPending, sharePayloadCallback, attachGraphCallback) {
|
|
@@ -43,19 +59,17 @@ class BlobHandle extends internal_3.FluidHandleBase {
|
|
|
43
59
|
this.payloadPending = payloadPending;
|
|
44
60
|
this.sharePayloadCallback = sharePayloadCallback;
|
|
45
61
|
this.attachGraphCallback = attachGraphCallback;
|
|
46
|
-
this.attached = false;
|
|
47
|
-
this.payloadSharingStarted = false;
|
|
48
|
-
this.notifyShared = () => {
|
|
49
|
-
this._payloadState = "shared";
|
|
50
|
-
this._events?.emit("payloadShared");
|
|
51
|
-
};
|
|
52
|
-
this.notifyFailed = (error) => {
|
|
53
|
-
this._payloadShareError = error;
|
|
54
|
-
this._events?.emit("payloadShareFailed", error);
|
|
55
|
-
};
|
|
56
62
|
this._payloadState = payloadPending ? "pending" : "shared";
|
|
57
63
|
this.absolutePath = (0, internal_3.generateHandleContextPath)(path, this.routeContext);
|
|
58
64
|
}
|
|
65
|
+
notifyShared = () => {
|
|
66
|
+
this._payloadState = "shared";
|
|
67
|
+
this._events?.emit("payloadShared");
|
|
68
|
+
};
|
|
69
|
+
notifyFailed = (error) => {
|
|
70
|
+
this._payloadShareError = error;
|
|
71
|
+
this._events?.emit("payloadShareFailed", error);
|
|
72
|
+
};
|
|
59
73
|
sharePayload() {
|
|
60
74
|
if (this.payloadState === "shared" || this.payloadSharingStarted) {
|
|
61
75
|
return;
|
|
@@ -84,228 +98,41 @@ const createAbortError = () => new internal_4.LoggingError("uploadBlob aborted")
|
|
|
84
98
|
*/
|
|
85
99
|
exports.blobManagerBasePath = "_blobs";
|
|
86
100
|
class BlobManager {
|
|
101
|
+
mc;
|
|
102
|
+
internalEvents = (0, client_utils_1.createEmitter)();
|
|
103
|
+
/**
|
|
104
|
+
* Map of local IDs to storage IDs. Also includes identity mappings of storage ID to storage ID for all known
|
|
105
|
+
* storage IDs. All requested IDs must be a key in this map. Blobs created while the container is detached are
|
|
106
|
+
* stored in IDetachedBlobStorage which gives pseudo storage IDs; the real storage IDs are filled in at attach
|
|
107
|
+
* time via setRedirectTable().
|
|
108
|
+
*/
|
|
109
|
+
redirectTable;
|
|
110
|
+
/**
|
|
111
|
+
* The localBlobCache has a dual role of caching locally-created blobs, as well as tracking their state as they
|
|
112
|
+
* are shared. Keys are localIds.
|
|
113
|
+
*/
|
|
114
|
+
localBlobCache = new Map();
|
|
115
|
+
/**
|
|
116
|
+
* Blobs with an attached handle that have not finished blob-attaching are the set we need to provide from
|
|
117
|
+
* getPendingState(). This stores their local IDs, and then we can look them up against the localBlobCache.
|
|
118
|
+
*/
|
|
119
|
+
pendingBlobsWithAttachedHandles = new Set();
|
|
120
|
+
/**
|
|
121
|
+
* Local IDs for any pending blobs we loaded with and have not yet started the upload/attach flow for.
|
|
122
|
+
*/
|
|
123
|
+
pendingOnlyLocalIds = new Set();
|
|
124
|
+
sendBlobAttachMessage;
|
|
125
|
+
routeContext;
|
|
126
|
+
storage;
|
|
127
|
+
// Called when a blob node is requested. blobPath is the path of the blob's node in GC's graph.
|
|
128
|
+
// blobPath's format - `/<basePath>/<localId>`.
|
|
129
|
+
blobRequested;
|
|
130
|
+
// Called to check if a blob has been deleted by GC.
|
|
131
|
+
// blobPath's format - `/<basePath>/<localId>`.
|
|
132
|
+
isBlobDeleted;
|
|
133
|
+
runtime;
|
|
134
|
+
createBlobPayloadPending;
|
|
87
135
|
constructor(props) {
|
|
88
|
-
this.internalEvents = (0, client_utils_1.createEmitter)();
|
|
89
|
-
/**
|
|
90
|
-
* The localBlobCache has a dual role of caching locally-created blobs, as well as tracking their state as they
|
|
91
|
-
* are shared. Keys are localIds.
|
|
92
|
-
*/
|
|
93
|
-
this.localBlobCache = new Map();
|
|
94
|
-
/**
|
|
95
|
-
* Blobs with an attached handle that have not finished blob-attaching are the set we need to provide from
|
|
96
|
-
* getPendingState(). This stores their local IDs, and then we can look them up against the localBlobCache.
|
|
97
|
-
*/
|
|
98
|
-
this.pendingBlobsWithAttachedHandles = new Set();
|
|
99
|
-
/**
|
|
100
|
-
* Local IDs for any pending blobs we loaded with and have not yet started the upload/attach flow for.
|
|
101
|
-
*/
|
|
102
|
-
this.pendingOnlyLocalIds = new Set();
|
|
103
|
-
/**
|
|
104
|
-
* Upload and attach the localBlobCache entry for the given localId.
|
|
105
|
-
*
|
|
106
|
-
* Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
|
|
107
|
-
* when called. Returns a promise that resolves when the blob completes uploading and attaching, or else
|
|
108
|
-
* rejects if an error is encountered or the signal is aborted.
|
|
109
|
-
*/
|
|
110
|
-
this.uploadAndAttach = async (localId, signal) => {
|
|
111
|
-
if (signal?.aborted === true) {
|
|
112
|
-
this.localBlobCache.delete(localId);
|
|
113
|
-
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
114
|
-
throw createAbortError();
|
|
115
|
-
}
|
|
116
|
-
const localBlobRecordInitial = this.localBlobCache.get(localId);
|
|
117
|
-
(0, internal_2.assert)(localBlobRecordInitial?.state === "localOnly" ||
|
|
118
|
-
localBlobRecordInitial?.state === "uploaded", 0xc80 /* Expect uploadAndAttach to be called with either localOnly or uploaded state */);
|
|
119
|
-
const { blob } = localBlobRecordInitial;
|
|
120
|
-
/**
|
|
121
|
-
* Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
|
|
122
|
-
* when called. Returns a promise that resolves when the blob is in uploaded or attached state, or else
|
|
123
|
-
* rejects on error during upload or if the signal is aborted.
|
|
124
|
-
*
|
|
125
|
-
* Most of the time this should be expected to exit in uploaded state, but if we are loading from pending
|
|
126
|
-
* state we may see an attach message from the client that generated the pending state, which can complete the
|
|
127
|
-
* attach while the upload is outstanding.
|
|
128
|
-
*/
|
|
129
|
-
const ensureUploaded = async () => {
|
|
130
|
-
const localBlobRecord = this.localBlobCache.get(localId);
|
|
131
|
-
if (localBlobRecord?.state === "uploaded") {
|
|
132
|
-
// In normal creation flows, the blob will be in localOnly state here. But in the case of loading
|
|
133
|
-
// with pending state we can call it with an uploaded-but-not-attached blob. Start the upload
|
|
134
|
-
// flow only if it's localOnly.
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
(0, internal_2.assert)(localBlobRecord?.state === "localOnly", 0xc81 /* Attempting to upload from unexpected state */);
|
|
138
|
-
this.localBlobCache.set(localId, { state: "uploading", blob });
|
|
139
|
-
await new Promise((resolve, reject) => {
|
|
140
|
-
// If we eventually have driver-level support for abort, then this can simplify a bit as we won't
|
|
141
|
-
// need to track upload completion and abort separately. Until then, we need to handle the case that
|
|
142
|
-
// the upload continues and settles after becoming irrelevant due to signal abort or blob attach.
|
|
143
|
-
let uploadHasBecomeIrrelevant = false;
|
|
144
|
-
const onSignalAbort = () => {
|
|
145
|
-
removeListeners();
|
|
146
|
-
uploadHasBecomeIrrelevant = true;
|
|
147
|
-
this.localBlobCache.delete(localId);
|
|
148
|
-
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
149
|
-
reject(createAbortError());
|
|
150
|
-
};
|
|
151
|
-
const onProcessedBlobAttach = (_localId, _storageId) => {
|
|
152
|
-
if (_localId === localId) {
|
|
153
|
-
removeListeners();
|
|
154
|
-
uploadHasBecomeIrrelevant = true;
|
|
155
|
-
resolve();
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
const removeListeners = () => {
|
|
159
|
-
this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
|
|
160
|
-
signal?.removeEventListener("abort", onSignalAbort);
|
|
161
|
-
};
|
|
162
|
-
this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
|
|
163
|
-
signal?.addEventListener("abort", onSignalAbort);
|
|
164
|
-
this.storage
|
|
165
|
-
.createBlob(blob)
|
|
166
|
-
.then((createBlobResponse) => {
|
|
167
|
-
if (!uploadHasBecomeIrrelevant) {
|
|
168
|
-
removeListeners();
|
|
169
|
-
this.localBlobCache.set(localId, {
|
|
170
|
-
state: "uploaded",
|
|
171
|
-
blob,
|
|
172
|
-
storageId: createBlobResponse.id,
|
|
173
|
-
uploadTime: Date.now(),
|
|
174
|
-
minTTLInSeconds: createBlobResponse.minTTLInSeconds,
|
|
175
|
-
});
|
|
176
|
-
resolve();
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
.catch((error) => {
|
|
180
|
-
if (!uploadHasBecomeIrrelevant) {
|
|
181
|
-
removeListeners();
|
|
182
|
-
// If the storage call errors, we can't recover. Reject to throw back to the caller.
|
|
183
|
-
this.localBlobCache.delete(localId);
|
|
184
|
-
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
186
|
-
reject(error);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Expects the localBlobCache entry for the given localId to be in uploaded or attached state when called.
|
|
193
|
-
* Returns a promise that resolves to true if the blob is successfully attached, or false if it cannot be
|
|
194
|
-
* attached and the upload flow needs to be restarted from the top (currently only if the TTL expires before
|
|
195
|
-
* attach can be completed). In the latter case, the localBlobRecord will also be reset to localOnly state.
|
|
196
|
-
* The promise rejects if the signal is aborted.
|
|
197
|
-
*/
|
|
198
|
-
const tryAttach = async () => {
|
|
199
|
-
const localBlobRecord = this.localBlobCache.get(localId);
|
|
200
|
-
if (localBlobRecord?.state === "attached") {
|
|
201
|
-
// In normal creation flows, the blob will be in uploaded state here. But if we are loading from pending
|
|
202
|
-
// state and see an attach message from the client that generated the pending state, we may have reached
|
|
203
|
-
// attached state in the middle of the upload attempt. In that case there's no more work to do and we
|
|
204
|
-
// can just return.
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
(0, internal_2.assert)(localBlobRecord?.state === "uploaded", 0xc82 /* Attempting to attach from unexpected state */);
|
|
208
|
-
// If we just uploaded the blob TTL really shouldn't be expired at this location. But if we loaded from
|
|
209
|
-
// pending state, the upload may have happened some time far in the past and could be expired here.
|
|
210
|
-
if (isTTLTooCloseToExpiry(localBlobRecord)) {
|
|
211
|
-
// If the TTL is expired, we assume it's gone from the storage and so is effectively localOnly again.
|
|
212
|
-
// Then when we re-enter the loop, we'll re-upload it.
|
|
213
|
-
this.localBlobCache.set(localId, { state: "localOnly", blob });
|
|
214
|
-
// Emitting here isn't really necessary since the only listener would be attached below. Including here
|
|
215
|
-
// for completeness though, in case we add other listeners in the future.
|
|
216
|
-
this.internalEvents.emit("blobExpired", localId);
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
this.localBlobCache.set(localId, {
|
|
221
|
-
...localBlobRecord,
|
|
222
|
-
state: "attaching",
|
|
223
|
-
});
|
|
224
|
-
// Send and await a blob attach op. This serves two purposes:
|
|
225
|
-
// 1. If its a new blob, i.e., it isn't de-duped, the server will keep the blob alive if it sees this op
|
|
226
|
-
// until its storage ID is added to the next summary.
|
|
227
|
-
// 2. It will create a local ID to storage ID mapping in all clients which is needed to retrieve the
|
|
228
|
-
// blob from the server via the storage ID.
|
|
229
|
-
return new Promise((resolve, reject) => {
|
|
230
|
-
const onProcessedBlobAttach = (_localId, _storageId) => {
|
|
231
|
-
if (_localId === localId) {
|
|
232
|
-
removeListeners();
|
|
233
|
-
resolve(true);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
// Although we already checked for TTL expiry above, the message we're about to send may later be asked
|
|
237
|
-
// to resubmit. Before we resubmit, we check again for TTL expiry - this listener is how we learn if
|
|
238
|
-
// we discovered expiry in the resubmit flow.
|
|
239
|
-
const onBlobExpired = (_localId) => {
|
|
240
|
-
if (_localId === localId) {
|
|
241
|
-
removeListeners();
|
|
242
|
-
resolve(false);
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
const onSignalAbort = () => {
|
|
246
|
-
removeListeners();
|
|
247
|
-
this.localBlobCache.delete(localId);
|
|
248
|
-
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
249
|
-
reject(createAbortError());
|
|
250
|
-
};
|
|
251
|
-
const removeListeners = () => {
|
|
252
|
-
this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
|
|
253
|
-
this.internalEvents.off("blobExpired", onBlobExpired);
|
|
254
|
-
signal?.removeEventListener("abort", onSignalAbort);
|
|
255
|
-
};
|
|
256
|
-
this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
|
|
257
|
-
this.internalEvents.on("blobExpired", onBlobExpired);
|
|
258
|
-
signal?.addEventListener("abort", onSignalAbort);
|
|
259
|
-
this.sendBlobAttachMessage(localId, localBlobRecord.storageId);
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
|
-
let attachCompleted = false;
|
|
264
|
-
while (!attachCompleted) {
|
|
265
|
-
await ensureUploaded();
|
|
266
|
-
attachCompleted = await tryAttach();
|
|
267
|
-
// If something stopped the attach from completing successfully (currently just TTL expiry),
|
|
268
|
-
// we expect that the blob was already updated to reflect the updated state (i.e. back to localOnly)
|
|
269
|
-
// and we'll try the loop again from the top.
|
|
270
|
-
}
|
|
271
|
-
// When the blob successfully attaches, the localBlobRecord will have been updated to attached state
|
|
272
|
-
// at the time we processed the op, so there's nothing else to do here.
|
|
273
|
-
};
|
|
274
|
-
/**
|
|
275
|
-
* Called in detached state just prior to attaching, this will update the redirect table by
|
|
276
|
-
* converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
|
|
277
|
-
* The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
|
|
278
|
-
* @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
|
|
279
|
-
*/
|
|
280
|
-
this.patchRedirectTable = (detachedStorageTable) => {
|
|
281
|
-
(0, internal_2.assert)(this.runtime.attachState === internal_1.AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
|
|
282
|
-
// The values of the redirect table are the pseudo storage IDs, which are the keys of the
|
|
283
|
-
// detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
|
|
284
|
-
// storage IDs (many in the case that the storage dedupes the blob).
|
|
285
|
-
(0, internal_2.assert)(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
|
|
286
|
-
// Taking a snapshot of the redirect table entries before iterating, because
|
|
287
|
-
// we will be adding identity mappings to the the redirect table as we iterate
|
|
288
|
-
// and we don't want to include those in the iteration.
|
|
289
|
-
const redirectTableEntries = [...this.redirectTable.entries()];
|
|
290
|
-
for (const [localId, detachedStorageId] of redirectTableEntries) {
|
|
291
|
-
const newStorageId = detachedStorageTable.get(detachedStorageId);
|
|
292
|
-
(0, internal_2.assert)(newStorageId !== undefined, 0xc53 /* Couldn't find a matching storage ID */);
|
|
293
|
-
this.redirectTable.set(localId, newStorageId);
|
|
294
|
-
// set identity (id -> id) entry
|
|
295
|
-
this.redirectTable.set(newStorageId, newStorageId);
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
/**
|
|
299
|
-
* Upload and attach any pending blobs that the BlobManager was loaded with that have not already
|
|
300
|
-
* been attached in the meantime.
|
|
301
|
-
* @returns A promise that resolves when all the uploads and attaches have completed, or rejects
|
|
302
|
-
* if any of them fail.
|
|
303
|
-
*/
|
|
304
|
-
this.sharePendingBlobs = async () => {
|
|
305
|
-
const localIdsToUpload = [...this.pendingOnlyLocalIds];
|
|
306
|
-
this.pendingOnlyLocalIds.clear();
|
|
307
|
-
await Promise.all(localIdsToUpload.map(async (localId) => this.uploadAndAttach(localId)));
|
|
308
|
-
};
|
|
309
136
|
const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachMessage, blobRequested, isBlobDeleted, runtime, pendingBlobs, createBlobPayloadPending, } = props;
|
|
310
137
|
this.routeContext = routeContext;
|
|
311
138
|
this.storage = storage;
|
|
@@ -473,6 +300,177 @@ class BlobManager {
|
|
|
473
300
|
});
|
|
474
301
|
return blobHandle;
|
|
475
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Upload and attach the localBlobCache entry for the given localId.
|
|
305
|
+
*
|
|
306
|
+
* Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
|
|
307
|
+
* when called. Returns a promise that resolves when the blob completes uploading and attaching, or else
|
|
308
|
+
* rejects if an error is encountered or the signal is aborted.
|
|
309
|
+
*/
|
|
310
|
+
uploadAndAttach = async (localId, signal) => {
|
|
311
|
+
if (signal?.aborted === true) {
|
|
312
|
+
this.localBlobCache.delete(localId);
|
|
313
|
+
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
314
|
+
throw createAbortError();
|
|
315
|
+
}
|
|
316
|
+
const localBlobRecordInitial = this.localBlobCache.get(localId);
|
|
317
|
+
(0, internal_2.assert)(localBlobRecordInitial?.state === "localOnly" ||
|
|
318
|
+
localBlobRecordInitial?.state === "uploaded", 0xc80 /* Expect uploadAndAttach to be called with either localOnly or uploaded state */);
|
|
319
|
+
const { blob } = localBlobRecordInitial;
|
|
320
|
+
/**
|
|
321
|
+
* Expects the localBlobCache entry for the given localId to be in either localOnly or uploaded state
|
|
322
|
+
* when called. Returns a promise that resolves when the blob is in uploaded or attached state, or else
|
|
323
|
+
* rejects on error during upload or if the signal is aborted.
|
|
324
|
+
*
|
|
325
|
+
* Most of the time this should be expected to exit in uploaded state, but if we are loading from pending
|
|
326
|
+
* state we may see an attach message from the client that generated the pending state, which can complete the
|
|
327
|
+
* attach while the upload is outstanding.
|
|
328
|
+
*/
|
|
329
|
+
const ensureUploaded = async () => {
|
|
330
|
+
const localBlobRecord = this.localBlobCache.get(localId);
|
|
331
|
+
if (localBlobRecord?.state === "uploaded") {
|
|
332
|
+
// In normal creation flows, the blob will be in localOnly state here. But in the case of loading
|
|
333
|
+
// with pending state we can call it with an uploaded-but-not-attached blob. Start the upload
|
|
334
|
+
// flow only if it's localOnly.
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
(0, internal_2.assert)(localBlobRecord?.state === "localOnly", 0xc81 /* Attempting to upload from unexpected state */);
|
|
338
|
+
this.localBlobCache.set(localId, { state: "uploading", blob });
|
|
339
|
+
await new Promise((resolve, reject) => {
|
|
340
|
+
// If we eventually have driver-level support for abort, then this can simplify a bit as we won't
|
|
341
|
+
// need to track upload completion and abort separately. Until then, we need to handle the case that
|
|
342
|
+
// the upload continues and settles after becoming irrelevant due to signal abort or blob attach.
|
|
343
|
+
let uploadHasBecomeIrrelevant = false;
|
|
344
|
+
const onSignalAbort = () => {
|
|
345
|
+
removeListeners();
|
|
346
|
+
uploadHasBecomeIrrelevant = true;
|
|
347
|
+
this.localBlobCache.delete(localId);
|
|
348
|
+
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
349
|
+
reject(createAbortError());
|
|
350
|
+
};
|
|
351
|
+
const onProcessedBlobAttach = (_localId, _storageId) => {
|
|
352
|
+
if (_localId === localId) {
|
|
353
|
+
removeListeners();
|
|
354
|
+
uploadHasBecomeIrrelevant = true;
|
|
355
|
+
resolve();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const removeListeners = () => {
|
|
359
|
+
this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
|
|
360
|
+
signal?.removeEventListener("abort", onSignalAbort);
|
|
361
|
+
};
|
|
362
|
+
this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
|
|
363
|
+
signal?.addEventListener("abort", onSignalAbort);
|
|
364
|
+
this.storage
|
|
365
|
+
.createBlob(blob)
|
|
366
|
+
.then((createBlobResponse) => {
|
|
367
|
+
if (!uploadHasBecomeIrrelevant) {
|
|
368
|
+
removeListeners();
|
|
369
|
+
this.localBlobCache.set(localId, {
|
|
370
|
+
state: "uploaded",
|
|
371
|
+
blob,
|
|
372
|
+
storageId: createBlobResponse.id,
|
|
373
|
+
uploadTime: Date.now(),
|
|
374
|
+
minTTLInSeconds: createBlobResponse.minTTLInSeconds,
|
|
375
|
+
});
|
|
376
|
+
resolve();
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
.catch((error) => {
|
|
380
|
+
if (!uploadHasBecomeIrrelevant) {
|
|
381
|
+
removeListeners();
|
|
382
|
+
// If the storage call errors, we can't recover. Reject to throw back to the caller.
|
|
383
|
+
this.localBlobCache.delete(localId);
|
|
384
|
+
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
385
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
386
|
+
reject(error);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
/**
|
|
392
|
+
* Expects the localBlobCache entry for the given localId to be in uploaded or attached state when called.
|
|
393
|
+
* Returns a promise that resolves to true if the blob is successfully attached, or false if it cannot be
|
|
394
|
+
* attached and the upload flow needs to be restarted from the top (currently only if the TTL expires before
|
|
395
|
+
* attach can be completed). In the latter case, the localBlobRecord will also be reset to localOnly state.
|
|
396
|
+
* The promise rejects if the signal is aborted.
|
|
397
|
+
*/
|
|
398
|
+
const tryAttach = async () => {
|
|
399
|
+
const localBlobRecord = this.localBlobCache.get(localId);
|
|
400
|
+
if (localBlobRecord?.state === "attached") {
|
|
401
|
+
// In normal creation flows, the blob will be in uploaded state here. But if we are loading from pending
|
|
402
|
+
// state and see an attach message from the client that generated the pending state, we may have reached
|
|
403
|
+
// attached state in the middle of the upload attempt. In that case there's no more work to do and we
|
|
404
|
+
// can just return.
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
(0, internal_2.assert)(localBlobRecord?.state === "uploaded", 0xc82 /* Attempting to attach from unexpected state */);
|
|
408
|
+
// If we just uploaded the blob TTL really shouldn't be expired at this location. But if we loaded from
|
|
409
|
+
// pending state, the upload may have happened some time far in the past and could be expired here.
|
|
410
|
+
if (isTTLTooCloseToExpiry(localBlobRecord)) {
|
|
411
|
+
// If the TTL is expired, we assume it's gone from the storage and so is effectively localOnly again.
|
|
412
|
+
// Then when we re-enter the loop, we'll re-upload it.
|
|
413
|
+
this.localBlobCache.set(localId, { state: "localOnly", blob });
|
|
414
|
+
// Emitting here isn't really necessary since the only listener would be attached below. Including here
|
|
415
|
+
// for completeness though, in case we add other listeners in the future.
|
|
416
|
+
this.internalEvents.emit("blobExpired", localId);
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
this.localBlobCache.set(localId, {
|
|
421
|
+
...localBlobRecord,
|
|
422
|
+
state: "attaching",
|
|
423
|
+
});
|
|
424
|
+
// Send and await a blob attach op. This serves two purposes:
|
|
425
|
+
// 1. If its a new blob, i.e., it isn't de-duped, the server will keep the blob alive if it sees this op
|
|
426
|
+
// until its storage ID is added to the next summary.
|
|
427
|
+
// 2. It will create a local ID to storage ID mapping in all clients which is needed to retrieve the
|
|
428
|
+
// blob from the server via the storage ID.
|
|
429
|
+
return new Promise((resolve, reject) => {
|
|
430
|
+
const onProcessedBlobAttach = (_localId, _storageId) => {
|
|
431
|
+
if (_localId === localId) {
|
|
432
|
+
removeListeners();
|
|
433
|
+
resolve(true);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
// Although we already checked for TTL expiry above, the message we're about to send may later be asked
|
|
437
|
+
// to resubmit. Before we resubmit, we check again for TTL expiry - this listener is how we learn if
|
|
438
|
+
// we discovered expiry in the resubmit flow.
|
|
439
|
+
const onBlobExpired = (_localId) => {
|
|
440
|
+
if (_localId === localId) {
|
|
441
|
+
removeListeners();
|
|
442
|
+
resolve(false);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const onSignalAbort = () => {
|
|
446
|
+
removeListeners();
|
|
447
|
+
this.localBlobCache.delete(localId);
|
|
448
|
+
this.pendingBlobsWithAttachedHandles.delete(localId);
|
|
449
|
+
reject(createAbortError());
|
|
450
|
+
};
|
|
451
|
+
const removeListeners = () => {
|
|
452
|
+
this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
|
|
453
|
+
this.internalEvents.off("blobExpired", onBlobExpired);
|
|
454
|
+
signal?.removeEventListener("abort", onSignalAbort);
|
|
455
|
+
};
|
|
456
|
+
this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
|
|
457
|
+
this.internalEvents.on("blobExpired", onBlobExpired);
|
|
458
|
+
signal?.addEventListener("abort", onSignalAbort);
|
|
459
|
+
this.sendBlobAttachMessage(localId, localBlobRecord.storageId);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
let attachCompleted = false;
|
|
464
|
+
while (!attachCompleted) {
|
|
465
|
+
await ensureUploaded();
|
|
466
|
+
attachCompleted = await tryAttach();
|
|
467
|
+
// If something stopped the attach from completing successfully (currently just TTL expiry),
|
|
468
|
+
// we expect that the blob was already updated to reflect the updated state (i.e. back to localOnly)
|
|
469
|
+
// and we'll try the loop again from the top.
|
|
470
|
+
}
|
|
471
|
+
// When the blob successfully attaches, the localBlobRecord will have been updated to attached state
|
|
472
|
+
// at the time we processed the op, so there's nothing else to do here.
|
|
473
|
+
};
|
|
476
474
|
/**
|
|
477
475
|
* Resubmit a BlobAttach op. Used to add storage IDs to messages that were
|
|
478
476
|
* submitted to runtime while disconnected.
|
|
@@ -620,6 +618,41 @@ class BlobManager {
|
|
|
620
618
|
}, error);
|
|
621
619
|
throw error;
|
|
622
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Called in detached state just prior to attaching, this will update the redirect table by
|
|
623
|
+
* converting the pseudo storage IDs into real storage IDs using the provided detachedStorageTable.
|
|
624
|
+
* The provided table must have exactly the same set of pseudo storage IDs as are found in the redirect table.
|
|
625
|
+
* @param detachedStorageTable - A map of pseudo storage IDs to real storage IDs.
|
|
626
|
+
*/
|
|
627
|
+
patchRedirectTable = (detachedStorageTable) => {
|
|
628
|
+
(0, internal_2.assert)(this.runtime.attachState === internal_1.AttachState.Detached, 0x252 /* "redirect table can only be set in detached container" */);
|
|
629
|
+
// The values of the redirect table are the pseudo storage IDs, which are the keys of the
|
|
630
|
+
// detachedStorageTable. We expect to have a many:1 mapping from local IDs to pseudo
|
|
631
|
+
// storage IDs (many in the case that the storage dedupes the blob).
|
|
632
|
+
(0, internal_2.assert)(new Set(this.redirectTable.values()).size === detachedStorageTable.size, 0x391 /* Redirect table size must match BlobManager's local ID count */);
|
|
633
|
+
// Taking a snapshot of the redirect table entries before iterating, because
|
|
634
|
+
// we will be adding identity mappings to the the redirect table as we iterate
|
|
635
|
+
// and we don't want to include those in the iteration.
|
|
636
|
+
const redirectTableEntries = [...this.redirectTable.entries()];
|
|
637
|
+
for (const [localId, detachedStorageId] of redirectTableEntries) {
|
|
638
|
+
const newStorageId = detachedStorageTable.get(detachedStorageId);
|
|
639
|
+
(0, internal_2.assert)(newStorageId !== undefined, 0xc53 /* Couldn't find a matching storage ID */);
|
|
640
|
+
this.redirectTable.set(localId, newStorageId);
|
|
641
|
+
// set identity (id -> id) entry
|
|
642
|
+
this.redirectTable.set(newStorageId, newStorageId);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
/**
|
|
646
|
+
* Upload and attach any pending blobs that the BlobManager was loaded with that have not already
|
|
647
|
+
* been attached in the meantime.
|
|
648
|
+
* @returns A promise that resolves when all the uploads and attaches have completed, or rejects
|
|
649
|
+
* if any of them fail.
|
|
650
|
+
*/
|
|
651
|
+
sharePendingBlobs = async () => {
|
|
652
|
+
const localIdsToUpload = [...this.pendingOnlyLocalIds];
|
|
653
|
+
this.pendingOnlyLocalIds.clear();
|
|
654
|
+
await Promise.all(localIdsToUpload.map(async (localId) => this.uploadAndAttach(localId)));
|
|
655
|
+
};
|
|
623
656
|
/**
|
|
624
657
|
* To be used in getPendingLocalState flow. Get a serializable record of the blobs that are
|
|
625
658
|
* pending upload and/or their BlobAttach op, which can be given to a new BlobManager to
|