Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【Bug】Memory Leak cause by toVue3ComponentInstance #991

Closed
dennis-leee opened this issue Jan 23, 2024 · 1 comment
Closed

【Bug】Memory Leak cause by toVue3ComponentInstance #991

dennis-leee opened this issue Jan 23, 2024 · 1 comment

Comments

@dennis-leee
Copy link

dennis-leee commented Jan 23, 2024

[Cause]
i found the logic in toVue3ComponentInstance function
it use a WeakMap to cache to the map of Vue2 instance to fake-Vue3 instance
But the fake-Vue3 instance has a property named proxy that references to the Vue2 instance.
This proxy forms a strong reference, which prevent Vue2 instance to be GC,
And If the Vue2 instance is not GC, then the weakmap mapping will not be automatically deleted,
then the fake-vue3 instance will keep by the WeakMap obj, also the strong reference to vue2 instance will be keep too
image

This is the result of the Memlab memory leak test
memlab修复后edit弹窗快照

[Affect]
All instances created after Vue-composition-API registration

[Solution]
there are to way to fix it;

  1. replace all strong reference of vue2 instance with WeakRef
function toVue3ComponentInstance(vm) {
  if (instanceMapCache.has(vm)) {
    console.info('get vm from cache');
    return instanceMapCache.get(vm);
  }
// use WeakRef to reference vue2 instance
  let weakRef = new WeakRef(vm);
  const vmProxy = {
    get proxy() {
        return weakRef.deref() || {};
    },
    set proxy(val) {
        const vm = weakRef.deref() || {};
        const newVal = { ...vm, ...val };
        weakRef = new WeakRef(newVal);
    }
  }.proxy;
  var instance = {
    proxy: vmProxy,
    update: vmProxy.$forceUpdate,
    type: vmProxy.$options,
    uid: vmProxy._uid,
    // $emit is defined on prototype and it expected to be bound
    emit: vmProxy.$emit.bind(vmProxy),
    parent: null,
    root: null, // to be immediately set
  };
  bindCurrentScopeToVM(instance);
  // map vm.$props =
  var instanceProps = [
    'data',
    'props',
    'attrs',
    'refs',
    'vnode',
    'slots',
  ];
  instanceProps.forEach(function(prop) {
    proxy(instance, prop, {
      get: function() {
        return vmProxy['$'.concat(prop)];
      },
    });
  });
  proxy(instance, 'isMounted', {
    get: function() {
      // @ts-expect-error private api
      return vmProxy._isMounted;
    },
  });
  proxy(instance, 'isUnmounted', {
    get: function() {
      // @ts-expect-error private api
      return vmProxy._isDestroyed;
    },
  });
  proxy(instance, 'isDeactivated', {
    get: function() {
      // @ts-expect-error private api
      return vmProxy._inactive;
    },
  });
  proxy(instance, 'emitted', {
    get: function() {
      // @ts-expect-error private api
      return vmProxy._events;
    },
  });
  instanceMapCache.set(vm, instance);
  vm.$on('hook:destroyed', function() { console.info('composition api cache has vm', instanceMapCache.has(vm))  });
  if (vmProxy.$parent) {
    instance.parent = toVue3ComponentInstance(vm.$parent);
  }
  if (vmProxy.$root) {
    instance.root = toVue3ComponentInstance(vm.$root);
  }
  return instance;
}
  1. delete the map manually
function toVue3ComponentInstance(vm) {
...
  instanceMapCache.set(vm, instance);
  // add this line
  vm.$on('hook:destroyed', function() { instanceMapCache.delete(vm) });
...
}

The first method is not very elegant, so I recommend using the second method

[Other]
Since this library has stopped updating now,
I just fix it in my own project.

hope this can help u if u meet the problem too.

Copy link

Stale issue message

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant