Fix possible unknown state (With reproducible test) (#4944)

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-08-01 11:13:35 +02:00
committed by GitHub
parent c4e1e0723e
commit c2d25d9377
2 changed files with 41 additions and 2 deletions
@@ -511,6 +511,32 @@ describe("MembershipManager", () => {
expect(client._unstable_updateDelayedEvent).toHaveBeenCalled();
});
it("updates the UpdateExpiry entry in the action scheduler", async () => {
const manager = new MembershipManager({}, room, client, () => undefined);
manager.join([focus], focusActive);
await jest.advanceTimersByTimeAsync(1);
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
(client.sendStateEvent as Mock).mockClear();
(client._unstable_updateDelayedEvent as Mock).mockClear();
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValueOnce(
new MatrixError({ errcode: "M_NOT_FOUND" }),
);
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
await jest.advanceTimersByTimeAsync(10_000);
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
resolve({ delay_id: "id" });
await jest.advanceTimersByTimeAsync(10_000);
expect(client.sendStateEvent).toHaveBeenCalled();
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled();
expect(client._unstable_updateDelayedEvent).toHaveBeenCalled();
expect(manager.status).toBe(Status.Connected);
});
});
// TODO: Not sure about this name
+15 -2
View File
@@ -243,7 +243,7 @@ export class MembershipManager
this.logger.warn("Missing own membership: force re-join");
this.state.hasMemberStateEvent = false;
if (this.scheduler.actions.find((a) => sendingMembershipActions.includes(a.type as MembershipActionType))) {
if (this.scheduler.actions.some((a) => sendingMembershipActions.includes(a.type as MembershipActionType))) {
this.logger.error(
"tried adding another `SendDelayedEvent` actions even though we already have one in the Queue\nActionQueueOnMemberUpdate:",
this.scheduler.actions,
@@ -624,8 +624,21 @@ export class MembershipManager
this.state.expireUpdateIterations = 1;
this.state.hasMemberStateEvent = true;
this.resetRateLimitCounter(MembershipActionType.SendJoinEvent);
// An UpdateExpiry action might be left over from a previous join event.
// We can reach sendJoinEvent when the delayed leave event gets send by the HS.
// The branch where we might have a leftover UpdateExpiry action is:
// RestartDelayedEvent (cannot find it, server removed it)
// -> SendDelayedEvent (send new delayed event)
// -> SendJoinEvent (here with a still scheduled UpdateExpiry action)
const actionsWithoutUpdateExpiry = this.scheduler.actions.filter(
(a) =>
a.type !== MembershipActionType.UpdateExpiry && // A new UpdateExpiry action with an updated will be scheduled,
a.type !== MembershipActionType.SendJoinEvent, // Manually remove the SendJoinEvent action,
);
return {
insert: [
replace: [
...actionsWithoutUpdateExpiry,
// To check if the delayed event is still there or got removed by inserting the stateEvent, we need to restart it.
{ ts: Date.now(), type: MembershipActionType.RestartDelayedEvent },
{
ts: this.computeNextExpiryActionTs(this.state.expireUpdateIterations),