Skip to content

Computed: memory leak #2261

Closed
Closed
@basvanmeurs

Description

@basvanmeurs

Version

3.0.0

Reproduction link

https://pastebin.pl/view/raw/ac6ade7c

Steps to reproduce

  1. Click on clear button
  2. In chrome, take a memory heap snapshot.

What is expected?

MyClass instances are not in the snapshot.

What is actually happening?

MyClass instances are in the snapshot.


The instances are retained by the 'computedField' computeds, which are retained in someRefThatExistsLongerThanA's depMaps:
https://github.com/vuejs/vue-next/blob/d52d139b854238708195f5d332b7d0ec3a4883a5/packages/reactivity/src/effect.ts#L143

This is because dependencies from someRefThatExistsLongerThanA to computedFields are never cleared, except immediately before re-evaluating them. Computed does not have an exported way to 'cleanup' them, like watch and watchEffect have (WatchStopHandle).

We ran into this when we found a memory leak in one of our applications.

Solutions:

  1. ComputedRef.cleanup
    ComputedRef could offer a cleanup method which simply runs the effects' cleanup (https://github.com/vuejs/vue-next/blob/d52d139b854238708195f5d332b7d0ec3a4883a5/packages/reactivity/src/effect.ts#L111), as well as triggering itself. The latter is necessary to recursively trigger computed dependees, so that they become dirty and don't return stale results when called later - if ever.
    Downsides are that this should be done manually by the developer, and that calling computedRef.cleanup will also invoke watchers if they are still active - so they should be unregistered before cleaning up the computeds that they depend on. In practice it will be quite cumbersome, and another chore for developers.

  2. Alternatively, we could clear the gathered Sets as part of add (https://github.com/vuejs/vue-next/blob/d52d139b854238708195f5d332b7d0ec3a4883a5/packages/reactivity/src/effect.ts#L180). There's no point in maintaining them anyway from this point, as all dependees are triggered and will re-evaluate.
    The benefit is that if all dependencies have been updated for some computed, that it will be totally dereferenced. The developer doesn't have to do anything except for - possibly - being careful with refs that are never or rarely updated. When such a case is encountered, triggerRef could be used to make sure that dependees are dereferenced once in a while. In practice this is not very likely to happen though.

There is another positive side effect, in that dependees that were marked dirty have been removed already, and do not have to be traversed upon every update of the dependency. In our own use case this is greatly beneficial, as we're having up to 20'000 rows that have a computed that depends on one global computed, while only a fraction of rows is actually invoked.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions