Merge pull request #253 from vector-im/bwindels/fix-160

Prevent endless loop when hitting server bug at top of timeline
This commit is contained in:
Bruno Windels 2021-03-02 18:38:05 +00:00 committed by GitHub
commit 5c02735ff7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 27 additions and 12 deletions

View file

@ -55,8 +55,8 @@ export class TimelineViewModel extends ViewModel {
if (firstTile.shape === "gap") { if (firstTile.shape === "gap") {
return await firstTile.fill(); return await firstTile.fill();
} else { } else {
await this._timeline.loadAtTop(10); const topReached = await this._timeline.loadAtTop(10);
return false; return topReached;
} }
} }

View file

@ -441,7 +441,7 @@ export class Room extends EventEmitter {
storage: this._storage, storage: this._storage,
fragmentIdComparer: this._fragmentIdComparer, fragmentIdComparer: this._fragmentIdComparer,
}); });
gapResult = await gapWriter.writeFragmentFill(fragmentEntry, response, txn); gapResult = await gapWriter.writeFragmentFill(fragmentEntry, response, txn, log);
} catch (err) { } catch (err) {
txn.abort(); txn.abort();
throw err; throw err;

View file

@ -74,13 +74,19 @@ export class Timeline {
} }
// tries to prepend `amount` entries to the `entries` list. // tries to prepend `amount` entries to the `entries` list.
/**
* [loadAtTop description]
* @param {[type]} amount [description]
* @return {boolean} true if the top of the timeline has been reached
*
*/
async loadAtTop(amount) { async loadAtTop(amount) {
if (this._disposables.isDisposed) { if (this._disposables.isDisposed) {
return; return true;
} }
const firstEventEntry = this._remoteEntries.array.find(e => !!e.eventType); const firstEventEntry = this._remoteEntries.array.find(e => !!e.eventType);
if (!firstEventEntry) { if (!firstEventEntry) {
return; return true;
} }
const readerRequest = this._disposables.track(this._timelineReader.readFrom( const readerRequest = this._disposables.track(this._timelineReader.readFrom(
firstEventEntry.asEventKey(), firstEventEntry.asEventKey(),
@ -90,6 +96,7 @@ export class Timeline {
try { try {
const entries = await readerRequest.complete(); const entries = await readerRequest.complete();
this._remoteEntries.setManySorted(entries); this._remoteEntries.setManySorted(entries);
return entries.length < amount;
} finally { } finally {
this._disposables.disposeTracked(readerRequest); this._disposables.disposeTracked(readerRequest);
} }

View file

@ -26,7 +26,7 @@ export class GapWriter {
this._fragmentIdComparer = fragmentIdComparer; this._fragmentIdComparer = fragmentIdComparer;
} }
// 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) { async _findOverlappingEvents(fragmentEntry, events, txn, log) {
let expectedOverlappingEventId; let expectedOverlappingEventId;
if (fragmentEntry.hasLinkedFragment) { if (fragmentEntry.hasLinkedFragment) {
expectedOverlappingEventId = await this._findExpectedOverlappingEventId(fragmentEntry, txn); expectedOverlappingEventId = await this._findExpectedOverlappingEventId(fragmentEntry, txn);
@ -49,8 +49,12 @@ export class GapWriter {
// TODO: check here that the neighbourEvent is at the correct edge of it's fragment // TODO: check here that the neighbourEvent is at the correct edge of it's fragment
// get neighbour fragment to link it up later on // get neighbour fragment to link it up later on
const neighbourEvent = await txn.timelineEvents.getByEventId(this._roomId, duplicateEventId); 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); const neighbourFragment = await txn.timelineFragments.get(this._roomId, neighbourEvent.fragmentId);
neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment); neighbourFragmentEntry = fragmentEntry.createNeighbourEntry(neighbourFragment);
}
// trim overlapping events // trim overlapping events
remainingEvents = null; remainingEvents = null;
} else { } else {
@ -192,10 +196,11 @@ export class GapWriter {
return changedFragments; return changedFragments;
} }
async writeFragmentFill(fragmentEntry, response, txn) { async writeFragmentFill(fragmentEntry, response, txn, log) {
const {fragmentId, direction} = fragmentEntry; const {fragmentId, direction} = fragmentEntry;
// chunk is in reverse-chronological order when backwards // chunk is in reverse-chronological order when backwards
const {chunk, start, end, state} = response; const {chunk, start, state} = response;
let {end} = response;
let entries; let entries;
if (!Array.isArray(chunk)) { if (!Array.isArray(chunk)) {
@ -229,8 +234,11 @@ export class GapWriter {
const { const {
nonOverlappingEvents, nonOverlappingEvents,
neighbourFragmentEntry neighbourFragmentEntry
} = await this._findOverlappingEvents(fragmentEntry, chunk, txn); } = await this._findOverlappingEvents(fragmentEntry, chunk, txn, log);
if (!neighbourFragmentEntry && nonOverlappingEvents.length === 0 && typeof end === "string") {
log.log("hit #160, clearing token", log.level.Warn);
end = null;
}
// create entries for all events in chunk, add them to entries // create entries for all events in chunk, add them to entries
entries = this._storeEvents(nonOverlappingEvents, lastKey, direction, state, txn); entries = this._storeEvents(nonOverlappingEvents, lastKey, direction, state, txn);
const fragments = await this._updateFragments(fragmentEntry, neighbourFragmentEntry, end, entries, txn); const fragments = await this._updateFragments(fragmentEntry, neighbourFragmentEntry, end, entries, txn);