Skip to content
This repository has been archived by the owner on May 19, 2021. It is now read-only.

Commit

Permalink
Workaround for synapse #7164 & extra robustness while filling a gap
Browse files Browse the repository at this point in the history
ignore duplicate events from synapse instead considering them an
overlapping event with the adjacent fragment
  • Loading branch information
bwindels committed Mar 28, 2020
1 parent 9b2f282 commit 086bd4e
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 18 deletions.
4 changes: 4 additions & 0 deletions src/matrix/room/timeline/Direction.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default class Direction {
return this.isForward ? "f" : "b";
}

reverse() {
return this.isForward ? Direction.Backward : Direction.Forward
}

static get Forward() {
return _forward;
}
Expand Down
6 changes: 5 additions & 1 deletion src/matrix/room/timeline/EventKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export default class EventKey {
}

static get defaultLiveKey() {
return new EventKey(Platform.minStorageKey, Platform.middleStorageKey);
return EventKey.defaultFragmentKey(Platform.minStorageKey);
}

static defaultFragmentKey(fragmentId) {
return new EventKey(fragmentId, Platform.middleStorageKey);
}

toString() {
Expand Down
92 changes: 75 additions & 17 deletions src/matrix/room/timeline/persistence/GapWriter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,74 @@ export default class GapWriter {
}
// events is in reverse-chronological order (last event comes at index 0) if backwards
async _findOverlappingEvents(fragmentEntry, events, txn) {
const eventIds = events.map(e => e.event_id);
let nonOverlappingEvents = events;
let expectedOverlappingEventId;
if (fragmentEntry.hasLinkedFragment) {
expectedOverlappingEventId = await this._findExpectedOverlappingEventId(fragmentEntry, txn);
}
let remainingEvents = events;
let nonOverlappingEvents = [];
let neighbourFragmentEntry;
const neighbourEventId = await txn.timelineEvents.findFirstOccurringEventId(this._roomId, eventIds);
if (neighbourEventId) {
// trim overlapping events
const neighbourEventIndex = events.findIndex(e => e.event_id === neighbourEventId);
nonOverlappingEvents = events.slice(0, neighbourEventIndex);
// get neighbour fragment to link it up later on
const neighbourEvent = await txn.timelineEvents.getByEventId(this._roomId, neighbourEventId);
const neighbourFragment = await txn.timelineFragments.get(this._roomId, neighbourEvent.fragmentId);
neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment);
while (remainingEvents.length) {
const eventIds = remainingEvents.map(e => e.event_id);
const duplicateEventId = await txn.timelineEvents.findFirstOccurringEventId(this._roomId, eventIds);
if (duplicateEventId) {
const duplicateEventIndex = remainingEvents.findIndex(e => e.event_id === duplicateEventId);
// should never happen, just being defensive as this *can't* go wrong
if (duplicateEventIndex === -1) {
throw new Error(`findFirstOccurringEventId returned ${duplicateEventIndex} which wasn't ` +
`in [${eventIds.join(",")}] in ${this._roomId}`);
}
nonOverlappingEvents.push(...remainingEvents.slice(0, duplicateEventIndex));
if (!expectedOverlappingEventId || duplicateEventId === expectedOverlappingEventId) {
// TODO: check here that the neighbourEvent is at the correct edge of it's fragment
// get neighbour fragment to link it up later on
const neighbourEvent = await txn.timelineEvents.getByEventId(this._roomId, duplicateEventId);
const neighbourFragment = await txn.timelineFragments.get(this._roomId, neighbourEvent.fragmentId);
neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment);
// trim overlapping events
remainingEvents = [];
} else {
// we've hit https://github.com/matrix-org/synapse/issues/7164,
// e.g. the event id we found is already in our store but it is not
// the adjacent fragment id. Ignore the event, but keep processing the ones after.
remainingEvents = remainingEvents.slice(duplicateEventIndex + 1);
}
} else {
nonOverlappingEvents.push(...remainingEvents);
}
}
return {nonOverlappingEvents, neighbourFragmentEntry};
}

async _findLastFragmentEventKey(fragmentEntry, txn) {
async _findExpectedOverlappingEventId(fragmentEntry, txn) {
const eventEntry = await this._findFragmentEdgeEvent(
fragmentEntry.linkedFragmentId,
// reverse because it's the oppose edge of the linked fragment
fragmentEntry.direction.reverse(),
txn);
if (eventEntry) {
return eventEntry.event.event_id;
}
}

async _findFragmentEdgeEventKey(fragmentEntry, txn) {
const {fragmentId, direction} = fragmentEntry;
const event = await this._findFragmentEdgeEvent(fragmentId, direction, txn);
if (event) {
return new EventKey(event.fragmentId, event.eventIndex);
} else {
// no events yet in the fragment ... odd, but let's not fail and take the default key
return EventKey.defaultFragmentKey(fragmentEntry.fragmentId);
}
}

async _findFragmentEdgeEvent(fragmentId, direction, txn) {
if (direction.isBackward) {
const [firstEvent] = await txn.timelineEvents.firstEvents(this._roomId, fragmentId, 1);
return new EventKey(firstEvent.fragmentId, firstEvent.eventIndex);
return firstEvent;
} else {
const [lastEvent] = await txn.timelineEvents.lastEvents(this._roomId, fragmentId, 1);
return new EventKey(lastEvent.fragmentId, lastEvent.eventIndex);
return lastEvent;
}
}

Expand All @@ -57,8 +101,22 @@ export default class GapWriter {
directionalAppend(entries, fragmentEntry, direction);
// set `end` as token, and if we found an event in the step before, link up the fragments in the fragment entry
if (neighbourFragmentEntry) {
fragmentEntry.linkedFragmentId = neighbourFragmentEntry.fragmentId;
neighbourFragmentEntry.linkedFragmentId = fragmentEntry.fragmentId;
// the throws here should never happen and are only here to detect client or unhandled server bugs
// and a last measure to prevent corrupting fragment links
if (!fragmentEntry.hasLinkedFragment) {
fragmentEntry.linkedFragmentId = neighbourFragmentEntry.fragmentId;
} else if (fragmentEntry.linkedFragmentId !== neighbourFragmentEntry.fragmentId) {
throw new Error(`Prevented changing fragment ${fragmentEntry.fragmentId} ` +
`${fragmentEntry.direction.asApiString()} link from ${fragmentEntry.linkedFragmentId} ` +
`to ${neighbourFragmentEntry.fragmentId} in ${this._roomId}`);
}
if (!neighbourFragmentEntry.hasLinkedFragment) {
neighbourFragmentEntry.linkedFragmentId = fragmentEntry.fragmentId;
} else if (neighbourFragmentEntry.linkedFragmentId !== fragmentEntry.fragmentId) {
throw new Error(`Prevented changing fragment ${neighbourFragmentEntry.fragmentId} ` +
`${neighbourFragmentEntry.direction.asApiString()} link from ${neighbourFragmentEntry.linkedFragmentId} ` +
`to ${fragmentEntry.fragmentId} in ${this._roomId}`);
}
// if neighbourFragmentEntry was found, it means the events were overlapping,
// so no pagination should happen anymore.
neighbourFragmentEntry.token = null;
Expand Down Expand Up @@ -100,7 +158,7 @@ export default class GapWriter {
throw new Error("start is not equal to prev_batch or next_batch");
}
// find last event in fragment so we get the eventIndex to begin creating keys at
let lastKey = await this._findLastFragmentEventKey(fragmentEntry, txn);
let lastKey = await this._findFragmentEdgeEventKey(fragmentEntry, txn);
// find out if any event in chunk is already present using findFirstOrLastOccurringEventId
const {
nonOverlappingEvents,
Expand Down

0 comments on commit 086bd4e

Please sign in to comment.