diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 7c60da47..99d7f607 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -2,6 +2,7 @@ * $Id$ */ +#include #include #include #include @@ -24,6 +25,7 @@ struct ptr_data { void *ptr; long size; freefunc_t free; + bool freed; VALUE wrap[2]; }; @@ -57,14 +59,19 @@ fiddle_ptr_mark(void *ptr) } static void -fiddle_ptr_free(void *ptr) +fiddle_ptr_free_ptr(void *ptr) { struct ptr_data *data = ptr; - if (data->ptr) { - if (data->free) { - (*(data->free))(data->ptr); - } + if (data->ptr && data->free && !data->freed) { + data->freed = true; + (*(data->free))(data->ptr); } +} + +static void +fiddle_ptr_free(void *ptr) +{ + fiddle_ptr_free_ptr(ptr); xfree(ptr); } @@ -89,6 +96,7 @@ rb_fiddle_ptr_new2(VALUE klass, void *ptr, long size, freefunc_t func) val = TypedData_Make_Struct(klass, struct ptr_data, &fiddle_ptr_data_type, data); data->ptr = ptr; data->free = func; + data->freed = false; data->size = size; return val; @@ -140,6 +148,7 @@ rb_fiddle_ptr_s_allocate(VALUE klass) data->ptr = 0; data->size = 0; data->free = 0; + data->freed = false; return obj; } @@ -197,11 +206,16 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self) * * == Examples * + * # Manually freeing but relying on the garbage collector otherwise + * pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) + * ... + * pointer.call_free + * * # Relying on the garbage collector - may lead to unlimited memory allocated before freeing any, but safe * pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE) * ... * - * # Manual freeing + * # Only manually freeing * pointer = Fiddle::Pointer.malloc(size) * begin * ... @@ -214,13 +228,15 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self) * ... * * Allocate +size+ bytes of memory and associate it with an optional - * +freefunc+ that will be called when the pointer is garbage collected. + * +freefunc+ that will be called when the pointer is garbage collected, + * if not already manually called via *call_free*. + * * +freefunc+ must be an address pointing to a function or an instance of - * +Fiddle::Function+. Using +freefunc+ may lead to unlimited memory being - * allocated before any is freed as the native memory the pointer references - * does not contribute to triggering the Ruby garbage collector. Consider - * manually freeing the memory as illustrated above. You cannot combine - * the techniques as this may lead to a double-free. + * +Fiddle::Function+. Using +freefunc+ without using +call_free+ may lead to + * unlimited memory being allocated before any is freed as the native memory + * the pointer references does not contribute to triggering the Ruby garbage + * collector. Consider manually freeing the memory as illustrated above. You + * can combine the techniques as +freefunc+ will not be called twice. */ static VALUE rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass) @@ -370,6 +386,34 @@ rb_fiddle_ptr_free_get(VALUE self) return rb_fiddle_new_function(address, arg_types, ret_type); } +/* + * call-seq: call_free => nil + * + * Call the free function for this pointer. Calling more than once will do + * nothing. Does nothing if there is no free function attached. + */ +static VALUE +rb_fiddle_ptr_call_free(VALUE self) +{ + struct ptr_data *pdata; + TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata); + fiddle_ptr_free_ptr(pdata); + return Qnil; +} + +/* + * call-seq: freed? => bool + * + * Returns if the free function for this pointer has been called. + */ +static VALUE +rb_fiddle_ptr_freed_p(VALUE self) +{ + struct ptr_data *pdata; + TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata); + return pdata->freed ? Qtrue : Qfalse; +} + /* * call-seq: * @@ -711,6 +755,8 @@ Init_fiddle_pointer(void) rb_define_method(rb_cPointer, "initialize", rb_fiddle_ptr_initialize, -1); rb_define_method(rb_cPointer, "free=", rb_fiddle_ptr_free_set, 1); rb_define_method(rb_cPointer, "free", rb_fiddle_ptr_free_get, 0); + rb_define_method(rb_cPointer, "call_free", rb_fiddle_ptr_call_free, 0); + rb_define_method(rb_cPointer, "freed?", rb_fiddle_ptr_freed_p, 0); rb_define_method(rb_cPointer, "to_i", rb_fiddle_ptr_to_i, 0); rb_define_method(rb_cPointer, "to_int", rb_fiddle_ptr_to_i, 0); rb_define_method(rb_cPointer, "to_value", rb_fiddle_ptr_to_value, 0); diff --git a/test/fiddle/test_pointer.rb b/test/fiddle/test_pointer.rb index c69e4f71..ee111778 100644 --- a/test/fiddle/test_pointer.rb +++ b/test/fiddle/test_pointer.rb @@ -170,6 +170,33 @@ def test_free= assert_equal free.ptr, ptr.free.ptr end + def test_free_with_func + ptr = Pointer.malloc(4, Fiddle::RUBY_FREE) + refute ptr.freed? + ptr.call_free + assert ptr.freed? + ptr.call_free # you can safely run it again + assert ptr.freed? + GC.start # you can safely run the GC routine + assert ptr.freed? + end + + def test_free_with_no_func + ptr = Pointer.malloc(4) + refute ptr.freed? + ptr.call_free + refute ptr.freed? + ptr.call_free # you can safely run it again + refute ptr.freed? + end + + def test_freed? + ptr = Pointer.malloc(4, Fiddle::RUBY_FREE) + refute ptr.freed? + ptr.call_free + assert ptr.freed? + end + def test_null? ptr = Pointer.new(0) assert ptr.null?