-
Notifications
You must be signed in to change notification settings - Fork 240
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
Add destructors for objects #196
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// A handful of classes and functions to support the generated data structures. | ||
// This would be a good candidate for isolating in its own ffi-support lib. | ||
|
||
abstract class FFIObject( | ||
private val handle: AtomicLong | ||
) { | ||
open fun destroy() { | ||
this.handle.set(0L) | ||
} | ||
|
||
internal inline fun <R> callWithHandle(block: (handle: Long) -> R) = | ||
this.handle.get().let { handle -> | ||
if (handle != 0L) { | ||
block(handle) | ||
} else { | ||
throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed") | ||
} | ||
} | ||
} | ||
|
||
inline fun <T : FFIObject, R> T.use(block: (T) -> R) = | ||
try { | ||
block(this) | ||
} finally { | ||
try { | ||
this.destroy() | ||
} catch (e: Throwable) { | ||
// swallow | ||
} | ||
} | ||
Comment on lines
+21
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We all liked |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,47 @@ | ||
class {{ obj.name()|class_name_kt }}( | ||
handle: Long | ||
) : FFIObject(AtomicLong(handle)) { | ||
|
||
class {{ obj.name()|class_name_kt }}(handle: Long) { | ||
private var handle: AtomicLong = AtomicLong(handle) | ||
{%- for cons in obj.constructors() %} | ||
constructor({% call kt::arg_list_decl(cons) -%}) : | ||
this({% call kt::to_rs_call(cons) %}) | ||
{%- endfor %} | ||
|
||
// XXX TODO: destructors or equivalent. | ||
/** | ||
* Disconnect the object from the underlying Rust object. | ||
* | ||
* It can be called more than once, but once called, interacting with the object | ||
* causes an `IllegalStateException`. | ||
* | ||
* Clients **must** call this method once done with the object, or cause a memory leak. | ||
*/ | ||
override fun destroy() { | ||
try { | ||
callWithHandle { | ||
super.destroy() // poison the handle so no-one else can use it before we tell rust. | ||
_UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(it) | ||
} | ||
} catch (e: IllegalStateException) { | ||
// The user called this more than once. Better than less than once. | ||
} | ||
} | ||
|
||
{% for meth in obj.methods() -%} | ||
{%- match meth.return_type() -%} | ||
|
||
{%- when Some with (return_type) -%} | ||
fun {{ meth.name()|fn_name_kt }}({% call kt::arg_list_decl(meth) %}): {{ return_type|type_kt }} { | ||
val _retval = {% call kt::to_rs_call_with_prefix("this.handle.get()", meth) %} | ||
return {{ "_retval"|lift_kt(return_type) }} | ||
} | ||
fun {{ meth.name()|fn_name_kt }}({% call kt::arg_list_decl(meth) %}): {{ return_type|type_kt }} = | ||
callWithHandle { | ||
{% call kt::to_rs_call_with_prefix("it", meth) %} | ||
}.let { | ||
{{ "it"|lift_kt(return_type) }} | ||
} | ||
|
||
{%- when None -%} | ||
fun {{ meth.name()|fn_name_kt }}({% call kt::arg_list_decl(meth) %}) = | ||
{% call kt::to_rs_call_with_prefix("this.handle.get()", meth) %} | ||
callWithHandle { | ||
{% call kt::to_rs_call_with_prefix("it", meth) %} | ||
} | ||
{% endmatch %} | ||
{% endfor %} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,8 @@ def __init__(self, {% call py::arg_list_decl(cons.arguments()) -%}): | |
self._handle = {% call py::to_rs_call(cons) %} | ||
{%- endfor %} | ||
|
||
# XXX TODO: destructors or equivalent. | ||
def __del__(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this similarly poison the object on the python side, like we do in Kotlin? IIRC it is possible that someone might subclass one of these and extend There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ug. I think I'm going to file a python-only issue to investigate stopping resurrecting python side objects. |
||
_UniFFILib.{{ obj.ffi_object_free().name() }}(self._handle) | ||
|
||
{% for meth in obj.methods() -%} | ||
{%- match meth.return_type() -%} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -143,7 +143,7 @@ impl<'ci> ComponentInterface { | |
|
||
pub fn ffi_bytebuffer_alloc(&self) -> FFIFunction { | ||
FFIFunction { | ||
name: format!("{}_bytebuffer_alloc", self.namespace()), | ||
name: format!("ffi_{}_bytebuffer_alloc", self.namespace()), | ||
arguments: vec![Argument { | ||
name: "size".to_string(), | ||
type_: TypeReference::U32, | ||
|
@@ -158,7 +158,7 @@ impl<'ci> ComponentInterface { | |
|
||
pub fn ffi_bytebuffer_free(&self) -> FFIFunction { | ||
FFIFunction { | ||
name: format!("{}_bytebuffer_free", self.namespace()), | ||
name: format!("ffi_{}_bytebuffer_free", self.namespace()), | ||
arguments: vec![Argument { | ||
name: "buf".to_string(), | ||
type_: TypeReference::Bytes, | ||
|
@@ -173,7 +173,7 @@ impl<'ci> ComponentInterface { | |
|
||
pub fn ffi_string_free(&self) -> FFIFunction { | ||
FFIFunction { | ||
name: format!("{}_string_free", self.namespace()), | ||
name: format!("ffi_{}_string_free", self.namespace()), | ||
arguments: vec![Argument { | ||
name: "str".to_string(), | ||
type_: TypeReference::RawStringPointer, | ||
|
@@ -190,9 +190,9 @@ impl<'ci> ComponentInterface { | |
self.objects | ||
.iter() | ||
.map(|obj| { | ||
obj.constructors | ||
.iter() | ||
.map(|f| f.ffi_func.clone()) | ||
vec![obj.ffi_object_free()] | ||
.into_iter() | ||
.chain(obj.constructors.iter().map(|f| f.ffi_func.clone())) | ||
.chain(obj.methods.iter().map(|f| f.ffi_func.clone())) | ||
}) | ||
.flatten() | ||
|
@@ -446,7 +446,6 @@ impl FFIFunction { | |
pub fn return_type(&self) -> Option<&TypeReference> { | ||
self.return_type.as_ref() | ||
} | ||
|
||
pub fn has_out_err(&self) -> bool { | ||
self.has_out_err | ||
} | ||
|
@@ -579,11 +578,21 @@ impl APIConverter<Enum> for weedle::EnumDefinition<'_> { | |
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Object { | ||
name: String, | ||
namespace: String, | ||
constructors: Vec<Constructor>, | ||
methods: Vec<Method>, | ||
} | ||
|
||
impl Object { | ||
fn new(name: String, namespace: String) -> Object { | ||
Object { | ||
name, | ||
namespace, | ||
constructors: Default::default(), | ||
methods: Default::default(), | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🦀 question: Should this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be However, I don't think you'll need this at all if you can avoid creating an API-level There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added However, avoiding adding an |
||
|
||
pub fn name(&self) -> &str { | ||
&self.name | ||
} | ||
|
@@ -596,6 +605,21 @@ impl Object { | |
self.methods.iter().collect() | ||
} | ||
|
||
pub fn ffi_object_free(&self) -> FFIFunction { | ||
FFIFunction { | ||
name: format!("ffi_{}_{}_object_free", self.namespace, self.name), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes, I see the trick here with needing more of the global context for the namespace. This seems OK I think, thanks for digging in. |
||
arguments: vec![Argument { | ||
name: "handle".to_string(), | ||
type_: TypeReference::Object(self.name.clone()), | ||
by_ref: false, | ||
optional: false, | ||
default: None, | ||
}], | ||
return_type: None, | ||
has_out_err: false, | ||
} | ||
} | ||
|
||
fn derive_ffi_funcs(&mut self, ci_prefix: &str) -> Result<()> { | ||
for cons in self.constructors.iter_mut() { | ||
cons.derive_ffi_func(ci_prefix, &self.name)? | ||
|
@@ -742,11 +766,7 @@ impl APIConverter<Object> for weedle::InterfaceDefinition<'_> { | |
if self.inheritance.is_some() { | ||
bail!("interface inheritence is not supported"); | ||
} | ||
let mut object = Object { | ||
name: self.identifier.0.to_string(), | ||
constructors: Default::default(), | ||
methods: Default::default(), | ||
}; | ||
let mut object = Object::new(self.identifier.0.to_string(), ci.namespace().to_string()); | ||
for member in &self.members.body { | ||
match member { | ||
weedle::interface::InterfaceMember::Constructor(t) => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering where we'd put documentation for this, so consumers know they need to call the
destroy
function to do the deallocation. Thoughts?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's a really interesting question.
For now, I've documented in the generated section, however, we do need a better solution for this.