@fluidframework/container-runtime 2.0.0-internal.7.0.1 → 2.0.0-internal.7.1.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 +8 -0
- package/api-extractor.json +13 -1
- package/api-report/container-runtime.api.md +800 -0
- package/dist/blobManager.js +1 -1
- package/dist/blobManager.js.map +1 -1
- package/dist/connectionTelemetry.d.ts.map +1 -1
- package/dist/connectionTelemetry.js +75 -42
- package/dist/connectionTelemetry.js.map +1 -1
- package/dist/container-runtime-alpha.d.ts +1554 -0
- package/dist/container-runtime-beta.d.ts +1554 -0
- package/dist/container-runtime-public.d.ts +1554 -0
- package/dist/container-runtime.d.ts +1611 -0
- package/dist/containerRuntime.d.ts +20 -14
- package/dist/containerRuntime.d.ts.map +1 -1
- package/dist/containerRuntime.js +53 -27
- package/dist/containerRuntime.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/gc/gcDefinitions.d.ts +2 -2
- package/dist/gc/gcDefinitions.d.ts.map +1 -1
- package/dist/gc/gcDefinitions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/opLifecycle/outbox.d.ts.map +1 -1
- package/dist/opLifecycle/outbox.js +7 -2
- package/dist/opLifecycle/outbox.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/pendingStateManager.d.ts.map +1 -1
- package/dist/pendingStateManager.js +3 -1
- package/dist/pendingStateManager.js.map +1 -1
- package/dist/scheduleManager.js +6 -2
- package/dist/scheduleManager.js.map +1 -1
- package/dist/summary/summarizer.d.ts +1 -0
- package/dist/summary/summarizer.d.ts.map +1 -1
- package/dist/summary/summarizer.js +14 -7
- package/dist/summary/summarizer.js.map +1 -1
- package/dist/summary/summaryCollection.d.ts.map +1 -1
- package/dist/summary/summaryCollection.js +1 -0
- package/dist/summary/summaryCollection.js.map +1 -1
- package/dist/summary/summaryGenerator.js.map +1 -1
- package/dist/summary/summaryManager.d.ts +2 -2
- package/dist/summary/summaryManager.d.ts.map +1 -1
- package/dist/summary/summaryManager.js +3 -3
- package/dist/summary/summaryManager.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/blobManager.js +1 -1
- package/lib/blobManager.js.map +1 -1
- package/lib/connectionTelemetry.d.ts.map +1 -1
- package/lib/connectionTelemetry.js +76 -43
- package/lib/connectionTelemetry.js.map +1 -1
- package/lib/containerRuntime.d.ts +20 -14
- package/lib/containerRuntime.d.ts.map +1 -1
- package/lib/containerRuntime.js +52 -29
- package/lib/containerRuntime.js.map +1 -1
- package/lib/error.d.ts.map +1 -1
- package/lib/error.js.map +1 -1
- package/lib/gc/gcDefinitions.d.ts +2 -2
- package/lib/gc/gcDefinitions.d.ts.map +1 -1
- package/lib/gc/gcDefinitions.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/opLifecycle/outbox.d.ts.map +1 -1
- package/lib/opLifecycle/outbox.js +7 -2
- package/lib/opLifecycle/outbox.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/pendingStateManager.d.ts.map +1 -1
- package/lib/pendingStateManager.js +3 -1
- package/lib/pendingStateManager.js.map +1 -1
- package/lib/scheduleManager.js +6 -2
- package/lib/scheduleManager.js.map +1 -1
- package/lib/summary/summarizer.d.ts +1 -0
- package/lib/summary/summarizer.d.ts.map +1 -1
- package/lib/summary/summarizer.js +15 -8
- package/lib/summary/summarizer.js.map +1 -1
- package/lib/summary/summaryCollection.d.ts.map +1 -1
- package/lib/summary/summaryCollection.js +1 -0
- package/lib/summary/summaryCollection.js.map +1 -1
- package/lib/summary/summaryGenerator.js.map +1 -1
- package/lib/summary/summaryManager.d.ts +2 -2
- package/lib/summary/summaryManager.d.ts.map +1 -1
- package/lib/summary/summaryManager.js +3 -3
- package/lib/summary/summaryManager.js.map +1 -1
- package/package.json +25 -53
- package/src/blobManager.ts +1 -1
- package/src/connectionTelemetry.ts +97 -52
- package/src/containerRuntime.ts +70 -43
- package/src/error.ts +4 -1
- package/src/gc/gcDefinitions.ts +2 -2
- package/src/index.ts +1 -0
- package/src/opLifecycle/README.md +53 -28
- package/src/opLifecycle/outbox.ts +3 -0
- package/src/packageVersion.ts +1 -1
- package/src/pendingStateManager.ts +1 -0
- package/src/scheduleManager.ts +2 -0
- package/src/summary/summarizer.ts +20 -9
- package/src/summary/summaryCollection.ts +1 -0
- package/src/summary/summaryGenerator.ts +3 -3
- package/src/summary/summaryManager.ts +2 -2
package/src/containerRuntime.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ICriticalContainerError,
|
|
24
24
|
AttachState,
|
|
25
25
|
ILoaderOptions,
|
|
26
|
+
ILoader,
|
|
26
27
|
LoaderHeader,
|
|
27
28
|
} from "@fluidframework/container-definitions";
|
|
28
29
|
import {
|
|
@@ -99,12 +100,11 @@ import {
|
|
|
99
100
|
create404Response,
|
|
100
101
|
exceptionToResponse,
|
|
101
102
|
GCDataBuilder,
|
|
102
|
-
// eslint-disable-next-line import/no-deprecated
|
|
103
|
-
requestFluidObject,
|
|
104
103
|
seqFromTree,
|
|
105
104
|
calculateStats,
|
|
106
105
|
TelemetryContext,
|
|
107
106
|
ReadAndParseBlob,
|
|
107
|
+
responseToException,
|
|
108
108
|
} from "@fluidframework/runtime-utils";
|
|
109
109
|
import { v4 as uuid } from "uuid";
|
|
110
110
|
import { ContainerFluidHandleContext } from "./containerHandleContext";
|
|
@@ -144,7 +144,6 @@ import {
|
|
|
144
144
|
IConnectableRuntime,
|
|
145
145
|
IGeneratedSummaryStats,
|
|
146
146
|
ISubmitSummaryOptions,
|
|
147
|
-
ISummarizer,
|
|
148
147
|
ISummarizerInternalsProvider,
|
|
149
148
|
ISummarizerRuntime,
|
|
150
149
|
IRefreshSummaryAckOptions,
|
|
@@ -157,6 +156,7 @@ import {
|
|
|
157
156
|
EnqueueSummarizeResult,
|
|
158
157
|
ISummarizerEvents,
|
|
159
158
|
IBaseSummarizeResult,
|
|
159
|
+
ISummarizer,
|
|
160
160
|
} from "./summary";
|
|
161
161
|
import { formExponentialFn, Throttler } from "./throttler";
|
|
162
162
|
import {
|
|
@@ -354,16 +354,17 @@ export interface ISummaryRuntimeOptions {
|
|
|
354
354
|
|
|
355
355
|
/**
|
|
356
356
|
* Options for op compression.
|
|
357
|
-
* @experimental - Not ready for use
|
|
358
357
|
*/
|
|
359
358
|
export interface ICompressionRuntimeOptions {
|
|
360
359
|
/**
|
|
361
|
-
* The
|
|
360
|
+
* The value the batch's content size must exceed for the batch to be compressed.
|
|
361
|
+
* By default the value is 600 * 1024 = 614400 bytes. If the value is set to `Infinity`, compression will be disabled.
|
|
362
362
|
*/
|
|
363
363
|
readonly minimumBatchSizeInBytes: number;
|
|
364
364
|
|
|
365
365
|
/**
|
|
366
366
|
* The compression algorithm that will be used to compress the op.
|
|
367
|
+
* By default the value is `lz4` which is the only compression algorithm currently supported.
|
|
367
368
|
*/
|
|
368
369
|
readonly compressionAlgorithm: CompressionAlgorithms;
|
|
369
370
|
}
|
|
@@ -391,8 +392,7 @@ export interface IContainerRuntimeOptions {
|
|
|
391
392
|
*/
|
|
392
393
|
readonly flushMode?: FlushMode;
|
|
393
394
|
/**
|
|
394
|
-
* Enables the runtime to compress ops.
|
|
395
|
-
* @experimental Not ready for use.
|
|
395
|
+
* Enables the runtime to compress ops. See {@link ICompressionRuntimeOptions}.
|
|
396
396
|
*/
|
|
397
397
|
readonly compressionOptions?: ICompressionRuntimeOptions;
|
|
398
398
|
/**
|
|
@@ -409,12 +409,15 @@ export interface IContainerRuntimeOptions {
|
|
|
409
409
|
/**
|
|
410
410
|
* If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
|
|
411
411
|
* how large the individual chunks will be. This is only supported when compression is enabled. If after compression, the
|
|
412
|
-
* batch size exceeds this value, it will be chunked into smaller ops of this size.
|
|
412
|
+
* batch content size exceeds this value, it will be chunked into smaller ops of this exact size.
|
|
413
413
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
414
|
+
* This value is a trade-off between having many small chunks vs fewer larger chunks and by default, the runtime is configured to use
|
|
415
|
+
* 200 * 1024 = 204800 bytes. This default value ensures that no compressed payload's content is able to exceed {@link IContainerRuntimeOptions.maxBatchSizeInBytes}
|
|
416
|
+
* regardless of the overhead of an individual op.
|
|
416
417
|
*
|
|
417
|
-
* @
|
|
418
|
+
* Any value of `chunkSizeInBytes` exceeding {@link IContainerRuntimeOptions.maxBatchSizeInBytes} will disable this feature, therefore if a compressed batch's content
|
|
419
|
+
* size exceeds {@link IContainerRuntimeOptions.maxBatchSizeInBytes} after compression, the container will close with an instance of `GenericError` with
|
|
420
|
+
* the `BatchTooLarge` message.
|
|
418
421
|
*/
|
|
419
422
|
readonly chunkSizeInBytes?: number;
|
|
420
423
|
|
|
@@ -630,6 +633,53 @@ type MessageWithContext =
|
|
|
630
633
|
local: boolean;
|
|
631
634
|
};
|
|
632
635
|
|
|
636
|
+
const summarizerRequestUrl = "_summarizer";
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Create and retrieve the summmarizer
|
|
640
|
+
*/
|
|
641
|
+
async function createSummarizer(loader: ILoader, url: string): Promise<ISummarizer> {
|
|
642
|
+
const request: IRequest = {
|
|
643
|
+
headers: {
|
|
644
|
+
[LoaderHeader.cache]: false,
|
|
645
|
+
[LoaderHeader.clientDetails]: {
|
|
646
|
+
capabilities: { interactive: false },
|
|
647
|
+
type: summarizerClientType,
|
|
648
|
+
},
|
|
649
|
+
[DriverHeader.summarizingClient]: true,
|
|
650
|
+
[LoaderHeader.reconnect]: false,
|
|
651
|
+
},
|
|
652
|
+
url,
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const resolvedContainer = await loader.resolve(request);
|
|
656
|
+
let fluidObject: FluidObject<ISummarizer> | undefined;
|
|
657
|
+
|
|
658
|
+
// Older containers may not have the "getEntryPoint" API
|
|
659
|
+
// ! This check will need to stay until LTS of loader moves past 2.0.0-internal.7.0.0
|
|
660
|
+
if (resolvedContainer.getEntryPoint !== undefined) {
|
|
661
|
+
fluidObject = await resolvedContainer.getEntryPoint();
|
|
662
|
+
} else {
|
|
663
|
+
const response = await resolvedContainer.request({ url: `/${summarizerRequestUrl}` });
|
|
664
|
+
if (response.status !== 200 || response.mimeType !== "fluid/object") {
|
|
665
|
+
throw responseToException(response, request);
|
|
666
|
+
}
|
|
667
|
+
fluidObject = response.value;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (fluidObject?.ISummarizer === undefined) {
|
|
671
|
+
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
672
|
+
}
|
|
673
|
+
return fluidObject.ISummarizer;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* This function is not supported publicly and exists for e2e testing
|
|
678
|
+
*/
|
|
679
|
+
export async function TEST_requestSummarizer(loader: ILoader, url: string): Promise<ISummarizer> {
|
|
680
|
+
return createSummarizer(loader, url);
|
|
681
|
+
}
|
|
682
|
+
|
|
633
683
|
/**
|
|
634
684
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
635
685
|
* It will define the store level mappings.
|
|
@@ -1578,7 +1628,7 @@ export class ContainerRuntime
|
|
|
1578
1628
|
this, // IConnectedState
|
|
1579
1629
|
this.summaryCollection,
|
|
1580
1630
|
this.logger,
|
|
1581
|
-
this.
|
|
1631
|
+
this.formCreateSummarizerFn(loader),
|
|
1582
1632
|
new Throttler(
|
|
1583
1633
|
60 * 1000, // 60 sec delay window
|
|
1584
1634
|
30 * 1000, // 30 sec max delay
|
|
@@ -1684,7 +1734,7 @@ export class ContainerRuntime
|
|
|
1684
1734
|
const parser = RequestParser.create(request);
|
|
1685
1735
|
const id = parser.pathParts[0];
|
|
1686
1736
|
|
|
1687
|
-
if (id ===
|
|
1737
|
+
if (id === summarizerRequestUrl && parser.pathParts.length === 1) {
|
|
1688
1738
|
if (this._summarizer !== undefined) {
|
|
1689
1739
|
return {
|
|
1690
1740
|
status: 200,
|
|
@@ -1695,6 +1745,7 @@ export class ContainerRuntime
|
|
|
1695
1745
|
return create404Response(request);
|
|
1696
1746
|
}
|
|
1697
1747
|
if (this.requestHandler !== undefined) {
|
|
1748
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1698
1749
|
return this.requestHandler(parser, this);
|
|
1699
1750
|
}
|
|
1700
1751
|
|
|
@@ -1714,6 +1765,7 @@ export class ContainerRuntime
|
|
|
1714
1765
|
const id = requestParser.pathParts[0];
|
|
1715
1766
|
|
|
1716
1767
|
if (id === "_channels") {
|
|
1768
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1717
1769
|
return this.resolveHandle(requestParser.createSubRequest(1));
|
|
1718
1770
|
}
|
|
1719
1771
|
|
|
@@ -1738,6 +1790,7 @@ export class ContainerRuntime
|
|
|
1738
1790
|
subRequest.url.startsWith("/"),
|
|
1739
1791
|
0x126 /* "Expected createSubRequest url to include a leading slash" */,
|
|
1740
1792
|
);
|
|
1793
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1741
1794
|
return dataStore.request(subRequest);
|
|
1742
1795
|
}
|
|
1743
1796
|
|
|
@@ -3928,37 +3981,11 @@ export class ContainerRuntime
|
|
|
3928
3981
|
}
|
|
3929
3982
|
|
|
3930
3983
|
/**
|
|
3931
|
-
*
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
// eslint-disable-next-line import/no-deprecated
|
|
3935
|
-
private formRequestSummarizerFn(loaderRouter: IFluidRouter) {
|
|
3984
|
+
* Forms a function that will create and retrieve a Summarizer.
|
|
3985
|
+
*/
|
|
3986
|
+
private formCreateSummarizerFn(loader: ILoader) {
|
|
3936
3987
|
return async () => {
|
|
3937
|
-
|
|
3938
|
-
headers: {
|
|
3939
|
-
[LoaderHeader.cache]: false,
|
|
3940
|
-
[LoaderHeader.clientDetails]: {
|
|
3941
|
-
capabilities: { interactive: false },
|
|
3942
|
-
type: summarizerClientType,
|
|
3943
|
-
},
|
|
3944
|
-
[DriverHeader.summarizingClient]: true,
|
|
3945
|
-
[LoaderHeader.reconnect]: false,
|
|
3946
|
-
},
|
|
3947
|
-
url: "/_summarizer",
|
|
3948
|
-
};
|
|
3949
|
-
|
|
3950
|
-
// eslint-disable-next-line import/no-deprecated
|
|
3951
|
-
const fluidObject = await requestFluidObject<FluidObject<ISummarizer>>(
|
|
3952
|
-
loaderRouter,
|
|
3953
|
-
request,
|
|
3954
|
-
);
|
|
3955
|
-
const summarizer = fluidObject.ISummarizer;
|
|
3956
|
-
|
|
3957
|
-
if (!summarizer) {
|
|
3958
|
-
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
3959
|
-
}
|
|
3960
|
-
|
|
3961
|
-
return summarizer;
|
|
3988
|
+
return createSummarizer(loader, `/${summarizerRequestUrl}`);
|
|
3962
3989
|
};
|
|
3963
3990
|
}
|
|
3964
3991
|
|
package/src/error.ts
CHANGED
|
@@ -12,7 +12,10 @@ import { IFluidErrorBase, LoggingError } from "@fluidframework/telemetry-utils";
|
|
|
12
12
|
export class ClientSessionExpiredError extends LoggingError implements IFluidErrorBase {
|
|
13
13
|
readonly errorType = ContainerErrorTypes.clientSessionExpiredError;
|
|
14
14
|
|
|
15
|
-
constructor(
|
|
15
|
+
constructor(
|
|
16
|
+
message: string,
|
|
17
|
+
readonly expiryMs: number,
|
|
18
|
+
) {
|
|
16
19
|
super(message, { timeoutMs: expiryMs });
|
|
17
20
|
}
|
|
18
21
|
}
|
package/src/gc/gcDefinitions.ts
CHANGED
|
@@ -166,7 +166,7 @@ export const GCNodeType = {
|
|
|
166
166
|
// Nodes that are neither of the above. For example, root node.
|
|
167
167
|
Other: "Other",
|
|
168
168
|
};
|
|
169
|
-
export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
|
|
169
|
+
export type GCNodeType = (typeof GCNodeType)[keyof typeof GCNodeType];
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Defines the APIs for the runtime object to be passed to the garbage collector.
|
|
@@ -375,7 +375,7 @@ export const UnreferencedState = {
|
|
|
375
375
|
/** The node is ready to be deleted by the sweep phase. */
|
|
376
376
|
SweepReady: "SweepReady",
|
|
377
377
|
} as const;
|
|
378
|
-
export type UnreferencedState = typeof UnreferencedState[keyof typeof UnreferencedState];
|
|
378
|
+
export type UnreferencedState = (typeof UnreferencedState)[keyof typeof UnreferencedState];
|
|
379
379
|
|
|
380
380
|
/**
|
|
381
381
|
* Represents the result of a GC run.
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Configs and feature gates for solving the 1MB limit.
|
|
2
2
|
|
|
3
|
+
## Table of contents
|
|
4
|
+
|
|
5
|
+
- [Introduction](#introduction)
|
|
6
|
+
- [How batching works](#how-batching-works)
|
|
7
|
+
- [Compression](#compression)
|
|
8
|
+
- [Grouped batching](#grouped-batching)
|
|
9
|
+
- [Risks](#risks)
|
|
10
|
+
- [Chunking for compression](#chunking-for-compression)
|
|
11
|
+
- [Disabling in case of emergency](#disabling-in-case-of-emergency)
|
|
12
|
+
- [Example configs](#example-configs)
|
|
13
|
+
- [Note about performance and latency](#note-about-performance-and-latency)
|
|
14
|
+
- [How it works](#how-it-works)
|
|
15
|
+
- [How grouped batching works](#how-grouped-batching-works)
|
|
16
|
+
|
|
3
17
|
## Introduction
|
|
4
18
|
|
|
5
19
|
There is a current limitation regarding the size of the payload a Fluid client can send and receive. [The limit is 1MB per payload](https://github.com/microsoft/FluidFramework/issues/9023) and it is currently enforced explicitly with the `BatchTooLarge` error which closes the container.
|
|
@@ -8,17 +22,35 @@ There are two features which can be used to work around this size limit, batch c
|
|
|
8
22
|
|
|
9
23
|
By default, the runtime is configured with a max batch size of `716800` bytes, which is lower than the 1MB limit. The reason for the lower value is to account for possible overhead from the op envelope and metadata.
|
|
10
24
|
|
|
11
|
-
|
|
25
|
+
### How batching works
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
Batching in the context of Fluid ops is a way in which the framework accumulates and applies ops. A batch is a group of ops accumulated within a single JS turn, which will be broadcasted in the same order to all the other connected clients and applied synchronously. Additional logic and validation ensure that batches are never interleaved, nested or interrupted and they are processed in isolation without interleaving of ops from other clients.
|
|
28
|
+
|
|
29
|
+
The way batches are formed is governed by the `FlushMode` setting of the `ContainerRuntimeOptions` and it is immutable for the entire lifetime of the runtime and subsequently the container.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
export enum FlushMode {
|
|
33
|
+
/**
|
|
34
|
+
* In Immediate flush mode the runtime will immediately send all operations to the driver layer.
|
|
35
|
+
*/
|
|
36
|
+
Immediate,
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* When in TurnBased flush mode the runtime will buffer operations in the current turn and send them as a single
|
|
40
|
+
* batch at the end of the turn. The flush call on the runtime can be used to force send the current batch.
|
|
41
|
+
*/
|
|
42
|
+
TurnBased,
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
What this means is that `FlushMode.Immediate` will send each op in its own payload to the server, while `FlushMode.TurnBased` will accumulate all ops in a single JS turn and send them together in the same payload. Technically, `FlushMode.Immediate` can be simulated with `FlushMode.TurnBased` by interrupting the JS turn after producing only one op (for example by pausing the execution to wait on a promise). Therefore, for all intents and purposes, `FlushMode.Immediate` enables all batches to have only one op.
|
|
47
|
+
|
|
48
|
+
**By default, Fluid uses `FlushMode.TurnBased`** as:
|
|
49
|
+
|
|
50
|
+
- it is more efficient from an I/O perspective (batching ops overall decrease the number of payloads sent to the server)
|
|
51
|
+
- reduces concurrency related bugs, as it ensures that all ops generated within the same JS turn are also applied by all other clients within a single JS turn. Clients using the same pattern can safely assume ops will be applied exactly as they are observed locally. The alternative would be for ops to be both produced and applied with interruptions (which may involve processing input or rendering), invalidating the state based off which the changes were produced.
|
|
52
|
+
|
|
53
|
+
As `FlushMode.TurnBased` accumulates ops, it is the most vulnerable to run into the 1MB socket limit.
|
|
22
54
|
|
|
23
55
|
## Compression
|
|
24
56
|
|
|
@@ -29,6 +61,8 @@ By default, the runtime is configured with a max batch size of `716800` bytes, w
|
|
|
29
61
|
- `minimumBatchSizeInBytes` – the minimum size of the batch for which compression should kick in. If the payload is too small, compression may not yield too many benefits. To target the original 1MB issue, a good value here would be to match the default maxBatchSizeInBytes (972800), however, experimentally, a good lower value could be at around 614400 bytes. Setting this value to `Number.POSITIVE_INFINITY` will disable compression.
|
|
30
62
|
- `compressionAlgorithm` – currently, only `lz4` is supported.
|
|
31
63
|
|
|
64
|
+
Compression is relevant for both `FlushMode.TurnBased` and `FlushMode.Immediate` as it only targets the contents of the ops and not the number of ops in a batch. Compression is opaque to the server and implementations of the Fluid protocol do not need to alter their behavior to support this client feature.
|
|
65
|
+
|
|
32
66
|
## Grouped batching
|
|
33
67
|
|
|
34
68
|
**Note: This feature is currently considered experimental and is not ready for production usage.**
|
|
@@ -71,6 +105,8 @@ If all prerequisites in the previous section are met, enabling the feature can b
|
|
|
71
105
|
|
|
72
106
|
In case of emergency grouped batching can be disabled at runtime, using feature gates. If `"Fluid.ContainerRuntime.DisableGroupedBatching"` is set to `true`, it will disable grouped batching if enabled from `IContainerRuntimeOptions` in the code.
|
|
73
107
|
|
|
108
|
+
Grouped batching is only relevant for `FlushMode.TurnBased` as it only targets the number of ops in a batch. Grouped batching is opaque to the server and implementations of the Fluid protocol do not need to alter their behavior to support this client feature.
|
|
109
|
+
|
|
74
110
|
## Chunking for compression
|
|
75
111
|
|
|
76
112
|
**Op chunking for compression targets payloads which exceed the max batch size after compression.** So, only payloads which are already compressed. By default, the feature is enabled.
|
|
@@ -79,6 +115,8 @@ The `IContainerRuntimeOptions.chunkSizeInBytes` property is the only configurati
|
|
|
79
115
|
|
|
80
116
|
This config would govern chunking compressed batches only. We will not be enabling chunking across all types of ops/batches but **only when compression is enabled and when the batch is compressed**, and its payload size is more than `IContainerRuntimeOptions.chunkSizeInBytes`.
|
|
81
117
|
|
|
118
|
+
Chunking is relevant for both `FlushMode.TurnBased` and `FlushMode.Immediate` as it only targets the contents of the ops and not the number of ops in a batch. Chunking is opaque to the server and implementations of the Fluid protocol do not need to alter their behavior to support this client feature.
|
|
119
|
+
|
|
82
120
|
## Disabling in case of emergency
|
|
83
121
|
|
|
84
122
|
If the features are enabled using the configs, they can be disabled at runtime via feature gates as following:
|
|
@@ -102,32 +140,19 @@ By default, the runtime is configured with the following values related to compr
|
|
|
102
140
|
}
|
|
103
141
|
```
|
|
104
142
|
|
|
105
|
-
To use
|
|
143
|
+
To enable grouped batching, use the following property:
|
|
106
144
|
|
|
107
145
|
```
|
|
108
146
|
const runtimeOptions: IContainerRuntimeOptions = {
|
|
109
|
-
|
|
147
|
+
enableGroupedBatching: true,
|
|
110
148
|
}
|
|
111
149
|
```
|
|
112
150
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
const runtimeOptions: IContainerRuntimeOptions = {
|
|
117
|
-
compressionOptions: {
|
|
118
|
-
minimumBatchSizeInBytes: Number.POSITIVE_INFINITY,
|
|
119
|
-
compressionAlgorithm: CompressionAlgorithms.lz4,
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
```
|
|
151
|
+
## Note about performance and latency
|
|
123
152
|
|
|
124
|
-
|
|
153
|
+
In terms of performance and impact on latency, the results greatly depend on payload size, payload structure, network speed and CPU speed. Therefore, customers must perform the required measurements and adjust the settings according to their scenarios.
|
|
125
154
|
|
|
126
|
-
|
|
127
|
-
const runtimeOptions: IContainerRuntimeOptions = {
|
|
128
|
-
enableGroupedBatching: true,
|
|
129
|
-
}
|
|
130
|
-
```
|
|
155
|
+
In general, compression offers a trade-off between higher compute costs, lower bandwidth consumption and lower storage requirements, while chunking slightly increases latency due to the overhead of splitting an op, sending the chunks and reconstructing them on each client. Grouped batching heavily decreases the number of ops observed by the server and slightly decreases the bandwidth requirements as it merges all the ops in a batch into a single op and also eliminates the op envelope overhead.
|
|
131
156
|
|
|
132
157
|
## How it works
|
|
133
158
|
|
|
@@ -65,10 +65,13 @@ export interface IOutboxParameters {
|
|
|
65
65
|
export function getLongStack<T>(action: () => T, length: number = 50): T {
|
|
66
66
|
const errorObj = Error as any;
|
|
67
67
|
if (
|
|
68
|
+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
69
|
+
// ?? is not logically equivalent when the first clause returns false.
|
|
68
70
|
(
|
|
69
71
|
Object.getOwnPropertyDescriptor(errorObj, "stackTraceLimit") ||
|
|
70
72
|
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(errorObj), "stackTraceLimit")
|
|
71
73
|
)?.writable !== true
|
|
74
|
+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
|
|
72
75
|
) {
|
|
73
76
|
return action();
|
|
74
77
|
}
|
package/src/packageVersion.ts
CHANGED
|
@@ -318,6 +318,7 @@ export class PendingStateManager implements IDisposable {
|
|
|
318
318
|
{
|
|
319
319
|
runtimeVersion: pkgVersion,
|
|
320
320
|
batchClientId:
|
|
321
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
321
322
|
this.pendingBatchBeginMessage.clientId === null
|
|
322
323
|
? "null"
|
|
323
324
|
: this.pendingBatchBeginMessage.clientId,
|
package/src/scheduleManager.ts
CHANGED
|
@@ -271,6 +271,7 @@ class ScheduleManagerCore {
|
|
|
271
271
|
{
|
|
272
272
|
runtimeVersion: pkgVersion,
|
|
273
273
|
batchClientId:
|
|
274
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
274
275
|
this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
|
|
275
276
|
pauseSequenceNumber: this.pauseSequenceNumber,
|
|
276
277
|
localBatch: this.currentBatchClientId === this.getClientId(),
|
|
@@ -306,6 +307,7 @@ class ScheduleManagerCore {
|
|
|
306
307
|
throw new DataCorruptionError("OpBatchIncomplete", {
|
|
307
308
|
runtimeVersion: pkgVersion,
|
|
308
309
|
batchClientId:
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
309
311
|
this.currentBatchClientId === null ? "null" : this.currentBatchClientId,
|
|
310
312
|
pauseSequenceNumber: this.pauseSequenceNumber,
|
|
311
313
|
localBatch: this.currentBatchClientId === this.getClientId(),
|
|
@@ -15,9 +15,8 @@ import {
|
|
|
15
15
|
} from "@fluidframework/telemetry-utils";
|
|
16
16
|
import { ILoader, LoaderHeader } from "@fluidframework/container-definitions";
|
|
17
17
|
import { DriverHeader } from "@fluidframework/driver-definitions";
|
|
18
|
-
// eslint-disable-next-line import/no-deprecated
|
|
19
|
-
import { requestFluidObject } from "@fluidframework/runtime-utils";
|
|
20
18
|
import { FluidObject, IFluidHandleContext, IRequest } from "@fluidframework/core-interfaces";
|
|
19
|
+
import { responseToException } from "@fluidframework/runtime-utils";
|
|
21
20
|
import { ISummaryConfiguration } from "../containerRuntime";
|
|
22
21
|
import { ICancellableSummarizerController } from "./runWhileConnectedCoordinator";
|
|
23
22
|
import { summarizerClientType } from "./summarizerClientElection";
|
|
@@ -50,7 +49,10 @@ export class SummarizingWarning
|
|
|
50
49
|
readonly errorType = summarizingError;
|
|
51
50
|
readonly canRetry = true;
|
|
52
51
|
|
|
53
|
-
constructor(
|
|
52
|
+
constructor(
|
|
53
|
+
errorMessage: string,
|
|
54
|
+
readonly logged: boolean = false,
|
|
55
|
+
) {
|
|
54
56
|
super(errorMessage);
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -108,6 +110,7 @@ export class Summarizer extends TypedEventEmitter<ISummarizerEvents> implements
|
|
|
108
110
|
* interface will expect an absolute URL and will not handle "/".
|
|
109
111
|
* @param loader - the loader that resolves the request
|
|
110
112
|
* @param url - the URL used to resolve the container
|
|
113
|
+
* @deprecated Creating a summarizer is not a publicly supported API. Please remove all usage of this static method.
|
|
111
114
|
*/
|
|
112
115
|
public static async create(loader: ILoader, url: string): Promise<ISummarizer> {
|
|
113
116
|
const request: IRequest = {
|
|
@@ -124,12 +127,20 @@ export class Summarizer extends TypedEventEmitter<ISummarizerEvents> implements
|
|
|
124
127
|
};
|
|
125
128
|
|
|
126
129
|
const resolvedContainer = await loader.resolve(request);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
let fluidObject: FluidObject<ISummarizer> | undefined;
|
|
131
|
+
|
|
132
|
+
// Older containers may not have the "getEntryPoint" API
|
|
133
|
+
// ! This check will need to stay until LTS of loader moves past 2.0.0-internal.7.0.0
|
|
134
|
+
if (resolvedContainer.getEntryPoint !== undefined) {
|
|
135
|
+
fluidObject = await resolvedContainer.getEntryPoint();
|
|
136
|
+
} else {
|
|
137
|
+
const response = await resolvedContainer.request({ url: "_summarizer" });
|
|
138
|
+
if (response.status !== 200 || response.mimeType !== "fluid/object") {
|
|
139
|
+
throw responseToException(response, request);
|
|
140
|
+
}
|
|
141
|
+
fluidObject = response.value;
|
|
142
|
+
}
|
|
143
|
+
|
|
133
144
|
if (fluidObject?.ISummarizer === undefined) {
|
|
134
145
|
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
135
146
|
}
|
|
@@ -405,6 +405,7 @@ export class SummaryCollection extends TypedEventEmitter<ISummaryCollectionOpEve
|
|
|
405
405
|
private handleSummaryAck(op: ISummaryAckMessage) {
|
|
406
406
|
const seq = op.contents.summaryProposal.summarySequenceNumber;
|
|
407
407
|
const summary = this.pendingSummaries.get(seq);
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain -- optional chain is not logically equivalent
|
|
408
409
|
if (!summary || summary.summaryOp === undefined) {
|
|
409
410
|
// Summary ack without an op should be rare. We could fetch the
|
|
410
411
|
// reference sequence number from the snapshot, but instead we
|
|
@@ -47,12 +47,12 @@ export async function raceTimer<T>(
|
|
|
47
47
|
cancellationToken?: ISummaryCancellationToken,
|
|
48
48
|
): Promise<raceTimerResult<T>> {
|
|
49
49
|
const promises: Promise<raceTimerResult<T>>[] = [
|
|
50
|
-
promise.then((value) => ({ result: "done", value } as const)
|
|
51
|
-
timer.then(({ timerResult: result }) => ({ result } as const)
|
|
50
|
+
promise.then((value) => ({ result: "done", value }) as const),
|
|
51
|
+
timer.then(({ timerResult: result }) => ({ result }) as const),
|
|
52
52
|
];
|
|
53
53
|
if (cancellationToken !== undefined) {
|
|
54
54
|
promises.push(
|
|
55
|
-
cancellationToken.waitCancelled.then(() => ({ result: "cancelled" } as const)
|
|
55
|
+
cancellationToken.waitCancelled.then(() => ({ result: "cancelled" }) as const),
|
|
56
56
|
);
|
|
57
57
|
}
|
|
58
58
|
return Promise.race(promises);
|
|
@@ -114,7 +114,7 @@ export class SummaryManager extends TypedEventEmitter<ISummarizerEvents> impleme
|
|
|
114
114
|
parentLogger: ITelemetryBaseLogger,
|
|
115
115
|
/** Creates summarizer by asking interactive container to spawn summarizing container and
|
|
116
116
|
* get back its Summarizer instance. */
|
|
117
|
-
private readonly
|
|
117
|
+
private readonly createSummarizerFn: () => Promise<ISummarizer>,
|
|
118
118
|
private readonly startThrottler: IThrottler,
|
|
119
119
|
{
|
|
120
120
|
initialDelayMs = defaultInitialDelayMs,
|
|
@@ -264,7 +264,7 @@ export class SummaryManager extends TypedEventEmitter<ISummarizerEvents> impleme
|
|
|
264
264
|
);
|
|
265
265
|
this.state = SummaryManagerState.Running;
|
|
266
266
|
|
|
267
|
-
const summarizer = await this.
|
|
267
|
+
const summarizer = await this.createSummarizerFn();
|
|
268
268
|
this.summarizer = summarizer;
|
|
269
269
|
this.summarizer.on("summarize", this.handleSummarizeEvent);
|
|
270
270
|
|