Skip to content

Weakref callback #488

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

Closed
51 changes: 50 additions & 1 deletion tests/snippets/weakrefs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
import sys
from _weakref import ref

from testutils import assert_raises

data_holder = {}


class X:
pass
def __init__(self, param=0):
self.param = param

def __str__(self):
return f"param: {self.param}"


a = X()
b = ref(a)


def callback(weak_ref):
assert weak_ref is c
assert b() is None, 'reference to same object is dead'
assert c() is None, 'reference is dead'
data_holder['first'] = True


c = ref(a, callback)


def never_callback(_weak_ref):
data_holder['never'] = True


# weakref should be cleaned up before object, so callback is never called
ref(a, never_callback)

assert callable(b)
assert b() is a

assert 'first' not in data_holder
del a
assert b() is None
assert 'first' in data_holder
assert 'never' not in data_holder

# TODO proper detection of RustPython if sys.implementation.name == 'RustPython':
if not hasattr(sys, 'implementation'):
# implementation detail that the object isn't dropped straight away
# this tests that when an object is resurrected it still acts as normal
delayed_drop = X(5)
delayed_drop_ref = ref(delayed_drop)

delayed_drop = None

assert delayed_drop_ref() is not None
value = delayed_drop_ref()
del delayed_drop # triggers process_deletes

assert str(value) == "param: 5"

assert_raises(TypeError, lambda: ref(1), "can't create weak reference to an int")
15 changes: 2 additions & 13 deletions vm/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ use super::obj::objstr;
use super::obj::objtype;

use super::pyobject::{
AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef,
PyResult, Scope, TypeProtocol,
AttributeProtocol, IdProtocol, PyContext, PyFuncArgs, PyObjectRef, PyResult, TypeProtocol,
};
use super::stdlib::io::io_open;

Expand Down Expand Up @@ -265,17 +264,7 @@ fn make_scope(vm: &mut VirtualMachine, locals: Option<&PyObjectRef>) -> PyObject
};

// TODO: handle optional globals
// Construct new scope:
let scope_inner = Scope {
locals,
parent: None,
};

PyObject {
payload: PyObjectPayload::Scope { scope: scope_inner },
typ: None,
}
.into_ref()
vm.ctx.new_scope_with_locals(None, locals)
}

fn builtin_format(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
Expand Down
6 changes: 6 additions & 0 deletions vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ impl Frame {
}
};

PyObjectRef::process_deletes(vm);

vm.current_frame = prev_frame;
value
}
Expand Down Expand Up @@ -844,6 +846,10 @@ impl Frame {
// Assume here that locals is a dict
let name = vm.ctx.new_str(name.to_string());
vm.call_method(&locals, "__delitem__", vec![name])?;

// process possible delete
PyObjectRef::process_deletes(vm);

Ok(None)
}

Expand Down
1 change: 1 addition & 0 deletions vm/src/obj/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ pub mod objstr;
pub mod objsuper;
pub mod objtuple;
pub mod objtype;
pub mod objweakref;
pub mod objzip;
6 changes: 3 additions & 3 deletions vm/src/obj/objstr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::super::format::{FormatParseError, FormatPart, FormatString};
use super::super::pyobject::{
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
PyContext, PyFuncArgs, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
};
use super::super::vm::VirtualMachine;
use super::objint;
Expand Down Expand Up @@ -1126,8 +1126,8 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu

// help get optional string indices
fn get_slice(
start: Option<&std::rc::Rc<std::cell::RefCell<PyObject>>>,
end: Option<&std::rc::Rc<std::cell::RefCell<PyObject>>>,
start: Option<&PyObjectRef>,
end: Option<&PyObjectRef>,
len: usize,
) -> Result<(usize, usize), String> {
let start_idx = match start {
Expand Down
2 changes: 1 addition & 1 deletion vm/src/obj/objtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes {
}

// Get instance attributes:
if let PyObjectPayload::Instance { dict } = &obj.borrow().payload {
if let PyObjectPayload::Instance { dict, .. } = &obj.borrow().payload {
for (name, value) in dict.borrow().iter() {
attributes.insert(name.to_string(), value.clone());
}
Expand Down
86 changes: 86 additions & 0 deletions vm/src/obj/objweakref.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use super::super::pyobject::{
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyObjectWeakRef, PyResult,
TypeProtocol,
};
use super::super::vm::VirtualMachine;
use super::objtype; // Required for arg_check! to use isinstance

fn ref_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
// TODO: check first argument for subclass of `ref`.
arg_check!(
vm,
args,
required = [(cls, Some(vm.ctx.type_type())), (referent, None)],
optional = [(callback, None)]
);
let weak_referent = PyObjectRef::downgrade(referent);
let weakref = PyObject::new(
PyObjectPayload::WeakRef {
referent: weak_referent,
callback: callback.cloned(),
},
cls.clone(),
);
if referent.borrow_mut().add_weakref(&weakref) {
Ok(weakref)
} else {
let referent_repr = vm.to_pystr(&referent.typ())?;
Err(vm.new_type_error(format!(
"cannot create weak reference to '{}' object",
referent_repr
)))
}
}

/// Dereference the weakref, and check if we still refer something.
fn ref_call(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.weakref_type()))]);
let referent = get_value(zelf);
let py_obj = if let Some(obj) = referent.upgrade() {
obj
} else {
vm.get_none()
};
Ok(py_obj)
}

fn get_value(obj: &PyObjectRef) -> PyObjectWeakRef {
if let PyObjectPayload::WeakRef { referent, .. } = &obj.borrow().payload {
referent.clone()
} else {
panic!("Inner error getting weak ref {:?}", obj);
}
}

fn get_callback(obj: &PyObjectRef) -> Option<PyObjectRef> {
if let PyObjectPayload::WeakRef { callback, .. } = &obj.borrow().payload {
callback.as_ref().cloned()
} else {
panic!("Inner error getting weak ref callback {:?}", obj);
}
}

pub fn clear_weak_ref(obj: &PyObjectRef) {
if let PyObjectPayload::WeakRef {
ref mut referent, ..
} = &mut obj.borrow_mut().payload
{
referent.clear();
} else {
panic!("Inner error getting weak ref {:?}", obj);
}
}

pub fn notify_weak_ref(vm: &mut VirtualMachine, obj: PyObjectRef) -> PyResult {
if let Some(callback) = get_callback(&obj) {
vm.invoke(callback.clone(), PyFuncArgs::new(vec![obj], vec![]))
} else {
Ok(vm.get_none())
}
}

pub fn init(context: &PyContext) {
let weakref_type = &context.weakref_type;
context.set_attr(weakref_type, "__new__", context.new_rustfunc(ref_new));
context.set_attr(weakref_type, "__call__", context.new_rustfunc(ref_call));
}
Loading