Skip to content

Commit 5cf0bb4

Browse files
authored
Messages sent out of order after one message fails (#3131)
* Instead of skipping, bail out by clearing queue * Allow additional status transition for events from QUEUED to NOT_SENT
1 parent 16672b3 commit 5cf0bb4

File tree

3 files changed

+28
-22
lines changed

3 files changed

+28
-22
lines changed

spec/unit/scheduler.spec.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe("MatrixScheduler", function () {
112112
expect(procCount).toEqual(2);
113113
});
114114

115-
it("should give up if the retryFn on failure returns -1 and try the next event", async function () {
115+
it("should give up if the retryFn on failure returns -1", async function () {
116116
// Queue A & B.
117117
// Reject A and return -1 on retry.
118118
// Expect B to be tried next and the promise for A to be rejected.
@@ -139,19 +139,15 @@ describe("MatrixScheduler", function () {
139139
return new Promise<Record<string, boolean>>(() => {});
140140
});
141141

142-
const globalA = scheduler.queueEvent(eventA);
143-
scheduler.queueEvent(eventB);
142+
const queuedA = scheduler.queueEvent(eventA);
143+
const queuedB = scheduler.queueEvent(eventB);
144+
await Promise.resolve();
145+
deferA.reject(new Error("Testerror"));
144146
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
145147
// wait just long enough before it does
146-
await Promise.resolve();
148+
await expect(queuedA).rejects.toThrow("Testerror");
149+
await expect(queuedB).rejects.toThrow("Testerror");
147150
expect(procCount).toEqual(1);
148-
deferA.reject({});
149-
try {
150-
await globalA;
151-
} catch (err) {
152-
await Promise.resolve();
153-
expect(procCount).toEqual(2);
154-
}
155151
});
156152

157153
it("should treat each queue separately", function (done) {

src/models/room.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3380,7 +3380,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
33803380
const ALLOWED_TRANSITIONS: Record<EventStatus, EventStatus[]> = {
33813381
[EventStatus.ENCRYPTING]: [EventStatus.SENDING, EventStatus.NOT_SENT, EventStatus.CANCELLED],
33823382
[EventStatus.SENDING]: [EventStatus.ENCRYPTING, EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.SENT],
3383-
[EventStatus.QUEUED]: [EventStatus.SENDING, EventStatus.CANCELLED],
3383+
[EventStatus.QUEUED]: [EventStatus.SENDING, EventStatus.NOT_SENT, EventStatus.CANCELLED],
33843384
[EventStatus.SENT]: [],
33853385
[EventStatus.NOT_SENT]: [EventStatus.SENDING, EventStatus.QUEUED, EventStatus.CANCELLED],
33863386
[EventStatus.CANCELLED]: [],

src/scheduler.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
245245
// get head of queue
246246
const obj = this.peekNextEvent(queueName);
247247
if (!obj) {
248-
// queue is empty. Mark as inactive and stop recursing.
249-
const index = this.activeQueues.indexOf(queueName);
250-
if (index >= 0) {
251-
this.activeQueues.splice(index, 1);
252-
}
253-
debuglog("Stopping queue '%s' as it is now empty", queueName);
248+
this.disableQueue(queueName);
254249
return;
255250
}
256251
debuglog("Queue '%s' has %s pending events", queueName, this.queues[queueName].length);
@@ -289,17 +284,32 @@ export class MatrixScheduler<T = ISendEventResponse> {
289284
// give up (you quitter!)
290285
debuglog("Queue '%s' giving up on event %s", queueName, obj.event.getId());
291286
// remove this from the queue
292-
this.removeNextEvent(queueName);
293-
obj.defer.reject(err);
294-
// process next event
295-
this.processQueue(queueName);
287+
this.clearQueue(queueName, err);
296288
} else {
297289
setTimeout(this.processQueue, waitTimeMs, queueName);
298290
}
299291
},
300292
);
301293
};
302294

295+
private disableQueue(queueName: string): void {
296+
// queue is empty. Mark as inactive and stop recursing.
297+
const index = this.activeQueues.indexOf(queueName);
298+
if (index >= 0) {
299+
this.activeQueues.splice(index, 1);
300+
}
301+
debuglog("Stopping queue '%s' as it is now empty", queueName);
302+
}
303+
304+
private clearQueue(queueName: string, err: unknown): void {
305+
debuglog("clearing queue '%s'", queueName);
306+
let obj: IQueueEntry<T> | undefined;
307+
while ((obj = this.removeNextEvent(queueName))) {
308+
obj.defer.reject(err);
309+
}
310+
this.disableQueue(queueName);
311+
}
312+
303313
private peekNextEvent(queueName: string): IQueueEntry<T> | undefined {
304314
const queue = this.queues[queueName];
305315
if (!Array.isArray(queue)) {

0 commit comments

Comments
 (0)