Skip to content

Commit f23e729

Browse files
jorendorffhansihe
authored andcommitted
Eliminate box indirection in resources. Fixes #40. (#73)
* Eliminate box indirection in resources. Fixes #40. * Add tests for resource cleanup.
1 parent 613f966 commit f23e729

File tree

6 files changed

+81
-8
lines changed

6 files changed

+81
-8
lines changed

src/codegen_runtime.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub unsafe fn handle_nif_init_call(function: Option<for<'a> fn(NifEnv<'a>, NifTe
6666
use std;
6767
use ::resource::align_alloced_mem_for_struct;
6868
pub unsafe fn handle_drop_resource_struct_handle<T: NifResourceTypeProvider>(_env: NIF_ENV, handle: MUTABLE_NIF_RESOURCE_HANDLE) {
69-
let aligned = align_alloced_mem_for_struct::<Box<T>>(handle);
70-
let res = aligned as *mut Box<T>;
69+
let aligned = align_alloced_mem_for_struct::<T>(handle);
70+
let res = aligned as *mut T;
7171
std::mem::drop(std::ptr::read(res));
7272
}

src/resource.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ pub unsafe fn align_alloced_mem_for_struct<T>(ptr: *const c_void) -> *const c_vo
8484
/// resource instance on creation, and decrements when dropped.
8585
pub struct ResourceCell<T> where T: NifResourceTypeProvider + Sync {
8686
raw: *const c_void,
87-
inner: *mut Box<T>,
87+
inner: *mut T,
8888
}
8989

9090
impl<T> ResourceCell<T> where T: NifResourceTypeProvider + Sync {
9191
/// Makes a new ResourceCell from the given type. Note that the type must have
9292
/// NifResourceTypeProvider implemented for it. See module documentation for info on this.
9393
pub fn new(data: T) -> Self {
94-
let alloc_size = get_alloc_size_struct::<Box<T>>();
94+
let alloc_size = get_alloc_size_struct::<T>();
9595
let mem_raw = unsafe { ::wrapper::resource::alloc_resource(T::get_type().res, alloc_size) };
96-
let aligned_mem = unsafe { align_alloced_mem_for_struct::<Box<T>>(mem_raw) } as *mut Box<T>;
96+
let aligned_mem = unsafe { align_alloced_mem_for_struct::<T>(mem_raw) as *mut T };
9797

98-
unsafe { ptr::write(aligned_mem, Box::new(data)) };
98+
unsafe { ptr::write(aligned_mem, data) };
9999

100100
ResourceCell {
101101
raw: mem_raw,
@@ -109,7 +109,7 @@ impl<T> ResourceCell<T> where T: NifResourceTypeProvider + Sync {
109109
None => return Err(NifError::BadArg),
110110
};
111111
unsafe { ::wrapper::resource::keep_resource(res_resource); }
112-
let casted_ptr = unsafe { align_alloced_mem_for_struct::<Box<T>>(res_resource) } as *mut Box<T>;
112+
let casted_ptr = unsafe { align_alloced_mem_for_struct::<T>(res_resource) as *mut T };
113113
Ok(ResourceCell {
114114
raw: res_resource,
115115
inner: casted_ptr,

test/lib/rustler_test.ex

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ defmodule RustlerTest do
2323
def resource_make(), do: err()
2424
def resource_set_integer_field(_, _), do: err()
2525
def resource_get_integer_field(_), do: err()
26+
def resource_make_immutable(_), do: err()
27+
def resource_immutable_count(), do: err()
2628

2729
def make_shorter_subbinary(_), do: err()
2830
def parse_integer(_), do: err()

test/src/lib.in.rs

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ rustler_export_nifs!(
2626
("resource_make", 0, test_resource::resource_make),
2727
("resource_set_integer_field", 2, test_resource::resource_set_integer_field),
2828
("resource_get_integer_field", 1, test_resource::resource_get_integer_field),
29+
("resource_make_immutable", 1, test_resource::resource_make_immutable),
30+
("resource_immutable_count", 0, test_resource::resource_immutable_count),
2931

3032
("atom_to_string", 1, test_atom::atom_to_string),
3133
("atom_equals_ok", 1, test_atom::atom_equals_ok),

test/src/test_resource.rs

+44
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,16 @@ struct TestResource {
77
test_field: RwLock<i32>,
88
}
99

10+
/// This one is designed to look more like pointer data, to increase the
11+
/// chance of segfaults if the implementation is wrong.
12+
struct ImmutableResource {
13+
a: u32,
14+
b: u32
15+
}
16+
1017
pub fn on_load<'a>(env: NifEnv<'a>) -> bool {
1118
resource_struct_init!(TestResource, env);
19+
resource_struct_init!(ImmutableResource, env);
1220
true
1321
}
1422

@@ -34,3 +42,39 @@ pub fn resource_get_integer_field<'a>(env: NifEnv<'a>, args: &Vec<NifTerm<'a>>)
3442
let test_field = resource.test_field.read().unwrap();
3543
Ok(test_field.encode(env))
3644
}
45+
46+
47+
use std::sync::atomic::{ AtomicUsize, Ordering };
48+
49+
lazy_static! {
50+
static ref COUNT: AtomicUsize = AtomicUsize::new(0);
51+
}
52+
53+
impl ImmutableResource {
54+
fn new(u: u32) -> ImmutableResource {
55+
COUNT.fetch_add(1, Ordering::SeqCst);
56+
ImmutableResource {
57+
a: u,
58+
b: !u
59+
}
60+
}
61+
}
62+
63+
impl Drop for ImmutableResource {
64+
fn drop(&mut self) {
65+
assert_eq!(self.a, !self.b);
66+
self.b = self.a;
67+
COUNT.fetch_sub(1, Ordering::SeqCst);
68+
}
69+
}
70+
71+
pub fn resource_make_immutable<'a>(env: NifEnv<'a>, args: &Vec<NifTerm<'a>>) -> NifResult<NifTerm<'a>> {
72+
let u: u32 = try!(args[0].decode());
73+
Ok(ResourceCell::new(ImmutableResource::new(u)).encode(env))
74+
}
75+
76+
/// Count how many instances of `ImmutableResource` are currently alive globally.
77+
pub fn resource_immutable_count<'a>(env: NifEnv<'a>, _args: &Vec<NifTerm<'a>>) -> NifResult<NifTerm<'a>> {
78+
let n = COUNT.load(Ordering::SeqCst) as u32;
79+
Ok(n.encode(env))
80+
}

test/test/resource_test.exs

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
11
defmodule RustlerTest.ResourceTest do
22
use ExUnit.Case, async: true
3+
use Bitwise
34

45
test "resource creation and interaction" do
5-
resource = RustlerTest.resource_make
6+
resource = RustlerTest.resource_make()
67
assert resource == "" # A resource looks like an empty binary :(
78
assert RustlerTest.resource_get_integer_field(resource) == 0
89
RustlerTest.resource_set_integer_field(resource, 10)
910
assert RustlerTest.resource_get_integer_field(resource) == 10
1011
end
12+
13+
test "resource cleanup" do
14+
# Create a bunch of unreferenced resources for the GC to cleanup.
15+
for i <- 0..1000 do
16+
resource = RustlerTest.resource_make()
17+
RustlerTest.resource_set_integer_field(resource, i)
18+
end
19+
20+
# Clean them up. Don't crash.
21+
:erlang.garbage_collect()
22+
end
23+
24+
test "resource cleanup 2" do
25+
# Create a bunch of unreferenced resources for the GC to cleanup.
26+
for i <- 0..1000 do
27+
RustlerTest.resource_make_immutable((i * 0x11235813) &&& 0xffffffff)
28+
end
29+
30+
# Clean them up. Don't crash.
31+
:erlang.garbage_collect()
32+
33+
# Erlang's exact GC should have cleaned all that up.
34+
assert RustlerTest.resource_immutable_count() == 0
35+
end
1136
end

0 commit comments

Comments
 (0)