Change the backfill code to only close gap when all events overlap

This commit is contained in:
Danila Fedorin 2021-09-21 11:18:44 -07:00
parent 92dcc6c980
commit 08461baedc
2 changed files with 28 additions and 53 deletions

View File

@ -26,47 +26,28 @@ export class GapWriter {
this._fragmentIdComparer = fragmentIdComparer; this._fragmentIdComparer = fragmentIdComparer;
this._relationWriter = relationWriter; this._relationWriter = relationWriter;
} }
async _pickNeighbourFragment(txn, duplcateEventIds) {
const duplicateEventId = duplcateEventIds[0];
const duplicateEvent = await txn.timelineEvents.getByEventId(this._roomId, duplicateEventId);
return duplicateEvent.fragmentId;
}
// events is in reverse-chronological order (last event comes at index 0) if backwards // events is in reverse-chronological order (last event comes at index 0) if backwards
async _findOverlappingEvents(fragmentEntry, events, txn, log) { async _findOverlappingEvents(fragmentEntry, events, txn, log) {
let expectedOverlappingEventId; const eventIds = events.map(e => e.event_id);
if (fragmentEntry.hasLinkedFragment) { const duplicateEventIds = await txn.timelineEvents.findOccurringEventIds(this._roomId, eventIds);
expectedOverlappingEventId = await this._findExpectedOverlappingEventId(fragmentEntry, txn); const nonOverlappingEvents = events.filter(e => !duplicateEventIds.includes(e.event_id));
}
let remainingEvents = events;
let nonOverlappingEvents = [];
let neighbourFragmentEntry; let neighbourFragmentEntry;
while (remainingEvents && remainingEvents.length) { if (eventIds.length && eventIds.length === duplicateEventIds.length) {
const eventIds = remainingEvents.map(e => e.event_id); // TODO: check here that the neighbourEvent is at the correct edge of it's fragment
const duplicateEventId = await txn.timelineEvents.findFirstOccurringEventId(this._roomId, eventIds); // get neighbour fragment to link it up later on
if (duplicateEventId) { const neighbourFragmentId = await this._pickNeighbourFragment(txn, duplicateEventIds);
const duplicateEventIndex = remainingEvents.findIndex(e => e.event_id === duplicateEventId); if (neighbourFragmentId === fragmentEntry.fragmentId) {
// should never happen, just being defensive as this *can't* go wrong log.log("hit #160, prevent fragment linking to itself", log.level.Warn);
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);
if (neighbourEvent.fragmentId === fragmentEntry.fragmentId) {
log.log("hit #160, prevent fragment linking to itself", log.level.Warn);
} else {
const neighbourFragment = await txn.timelineFragments.get(this._roomId, neighbourEvent.fragmentId);
neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment);
}
// trim overlapping events
remainingEvents = null;
} 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 { } else {
nonOverlappingEvents.push(...remainingEvents); const neighbourFragment = await txn.timelineFragments.get(this._roomId, neighbourFragmentId);
remainingEvents = null; neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment);
} }
} }
return {nonOverlappingEvents, neighbourFragmentEntry}; return {nonOverlappingEvents, neighbourFragmentEntry};

View File

@ -234,31 +234,25 @@ export class TimelineEventStore {
// In that case we could avoid running over all eventIds, as the reported order by findExistingKeys // In that case we could avoid running over all eventIds, as the reported order by findExistingKeys
// would match the order of eventIds. That's why findLast is also passed as backwards to keysExist. // would match the order of eventIds. That's why findLast is also passed as backwards to keysExist.
// also passing them in chronological order makes sense as that's how we'll receive them almost always. // also passing them in chronological order makes sense as that's how we'll receive them almost always.
async findFirstOccurringEventId(roomId: string, eventIds: string[]): Promise<string | undefined> { async findOccurringEventIds(roomId: string, eventIds: string[]): Promise<string[]> {
const byEventId = this._timelineStore.index("byEventId"); const byEventId = this._timelineStore.index("byEventId");
const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId)); const keys = eventIds.map(eventId => encodeEventIdKey(roomId, eventId));
const results = new Array(keys.length); const results = new Array(keys.length);
let firstFoundKey: string | undefined; const occuringEventIds: string[] = [];
// find first result that is found and has no undefined results before it
function firstFoundAndPrecedingResolved(): string | undefined {
for(let i = 0; i < results.length; ++i) {
if (results[i] === undefined) {
return;
} else if(results[i] === true) {
return keys[i];
}
}
}
await byEventId.findExistingKeys(keys, false, (key, found) => { await byEventId.findExistingKeys(keys, false, (key, found) => {
// T[].search(T, number), but we want T[].search(R, number), so cast // T[].search(T, number), but we want T[].search(R, number), so cast
const index = (keys as IDBValidKey[]).indexOf(key); const index = (keys as IDBValidKey[]).indexOf(key);
results[index] = found; results[index] = found;
firstFoundKey = firstFoundAndPrecedingResolved(); return false;
return !!firstFoundKey;
}); });
return firstFoundKey && decodeEventIdKey(firstFoundKey).eventId; for(let i = 0; i < results.length; ++i) {
console.log(i, eventIds[i], results[i]);
if (!results[i]) continue;
occuringEventIds.push(eventIds[i]);
}
console.log(occuringEventIds);
return occuringEventIds;
} }
/** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown. /** Inserts a new entry into the store. The combination of roomId and eventKey should not exist yet, or an error is thrown.