@fluidframework/container-runtime 2.0.0-internal.7.0.0 → 2.0.0-internal.7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/api-extractor.json +13 -1
- package/api-report/container-runtime.api.md +799 -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 -60
- 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 {
|
|
@@ -355,16 +355,17 @@ export interface ISummaryRuntimeOptions {
|
|
|
355
355
|
|
|
356
356
|
/**
|
|
357
357
|
* Options for op compression.
|
|
358
|
-
* @experimental - Not ready for use
|
|
359
358
|
*/
|
|
360
359
|
export interface ICompressionRuntimeOptions {
|
|
361
360
|
/**
|
|
362
|
-
* The
|
|
361
|
+
* The value the batch's content size must exceed for the batch to be compressed.
|
|
362
|
+
* By default the value is 600 * 1024 = 614400 bytes. If the value is set to `Infinity`, compression will be disabled.
|
|
363
363
|
*/
|
|
364
364
|
readonly minimumBatchSizeInBytes: number;
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
367
|
* The compression algorithm that will be used to compress the op.
|
|
368
|
+
* By default the value is `lz4` which is the only compression algorithm currently supported.
|
|
368
369
|
*/
|
|
369
370
|
readonly compressionAlgorithm: CompressionAlgorithms;
|
|
370
371
|
}
|
|
@@ -392,8 +393,7 @@ export interface IContainerRuntimeOptions {
|
|
|
392
393
|
*/
|
|
393
394
|
readonly flushMode?: FlushMode;
|
|
394
395
|
/**
|
|
395
|
-
* Enables the runtime to compress ops.
|
|
396
|
-
* @experimental Not ready for use.
|
|
396
|
+
* Enables the runtime to compress ops. See {@link ICompressionRuntimeOptions}.
|
|
397
397
|
*/
|
|
398
398
|
readonly compressionOptions?: ICompressionRuntimeOptions;
|
|
399
399
|
/**
|
|
@@ -410,12 +410,15 @@ export interface IContainerRuntimeOptions {
|
|
|
410
410
|
/**
|
|
411
411
|
* If the op payload needs to be chunked in order to work around the maximum size of the batch, this value represents
|
|
412
412
|
* how large the individual chunks will be. This is only supported when compression is enabled. If after compression, the
|
|
413
|
-
* batch size exceeds this value, it will be chunked into smaller ops of this size.
|
|
413
|
+
* batch content size exceeds this value, it will be chunked into smaller ops of this exact size.
|
|
414
414
|
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
415
|
+
* This value is a trade-off between having many small chunks vs fewer larger chunks and by default, the runtime is configured to use
|
|
416
|
+
* 200 * 1024 = 204800 bytes. This default value ensures that no compressed payload's content is able to exceed {@link IContainerRuntimeOptions.maxBatchSizeInBytes}
|
|
417
|
+
* regardless of the overhead of an individual op.
|
|
417
418
|
*
|
|
418
|
-
* @
|
|
419
|
+
* Any value of `chunkSizeInBytes` exceeding {@link IContainerRuntimeOptions.maxBatchSizeInBytes} will disable this feature, therefore if a compressed batch's content
|
|
420
|
+
* size exceeds {@link IContainerRuntimeOptions.maxBatchSizeInBytes} after compression, the container will close with an instance of `GenericError` with
|
|
421
|
+
* the `BatchTooLarge` message.
|
|
419
422
|
*/
|
|
420
423
|
readonly chunkSizeInBytes?: number;
|
|
421
424
|
|
|
@@ -629,6 +632,53 @@ type MessageWithContext =
|
|
|
629
632
|
local: boolean;
|
|
630
633
|
};
|
|
631
634
|
|
|
635
|
+
const summarizerRequestUrl = "_summarizer";
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Create and retrieve the summmarizer
|
|
639
|
+
*/
|
|
640
|
+
async function createSummarizer(loader: ILoader, url: string): Promise<ISummarizer> {
|
|
641
|
+
const request: IRequest = {
|
|
642
|
+
headers: {
|
|
643
|
+
[LoaderHeader.cache]: false,
|
|
644
|
+
[LoaderHeader.clientDetails]: {
|
|
645
|
+
capabilities: { interactive: false },
|
|
646
|
+
type: summarizerClientType,
|
|
647
|
+
},
|
|
648
|
+
[DriverHeader.summarizingClient]: true,
|
|
649
|
+
[LoaderHeader.reconnect]: false,
|
|
650
|
+
},
|
|
651
|
+
url,
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const resolvedContainer = await loader.resolve(request);
|
|
655
|
+
let fluidObject: FluidObject<ISummarizer> | undefined;
|
|
656
|
+
|
|
657
|
+
// Older containers may not have the "getEntryPoint" API
|
|
658
|
+
// ! This check will need to stay until LTS of loader moves past 2.0.0-internal.7.0.0
|
|
659
|
+
if (resolvedContainer.getEntryPoint !== undefined) {
|
|
660
|
+
fluidObject = await resolvedContainer.getEntryPoint();
|
|
661
|
+
} else {
|
|
662
|
+
const response = await resolvedContainer.request({ url: `/${summarizerRequestUrl}` });
|
|
663
|
+
if (response.status !== 200 || response.mimeType !== "fluid/object") {
|
|
664
|
+
throw responseToException(response, request);
|
|
665
|
+
}
|
|
666
|
+
fluidObject = response.value;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (fluidObject?.ISummarizer === undefined) {
|
|
670
|
+
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
671
|
+
}
|
|
672
|
+
return fluidObject.ISummarizer;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* This function is not supported publicly and exists for e2e testing
|
|
677
|
+
*/
|
|
678
|
+
export async function TEST_requestSummarizer(loader: ILoader, url: string): Promise<ISummarizer> {
|
|
679
|
+
return createSummarizer(loader, url);
|
|
680
|
+
}
|
|
681
|
+
|
|
632
682
|
/**
|
|
633
683
|
* Represents the runtime of the container. Contains helper functions/state of the container.
|
|
634
684
|
* It will define the store level mappings.
|
|
@@ -1577,7 +1627,7 @@ export class ContainerRuntime
|
|
|
1577
1627
|
this, // IConnectedState
|
|
1578
1628
|
this.summaryCollection,
|
|
1579
1629
|
this.logger,
|
|
1580
|
-
this.
|
|
1630
|
+
this.formCreateSummarizerFn(loader),
|
|
1581
1631
|
new Throttler(
|
|
1582
1632
|
60 * 1000, // 60 sec delay window
|
|
1583
1633
|
30 * 1000, // 30 sec max delay
|
|
@@ -1683,7 +1733,7 @@ export class ContainerRuntime
|
|
|
1683
1733
|
const parser = RequestParser.create(request);
|
|
1684
1734
|
const id = parser.pathParts[0];
|
|
1685
1735
|
|
|
1686
|
-
if (id ===
|
|
1736
|
+
if (id === summarizerRequestUrl && parser.pathParts.length === 1) {
|
|
1687
1737
|
if (this._summarizer !== undefined) {
|
|
1688
1738
|
return {
|
|
1689
1739
|
status: 200,
|
|
@@ -1694,6 +1744,7 @@ export class ContainerRuntime
|
|
|
1694
1744
|
return create404Response(request);
|
|
1695
1745
|
}
|
|
1696
1746
|
if (this.requestHandler !== undefined) {
|
|
1747
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1697
1748
|
return this.requestHandler(parser, this);
|
|
1698
1749
|
}
|
|
1699
1750
|
|
|
@@ -1713,6 +1764,7 @@ export class ContainerRuntime
|
|
|
1713
1764
|
const id = requestParser.pathParts[0];
|
|
1714
1765
|
|
|
1715
1766
|
if (id === "_channels") {
|
|
1767
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1716
1768
|
return this.resolveHandle(requestParser.createSubRequest(1));
|
|
1717
1769
|
}
|
|
1718
1770
|
|
|
@@ -1734,6 +1786,7 @@ export class ContainerRuntime
|
|
|
1734
1786
|
subRequest.url.startsWith("/"),
|
|
1735
1787
|
0x126 /* "Expected createSubRequest url to include a leading slash" */,
|
|
1736
1788
|
);
|
|
1789
|
+
// eslint-disable-next-line @typescript-eslint/return-await -- Adding an await here causes test failures
|
|
1737
1790
|
return dataStore.request(subRequest);
|
|
1738
1791
|
}
|
|
1739
1792
|
|
|
@@ -3905,37 +3958,11 @@ export class ContainerRuntime
|
|
|
3905
3958
|
}
|
|
3906
3959
|
|
|
3907
3960
|
/**
|
|
3908
|
-
*
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
// eslint-disable-next-line import/no-deprecated
|
|
3912
|
-
private formRequestSummarizerFn(loaderRouter: IFluidRouter) {
|
|
3961
|
+
* Forms a function that will create and retrieve a Summarizer.
|
|
3962
|
+
*/
|
|
3963
|
+
private formCreateSummarizerFn(loader: ILoader) {
|
|
3913
3964
|
return async () => {
|
|
3914
|
-
|
|
3915
|
-
headers: {
|
|
3916
|
-
[LoaderHeader.cache]: false,
|
|
3917
|
-
[LoaderHeader.clientDetails]: {
|
|
3918
|
-
capabilities: { interactive: false },
|
|
3919
|
-
type: summarizerClientType,
|
|
3920
|
-
},
|
|
3921
|
-
[DriverHeader.summarizingClient]: true,
|
|
3922
|
-
[LoaderHeader.reconnect]: false,
|
|
3923
|
-
},
|
|
3924
|
-
url: "/_summarizer",
|
|
3925
|
-
};
|
|
3926
|
-
|
|
3927
|
-
// eslint-disable-next-line import/no-deprecated
|
|
3928
|
-
const fluidObject = await requestFluidObject<FluidObject<ISummarizer>>(
|
|
3929
|
-
loaderRouter,
|
|
3930
|
-
request,
|
|
3931
|
-
);
|
|
3932
|
-
const summarizer = fluidObject.ISummarizer;
|
|
3933
|
-
|
|
3934
|
-
if (!summarizer) {
|
|
3935
|
-
throw new UsageError("Fluid object does not implement ISummarizer");
|
|
3936
|
-
}
|
|
3937
|
-
|
|
3938
|
-
return summarizer;
|
|
3965
|
+
return createSummarizer(loader, `/${summarizerRequestUrl}`);
|
|
3939
3966
|
};
|
|
3940
3967
|
}
|
|
3941
3968
|
|
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
|
@@ -165,7 +165,7 @@ export const GCNodeType = {
|
|
|
165
165
|
// Nodes that are neither of the above. For example, root node.
|
|
166
166
|
Other: "Other",
|
|
167
167
|
};
|
|
168
|
-
export type GCNodeType = typeof GCNodeType[keyof typeof GCNodeType];
|
|
168
|
+
export type GCNodeType = (typeof GCNodeType)[keyof typeof GCNodeType];
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
171
|
* Defines the APIs for the runtime object to be passed to the garbage collector.
|
|
@@ -360,7 +360,7 @@ export const UnreferencedState = {
|
|
|
360
360
|
/** The node is ready to be deleted by the sweep phase. */
|
|
361
361
|
SweepReady: "SweepReady",
|
|
362
362
|
} as const;
|
|
363
|
-
export type UnreferencedState = typeof UnreferencedState[keyof typeof UnreferencedState];
|
|
363
|
+
export type UnreferencedState = (typeof UnreferencedState)[keyof typeof UnreferencedState];
|
|
364
364
|
|
|
365
365
|
/**
|
|
366
366
|
* 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
|
|