mobx: Question: How do you avoid "leaking" memory while making sure there is only one instance of each domain object?

One of the benefits of MobX is using real references to domain objects and knowing that you only have one instance of each domain object. The documentation suggests using stores to instantiate domain objects and to ensure there is only one instance of each. To do this the store needs to keep track of existing instances based on a domain object ID, for example using a simple ID-to-DomainObject lookup structure like Map<string,DomainObject>.

The thing I cannot figure out is how to avoid “leaking” those domain object instances when they are no longer needed by the application.

Example

Suppose you are implementing a typical forum with threads for discussion. A central domain object would be the Thread. There will typically be thousands of threads on the server. The user arrives at the front page which displays the 50 most recent threads as well as the 10 most active (hot) threads. Some of the hot threads are also in the list of 50 most recent threads. The ThreadStore creates 50-60 Thread instances depending on how many are common between the two lists.

The user moves on to the second page of most recent threads. The list of 10 hot threads remains the same, while the 50 most recent threads are replaced with the 51-100 most recent. If there was no global store referencing the Thread instances then the previous threads could be garbage collected. However the ThreadStore doesn’t know if the “hot threads” list (or some other part of the application) is still referencing any of those Threads so it must keep tracking them. The ThreadStore cannot forget the previous 1-50 instances, so there are now 40-50 unused Thread instances which are still in memory. If the user keeps navigating through the pages then there will be hundreds and eventually thousands of Thread instances remaining in memory. If the app is intended to be realtime then there will also be subscriptions per Thread causing wasted network traffic and work for the server.

MobX vs other systems

This challenge isn’t really specific to MobX. Any system which tries to only have a single instance of each domain object will have the same problem. I think most systems avoid the problem by not having single instances. Instead they let each component own and manage the instances themselves. Instances can be garbage collected when no components reference them anymore since there are no global/long-living store keep them in memory.

How to approach this?

So I am wondering how people are dealing with this (if at all). I even expected to find libraries which handle this kind of maintenance since it seems like it would be a common challenge, but so far I have come up short. The lack of libraries and discussions about this makes me suspect that I am overthinking this, but I also can’t see a simple and clean solution.

Some of the approaches I have considered:

  1. Don’t worry about it. Users will normally not accumulate enough instances for this to be a problem. Probably applies to 9 out of 10 apps. However, some applications (mail client, issue tracking system, dashboards, facebook, etc) can potentially be left running for days and weeks without reload.
  2. Don’t worry about it, but handle the long-running edge cases by triggering a page reload when total number of instances reaches a certain threshold. Dirty, but reload would be a rare failsafe. Caching and saving state in local storage could make it nearly invisible to the user.
  3. Manual reference counting. Any time a component keeps a reference to an instance it should notify the store through something like object.takeRef(), and release when finished using object.releaseRef(). The store or domain object maintain a reference counter, and the store can forget an instance when its reference count reaches 0. This would be slightly annoying and fairly error prone though.
  4. Aggregate domain objects in query result sets to avoid explicitly calling .takeRef(). A component can create needed queries on construction and destroy them on unmount. The query result set calls .takeRef() and .releaseRef() automatically as domain objects are added to or removed from the result set. The problem with this is that nothing stops a component from passing a domain object along to a different part of the application, which has the same downside as point 3.
  5. Don’t try to have a single instance for each domain object. This removes the need for a central store keeping references to all domain objects, so instances will be garbage collected when no component are using them any more. Need to use ‘object.id’ to compare identities. If instances are to share a subscription to be updated then the component will need to remember to unsubscribe. This could cause issues if the component has passed the instance along to another part of the application since the instance will still exist yet its subscription is ended.

Is there an obvious way to think about this that I am missing here? Most examples seem to go with point 1 (I think).

Thanks for reading! 😃

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 11
  • Comments: 21 (7 by maintainers)

Most upvoted comments

My preferred solution is “don’t worry about it” combined with “don’t use global singleton stores for lists of things” (instead, use per-“page” stores)

So once a user navigates away “sufficiently” from the original UI, that UI “page”'s object and all its stores go away. If data needs to be saved, its better to save it to a more persistent storage anyway to avoid data loss in the event of a page reload.

Additionally, you could have a routing system that allows page stores to pass model objects to eachother, thereby preserving them only while necessary.

@sveinatle, haven’t you considered using Maps of maximum size, so the last used (via .get()) keys are being deleted on exceeding this limit?