fix props memory leak

This commit is contained in:
Evan Almloff 2023-10-26 13:06:42 -05:00
commit fca9d95519
5 changed files with 69 additions and 18 deletions

View file

@ -164,17 +164,11 @@ impl VirtualDom {
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
let mut listeners = scope.attributes_to_drop.borrow_mut();
let mut listeners = scope.attributes_to_drop_before_render.borrow_mut();
listeners.drain(..).for_each(|listener| {
let listener = unsafe { &*listener };
match &listener.value {
AttributeValue::Listener(l) => {
_ = l.take();
}
AttributeValue::Any(a) => {
_ = a.take();
}
_ => (),
if let AttributeValue::Listener(l) = &listener.value {
_ = l.take();
}
});
}

View file

@ -1,10 +1,14 @@
use crate::nodes::RenderReturn;
use crate::{Attribute, AttributeValue, VComponent};
use bumpalo::Bump;
use std::cell::RefCell;
use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: UnsafeCell<Bump>,
pub node: Cell<*const RenderReturn<'static>>,
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
}
impl BumpFrame {
@ -13,6 +17,8 @@ impl BumpFrame {
Self {
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
attributes_to_drop_before_reset: Default::default(),
props_to_drop_before_reset: Default::default(),
}
}
@ -31,8 +37,34 @@ impl BumpFrame {
unsafe { &*self.bump.get() }
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
unsafe { &mut *self.bump.get() }
pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) {
self.attributes_to_drop_before_reset
.borrow_mut()
.push(attribute);
}
pub(crate) unsafe fn reset(&self) {
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
attributes.drain(..).for_each(|attribute| {
let attribute = unsafe { &*attribute };
if let AttributeValue::Any(l) = &attribute.value {
_ = l.take();
}
});
let mut props = self.props_to_drop_before_reset.borrow_mut();
props.drain(..).for_each(|prop| {
let prop = unsafe { &*prop };
_ = prop.props.borrow_mut().take();
});
unsafe {
let bump = &mut *self.bump.get();
bump.reset();
}
}
}
impl Drop for BumpFrame {
fn drop(&mut self) {
unsafe { self.reset() }
}
}

View file

@ -35,7 +35,7 @@ impl VirtualDom {
hook_idx: Default::default(),
borrowed_props: Default::default(),
attributes_to_drop: Default::default(),
attributes_to_drop_before_render: Default::default(),
}));
let context =
@ -54,7 +54,7 @@ impl VirtualDom {
let new_nodes = unsafe {
let scope = &self.scopes[scope_id.0];
scope.previous_frame().bump_mut().reset();
scope.previous_frame().reset();
scope.context().suspended.set(false);

View file

@ -94,7 +94,7 @@ pub struct ScopeState {
pub(crate) hook_idx: Cell<usize>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
}
@ -348,25 +348,36 @@ impl<'src> ScopeState {
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
let element = rsx.call(self);
let mut listeners = self.attributes_to_drop.borrow_mut();
let mut listeners = self.attributes_to_drop_before_render.borrow_mut();
for attr in element.dynamic_attrs {
match attr.value {
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
// We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped
AttributeValue::Listener(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
listeners.push(unbounded);
}
// We need to drop any values manually to make sure that their drop implementation is called before the next render
AttributeValue::Any(_) => {
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
self.previous_frame().add_attribute_to_drop(unbounded);
}
_ => (),
}
}
let mut props = self.borrowed_props.borrow_mut();
let mut drop_props = self
.previous_frame()
.props_to_drop_before_reset
.borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
props.push(unbounded);
}
drop_props.push(unbounded);
}
}

View file

@ -11,6 +11,8 @@ use crate::navigation::NavigationTarget;
use crate::prelude::Routable;
use crate::utils::use_router_internal::use_router_internal;
use url::Url;
/// Something that can be converted into a [`NavigationTarget`].
#[derive(Clone)]
pub enum IntoRoutable {
@ -53,6 +55,18 @@ impl From<&str> for IntoRoutable {
}
}
impl From<Url> for IntoRoutable {
fn from(url: Url) -> Self {
IntoRoutable::FromStr(url.to_string())
}
}
impl From<&Url> for IntoRoutable {
fn from(url: &Url) -> Self {
IntoRoutable::FromStr(url.to_string())
}
}
/// The properties for a [`Link`].
#[derive(Props)]
pub struct LinkProps<'a> {