From a579ce6cbb91ac8c371c9935074915b7982f65a6 Mon Sep 17 00:00:00 2001 From: fkwp Date: Wed, 2 Jul 2025 13:12:08 +0200 Subject: [PATCH 1/6] add a request timeout to restartDelayedEvent --- src/matrixrtc/MembershipManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index 6839469c42..af61e6f04b 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -19,6 +19,7 @@ import { UpdateDelayedEventAction } from "../@types/requests.ts"; import { type MatrixClient } from "../client.ts"; import { UnsupportedDelayedEventsEndpointError } from "../errors.ts"; import { ConnectionError, HTTPError, MatrixError } from "../http-api/errors.ts"; +import { IRequestOpts } from "../http-api/index.ts"; import { type Logger, logger as rootLogger } from "../logger.ts"; import { type Room } from "../models/room.ts"; import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "./CallMembership.ts"; @@ -525,8 +526,9 @@ export class MembershipManager } private async restartDelayedEvent(delayId: string): Promise { + const requestOptions: IRequestOpts = { localTimeoutMs: 1500, priority: "auto" }; return await this.client - ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart) + ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart, requestOptions) .then(() => { this.resetRateLimitCounter(MembershipActionType.RestartDelayedEvent); return createInsertActionUpdate( From ac4caa4c6af35444178af3122f9868aab2056e2a Mon Sep 17 00:00:00 2001 From: fkwp Date: Wed, 2 Jul 2025 13:22:36 +0200 Subject: [PATCH 2/6] fix types --- src/matrixrtc/MembershipManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index af61e6f04b..8663b15390 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -19,7 +19,7 @@ import { UpdateDelayedEventAction } from "../@types/requests.ts"; import { type MatrixClient } from "../client.ts"; import { UnsupportedDelayedEventsEndpointError } from "../errors.ts"; import { ConnectionError, HTTPError, MatrixError } from "../http-api/errors.ts"; -import { IRequestOpts } from "../http-api/index.ts"; +import { type IRequestOpts } from "../http-api/index.ts"; import { type Logger, logger as rootLogger } from "../logger.ts"; import { type Room } from "../models/room.ts"; import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "./CallMembership.ts"; From 13ff35d3747ad7829732c09e9b9276f5a17e3e0e Mon Sep 17 00:00:00 2001 From: fkwp Date: Wed, 2 Jul 2025 14:06:30 +0200 Subject: [PATCH 3/6] increase timeout to 2300ms --- src/matrixrtc/MembershipManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index 8663b15390..63cd6cc6ca 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -526,7 +526,7 @@ export class MembershipManager } private async restartDelayedEvent(delayId: string): Promise { - const requestOptions: IRequestOpts = { localTimeoutMs: 1500, priority: "auto" }; + const requestOptions: IRequestOpts = { localTimeoutMs: 2300, priority: "auto" }; return await this.client ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart, requestOptions) .then(() => { From 22efec0404cc3cd1bc64e20c4aa35aab457bc7c2 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 14 Jul 2025 13:31:24 +0200 Subject: [PATCH 4/6] make the localTimeoutMs configurable via the joinConfig --- src/matrixrtc/MatrixRTCSession.ts | 9 +++++++++ src/matrixrtc/MembershipManager.ts | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/matrixrtc/MatrixRTCSession.ts b/src/matrixrtc/MatrixRTCSession.ts index 1951b676a9..58d413f12f 100644 --- a/src/matrixrtc/MatrixRTCSession.ts +++ b/src/matrixrtc/MatrixRTCSession.ts @@ -136,6 +136,15 @@ export interface MembershipConfig { /** @deprecated renamed to `networkErrorRetryMs`*/ callMemberEventRetryDelayMinimum?: number; + /** + * The time (in milliseconds) after which a we consider a delayed event restart http request to have failed. + * Setting this to a lower value will result in more frequent retries but also a higher chance of failiour. + * + * the default local timeout in the js-sdk is 5 seconds. + * @default 5000 + */ + delayedEventRestartLocalTimeoutMs?: number; + /** * If true, use the new to-device transport for sending encryption keys. */ diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index 63cd6cc6ca..144509713a 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -526,7 +526,10 @@ export class MembershipManager } private async restartDelayedEvent(delayId: string): Promise { - const requestOptions: IRequestOpts = { localTimeoutMs: 2300, priority: "auto" }; + const requestOptions: IRequestOpts = { + localTimeoutMs: this.joinConfig?.delayedEventRestartLocalTimeoutMs, + priority: "auto", + }; return await this.client ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart, requestOptions) .then(() => { From 16e1fc94e03811f6e8c28f1aedbd562198da8f77 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 14 Jul 2025 14:32:11 +0200 Subject: [PATCH 5/6] add test --- spec/unit/matrixrtc/MembershipManager.spec.ts | 25 +++++++++++++++++++ src/matrixrtc/MembershipManager.ts | 5 +++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/unit/matrixrtc/MembershipManager.spec.ts b/spec/unit/matrixrtc/MembershipManager.spec.ts index 18b2fb6309..71f12c5a41 100644 --- a/spec/unit/matrixrtc/MembershipManager.spec.ts +++ b/spec/unit/matrixrtc/MembershipManager.spec.ts @@ -602,6 +602,31 @@ describe("MembershipManager", () => { expect(connectEmit).toHaveBeenCalledWith(Status.Disconnecting, Status.Disconnected); }); }); + describe("local timeout error handling", () => { + it("retries sending restart delayed leave request after configure local timeout", async () => { + const manager = new MembershipManager( + { delayedEventRestartLocalTimeoutMs: 1000 }, + room, + client, + () => undefined, + ); + manager.join([focus], focusActive); + expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1); + await jest.advanceTimersByTimeAsync(1); + + expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1); + + // Simulate a local timeout error + // (client._unstable_updateDelayedEvent as Mock).mockRejectedValue(new HTTPError("timeout", 408)); + // Advance the timers so that the retry happens. + await jest.advanceTimersByTimeAsync(1100); + expect(client._unstable_updateDelayedEvent).toHaveBeenCalledWith("id", "restart", { + localTimeoutMs: 1000, + priority: "auto", + }); + }); + }); + describe("server error handling", () => { // Types of server error: 429 rate limit with no retry-after header, 429 with retry-after, 50x server error (maybe retry every second), connection/socket timeout describe("retries sending delayed leave event", () => { diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index 144509713a..de6ab0aebc 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -375,6 +375,9 @@ export class MembershipManager return this.joinConfig?.maximumNetworkErrorRetryCount ?? 10; } + private get delayedEventRestartLocalTimeoutMs(): number | undefined { + return this.joinConfig?.delayedEventRestartLocalTimeoutMs; + } // LOOP HANDLER: private async membershipLoopHandler(type: MembershipActionType): Promise { switch (type) { @@ -527,7 +530,7 @@ export class MembershipManager private async restartDelayedEvent(delayId: string): Promise { const requestOptions: IRequestOpts = { - localTimeoutMs: this.joinConfig?.delayedEventRestartLocalTimeoutMs, + localTimeoutMs: this.delayedEventRestartLocalTimeoutMs, priority: "auto", }; return await this.client From 3294d1aa94bca7a04cc0afcadc437a8b6edf520e Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 14 Jul 2025 18:23:31 +0200 Subject: [PATCH 6/6] try without `priority: "auto"` --- src/matrixrtc/MembershipManager.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/matrixrtc/MembershipManager.ts b/src/matrixrtc/MembershipManager.ts index de6ab0aebc..cca3ce7424 100644 --- a/src/matrixrtc/MembershipManager.ts +++ b/src/matrixrtc/MembershipManager.ts @@ -531,7 +531,6 @@ export class MembershipManager private async restartDelayedEvent(delayId: string): Promise { const requestOptions: IRequestOpts = { localTimeoutMs: this.delayedEventRestartLocalTimeoutMs, - priority: "auto", }; return await this.client ._unstable_updateDelayedEvent(delayId, UpdateDelayedEventAction.Restart, requestOptions)