|
4 | 4 | */
|
5 | 5 |
|
6 | 6 | import { strict as assert } from "assert";
|
7 |
| - |
| 7 | +import { useFakeTimers } from "sinon"; |
8 | 8 | import { MockDocumentDeltaConnection } from "@fluid-private/test-loader-utils";
|
9 | 9 | import {
|
10 | 10 | ITestDataObject,
|
@@ -40,6 +40,7 @@ import { FiveDaysMs, IDocumentServiceFactory } from "@fluidframework/driver-defi
|
40 | 40 | import {
|
41 | 41 | DeltaStreamConnectionForbiddenError,
|
42 | 42 | NonRetryableError,
|
| 43 | + RetryableError, |
43 | 44 | } from "@fluidframework/driver-utils/internal";
|
44 | 45 | import { IClient } from "@fluidframework/protocol-definitions";
|
45 | 46 | import { DataCorruptionError } from "@fluidframework/telemetry-utils/internal";
|
@@ -857,3 +858,94 @@ describeCompat("Driver", "NoCompat", (getTestObjectProvider) => {
|
857 | 858 | assert.equal(storage.policies?.maximumCacheDurationMs, fiveDaysMs);
|
858 | 859 | });
|
859 | 860 | });
|
| 861 | + |
| 862 | +describeCompat("Container connections", "NoCompat", (getTestObjectProvider) => { |
| 863 | + let provider: ITestObjectProvider; |
| 864 | + let clock; |
| 865 | + before(() => { |
| 866 | + clock = useFakeTimers(); |
| 867 | + }); |
| 868 | + beforeEach("", async function () { |
| 869 | + provider = getTestObjectProvider(); |
| 870 | + if (provider.driver.type !== "local") { |
| 871 | + this.skip(); |
| 872 | + } |
| 873 | + }); |
| 874 | + afterEach(() => { |
| 875 | + clock.reset(); |
| 876 | + }); |
| 877 | + after(() => { |
| 878 | + clock.restore(); |
| 879 | + }); |
| 880 | + it("container disconnect() stops the connection re-attempt loop", async () => { |
| 881 | + let emulateThrowErrorOnConnection = false; |
| 882 | + const retryAfter = 3; |
| 883 | + let reconnectionAttemptCount = 0; |
| 884 | + (provider as any)._documentServiceFactory = wrapObjectAndOverride<IDocumentServiceFactory>( |
| 885 | + provider.documentServiceFactory, |
| 886 | + { |
| 887 | + createDocumentService: { |
| 888 | + connectToDeltaStream: (_ds) => async (client) => { |
| 889 | + // We let the container get created first before starting emulate throwing of errors. |
| 890 | + if (emulateThrowErrorOnConnection) { |
| 891 | + reconnectionAttemptCount++; |
| 892 | + throw new RetryableError("Test message", "ThrottlingError", { |
| 893 | + retryAfterSeconds: retryAfter, |
| 894 | + driverVersion: "1", |
| 895 | + }); |
| 896 | + } else { |
| 897 | + return _ds.connectToDeltaStream(client); |
| 898 | + } |
| 899 | + }, |
| 900 | + }, |
| 901 | + }, |
| 902 | + ); |
| 903 | + // Create container |
| 904 | + const container = await provider.makeTestContainer(); |
| 905 | + await waitForContainerConnection(container); |
| 906 | + emulateThrowErrorOnConnection = true; |
| 907 | + |
| 908 | + // This flag will ensure that the container warnings were observed when throttling error was thrown |
| 909 | + let didReceiveContainerWarning = false; |
| 910 | + |
| 911 | + // Host apps can chose to listen to container warning events and disconnect the container if they observe throttling errors |
| 912 | + container.once("warning", (warning) => { |
| 913 | + assert.equal( |
| 914 | + warning.errorType, |
| 915 | + "throttlingError", |
| 916 | + "Error type thrown by the warning message is incorrect", |
| 917 | + ); |
| 918 | + |
| 919 | + // disconnecting the container should also stop re-connects to the service |
| 920 | + container.disconnect(); |
| 921 | + const countUntilDisconnectWasCalled = reconnectionAttemptCount; |
| 922 | + |
| 923 | + clock.tick(retryAfter * 1000 + 10); |
| 924 | + // Check if there has been any retry attempt after some time greater than retry after has elapsed |
| 925 | + assert.equal( |
| 926 | + reconnectionAttemptCount, |
| 927 | + countUntilDisconnectWasCalled, |
| 928 | + "Connection should not have been attempted, even after the retry timedout", |
| 929 | + ); |
| 930 | + |
| 931 | + clock.tick(retryAfter * 1000 + 10); |
| 932 | + // Check if there has been any retry attempt after more time has elapsed |
| 933 | + assert.equal( |
| 934 | + reconnectionAttemptCount, |
| 935 | + countUntilDisconnectWasCalled, |
| 936 | + "Connection should not have been attempted after some more time", |
| 937 | + ); |
| 938 | + didReceiveContainerWarning = true; |
| 939 | + }); |
| 940 | + |
| 941 | + // Disconnect and connect the container again to trigger the connection to the delta service |
| 942 | + // to test the container warning behavior above |
| 943 | + container.disconnect(); |
| 944 | + container.connect(); |
| 945 | + await clock.tickAsync(retryAfter * 1000 + 20); |
| 946 | + assert( |
| 947 | + didReceiveContainerWarning, |
| 948 | + "Container warning event should happen when throttling error occurs", |
| 949 | + ); |
| 950 | + }); |
| 951 | +}); |
0 commit comments