also resolve related event ids when removing remote echo during sync

as /sync races with /send, and remote echo may happen first.
It's important for local echo that the pending redaction/relation
will also get attached to the remote echo before /send returns,
otherwise the remote echo would be "unannotated" until /send returns
This commit is contained in:
Bruno Windels 2021-05-21 10:47:48 +02:00
parent c3fb35848b
commit c934049523
4 changed files with 48 additions and 39 deletions

View file

@ -263,7 +263,7 @@ export class BaseRoom extends EventEmitter {
let gapResult; let gapResult;
try { try {
// detect remote echos of pending messages in the gap // detect remote echos of pending messages in the gap
extraGapFillChanges = this._writeGapFill(response.chunk, txn, log); extraGapFillChanges = await this._writeGapFill(response.chunk, txn, log);
// write new events into gap // write new events into gap
const gapWriter = new GapWriter({ const gapWriter = new GapWriter({
roomId: this._roomId, roomId: this._roomId,
@ -300,7 +300,7 @@ export class BaseRoom extends EventEmitter {
JoinedRoom uses this update remote echos. JoinedRoom uses this update remote echos.
*/ */
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
_writeGapFill(chunk, txn, log) {} async _writeGapFill(chunk, txn, log) {}
_applyGapFill() {} _applyGapFill() {}
/** @public */ /** @public */

View file

@ -159,7 +159,7 @@ export class Room extends BaseRoom {
} }
let removedPendingEvents; let removedPendingEvents;
if (Array.isArray(roomResponse.timeline?.events)) { if (Array.isArray(roomResponse.timeline?.events)) {
removedPendingEvents = this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log); removedPendingEvents = await this._sendQueue.removeRemoteEchos(roomResponse.timeline.events, txn, log);
} }
return { return {
summaryChanges, summaryChanges,
@ -280,8 +280,8 @@ export class Room extends BaseRoom {
} }
} }
_writeGapFill(gapChunk, txn, log) { async _writeGapFill(gapChunk, txn, log) {
const removedPendingEvents = this._sendQueue.removeRemoteEchos(gapChunk, txn, log); const removedPendingEvents = await this._sendQueue.removeRemoteEchos(gapChunk, txn, log);
return removedPendingEvents; return removedPendingEvents;
} }

View file

@ -16,7 +16,6 @@ limitations under the License.
import {createEnum} from "../../../utils/enum.js"; import {createEnum} from "../../../utils/enum.js";
import {AbortError} from "../../../utils/error.js"; import {AbortError} from "../../../utils/error.js";
import {REDACTION_TYPE} from "../common.js"; import {REDACTION_TYPE} from "../common.js";
import {isTxnId} from "../../common.js";
export const SendStatus = createEnum( export const SendStatus = createEnum(
"Waiting", "Waiting",
@ -49,6 +48,13 @@ export class PendingEvent {
get txnId() { return this._data.txnId; } get txnId() { return this._data.txnId; }
get remoteId() { return this._data.remoteId; } get remoteId() { return this._data.remoteId; }
get content() { return this._data.content; } get content() { return this._data.content; }
get relatedTxnId() { return this._data.relatedTxnId; }
get relatedEventId() { return this._data.relatedEventId; }
setRelatedEventId(eventId) {
this._data.relatedEventId = eventId;
}
get data() { return this._data; } get data() { return this._data; }
getAttachment(key) { getAttachment(key) {
@ -164,10 +170,9 @@ export class PendingEvent {
const eventType = this._data.encryptedEventType || this._data.eventType; const eventType = this._data.encryptedEventType || this._data.eventType;
const content = this._data.encryptedContent || this._data.content; const content = this._data.encryptedContent || this._data.content;
if (eventType === REDACTION_TYPE) { if (eventType === REDACTION_TYPE) {
// TODO: should we double check here that this._data.redacts is not a txnId here anymore?
this._sendRequest = hsApi.redact( this._sendRequest = hsApi.redact(
this.roomId, this.roomId,
this._data.redacts, this._data.relatedEventId,
this.txnId, this.txnId,
content, content,
{log} {log}
@ -197,17 +202,4 @@ export class PendingEvent {
} }
} }
} }
get relatedTxnId() {
if (isTxnId(this._data.redacts)) {
return this._data.redacts;
}
return null;
}
setRelatedEventId(eventId) {
if (this._data.redacts) {
this._data.redacts = eventId;
}
}
} }

View file

@ -104,16 +104,11 @@ export class SendQueue {
// the relatedTxnId to a related event id, they need to do so now. // the relatedTxnId to a related event id, they need to do so now.
// We ensure this by writing the new remote id for the pending event and all related events // We ensure this by writing the new remote id for the pending event and all related events
// with unresolved relatedTxnId in the queue in one transaction. // with unresolved relatedTxnId in the queue in one transaction.
const relatedEvents = this._pendingEvents.array.filter(pe => pe.relatedTxnId === pendingEvent.txnId);
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]); const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
try { try {
await this._tryUpdateEventWithTxn(pendingEvent, txn); await this._tryUpdateEventWithTxn(pendingEvent, txn);
for (const relatedPE of relatedEvents) { await this._resolveRemoteIdInPendingRelations(
relatedPE.setRelatedEventId(pendingEvent.remoteId); pendingEvent.txnId, pendingEvent.remoteId, txn);
await this._tryUpdateEventWithTxn(relatedPE, txn);
// emit that we now have a related remote id
this._pendingEvents.update(relatedPE)
}
} catch (err) { } catch (err) {
txn.abort(); txn.abort();
throw err; throw err;
@ -122,7 +117,20 @@ export class SendQueue {
} }
} }
removeRemoteEchos(events, txn, parentLog) { async _resolveRemoteIdInPendingRelations(txnId, remoteId, txn) {
const relatedEventWithoutRemoteId = this._pendingEvents.array.filter(pe => {
return pe.relatedTxnId === txnId && pe.relatedEventId !== remoteId;
});
for (const relatedPE of relatedEventWithoutRemoteId) {
relatedPE.setRelatedEventId(remoteId);
await this._tryUpdateEventWithTxn(relatedPE, txn);
// emit that we now have a related remote id
// this._pendingEvents.update(relatedPE);
}
return relatedEventWithoutRemoteId;
}
async removeRemoteEchos(events, txn, parentLog) {
const removed = []; const removed = [];
for (const event of events) { for (const event of events) {
const txnId = event.unsigned && event.unsigned.transaction_id; const txnId = event.unsigned && event.unsigned.transaction_id;
@ -134,9 +142,11 @@ export class SendQueue {
} }
if (idx !== -1) { if (idx !== -1) {
const pendingEvent = this._pendingEvents.get(idx); const pendingEvent = this._pendingEvents.get(idx);
parentLog.log({l: "removeRemoteEcho", queueIndex: pendingEvent.queueIndex, remoteId: event.event_id, txnId}); const remoteId = event.event_id;
parentLog.log({l: "removeRemoteEcho", queueIndex: pendingEvent.queueIndex, remoteId, txnId});
txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex); txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex);
removed.push(pendingEvent); removed.push(pendingEvent);
await this._resolveRemoteIdInPendingRelations(txnId, remoteId, txn);
} }
} }
return removed; return removed;
@ -184,12 +194,12 @@ export class SendQueue {
} }
async enqueueEvent(eventType, content, attachments, log) { async enqueueEvent(eventType, content, attachments, log) {
await this._enqueueEvent(eventType, content, attachments, null, log); await this._enqueueEvent(eventType, content, attachments, null, null, log);
} }
async _enqueueEvent(eventType, content, attachments, redacts, log) { async _enqueueEvent(eventType, content, attachments, relatedTxnId, relatedEventId, log) {
const pendingEvent = await this._createAndStoreEvent(eventType, content, redacts, attachments); const pendingEvent = await this._createAndStoreEvent(eventType, content, relatedTxnId, relatedEventId, attachments);
this._pendingEvents.set(pendingEvent); this._pendingEvents.set(pendingEvent);
log.set("queueIndex", pendingEvent.queueIndex); log.set("queueIndex", pendingEvent.queueIndex);
log.set("pendingEvents", this._pendingEvents.length); log.set("pendingEvents", this._pendingEvents.length);
@ -202,8 +212,11 @@ export class SendQueue {
} }
async enqueueRedaction(eventIdOrTxnId, reason, log) { async enqueueRedaction(eventIdOrTxnId, reason, log) {
let relatedTxnId;
let relatedEventId;
if (isTxnId(eventIdOrTxnId)) { if (isTxnId(eventIdOrTxnId)) {
log.set("txnIdToRedact", eventIdOrTxnId); relatedTxnId = eventIdOrTxnId;
log.set("relatedTxnId", eventIdOrTxnId);
const txnId = eventIdOrTxnId; const txnId = eventIdOrTxnId;
const pe = this._pendingEvents.array.find(pe => pe.txnId === txnId); const pe = this._pendingEvents.array.find(pe => pe.txnId === txnId);
if (pe && !pe.remoteId && pe.status !== SendStatus.Sending) { if (pe && !pe.remoteId && pe.status !== SendStatus.Sending) {
@ -211,7 +224,9 @@ export class SendQueue {
// just remove it from the queue // just remove it from the queue
await pe.abort(); await pe.abort();
return; return;
} else if (!pe) { } else if (pe) {
relatedEventId = pe.remoteId;
} else {
// we don't have the pending event anymore, // we don't have the pending event anymore,
// the remote echo must have arrived in the meantime. // the remote echo must have arrived in the meantime.
// we could look for it in the timeline, but for now // we could look for it in the timeline, but for now
@ -220,9 +235,10 @@ export class SendQueue {
return; return;
} }
} else { } else {
log.set("eventIdToRedact", eventIdOrTxnId); relatedEventId = eventIdOrTxnId;
log.set("relatedEventId", relatedEventId);
} }
await this._enqueueEvent(REDACTION_TYPE, {reason}, null, eventIdOrTxnId, log); await this._enqueueEvent(REDACTION_TYPE, {reason}, null, relatedTxnId, relatedEventId, log);
} }
get pendingEvents() { get pendingEvents() {
@ -248,7 +264,7 @@ export class SendQueue {
} }
} }
async _createAndStoreEvent(eventType, content, redacts, attachments) { async _createAndStoreEvent(eventType, content, relatedTxnId, relatedEventId, attachments) {
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]); const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
let pendingEvent; let pendingEvent;
try { try {
@ -261,7 +277,8 @@ export class SendQueue {
queueIndex, queueIndex,
eventType, eventType,
content, content,
redacts, relatedTxnId,
relatedEventId,
txnId: makeTxnId(), txnId: makeTxnId(),
needsEncryption, needsEncryption,
needsUpload: !!attachments needsUpload: !!attachments