@fluidframework/container-runtime 0.58.3000-61081 → 0.59.1001-62246

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.
Files changed (97) hide show
  1. package/dist/blobManager.d.ts +13 -1
  2. package/dist/blobManager.d.ts.map +1 -1
  3. package/dist/blobManager.js +52 -0
  4. package/dist/blobManager.js.map +1 -1
  5. package/dist/connectionTelemetry.js +7 -7
  6. package/dist/connectionTelemetry.js.map +1 -1
  7. package/dist/containerRuntime.d.ts +27 -3
  8. package/dist/containerRuntime.d.ts.map +1 -1
  9. package/dist/containerRuntime.js +100 -14
  10. package/dist/containerRuntime.js.map +1 -1
  11. package/dist/dataStore.js +8 -1
  12. package/dist/dataStore.js.map +1 -1
  13. package/dist/dataStoreContext.d.ts +9 -3
  14. package/dist/dataStoreContext.d.ts.map +1 -1
  15. package/dist/dataStoreContext.js +22 -6
  16. package/dist/dataStoreContext.js.map +1 -1
  17. package/dist/dataStores.d.ts +13 -5
  18. package/dist/dataStores.d.ts.map +1 -1
  19. package/dist/dataStores.js +39 -18
  20. package/dist/dataStores.js.map +1 -1
  21. package/dist/deltaScheduler.d.ts +4 -5
  22. package/dist/deltaScheduler.d.ts.map +1 -1
  23. package/dist/deltaScheduler.js +54 -35
  24. package/dist/deltaScheduler.js.map +1 -1
  25. package/dist/garbageCollection.d.ts +31 -27
  26. package/dist/garbageCollection.d.ts.map +1 -1
  27. package/dist/garbageCollection.js +76 -75
  28. package/dist/garbageCollection.js.map +1 -1
  29. package/dist/orderedClientElection.d.ts +6 -57
  30. package/dist/orderedClientElection.d.ts.map +1 -1
  31. package/dist/orderedClientElection.js +25 -140
  32. package/dist/orderedClientElection.js.map +1 -1
  33. package/dist/packageVersion.d.ts +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/summarizerClientElection.d.ts +0 -2
  37. package/dist/summarizerClientElection.d.ts.map +1 -1
  38. package/dist/summarizerClientElection.js +2 -7
  39. package/dist/summarizerClientElection.js.map +1 -1
  40. package/dist/summaryManager.d.ts.map +1 -1
  41. package/dist/summaryManager.js +3 -14
  42. package/dist/summaryManager.js.map +1 -1
  43. package/lib/blobManager.d.ts +13 -1
  44. package/lib/blobManager.d.ts.map +1 -1
  45. package/lib/blobManager.js +52 -0
  46. package/lib/blobManager.js.map +1 -1
  47. package/lib/connectionTelemetry.js +7 -7
  48. package/lib/connectionTelemetry.js.map +1 -1
  49. package/lib/containerRuntime.d.ts +27 -3
  50. package/lib/containerRuntime.d.ts.map +1 -1
  51. package/lib/containerRuntime.js +101 -15
  52. package/lib/containerRuntime.js.map +1 -1
  53. package/lib/dataStore.js +8 -1
  54. package/lib/dataStore.js.map +1 -1
  55. package/lib/dataStoreContext.d.ts +9 -3
  56. package/lib/dataStoreContext.d.ts.map +1 -1
  57. package/lib/dataStoreContext.js +22 -6
  58. package/lib/dataStoreContext.js.map +1 -1
  59. package/lib/dataStores.d.ts +13 -5
  60. package/lib/dataStores.d.ts.map +1 -1
  61. package/lib/dataStores.js +39 -18
  62. package/lib/dataStores.js.map +1 -1
  63. package/lib/deltaScheduler.d.ts +4 -5
  64. package/lib/deltaScheduler.d.ts.map +1 -1
  65. package/lib/deltaScheduler.js +54 -35
  66. package/lib/deltaScheduler.js.map +1 -1
  67. package/lib/garbageCollection.d.ts +31 -27
  68. package/lib/garbageCollection.d.ts.map +1 -1
  69. package/lib/garbageCollection.js +75 -74
  70. package/lib/garbageCollection.js.map +1 -1
  71. package/lib/orderedClientElection.d.ts +6 -57
  72. package/lib/orderedClientElection.d.ts.map +1 -1
  73. package/lib/orderedClientElection.js +25 -140
  74. package/lib/orderedClientElection.js.map +1 -1
  75. package/lib/packageVersion.d.ts +1 -1
  76. package/lib/packageVersion.js +1 -1
  77. package/lib/packageVersion.js.map +1 -1
  78. package/lib/summarizerClientElection.d.ts +0 -2
  79. package/lib/summarizerClientElection.d.ts.map +1 -1
  80. package/lib/summarizerClientElection.js +2 -7
  81. package/lib/summarizerClientElection.js.map +1 -1
  82. package/lib/summaryManager.d.ts.map +1 -1
  83. package/lib/summaryManager.js +3 -14
  84. package/lib/summaryManager.js.map +1 -1
  85. package/package.json +33 -21
  86. package/src/blobManager.ts +60 -1
  87. package/src/connectionTelemetry.ts +7 -7
  88. package/src/containerRuntime.ts +106 -17
  89. package/src/dataStore.ts +7 -1
  90. package/src/dataStoreContext.ts +22 -7
  91. package/src/dataStores.ts +40 -19
  92. package/src/deltaScheduler.ts +65 -39
  93. package/src/garbageCollection.ts +92 -78
  94. package/src/orderedClientElection.ts +25 -154
  95. package/src/packageVersion.ts +1 -1
  96. package/src/summarizerClientElection.ts +2 -7
  97. package/src/summaryManager.ts +4 -15
@@ -1 +1 @@
1
- {"version":3,"file":"summaryManager.js","sourceRoot":"","sources":["../src/summaryManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable, IEvent, IEventProvider, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ChildLogger, PerformanceEvent } from \"@fluidframework/telemetry-utils\";\nimport { DriverErrorType } from \"@fluidframework/driver-definitions\";\nimport { ISummarizerClientElection } from \"./summarizerClientElection\";\nimport { IThrottler } from \"./throttler\";\nimport {\n ISummarizer,\n ISummarizerOptions,\n SummarizerStopReason,\n} from \"./summarizerTypes\";\nimport { SummaryCollection } from \"./summaryCollection\";\n\nconst defaultInitialDelayMs = 5000;\nconst defaultOpsToBypassInitialDelay = 4000;\n\nexport enum SummaryManagerState {\n Off = 0,\n Starting = 1,\n Running = 2,\n Stopping = 3,\n}\n\n// Please note that all reasons in this list are not errors,\n// and thus they are not raised today to parent container as error.\n// If this needs to be changed in future, we should re-evaluate what and how we raise to summarizer\ntype StopReason = Extract<SummarizerStopReason, \"parentNotConnected\" | \"parentShouldNotSummarize\">;\ntype ShouldSummarizeState =\n | { shouldSummarize: true; }\n | { shouldSummarize: false; stopReason: StopReason; };\n\nexport interface IConnectedEvents extends IEvent {\n (event: \"connected\", listener: (clientId: string) => void);\n (event: \"disconnected\", listener: () => void);\n}\n\n/**\n * IConnectedState describes an object that SummaryManager can watch to observe connection/disconnection.\n *\n * Under current implementation, its role will be fulfilled by the ContainerRuntime, but this could be replaced\n * with anything else that fulfills the contract if we want to shift the layer that the SummaryManager lives at.\n */\nexport interface IConnectedState extends IEventProvider<IConnectedEvents> {\n readonly connected: boolean;\n\n /**\n * Under current implementation this is undefined if we've never connected, otherwise it's the clientId from our\n * latest connection (even if we've since disconnected!). Although this happens to be the behavior we want in\n * SummaryManager, I suspect that globally we may eventually want to modify this behavior (e.g. make clientId\n * undefined while disconnected). To protect against this, let's assume this field can't be trusted while\n * disconnected and instead separately track \"latest clientId\" in SummaryManager.\n */\n readonly clientId: string | undefined;\n}\n\nexport interface ISummaryManagerConfig {\n initialDelayMs: number;\n opsToBypassInitialDelay: number;\n}\n\n/**\n * SummaryManager is created by parent container (i.e. interactive container with clientType !== \"summarizer\") only.\n * It observes changes in calculated summarizer and reacts to changes by either creating summarizer client or\n * stopping existing summarizer client.\n */\nexport class SummaryManager implements IDisposable {\n private readonly logger: ITelemetryLogger;\n private readonly opsToBypassInitialDelay: number;\n private readonly initialDelayMs: number;\n private latestClientId: string | undefined;\n private state = SummaryManagerState.Off;\n private summarizer?: ISummarizer;\n private _disposed = false;\n\n public get disposed() {\n return this._disposed;\n }\n\n public get currentState() { return this.state; }\n\n constructor(\n private readonly clientElection: ISummarizerClientElection,\n private readonly connectedState: IConnectedState,\n private readonly summaryCollection:\n Pick<SummaryCollection, \"opsSinceLastAck\" | \"addOpListener\" | \"removeOpListener\">,\n parentLogger: ITelemetryLogger,\n /** Creates summarizer by asking interactive container to spawn summarizing container and\n * get back its Summarizer instance. */\n private readonly requestSummarizerFn: () => Promise<ISummarizer>,\n private readonly startThrottler: IThrottler,\n {\n initialDelayMs = defaultInitialDelayMs,\n opsToBypassInitialDelay = defaultOpsToBypassInitialDelay,\n }: Readonly<Partial<ISummaryManagerConfig>> = {},\n private readonly summarizerOptions?: Readonly<Partial<ISummarizerOptions>>,\n ) {\n this.logger = ChildLogger.create(\n parentLogger,\n \"SummaryManager\",\n {all:{ clientId: () => this.latestClientId }});\n\n this.connectedState.on(\"connected\", this.handleConnected);\n this.connectedState.on(\"disconnected\", this.handleDisconnected);\n this.latestClientId = this.connectedState.clientId;\n\n this.opsToBypassInitialDelay = opsToBypassInitialDelay;\n this.initialDelayMs = initialDelayMs;\n }\n\n /**\n * Until start is called, the SummaryManager won't begin attempting to start summarization. This ensures there's\n * a window between construction and starting where the caller can attach listeners.\n */\n public start(): void {\n this.clientElection.on(\"electedSummarizerChanged\", this.refreshSummarizer);\n this.refreshSummarizer();\n }\n\n private readonly handleConnected = (clientId: string) => {\n this.latestClientId = clientId;\n // If we have a summarizer, it should have been either cancelled on disconnected by now.\n // But because of lastSummary process, it can still hang around, so there is not much we can\n // check or assert.\n this.refreshSummarizer();\n };\n\n private readonly handleDisconnected = () => {\n this.refreshSummarizer();\n };\n\n private static readonly isStartingOrRunning = (state: SummaryManagerState) =>\n state === SummaryManagerState.Starting || state === SummaryManagerState.Running;\n\n private getShouldSummarizeState(): ShouldSummarizeState {\n // Note that if we're in the Running state, the electedClient may be a summarizer client, so we can't\n // enforce connectedState.clientId === clientElection.electedClientId. But once we're Running, we should\n // only transition to Stopping when the electedParentId changes. Stopping the summarizer without\n // changing the electedParent will just cause us to transition to Starting again.\n if (!this.connectedState.connected) {\n return { shouldSummarize: false, stopReason: \"parentNotConnected\" };\n } else if (this.connectedState.clientId !== this.clientElection.electedParentId ||\n (this.state !== SummaryManagerState.Running &&\n this.connectedState.clientId !== this.clientElection.electedClientId)) {\n return { shouldSummarize: false, stopReason: \"parentShouldNotSummarize\" };\n } else if (this.disposed) {\n assert(false, 0x260 /* \"Disposed should mean disconnected!\" */);\n } else {\n return { shouldSummarize: true };\n }\n }\n\n private readonly refreshSummarizer = () => {\n // Transition states depending on shouldSummarize, which is a calculated property\n // that is only true if this client is connected and is the elected summarizer.\n const shouldSummarizeState = this.getShouldSummarizeState();\n switch (this.state) {\n case SummaryManagerState.Off: {\n if (shouldSummarizeState.shouldSummarize) {\n this.startSummarization();\n }\n return;\n }\n case SummaryManagerState.Starting: {\n // Cannot take any action until summarizer is created\n // state transition will occur after creation\n return;\n }\n case SummaryManagerState.Running: {\n if (shouldSummarizeState.shouldSummarize === false) {\n this.stop(shouldSummarizeState.stopReason);\n }\n return;\n }\n case SummaryManagerState.Stopping: {\n // Cannot take any action until running summarizer finishes\n // state transition will occur after it stops\n return;\n }\n default: {\n return;\n }\n }\n };\n\n private startSummarization() {\n assert(this.state === SummaryManagerState.Off, 0x261 /* \"Expected: off\" */);\n this.state = SummaryManagerState.Starting;\n\n assert(this.summarizer === undefined, 0x262 /* \"Old summarizer is still working!\" */);\n\n let reason = \"unknown\";\n\n this.delayBeforeCreatingSummarizer().then(async (startWithInitialDelay: boolean) => {\n // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore\n // but only if creation was delayed. If it was not, then we want to ensure we always create\n // a summarizer to kick off lastSummary. Without that, we would not be able to summarize and get\n // document out of broken state if it has too many ops and ordering service keeps nacking main\n // container (and thus it goes into cycle of reconnects)\n if (startWithInitialDelay && this.getShouldSummarizeState().shouldSummarize === false) {\n return;\n }\n\n // We transition to Running before requesting the summarizer, because after requesting we can't predict\n // when the electedClient will be replaced with the new summarizer client.\n // The alternative would be to let connectedState.clientId !== clientElection.electedClientId when\n // state === Starting || state === Running.\n assert(this.state === SummaryManagerState.Starting, 0x263 /* \"Expected: starting\" */);\n this.state = SummaryManagerState.Running;\n\n const summarizer = await this.requestSummarizerFn();\n\n // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore\n const shouldSummarizeState = this.getShouldSummarizeState();\n if (shouldSummarizeState.shouldSummarize === false) {\n this.state = SummaryManagerState.Starting;\n summarizer.stop(shouldSummarizeState.stopReason);\n return;\n }\n\n this.summarizer = summarizer;\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const clientId = this.latestClientId!;\n\n reason = await PerformanceEvent.timedExecAsync(\n this.logger,\n { eventName: \"RunningSummarizer\", attempt: this.startThrottler.numAttempts },\n async () => summarizer.run(clientId, this.summarizerOptions),\n );\n }).catch((error) => {\n // Most of exceptions happen due to container being closed while loading it, due to\n // summarizer container loosing connection while load.\n // Not worth reporting such errors as errors. That said, we might miss some real errors if\n // we ignore blindly, so try to narrow signature we are looking for - skip logging\n // error only if this client should no longer be a summarizer (which in practice\n // means it also lost connection), and error happened on load (we do not have summarizer).\n // We could annotate the error raised in Container.load where the container closed during load with no error\n // and check for that case here, but that does not seem to be necessary.\n if (this.getShouldSummarizeState().shouldSummarize || this.summarizer !== undefined) {\n // Report any failure as an error unless it was due to cancellation (like \"disconnected\" error)\n // If failure happened on container load, we may not yet realized that socket disconnected, so check\n // offlineError.\n const category = error?.errorType === DriverErrorType.offlineError ? \"generic\" : \"error\";\n this.logger.sendTelemetryEvent(\n {\n eventName: \"SummarizerException\",\n category,\n },\n error);\n\n // Note that summarizer may keep going (like doing last summary).\n // Ideally we await stopping process, but this code path is due to a bug\n // that needs to be fixed either way.\n if (SummaryManager.isStartingOrRunning(this.state)) {\n this.stop(\"summarizerException\");\n }\n }\n }).finally(() => {\n assert(this.state !== SummaryManagerState.Off, 0x264 /* \"Expected: Not Off\" */);\n this.state = SummaryManagerState.Off;\n\n this.summarizer = undefined;\n\n this.logger.sendTelemetryEvent({\n eventName: \"EndingSummarizer\",\n reason,\n });\n\n if (this.getShouldSummarizeState().shouldSummarize) {\n this.startSummarization();\n }\n });\n }\n\n private stop(reason: SummarizerStopReason) {\n if (!SummaryManager.isStartingOrRunning(this.state)) {\n return;\n }\n this.state = SummaryManagerState.Stopping;\n\n // Stopping the running summarizer client should trigger a change\n // in states when the running summarizer closes\n this.summarizer?.stop(reason);\n }\n\n /**\n * Implements initial delay before creating summarizer\n * @returns true, if creation is delayed due to heuristics (not many ops to summarize).\n * False if summarizer should start immediately due to too many unsummarized ops.\n */\n private async delayBeforeCreatingSummarizer(): Promise<boolean> {\n // throttle creation of new summarizer containers to prevent spamming the server with websocket connections\n let delayMs = this.startThrottler.getDelay();\n\n // We have been elected the summarizer. Some day we may be able to summarize with a live document but for\n // now we play it safe and launch a second copy.\n this.logger.sendTelemetryEvent({\n eventName: \"CreatingSummarizer\",\n throttlerDelay: delayMs,\n initialDelay: this.initialDelayMs,\n startThrottlerMaxDelayMs: this.startThrottler.maxDelayMs,\n opsSinceLastAck: this.summaryCollection.opsSinceLastAck,\n opsToBypassInitialDelay: this.opsToBypassInitialDelay,\n });\n\n // This delay helps ensure that last summarizer that might be left from previous client\n // has enough time to complete its last summary and thus new summarizer not conflict with previous one.\n // If, however, there are too many unsummarized ops, try to resolve it as quickly as possible, with\n // understanding that we may see nacks because of such quick action.\n // A better design would be for summarizer election logic to always select current summarizer as\n // summarizing client (i.e. clientType === \"summarizer\" can be elected) to ensure that nobody else can\n // summarizer while it finishes its work and moves to exit.\n // It also helps with pure boot scenario (single client) to offset expensive work a bit out from\n // critical boot sequence.\n let startWithInitialDelay = false;\n if (this.summaryCollection.opsSinceLastAck < this.opsToBypassInitialDelay) {\n startWithInitialDelay = true;\n delayMs = Math.max(delayMs, this.initialDelayMs);\n }\n\n if (delayMs > 0) {\n let timer;\n let resolveOpPromiseFn;\n // Create a listener that will break the delay if we've exceeded the initial delay ops count.\n const opsListenerFn = () => {\n if (this.summaryCollection.opsSinceLastAck >= this.opsToBypassInitialDelay) {\n clearTimeout(timer);\n resolveOpPromiseFn();\n }\n };\n // Create a Promise that will resolve when the delay expires.\n const delayPromise = new Promise<void>((resolve) => {\n timer = setTimeout(() => resolve(), delayMs);\n });\n // Create a Promise that will resolve if the ops count passes the threshold.\n const opPromise = new Promise<void>((resolve) => { resolveOpPromiseFn = resolve; });\n this.summaryCollection.addOpListener(opsListenerFn);\n await Promise.race([ delayPromise, opPromise ]);\n this.summaryCollection.removeOpListener(opsListenerFn);\n }\n return startWithInitialDelay;\n }\n\n public readonly summarizeOnDemand: ISummarizer[\"summarizeOnDemand\"] = (...args) => {\n if (this.summarizer === undefined) {\n throw Error(\"No running summarizer client\");\n // TODO: could spawn a summarizer client temporarily.\n }\n return this.summarizer.summarizeOnDemand(...args);\n };\n\n public readonly enqueueSummarize: ISummarizer[\"enqueueSummarize\"] = (...args) => {\n if (this.summarizer === undefined) {\n throw Error(\"No running summarizer client\");\n // TODO: could spawn a summarizer client temporarily.\n }\n return this.summarizer.enqueueSummarize(...args);\n };\n\n public dispose() {\n this.clientElection.off(\"electedSummarizerChanged\", this.refreshSummarizer);\n this.connectedState.off(\"connected\", this.handleConnected);\n this.connectedState.off(\"disconnected\", this.handleDisconnected);\n this._disposed = true;\n }\n}\n"]}
1
+ {"version":3,"file":"summaryManager.js","sourceRoot":"","sources":["../src/summaryManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAUrE,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,8BAA8B,GAAG,IAAI,CAAC;AAE5C,MAAM,CAAN,IAAY,mBAKX;AALD,WAAY,mBAAmB;IAC3B,2DAAO,CAAA;IACP,qEAAY,CAAA;IACZ,mEAAW,CAAA;IACX,qEAAY,CAAA;AAChB,CAAC,EALW,mBAAmB,KAAnB,mBAAmB,QAK9B;AAuCD;;;;GAIG;AACH,MAAM,OAAO,cAAc;IAevB,YACqB,cAAyC,EACzC,cAA+B,EAC/B,iBACoE,EACrF,YAA8B;IAC9B;2CACuC;IACtB,mBAA+C,EAC/C,cAA0B,EAC3C,EACI,cAAc,GAAG,qBAAqB,EACtC,uBAAuB,GAAG,8BAA8B,MACd,EAAE,EAC/B,iBAAyD;QAbzD,mBAAc,GAAd,cAAc,CAA2B;QACzC,mBAAc,GAAd,cAAc,CAAiB;QAC/B,sBAAiB,GAAjB,iBAAiB,CACmD;QAIpE,wBAAmB,GAAnB,mBAAmB,CAA4B;QAC/C,mBAAc,GAAd,cAAc,CAAY;QAK1B,sBAAiB,GAAjB,iBAAiB,CAAwC;QAxBtE,UAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC;QAEhC,cAAS,GAAG,KAAK,CAAC;QA8CT,oBAAe,GAAG,CAAC,QAAgB,EAAE,EAAE;YACpD,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;YAC/B,wFAAwF;YACxF,4FAA4F;YAC5F,mBAAmB;YACnB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC7B,CAAC,CAAC;QAEe,uBAAkB,GAAG,GAAG,EAAE;YACvC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC7B,CAAC,CAAC;QAiBe,sBAAiB,GAAG,GAAG,EAAE;YACtC,iFAAiF;YACjF,+EAA+E;YAC/E,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC5D,QAAQ,IAAI,CAAC,KAAK,EAAE;gBAChB,KAAK,mBAAmB,CAAC,GAAG,CAAC,CAAC;oBAC1B,IAAI,oBAAoB,CAAC,eAAe,EAAE;wBACtC,IAAI,CAAC,kBAAkB,EAAE,CAAC;qBAC7B;oBACD,OAAO;iBACV;gBACD,KAAK,mBAAmB,CAAC,QAAQ,CAAC,CAAC;oBAC/B,qDAAqD;oBACrD,6CAA6C;oBAC7C,OAAO;iBACV;gBACD,KAAK,mBAAmB,CAAC,OAAO,CAAC,CAAC;oBAC9B,IAAI,oBAAoB,CAAC,eAAe,KAAK,KAAK,EAAE;wBAChD,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;qBAC9C;oBACD,OAAO;iBACV;gBACD,KAAK,mBAAmB,CAAC,QAAQ,CAAC,CAAC;oBAC/B,2DAA2D;oBAC3D,6CAA6C;oBAC7C,OAAO;iBACV;gBACD,OAAO,CAAC,CAAC;oBACL,OAAO;iBACV;aACJ;QACL,CAAC,CAAC;QA4Jc,sBAAiB,GAAqC,CAAC,GAAG,IAAI,EAAE,EAAE;YAC9E,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE;gBAC/B,MAAM,KAAK,CAAC,8BAA8B,CAAC,CAAC;gBAC5C,qDAAqD;aACxD;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,GAAG,IAAI,CAAC,CAAC;QACtD,CAAC,CAAC;QAEc,qBAAgB,GAAoC,CAAC,GAAG,IAAI,EAAE,EAAE;YAC5E,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE;gBAC/B,MAAM,KAAK,CAAC,8BAA8B,CAAC,CAAC;gBAC5C,qDAAqD;aACxD;YACD,OAAO,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC;QA1PE,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAC5B,YAAY,EACZ,gBAAgB,EAChB,EAAC,GAAG,EAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,EAAC,CAAC,CAAC;QAEnD,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAC1D,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAChE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC;QAEnD,IAAI,CAAC,uBAAuB,GAAG,uBAAuB,CAAC;QACvD,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACzC,CAAC;IAjCD,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,SAAS,CAAC;IAC1B,CAAC;IAED,IAAW,YAAY,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IA+BhD;;;OAGG;IACI,KAAK;QACR,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC,0BAA0B,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC3E,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAiBO,uBAAuB;QAC3B,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE;YAChC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,CAAC;SACvE;aAAM,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,KAAK,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE;YAC7E,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,EAAE,0BAA0B,EAAE,CAAC;SAC7E;aAAM,IAAI,IAAI,CAAC,QAAQ,EAAE;YACtB,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,0CAA0C,CAAC,CAAC;SACnE;aAAM;YACH,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;SACpC;IACL,CAAC;IAmCO,kBAAkB;QACtB,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,QAAQ,CAAC;QAE1C,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAEtF,IAAI,MAAM,GAAG,SAAS,CAAC;QAEvB,IAAI,CAAC,6BAA6B,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,qBAA8B,EAAE,EAAE;YAC/E,4FAA4F;YAC5F,2FAA2F;YAC3F,gGAAgG;YAChG,8FAA8F;YAC9F,wDAAwD;YACxD,IAAI,qBAAqB,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC,eAAe,KAAK,KAAK,EAAE;gBACnF,OAAO;aACV;YAED,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAEpD,4FAA4F;YAC5F,MAAM,oBAAoB,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC5D,IAAI,oBAAoB,CAAC,eAAe,KAAK,KAAK,EAAE;gBAChD,UAAU,CAAC,IAAI,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;gBACjD,OAAO;aACV;YAED,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,0BAA0B,CAAC,CAAC;YACtF,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,OAAO,CAAC;YAEzC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;YAE7B,oEAAoE;YACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAe,CAAC;YAEtC,MAAM,GAAG,MAAM,gBAAgB,CAAC,cAAc,CAC1C,IAAI,CAAC,MAAM,EACX,EAAE,SAAS,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,EAC5E,KAAK,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAC/D,CAAC;QACN,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACf,mFAAmF;YACnF,sDAAsD;YACtD,0FAA0F;YAC1F,kFAAkF;YAClF,gFAAgF;YAChF,0FAA0F;YAC1F,4GAA4G;YAC5G,wEAAwE;YACxE,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC,eAAe,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,EAAE;gBACjF,+FAA+F;gBAC/F,oGAAoG;gBACpG,gBAAgB;gBAChB,MAAM,QAAQ,GAAG,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,SAAS,MAAK,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;gBACzF,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAC1B;oBACI,SAAS,EAAE,qBAAqB;oBAChC,QAAQ;iBACX,EACD,KAAK,CAAC,CAAC;gBAEX,iEAAiE;gBACjE,wEAAwE;gBACxE,qCAAqC;gBACrC,IAAI,cAAc,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;oBAChD,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;iBACpC;aACJ;QACL,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACZ,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,yBAAyB,CAAC,CAAC;YAChF,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC;YAErC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;YAE5B,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;gBAC3B,SAAS,EAAE,kBAAkB;gBAC7B,MAAM;aACT,CAAC,CAAC;YAEH,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC,eAAe,EAAE;gBAChD,IAAI,CAAC,kBAAkB,EAAE,CAAC;aAC7B;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,IAAI,CAAC,MAA4B;;QACrC,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;YACjD,OAAO;SACV;QACD,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,QAAQ,CAAC;QAE1C,iEAAiE;QACjE,+CAA+C;QAC/C,MAAA,IAAI,CAAC,UAAU,0CAAE,IAAI,CAAC,MAAM,EAAE;IAClC,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,6BAA6B;QACvC,2GAA2G;QAC3G,IAAI,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;QAE7C,yGAAyG;QACzG,gDAAgD;QAChD,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC;YAC3B,SAAS,EAAE,oBAAoB;YAC/B,cAAc,EAAE,OAAO;YACvB,YAAY,EAAE,IAAI,CAAC,cAAc;YACjC,wBAAwB,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU;YACxD,eAAe,EAAE,IAAI,CAAC,iBAAiB,CAAC,eAAe;YACvD,uBAAuB,EAAE,IAAI,CAAC,uBAAuB;SACxD,CAAC,CAAC;QAEH,uFAAuF;QACvF,uGAAuG;QACvG,mGAAmG;QACnG,oEAAoE;QACpE,gGAAgG;QAChG,sGAAsG;QACtG,2DAA2D;QAC3D,gGAAgG;QAChG,0BAA0B;QAC1B,IAAI,qBAAqB,GAAG,KAAK,CAAC;QAClC,IAAI,IAAI,CAAC,iBAAiB,CAAC,eAAe,GAAG,IAAI,CAAC,uBAAuB,EAAE;YACvE,qBAAqB,GAAG,IAAI,CAAC;YAC7B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;SACpD;QAED,IAAI,OAAO,GAAG,CAAC,EAAE;YACb,IAAI,KAAK,CAAC;YACV,IAAI,kBAAkB,CAAC;YACvB,6FAA6F;YAC7F,MAAM,aAAa,GAAG,GAAG,EAAE;gBACvB,IAAI,IAAI,CAAC,iBAAiB,CAAC,eAAe,IAAI,IAAI,CAAC,uBAAuB,EAAE;oBACxE,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,kBAAkB,EAAE,CAAC;iBACxB;YACL,CAAC,CAAC;YACF,6DAA6D;YAC7D,MAAM,YAAY,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;gBAC/C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YACH,4EAA4E;YAC5E,MAAM,SAAS,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;YACpD,MAAM,OAAO,CAAC,IAAI,CAAC,CAAE,YAAY,EAAE,SAAS,CAAE,CAAC,CAAC;YAChD,IAAI,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;SAC1D;QACD,OAAO,qBAAqB,CAAC;IACjC,CAAC;IAkBM,OAAO;QACV,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC5E,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAC3D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACjE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IAC1B,CAAC;;AA/NuB,kCAAmB,GAAG,CAAC,KAA0B,EAAE,EAAE,CACzE,KAAK,KAAK,mBAAmB,CAAC,QAAQ,IAAI,KAAK,KAAK,mBAAmB,CAAC,OAAO,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable, IEvent, IEventProvider, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { assert } from \"@fluidframework/common-utils\";\nimport { ChildLogger, PerformanceEvent } from \"@fluidframework/telemetry-utils\";\nimport { DriverErrorType } from \"@fluidframework/driver-definitions\";\nimport { ISummarizerClientElection } from \"./summarizerClientElection\";\nimport { IThrottler } from \"./throttler\";\nimport {\n ISummarizer,\n ISummarizerOptions,\n SummarizerStopReason,\n} from \"./summarizerTypes\";\nimport { SummaryCollection } from \"./summaryCollection\";\n\nconst defaultInitialDelayMs = 5000;\nconst defaultOpsToBypassInitialDelay = 4000;\n\nexport enum SummaryManagerState {\n Off = 0,\n Starting = 1,\n Running = 2,\n Stopping = 3,\n}\n\n// Please note that all reasons in this list are not errors,\n// and thus they are not raised today to parent container as error.\n// If this needs to be changed in future, we should re-evaluate what and how we raise to summarizer\ntype StopReason = Extract<SummarizerStopReason, \"parentNotConnected\" | \"parentShouldNotSummarize\">;\ntype ShouldSummarizeState =\n | { shouldSummarize: true; }\n | { shouldSummarize: false; stopReason: StopReason; };\n\nexport interface IConnectedEvents extends IEvent {\n (event: \"connected\", listener: (clientId: string) => void);\n (event: \"disconnected\", listener: () => void);\n}\n\n/**\n * IConnectedState describes an object that SummaryManager can watch to observe connection/disconnection.\n *\n * Under current implementation, its role will be fulfilled by the ContainerRuntime, but this could be replaced\n * with anything else that fulfills the contract if we want to shift the layer that the SummaryManager lives at.\n */\nexport interface IConnectedState extends IEventProvider<IConnectedEvents> {\n readonly connected: boolean;\n\n /**\n * Under current implementation this is undefined if we've never connected, otherwise it's the clientId from our\n * latest connection (even if we've since disconnected!). Although this happens to be the behavior we want in\n * SummaryManager, I suspect that globally we may eventually want to modify this behavior (e.g. make clientId\n * undefined while disconnected). To protect against this, let's assume this field can't be trusted while\n * disconnected and instead separately track \"latest clientId\" in SummaryManager.\n */\n readonly clientId: string | undefined;\n}\n\nexport interface ISummaryManagerConfig {\n initialDelayMs: number;\n opsToBypassInitialDelay: number;\n}\n\n/**\n * SummaryManager is created by parent container (i.e. interactive container with clientType !== \"summarizer\") only.\n * It observes changes in calculated summarizer and reacts to changes by either creating summarizer client or\n * stopping existing summarizer client.\n */\nexport class SummaryManager implements IDisposable {\n private readonly logger: ITelemetryLogger;\n private readonly opsToBypassInitialDelay: number;\n private readonly initialDelayMs: number;\n private latestClientId: string | undefined;\n private state = SummaryManagerState.Off;\n private summarizer?: ISummarizer;\n private _disposed = false;\n\n public get disposed() {\n return this._disposed;\n }\n\n public get currentState() { return this.state; }\n\n constructor(\n private readonly clientElection: ISummarizerClientElection,\n private readonly connectedState: IConnectedState,\n private readonly summaryCollection:\n Pick<SummaryCollection, \"opsSinceLastAck\" | \"addOpListener\" | \"removeOpListener\">,\n parentLogger: ITelemetryLogger,\n /** Creates summarizer by asking interactive container to spawn summarizing container and\n * get back its Summarizer instance. */\n private readonly requestSummarizerFn: () => Promise<ISummarizer>,\n private readonly startThrottler: IThrottler,\n {\n initialDelayMs = defaultInitialDelayMs,\n opsToBypassInitialDelay = defaultOpsToBypassInitialDelay,\n }: Readonly<Partial<ISummaryManagerConfig>> = {},\n private readonly summarizerOptions?: Readonly<Partial<ISummarizerOptions>>,\n ) {\n this.logger = ChildLogger.create(\n parentLogger,\n \"SummaryManager\",\n {all:{ clientId: () => this.latestClientId }});\n\n this.connectedState.on(\"connected\", this.handleConnected);\n this.connectedState.on(\"disconnected\", this.handleDisconnected);\n this.latestClientId = this.connectedState.clientId;\n\n this.opsToBypassInitialDelay = opsToBypassInitialDelay;\n this.initialDelayMs = initialDelayMs;\n }\n\n /**\n * Until start is called, the SummaryManager won't begin attempting to start summarization. This ensures there's\n * a window between construction and starting where the caller can attach listeners.\n */\n public start(): void {\n this.clientElection.on(\"electedSummarizerChanged\", this.refreshSummarizer);\n this.refreshSummarizer();\n }\n\n private readonly handleConnected = (clientId: string) => {\n this.latestClientId = clientId;\n // If we have a summarizer, it should have been either cancelled on disconnected by now.\n // But because of lastSummary process, it can still hang around, so there is not much we can\n // check or assert.\n this.refreshSummarizer();\n };\n\n private readonly handleDisconnected = () => {\n this.refreshSummarizer();\n };\n\n private static readonly isStartingOrRunning = (state: SummaryManagerState) =>\n state === SummaryManagerState.Starting || state === SummaryManagerState.Running;\n\n private getShouldSummarizeState(): ShouldSummarizeState {\n if (!this.connectedState.connected) {\n return { shouldSummarize: false, stopReason: \"parentNotConnected\" };\n } else if (this.connectedState.clientId !== this.clientElection.electedClientId) {\n return { shouldSummarize: false, stopReason: \"parentShouldNotSummarize\" };\n } else if (this.disposed) {\n assert(false, 0x260 /* \"Disposed should mean disconnected!\" */);\n } else {\n return { shouldSummarize: true };\n }\n }\n\n private readonly refreshSummarizer = () => {\n // Transition states depending on shouldSummarize, which is a calculated property\n // that is only true if this client is connected and is the elected summarizer.\n const shouldSummarizeState = this.getShouldSummarizeState();\n switch (this.state) {\n case SummaryManagerState.Off: {\n if (shouldSummarizeState.shouldSummarize) {\n this.startSummarization();\n }\n return;\n }\n case SummaryManagerState.Starting: {\n // Cannot take any action until summarizer is created\n // state transition will occur after creation\n return;\n }\n case SummaryManagerState.Running: {\n if (shouldSummarizeState.shouldSummarize === false) {\n this.stop(shouldSummarizeState.stopReason);\n }\n return;\n }\n case SummaryManagerState.Stopping: {\n // Cannot take any action until running summarizer finishes\n // state transition will occur after it stops\n return;\n }\n default: {\n return;\n }\n }\n };\n\n private startSummarization() {\n assert(this.state === SummaryManagerState.Off, 0x261 /* \"Expected: off\" */);\n this.state = SummaryManagerState.Starting;\n\n assert(this.summarizer === undefined, 0x262 /* \"Old summarizer is still working!\" */);\n\n let reason = \"unknown\";\n\n this.delayBeforeCreatingSummarizer().then(async (startWithInitialDelay: boolean) => {\n // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore\n // but only if creation was delayed. If it was not, then we want to ensure we always create\n // a summarizer to kick off lastSummary. Without that, we would not be able to summarize and get\n // document out of broken state if it has too many ops and ordering service keeps nacking main\n // container (and thus it goes into cycle of reconnects)\n if (startWithInitialDelay && this.getShouldSummarizeState().shouldSummarize === false) {\n return;\n }\n\n const summarizer = await this.requestSummarizerFn();\n\n // Re-validate that it need to be running. Due to asynchrony, it may be not the case anymore\n const shouldSummarizeState = this.getShouldSummarizeState();\n if (shouldSummarizeState.shouldSummarize === false) {\n summarizer.stop(shouldSummarizeState.stopReason);\n return;\n }\n\n assert(this.state === SummaryManagerState.Starting, 0x263 /* \"Expected: starting\" */);\n this.state = SummaryManagerState.Running;\n\n this.summarizer = summarizer;\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const clientId = this.latestClientId!;\n\n reason = await PerformanceEvent.timedExecAsync(\n this.logger,\n { eventName: \"RunningSummarizer\", attempt: this.startThrottler.numAttempts },\n async () => summarizer.run(clientId, this.summarizerOptions),\n );\n }).catch((error) => {\n // Most of exceptions happen due to container being closed while loading it, due to\n // summarizer container loosing connection while load.\n // Not worth reporting such errors as errors. That said, we might miss some real errors if\n // we ignore blindly, so try to narrow signature we are looking for - skip logging\n // error only if this client should no longer be a summarizer (which in practice\n // means it also lost connection), and error happened on load (we do not have summarizer).\n // We could annotate the error raised in Container.load where the container closed during load with no error\n // and check for that case here, but that does not seem to be necessary.\n if (this.getShouldSummarizeState().shouldSummarize || this.summarizer !== undefined) {\n // Report any failure as an error unless it was due to cancellation (like \"disconnected\" error)\n // If failure happened on container load, we may not yet realized that socket disconnected, so check\n // offlineError.\n const category = error?.errorType === DriverErrorType.offlineError ? \"generic\" : \"error\";\n this.logger.sendTelemetryEvent(\n {\n eventName: \"SummarizerException\",\n category,\n },\n error);\n\n // Note that summarizer may keep going (like doing last summary).\n // Ideally we await stopping process, but this code path is due to a bug\n // that needs to be fixed either way.\n if (SummaryManager.isStartingOrRunning(this.state)) {\n this.stop(\"summarizerException\");\n }\n }\n }).finally(() => {\n assert(this.state !== SummaryManagerState.Off, 0x264 /* \"Expected: Not Off\" */);\n this.state = SummaryManagerState.Off;\n\n this.summarizer = undefined;\n\n this.logger.sendTelemetryEvent({\n eventName: \"EndingSummarizer\",\n reason,\n });\n\n if (this.getShouldSummarizeState().shouldSummarize) {\n this.startSummarization();\n }\n });\n }\n\n private stop(reason: SummarizerStopReason) {\n if (!SummaryManager.isStartingOrRunning(this.state)) {\n return;\n }\n this.state = SummaryManagerState.Stopping;\n\n // Stopping the running summarizer client should trigger a change\n // in states when the running summarizer closes\n this.summarizer?.stop(reason);\n }\n\n /**\n * Implements initial delay before creating summarizer\n * @returns true, if creation is delayed due to heuristics (not many ops to summarize).\n * False if summarizer should start immediately due to too many unsummarized ops.\n */\n private async delayBeforeCreatingSummarizer(): Promise<boolean> {\n // throttle creation of new summarizer containers to prevent spamming the server with websocket connections\n let delayMs = this.startThrottler.getDelay();\n\n // We have been elected the summarizer. Some day we may be able to summarize with a live document but for\n // now we play it safe and launch a second copy.\n this.logger.sendTelemetryEvent({\n eventName: \"CreatingSummarizer\",\n throttlerDelay: delayMs,\n initialDelay: this.initialDelayMs,\n startThrottlerMaxDelayMs: this.startThrottler.maxDelayMs,\n opsSinceLastAck: this.summaryCollection.opsSinceLastAck,\n opsToBypassInitialDelay: this.opsToBypassInitialDelay,\n });\n\n // This delay helps ensure that last summarizer that might be left from previous client\n // has enough time to complete its last summary and thus new summarizer not conflict with previous one.\n // If, however, there are too many unsummarized ops, try to resolve it as quickly as possible, with\n // understanding that we may see nacks because of such quick action.\n // A better design would be for summarizer election logic to always select current summarizer as\n // summarizing client (i.e. clientType === \"summarizer\" can be elected) to ensure that nobody else can\n // summarizer while it finishes its work and moves to exit.\n // It also helps with pure boot scenario (single client) to offset expensive work a bit out from\n // critical boot sequence.\n let startWithInitialDelay = false;\n if (this.summaryCollection.opsSinceLastAck < this.opsToBypassInitialDelay) {\n startWithInitialDelay = true;\n delayMs = Math.max(delayMs, this.initialDelayMs);\n }\n\n if (delayMs > 0) {\n let timer;\n let resolveOpPromiseFn;\n // Create a listener that will break the delay if we've exceeded the initial delay ops count.\n const opsListenerFn = () => {\n if (this.summaryCollection.opsSinceLastAck >= this.opsToBypassInitialDelay) {\n clearTimeout(timer);\n resolveOpPromiseFn();\n }\n };\n // Create a Promise that will resolve when the delay expires.\n const delayPromise = new Promise<void>((resolve) => {\n timer = setTimeout(() => resolve(), delayMs);\n });\n // Create a Promise that will resolve if the ops count passes the threshold.\n const opPromise = new Promise<void>((resolve) => { resolveOpPromiseFn = resolve; });\n this.summaryCollection.addOpListener(opsListenerFn);\n await Promise.race([ delayPromise, opPromise ]);\n this.summaryCollection.removeOpListener(opsListenerFn);\n }\n return startWithInitialDelay;\n }\n\n public readonly summarizeOnDemand: ISummarizer[\"summarizeOnDemand\"] = (...args) => {\n if (this.summarizer === undefined) {\n throw Error(\"No running summarizer client\");\n // TODO: could spawn a summarizer client temporarily.\n }\n return this.summarizer.summarizeOnDemand(...args);\n };\n\n public readonly enqueueSummarize: ISummarizer[\"enqueueSummarize\"] = (...args) => {\n if (this.summarizer === undefined) {\n throw Error(\"No running summarizer client\");\n // TODO: could spawn a summarizer client temporarily.\n }\n return this.summarizer.enqueueSummarize(...args);\n };\n\n public dispose() {\n this.clientElection.off(\"electedSummarizerChanged\", this.refreshSummarizer);\n this.connectedState.off(\"connected\", this.handleConnected);\n this.connectedState.off(\"disconnected\", this.handleDisconnected);\n this._disposed = true;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-runtime",
3
- "version": "0.58.3000-61081",
3
+ "version": "0.59.1001-62246",
4
4
  "description": "Fluid container runtime",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -64,29 +64,29 @@
64
64
  "dependencies": {
65
65
  "@fluidframework/common-definitions": "^0.20.1",
66
66
  "@fluidframework/common-utils": "^0.32.1",
67
- "@fluidframework/container-definitions": "^0.47.1000",
68
- "@fluidframework/container-runtime-definitions": "0.58.3000-61081",
69
- "@fluidframework/container-utils": "0.58.3000-61081",
70
- "@fluidframework/core-interfaces": "^0.42.0",
71
- "@fluidframework/datastore": "0.58.3000-61081",
72
- "@fluidframework/driver-definitions": "^0.45.2000-0",
73
- "@fluidframework/driver-utils": "0.58.3000-61081",
74
- "@fluidframework/garbage-collector": "0.58.3000-61081",
75
- "@fluidframework/protocol-base": "^0.1035.1000",
76
- "@fluidframework/protocol-definitions": "^0.1027.1000",
77
- "@fluidframework/runtime-definitions": "0.58.3000-61081",
78
- "@fluidframework/runtime-utils": "0.58.3000-61081",
79
- "@fluidframework/telemetry-utils": "0.58.3000-61081",
67
+ "@fluidframework/container-definitions": "^0.48.1000",
68
+ "@fluidframework/container-runtime-definitions": "0.59.1001-62246",
69
+ "@fluidframework/container-utils": "0.59.1001-62246",
70
+ "@fluidframework/core-interfaces": "^0.43.1000",
71
+ "@fluidframework/datastore": "0.59.1001-62246",
72
+ "@fluidframework/driver-definitions": "^0.46.1000",
73
+ "@fluidframework/driver-utils": "0.59.1001-62246",
74
+ "@fluidframework/garbage-collector": "0.59.1001-62246",
75
+ "@fluidframework/protocol-base": "^0.1036.1000",
76
+ "@fluidframework/protocol-definitions": "^0.1028.1000",
77
+ "@fluidframework/runtime-definitions": "0.59.1001-62246",
78
+ "@fluidframework/runtime-utils": "0.59.1001-62246",
79
+ "@fluidframework/telemetry-utils": "0.59.1001-62246",
80
80
  "double-ended-queue": "^2.1.0-0",
81
81
  "uuid": "^8.3.1"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@fluidframework/build-common": "^0.23.0",
85
- "@fluidframework/build-tools": "^0.2.60108",
86
- "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@0.58.2000",
87
- "@fluidframework/eslint-config-fluid": "^0.27.2000-59622",
88
- "@fluidframework/mocha-test-setup": "0.58.3000-61081",
89
- "@fluidframework/test-runtime-utils": "0.58.3000-61081",
85
+ "@fluidframework/build-tools": "^0.2.61288",
86
+ "@fluidframework/container-runtime-previous": "npm:@fluidframework/container-runtime@^0.58.0",
87
+ "@fluidframework/eslint-config-fluid": "^0.28.1000",
88
+ "@fluidframework/mocha-test-setup": "0.59.1001-62246",
89
+ "@fluidframework/test-runtime-utils": "0.59.1001-62246",
90
90
  "@microsoft/api-extractor": "^7.16.1",
91
91
  "@rushstack/eslint-config": "^2.5.1",
92
92
  "@types/double-ended-queue": "^2.1.0",
@@ -114,9 +114,9 @@
114
114
  "typescript-formatter": "7.1.0"
115
115
  },
116
116
  "typeValidation": {
117
- "version": "0.58.3000",
117
+ "version": "0.59.1000",
118
118
  "broken": {
119
- "0.58.2000": {
119
+ "0.58.2002": {
120
120
  "InterfaceDeclaration_IBaseSummarizeResult": {
121
121
  "forwardCompat": false
122
122
  },
@@ -137,6 +137,18 @@
137
137
  },
138
138
  "InterfaceDeclaration_IGeneratedSummaryStats": {
139
139
  "forwardCompat": false
140
+ },
141
+ "ClassDeclaration_ContainerRuntime": {
142
+ "forwardCompat": false
143
+ },
144
+ "InterfaceDeclaration_IGarbageCollectionRuntime": {
145
+ "forwardCompat": false
146
+ },
147
+ "InterfaceDeclaration_IGCStats": {
148
+ "forwardCompat": false
149
+ },
150
+ "InterfaceDeclaration_IRootSummaryTreeWithStats": {
151
+ "forwardCompat": false
140
152
  }
141
153
  }
142
154
  }
@@ -11,7 +11,7 @@ import { ITelemetryLogger } from "@fluidframework/common-definitions";
11
11
  import { assert, Deferred } from "@fluidframework/common-utils";
12
12
  import { IContainerRuntime } from "@fluidframework/container-runtime-definitions";
13
13
  import { AttachState } from "@fluidframework/container-definitions";
14
- import { ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
14
+ import { IGarbageCollectionData, ISummaryTreeWithStats } from "@fluidframework/runtime-definitions";
15
15
 
16
16
  /**
17
17
  * This class represents blob (long string)
@@ -205,6 +205,65 @@ export class BlobManager {
205
205
  });
206
206
  }
207
207
 
208
+ /**
209
+ * Generates data used for garbage collection. Each blob uploaded represents a node in the GC graph as it can be
210
+ * individually referenced by storing its handle in a referenced DDS. Returns the list of blob ids as GC nodes.
211
+ * @param fullGC - true to bypass optimizations and force full generation of GC data. BlobManager doesn't care
212
+ * about this for now because the data is a simple list of blob ids.
213
+ */
214
+ public getGCData(fullGC: boolean = false): IGarbageCollectionData {
215
+ const getGCNodePath = (blobId: string) => { return `/${BlobManager.basePath}/${blobId}`; };
216
+ const gcData: IGarbageCollectionData = { gcNodes: {} };
217
+ /**
218
+ * The node path is of the format `/_blobs/blobId`. This path must match the path of the blob handle returned
219
+ * by the createBlob API because blobs are marked referenced by storing these handles in a referenced DDS.
220
+ */
221
+ this.blobIds.forEach((blobId: string) => {
222
+ gcData.gcNodes[getGCNodePath(blobId)] = [];
223
+ });
224
+
225
+ /**
226
+ * For all blobs in the redirect table, the handle returned on creation is based off of the localId. So, these
227
+ * nodes can be referenced by storing the localId handle. When that happens, the corresponding storageId node
228
+ * must also be marked referenced. So, we add a route from the localId node to the storageId node.
229
+ * Note that because of de-duping, there can be multiple localIds that all redirect to the same storageId or
230
+ * a blob may be referenced via its storageId handle.
231
+ */
232
+ if (this.redirectTable !== undefined) {
233
+ for (const [localId, storageId] of this.redirectTable) {
234
+ // Add node for the localId and add a route to the storageId node. The storageId node will have been
235
+ // added above when adding nodes for this.blobIds.
236
+ gcData.gcNodes[getGCNodePath(localId)] = [getGCNodePath(storageId)];
237
+ }
238
+ }
239
+
240
+ return gcData;
241
+ }
242
+
243
+ /**
244
+ * When running GC in test mode, this is called to delete blobs that are unused.
245
+ * @param unusedRoutes - These are the blob node ids that are unused and should be deleted.
246
+ */
247
+ public deleteUnusedRoutes(unusedRoutes: string[]): void {
248
+ // The routes or blob node paths are in the same format as returned in getGCData - `/_blobs/blobId`.
249
+ for (const route of unusedRoutes) {
250
+ const pathParts = route.split("/");
251
+ assert(
252
+ pathParts.length === 3 && pathParts[1] === BlobManager.basePath,
253
+ 0x2d5 /* "Invalid blob node id in unused routes." */,
254
+ );
255
+ const blobId = pathParts[2];
256
+
257
+ // The unused blobId could be a localId. If so, remove it from the redirect table and continue. The
258
+ // corresponding storageId may still be used either directly or via other localIds.
259
+ if (this.redirectTable?.has(blobId)) {
260
+ this.redirectTable.delete(blobId);
261
+ continue;
262
+ }
263
+ this.blobIds.delete(blobId);
264
+ }
265
+ }
266
+
208
267
  public summarize(): ISummaryTreeWithStats {
209
268
  // If we have a redirect table it means the container is about to transition to "Attaching" state, so we need
210
269
  // to return an actual snapshot containing all the real storage IDs we know about.
@@ -109,16 +109,16 @@ class OpPerfTelemetry {
109
109
  if (msg.type === MessageType.Operation &&
110
110
  this.clientSequenceNumberForLatencyStatistics === msg.clientSequenceNumber) {
111
111
  assert(this.opProcessingTimes.opStartTimeSittingInboundQueue === undefined,
112
- "opStartTimeSittingInboundQueue should be undefined");
112
+ 0x2c8 /* "opStartTimeSittingInboundQueue should be undefined" */);
113
113
  assert(this.opPerfData.durationInboundQueue === undefined,
114
- "durationInboundQueue should be undefined");
114
+ 0x2c9 /* "durationInboundQueue should be undefined" */);
115
115
  this.opProcessingTimes.opStartTimeSittingInboundQueue = Date.now();
116
116
 
117
117
  assert(this.opPerfData.durationOutboundQueue === undefined,
118
- "durationOutboundQueue should be undefined");
118
+ 0x2ca /* "durationOutboundQueue should be undefined" */);
119
119
 
120
120
  assert(this.opProcessingTimes.opStartTimeForLatencyStatistics !== undefined,
121
- "opStartTimeForLatencyStatistics should be undefined");
121
+ 0x2cb /* "opStartTimeForLatencyStatistics should be undefined" */);
122
122
 
123
123
  this.opPerfData.durationOutboundQueue = this.opProcessingTimes.opStartTimeSittingInboundQueue
124
124
  - this.opProcessingTimes.opStartTimeForLatencyStatistics;
@@ -188,9 +188,9 @@ class OpPerfTelemetry {
188
188
  if (this.clientSequenceNumberForLatencyStatistics === undefined &&
189
189
  message.clientSequenceNumber % 500 === 1) {
190
190
  assert(this.opProcessingTimes.opStartTimeSittingInboundQueue === undefined,
191
- "OpTimeSittingInboundQueue should be undefined");
191
+ 0x2cc /* "OpTimeSittingInboundQueue should be undefined" */);
192
192
  assert(this.opPerfData.durationInboundQueue === undefined,
193
- "durationInboundQueue should be undefined");
193
+ 0x2cd /* "durationInboundQueue should be undefined" */);
194
194
  this.opProcessingTimes.opStartTimeForLatencyStatistics = Date.now();
195
195
  this.clientSequenceNumberForLatencyStatistics = message.clientSequenceNumber;
196
196
  }
@@ -211,7 +211,7 @@ class OpPerfTelemetry {
211
211
  if (this.sequenceNumberForMsnTracking !== undefined &&
212
212
  message.minimumSequenceNumber >= this.sequenceNumberForMsnTracking) {
213
213
  assert(this.msnTrackingTimestamp !== undefined,
214
- "msnTrackingTimestamp should not be undefined");
214
+ 0x2ce /* "msnTrackingTimestamp should not be undefined" */);
215
215
  this.logger.sendPerformanceEvent({
216
216
  eventName: "MsnStatistics",
217
217
  sequenceNumber,
@@ -10,7 +10,6 @@ import {
10
10
  FluidObject,
11
11
  IFluidHandle,
12
12
  IFluidHandleContext,
13
- IFluidObject,
14
13
  IFluidRouter,
15
14
  IRequest,
16
15
  IResponse,
@@ -101,6 +100,7 @@ import {
101
100
  seqFromTree,
102
101
  calculateStats,
103
102
  } from "@fluidframework/runtime-utils";
103
+ import { GCDataBuilder } from "@fluidframework/garbage-collector";
104
104
  import { v4 as uuid } from "uuid";
105
105
  import { ContainerFluidHandleContext } from "./containerHandleContext";
106
106
  import { FluidDataStoreRegistry } from "./dataStoreRegistry";
@@ -141,6 +141,7 @@ import { formExponentialFn, Throttler } from "./throttler";
141
141
  import { RunWhileConnectedCoordinator } from "./runWhileConnectedCoordinator";
142
142
  import {
143
143
  GarbageCollector,
144
+ GCNodeType,
144
145
  gcTreeKey,
145
146
  IGarbageCollectionRuntime,
146
147
  IGarbageCollector,
@@ -637,7 +638,7 @@ export class ScheduleManager {
637
638
 
638
639
  // This could be the beginning of a new batch or an individual message.
639
640
  this.emitter.emit("batchBegin", message);
640
- this.deltaScheduler.batchBegin();
641
+ this.deltaScheduler.batchBegin(message);
641
642
 
642
643
  const batch = (message?.metadata as IRuntimeMessageMetadata)?.batch;
643
644
  if (batch) {
@@ -658,7 +659,7 @@ export class ScheduleManager {
658
659
  this.hitError = true;
659
660
  this.batchClientId = undefined;
660
661
  this.emitter.emit("batchEnd", error, message);
661
- this.deltaScheduler.batchEnd();
662
+ this.deltaScheduler.batchEnd(message);
662
663
  return;
663
664
  }
664
665
 
@@ -668,7 +669,7 @@ export class ScheduleManager {
668
669
  if (this.batchClientId === undefined || batch === false) {
669
670
  this.batchClientId = undefined;
670
671
  this.emitter.emit("batchEnd", undefined, message);
671
- this.deltaScheduler.batchEnd();
672
+ this.deltaScheduler.batchEnd(message);
672
673
  return;
673
674
  }
674
675
  }
@@ -907,7 +908,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
907
908
  return this._flushMode;
908
909
  }
909
910
 
910
- public get scope(): IFluidObject & FluidObject {
911
+ public get scope(): FluidObject {
911
912
  return this.containerScope;
912
913
  }
913
914
 
@@ -1076,13 +1077,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1076
1077
  this.garbageCollector = GarbageCollector.create(
1077
1078
  this,
1078
1079
  this.runtimeOptions.gcOptions,
1079
- (unusedRoutes: string[]) => this.dataStores.deleteUnusedRoutes(unusedRoutes),
1080
- (nodePath: string) => this.dataStores.getNodePackagePath(nodePath),
1081
- /**
1082
- * Returns the timestamp of the last message seen by this client. This is used by garbage collector as
1083
- * the current reference timestamp for tracking unreferenced objects.
1084
- */
1085
- () => this.deltaManager.lastMessage?.timestamp ?? this.messageAtLastSummary?.timestamp,
1080
+ (nodePath: string) => this.getGCNodePackagePath(nodePath),
1086
1081
  () => this.messageAtLastSummary?.timestamp,
1087
1082
  context.baseSnapshot,
1088
1083
  async <T>(id: string) => readAndParse<T>(this.storage, id),
@@ -1134,7 +1129,7 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1134
1129
  ),
1135
1130
  (id: string) => this.summarizerNode.deleteChild(id),
1136
1131
  this.mc.logger,
1137
- async () => this.garbageCollector.getDataStoreBaseGCDetails(),
1132
+ async () => this.garbageCollector.getBaseGCDetails(),
1138
1133
  (path: string, timestampMs: number, packagePath?: readonly string[]) => this.garbageCollector.nodeUpdated(
1139
1134
  path,
1140
1135
  "Changed",
@@ -1833,7 +1828,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1833
1828
  */
1834
1829
  private async createRootDataStoreLegacy(pkg: string | string[], rootDataStoreId: string): Promise<IFluidRouter> {
1835
1830
  const fluidDataStore = await this._createDataStore(pkg, true /* isRoot */, rootDataStoreId);
1836
- fluidDataStore.bindToContext();
1831
+ // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
1832
+ // older versions, we still have to call bindToContext.
1833
+ if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1834
+ fluidDataStore.makeVisibleAndAttachGraph();
1835
+ } else {
1836
+ fluidDataStore.bindToContext();
1837
+ }
1837
1838
  return fluidDataStore;
1838
1839
  }
1839
1840
 
@@ -1905,7 +1906,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
1905
1906
  const fluidDataStore = await this.dataStores._createFluidDataStoreContext(
1906
1907
  Array.isArray(pkg) ? pkg : [pkg], id, isRoot, props).realize();
1907
1908
  if (isRoot) {
1908
- fluidDataStore.bindToContext();
1909
+ // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
1910
+ // For older versions, we still have to call bindToContext.
1911
+ if (fluidDataStore.makeVisibleAndAttachGraph !== undefined) {
1912
+ fluidDataStore.makeVisibleAndAttachGraph();
1913
+ } else {
1914
+ fluidDataStore.bindToContext();
1915
+ }
1909
1916
  this.logger.sendTelemetryEvent({
1910
1917
  eventName: "Root datastore with props",
1911
1918
  hasProps: props !== undefined,
@@ -2111,7 +2118,13 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2111
2118
  * @param fullGC - true to bypass optimizations and force full generation of GC data.
2112
2119
  */
2113
2120
  public async getGCData(fullGC?: boolean): Promise<IGarbageCollectionData> {
2114
- return this.dataStores.getGCData(fullGC);
2121
+ const builder = new GCDataBuilder();
2122
+ const dsGCData = await this.dataStores.getGCData(fullGC);
2123
+ builder.addNodes(dsGCData.gcNodes);
2124
+
2125
+ const blobsGCData = this.blobManager.getGCData(fullGC);
2126
+ builder.addNodes(blobsGCData.gcNodes);
2127
+ return builder.getGCData();
2115
2128
  }
2116
2129
 
2117
2130
  /**
@@ -2127,7 +2140,83 @@ export class ContainerRuntime extends TypedEventEmitter<IContainerRuntimeEvents>
2127
2140
  // always referenced, so the used routes is only self-route (empty string).
2128
2141
  this.summarizerNode.updateUsedRoutes([""]);
2129
2142
 
2130
- return this.dataStores.updateUsedRoutes(usedRoutes, gcTimestamp);
2143
+ const dataStoreUsedRoutes: string[] = [];
2144
+ for (const route of usedRoutes) {
2145
+ if (route.split("/")[1] !== BlobManager.basePath) {
2146
+ dataStoreUsedRoutes.push(route);
2147
+ }
2148
+ }
2149
+
2150
+ return this.dataStores.updateUsedRoutes(dataStoreUsedRoutes, gcTimestamp);
2151
+ }
2152
+
2153
+ /**
2154
+ * When running GC in test mode, this is called to delete objects whose routes are unused. This enables testing
2155
+ * scenarios with accessing deleted content.
2156
+ * @param unusedRoutes - The routes that are unused in all data stores in this Container.
2157
+ */
2158
+ public deleteUnusedRoutes(unusedRoutes: string[]) {
2159
+ const blobManagerUnusedRoutes: string[] = [];
2160
+ const dataStoreUnusedRoutes: string[] = [];
2161
+ for (const route of unusedRoutes) {
2162
+ if (this.isBlobPath(route)) {
2163
+ blobManagerUnusedRoutes.push(route);
2164
+ } else {
2165
+ dataStoreUnusedRoutes.push(route);
2166
+ }
2167
+ }
2168
+
2169
+ this.blobManager.deleteUnusedRoutes(blobManagerUnusedRoutes);
2170
+ this.dataStores.deleteUnusedRoutes(dataStoreUnusedRoutes);
2171
+ }
2172
+
2173
+ /**
2174
+ * Returns a server generated referenced timestamp to be used to track unreferenced nodes by GC.
2175
+ */
2176
+ public getCurrentReferenceTimestampMs(): number | undefined {
2177
+ // Use the timestamp of the last message seen by this client as that is server generated. If no messages have
2178
+ // been processed, use the timestamp of the message from the last summary.
2179
+ return this.deltaManager.lastMessage?.timestamp ?? this.messageAtLastSummary?.timestamp;
2180
+ }
2181
+
2182
+ /**
2183
+ * Returns the type of the GC node. Currently, there are nodes that belong to data store and nodes that belong
2184
+ * to the blob manager.
2185
+ */
2186
+ public getNodeType(nodePath: string): GCNodeType {
2187
+ if (this.isBlobPath(nodePath)) {
2188
+ return GCNodeType.Blob;
2189
+ }
2190
+ if (this.dataStores.isDataStoreNode(nodePath)) {
2191
+ return GCNodeType.DataStore;
2192
+ }
2193
+ // Root node ("/") and DDS nodes belong to "Other" node types.
2194
+ return GCNodeType.Other;
2195
+ }
2196
+
2197
+ /**
2198
+ * Called by GC to retrieve the package path of the node with the given path. The node should belong to a
2199
+ * data store or an attachment blob.
2200
+ */
2201
+ public getGCNodePackagePath(nodePath: string): readonly string[] | undefined {
2202
+ // If the node is a blob, return "_blobs" as the package path.
2203
+ if (this.isBlobPath(nodePath)) {
2204
+ return ["_blobs"];
2205
+ }
2206
+ const dataStorePkgPath = this.dataStores.getDataStorePackagePath(nodePath);
2207
+ assert(dataStorePkgPath !== undefined, 0x2d6 /* "Package path requested for unknown node type." */);
2208
+ return dataStorePkgPath;
2209
+ }
2210
+
2211
+ /**
2212
+ * Returns whether a given path is for attachment blobs that are in the format - "/BlobManager.basePath/...".
2213
+ */
2214
+ private isBlobPath(path: string): boolean {
2215
+ const pathParts = path.split("/");
2216
+ if (pathParts.length < 2 || pathParts[1] !== BlobManager.basePath) {
2217
+ return false;
2218
+ }
2219
+ return true;
2131
2220
  }
2132
2221
 
2133
2222
  /**
package/src/dataStore.ts CHANGED
@@ -75,7 +75,13 @@ class DataStore implements IDataStore {
75
75
  alias,
76
76
  };
77
77
 
78
- this.fluidDataStoreChannel.bindToContext();
78
+ // back-compat 0.58.2000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel. For
79
+ // older versions, we still have to call bindToContext.
80
+ if (this.fluidDataStoreChannel.makeVisibleAndAttachGraph !== undefined) {
81
+ this.fluidDataStoreChannel.makeVisibleAndAttachGraph();
82
+ } else {
83
+ this.fluidDataStoreChannel.bindToContext();
84
+ }
79
85
 
80
86
  if (this.runtime.attachState === AttachState.Detached) {
81
87
  const localResult = this.datastores.processAliasMessageCore(message);
@@ -132,7 +132,7 @@ export interface ILocalFluidDataStoreContextProps extends IFluidDataStoreContext
132
132
  readonly pkg: Readonly<string[]> | undefined;
133
133
  readonly snapshotTree: ISnapshotTree | undefined;
134
134
  readonly isRootDataStore: boolean | undefined;
135
- readonly bindChannelFn: (channel: IFluidDataStoreChannel) => void;
135
+ readonly makeLocallyVisibleFn: () => void;
136
136
  /**
137
137
  * @deprecated 0.16 Issue #1635, #3631
138
138
  */
@@ -261,7 +261,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
261
261
  private readonly existing: boolean,
262
262
  private bindState: BindState,
263
263
  public readonly isLocalDataStore: boolean,
264
- bindChannelFn: (channel: IFluidDataStoreChannel) => void,
264
+ private readonly makeLocallyVisibleFn: () => void,
265
265
  ) {
266
266
  super();
267
267
 
@@ -284,7 +284,7 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
284
284
  assert(this.bindState === BindState.NotBound, 0x13b /* "datastore context is already in bound state" */);
285
285
  this.bindState = BindState.Binding;
286
286
  assert(this.channel !== undefined, 0x13c /* "undefined channel on datastore context" */);
287
- bindChannelFn(this.channel);
287
+ this.makeLocallyVisible();
288
288
  this.bindState = BindState.Bound;
289
289
  };
290
290
 
@@ -627,6 +627,15 @@ export abstract class FluidDataStoreContext extends TypedEventEmitter<IFluidData
627
627
  return this._containerRuntime.submitDataStoreSignal(this.id, type, content);
628
628
  }
629
629
 
630
+ /**
631
+ * This is called by the data store channel when it becomes locally visible indicating that it is ready to become
632
+ * globally visible now.
633
+ */
634
+ public makeLocallyVisible() {
635
+ assert(this.channel !== undefined, 0x2cf /* "undefined channel on datastore context" */);
636
+ this.makeLocallyVisibleFn();
637
+ }
638
+
630
639
  protected bindRuntime(channel: IFluidDataStoreChannel) {
631
640
  if (this.channel) {
632
641
  throw new Error("Runtime already bound");
@@ -864,7 +873,7 @@ export class LocalFluidDataStoreContextBase extends FluidDataStoreContext {
864
873
  props.snapshotTree !== undefined ? true : false /* existing */,
865
874
  props.snapshotTree ? BindState.Bound : BindState.NotBound,
866
875
  true /* isLocalDataStore */,
867
- props.bindChannelFn,
876
+ props.makeLocallyVisibleFn,
868
877
  );
869
878
 
870
879
  this.snapshotTree = props.snapshotTree;
@@ -993,7 +1002,7 @@ export class LocalDetachedFluidDataStoreContext
993
1002
 
994
1003
  public async attachRuntime(
995
1004
  registry: IProvideFluidDataStoreFactory,
996
- dataStoreRuntime: IFluidDataStoreChannel)
1005
+ dataStoreChannel: IFluidDataStoreChannel)
997
1006
  {
998
1007
  assert(this.detachedRuntimeCreation, 0x154 /* "runtime creation is already attached" */);
999
1008
  assert(this.channelDeferred === undefined, 0x155 /* "channel deferral is already set" */);
@@ -1009,10 +1018,16 @@ export class LocalDetachedFluidDataStoreContext
1009
1018
  this.detachedRuntimeCreation = false;
1010
1019
  this.channelDeferred = new Deferred<IFluidDataStoreChannel>();
1011
1020
 
1012
- super.bindRuntime(dataStoreRuntime);
1021
+ super.bindRuntime(dataStoreChannel);
1013
1022
 
1014
1023
  if (await this.isRoot()) {
1015
- dataStoreRuntime.bindToContext();
1024
+ // back-compat 0.59.1000 - makeVisibleAndAttachGraph was added in this version to IFluidDataStoreChannel.
1025
+ // For older versions, we still have to call bindToContext.
1026
+ if (dataStoreChannel.makeVisibleAndAttachGraph !== undefined) {
1027
+ dataStoreChannel.makeVisibleAndAttachGraph();
1028
+ } else {
1029
+ dataStoreChannel.bindToContext();
1030
+ }
1016
1031
  }
1017
1032
  }
1018
1033