matrix-js-sdk icon indicating copy to clipboard operation
matrix-js-sdk copied to clipboard

MaxListenersExceededWarning on threads; many redundant fetches of thread root event

Open davidisaaclee opened this issue 2 years ago • 9 comments

Using an account that has some threads, I get the following warning around the first sync:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 Thread.update listeners added. Use emitter.setMaxListeners() to increase limit
Stack trace
MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 Thread.update listeners added. Use emitter.setMaxListeners() to increase limit
    _addListener events.js:211
    addListener events.js:227
    on typed-event-emitter.ts:133
    reEmit ReEmitter.ts:57
    reEmit ReEmitter.ts:84
    setThread event.ts:1618
    mapper event-mapper.ts:74
    fetchRootEvent thread.ts:148
    updateThreadMetadata thread.ts:482
    addEvents thread.ts:291
    createThread room.ts:2277
    addThreadedEvents room.ts:2183
    addLiveEvents room.ts:2843
    addLiveEvents room.ts:2842
    injectRoomEvents sync.ts:1779
    processSyncResponse sync.ts:1437
    promiseMapSeries utils.ts:425
    processSyncResponse sync.ts:1293
    doSync sync.ts:915
    sync sync.ts:798
    startClient client.ts:1513
    initializeMatrixConnection App.tsx:39
    App App.tsx:46

I haven't encountered any user-impacting issues around this, ~but it sounds like this could cause MatrixEvents to fail to emit Thread.update to certain listeners.~ It looks like this is simply a warning and does not prevent listeners from being added: https://github.com/browserify/events/blob/48e3d18659caf72d94d319871106f089bb40002d/events.js#L211 . This is still noise that could mask a real, easily-identifiable memory leak.

More importantly, I believe this is caused by a bug in Thread#fetchRootEvent where the remote root event is fetched on every call to updateThreadMetadata (e.g. when adding an event to the thread, as will be done for each thread event received during a sync):

https://github.com/matrix-org/matrix-js-sdk/blob/dfb079a76fcdf2c218e8b6ba08d4d8f5f85fa10c/src/models/thread.ts#L142-L153

Note that this code both checks the cache for an event, then seems to indiscriminately fetch the root event from remote. According to the stack trace, this remote fetch is what leads to the redundant event subscriptions. (If I look at the network tab, I can see that there are many calls to /rooms/{roomId}/event/{eventId} for the same event, which I think is the thread root.)

I can't say for sure that this is the cause, but it at least seems like a bug in itself. If these are not related issues, I'd be happy to split this issue.

If this is a correct diagnostic, you should be able to reproduce the issue using this repo on an account that has a thread with at least 11 replies (11 = EventEmitter max listeners).

davidisaaclee avatar Jun 11 '23 21:06 davidisaaclee

Here's a branch that I started that I think fixes the unnecessary fetch of the thread root: https://github.com/matrix-org/matrix-js-sdk/compare/develop...davidisaaclee:matrix-js-sdk:avoid-unnecessary-fetch-thread-root?expand=1

It's pretty tough to write a test around this code – I started a test a week ago and I forget the state of it. I can pick this back up later but thought I'd post what I have so far.

davidisaaclee avatar Jun 12 '23 17:06 davidisaaclee

https://github.com/matrix-org/matrix-js-sdk/compare/develop...davidisaaclee:matrix-js-sdk:avoid-unnecessary-fetch-thread-root?expand=1 does not fix the MaxListenersExceededWarning:

  • on initial sync, many messages will be added to a thread "at once"
  • each of these will trigger a fetchRootEvent
  • none of these will pass the this.rootEvent != null check, since they all start fetching at the same time
  • they'll all start a request to fetch the root event

I'm not sure what the solution here is. Are there other similar situations in this library that already have solutions?

We could memoize the call to client.fetchRoomEvent, but this would be tricky, as I think fetchRoomEvent is not idempotent (e.g. server-side aggregated message edits).

It looks like there's a similar solution used for scrollbacks (actually, a couple of these stored promises in that file): https://github.com/matrix-org/matrix-js-sdk/blob/9849818efa1d9101e68126af60fba180d7a5f756/src/client.ts#L5537-L5540

davidisaaclee avatar Jun 28 '23 03:06 davidisaaclee

I tried reusing ongoing promises for thread.fetchRootEvent and thread.processRootEvent, which got rid of the unnecessary HTTP calls: https://github.com/davidisaaclee/matrix-js-sdk/commit/891e2cb0a90a2670d60a4a3371a072ef32328a2f

But I still get MaxListenersExceededWarning. Looking at the new offending line of code, I see https://github.com/matrix-org/matrix-js-sdk/blob/891e2cb0a90a2670d60a4a3371a072ef32328a2f/src/models/event.ts#L1617-L1619

My reading of this code is that: for each event in a thread, that event re-emits ThreadEvent.Update events. The way this re-emit works for this concrete case is something like:

thread.addEventListener(ThreadEvent.Update, event => {
  this.reEmitter.emit(ThreadEvent.Update, event);
});

... which obviously leads to many event listeners (as many as there are processed events in the thread).


I don't get the design choice to re-emit ThreadEvent.Update on each event: any caller could just do someEvent.thread?.addEventListener(ThreadEvent.Update, ...). Would you consider a breaking change here to remove this re-emit?

davidisaaclee avatar Jun 28 '23 04:06 davidisaaclee

Yes it'd be a breaking change but likely a welcome one

t3chguy avatar Jun 28 '23 09:06 t3chguy

@t3chguy thank you – I've started the work here, which begins with reducing the unnecessary fetches of thread root events https://github.com/matrix-org/matrix-js-sdk/pull/3530

I plan on adding the code to remove the ThreadEvent.Update re-emit in the same PR unless you have objections; stepping away from computer for a few hours

davidisaaclee avatar Jun 28 '23 18:06 davidisaaclee

@davidisaaclee no objections at this time

t3chguy avatar Jun 29 '23 08:06 t3chguy

I'm giving up on the fixes in #3530 for now, since they are causing other issues: https://github.com/matrix-org/matrix-js-sdk/pull/3530#issuecomment-1616022487 – the reported issues are still in the SDK, so I'd love if someone else wanted to try fixing.

davidisaaclee avatar Jul 01 '23 18:07 davidisaaclee

Did #3541 fix this?

clokep avatar Aug 28 '23 14:08 clokep

@clokep not fully, there's still redundant fetches happening where the data model is inadequate and asks the server for a re-up

t3chguy avatar Aug 31 '23 07:08 t3chguy