Rb_yield(), break, and C extensions


#1

So, I’m working on a C extension.

One of my C functions uses a callback function, so when I wrap that
function in a method, I pass it a callback function that just wraps a
call to rb_yield().

This works great, except that when the block passed to the method
includes a ‘break’ statement, the end of the C function is bypassed,
and so it can’t do its own cleanup.

What I’d like to do is “catch” the break somehow, so I can tell my C
function to exit and cleanup before restoring control to the ruby
script.

Is there any way to do this? I realize it could be a Bad Thing if
abused, but this is for good, I swear.

I guess I could try to pull all the memory allocation/file opening/etc
out to the containing object, but
[1] I’m not sure it’s possible.
[2] It seems kind of awkward.
[3] This wouldn’t solve the problem if I was linking in to an existing
object file that I didn’t have source for.

Here’s some toy code, if I didn’t get my point across:

lib.c
void func( int n, int (*callback)(void) )
{
void * mem = malloc(sizeof(int));
FILE * file = fopen(“filename”, “r”);
int i;
// … do stuff
for (i = 0; i < n; i++) { if (callback() < 0) { goto cleanup; } }
// … do more stuff
cleanup:
if (mem) { free(mem); }
if (file) { fclose(file); }
}

ext.c
static int
meth_callback(void)
{
rb_yield(Qnil);
return 0;
}
static VALUE
yyy_meth(VALUE self, VALUE count)
{
func(FIX2INT(count), meth_callback);
return Qnil;
}

test.rb
require ‘ext’
YYY.new.meth(100) { break }


#2

On 4/7/07, Noah E. removed_email_address@domain.invalid wrote:

So, I’m working on a C extension.

One of my C functions uses a callback function, so when I wrap that
function in a method, I pass it a callback function that just wraps a
call to rb_yield().

This works great, except that when the block passed to the method
includes a ‘break’ statement, the end of the C function is bypassed,
and so it can’t do its own cleanup.

You should wrap your rb_yield call with rb_ensure. From README.EXT:

VALUE rb_ensure(VALUE (*func1)(), void *arg1, void (*func2)(), void
*arg2)

Calls the function func1 with arg1 as the argument, then calls func2
with arg2 if execution terminated. The return value from
rb_ensure() is that of func1.


#3

On Apr 7, 1:08 pm, “Kent S.” removed_email_address@domain.invalid wrote:

You should wrap your rb_yield call with rb_ensure. From README.EXT:

VALUE rb_ensure(VALUE (*func1)(), void *arg1, void (*func2)(), void *arg2)

Calls the function func1 with arg1 as the argument, then calls func2
with arg2 if execution terminated. The return value from
rb_ensure() is that of func1.

Hmm… this doesn’t seem to have the desired effect. I’m pretty sure
rb_ensure() is just for handling exceptions, not breaks.

According to ruby.h and the Pickaxe, the function def’n has changed a
bit too. From Programming Ruby(2nd ed):

VALUE rb_ensure(VALUE(*body)(), VALUE args, VALUE(*ensure)(), VALUE
eargs)

Executes body with the given args. Whether or not an exception is
raised, execute ensure with the given eargs after body has completed.

So it seems that rb_ensure is just for exceptions, not ‘break’. I
tried tweaking my code to use it anyway, but the ensure call is still
bypassed when there is a break. For example:

lib.c
void func( int n, int (*callback)(void) )
{
printf(“before callback…\n”);
if (callback() < 0)
goto cleanup;
printf(“after callback…\n”);
cleanup:
printf(“in cleanup…\n”);
}

ext.c
static void
my_ensure(void * ptr)
{
*(int *)ptr = -1;
}
static int
meth_callback(void)
{
int retval = 0;
rb_ensure(rb_yield, Qnil, my_ensure, &retval);
return retval;
}
// …

test.rb

require ‘ext’
YYY.new.meth(100) { } # outputs “before callback…\n”, “in cleanup…
\n”
YYY.new.meth(100) { break } # outputs “before callback…\n”

Which makes sense if, when there is no break, the my_ensure func gets
called (as it should), so processing skips to cleanup, and when there
is a break, the ensure func does not get called, and processing skips
back to the ruby script.

I suppose I could switch to having people ‘raise BreakException’,
rather than ‘break’ within the block, but that seems like its hobbling
the ruby language.

Thanks for the idea, though!


#4

On 4/7/07, Noah E. removed_email_address@domain.invalid wrote:

rb_ensure() is just for handling exceptions, not breaks.
So it seems that rb_ensure is just for exceptions, not ‘break’. I
cleanup:
{
YYY.new.meth(100) { break } # outputs “before callback…\n”

What I meant was that your cleanup code should be in my_ensure
function and not in func itself. Just to prove my point:

$ cat t.rb
def test
puts ‘pre’
yield
puts ‘post’
ensure
puts ‘ensure’
end

test {break}

$ ruby t.rb
pre
ensure


#5

On 4/7/07, Kent S. removed_email_address@domain.invalid wrote:

Hmm… this doesn’t seem to have the desired effect. I’m pretty sure

printf(“after callback…\n”);
meth_callback(void)
\n"
puts ‘post’

OK, maybe this code will clean things up:

$ cat t.rb
require ‘rubygems’
require ‘inline’

class MyTest
inline do |builder|
builder.prefix <<-EOC
static VALUE
func(VALUE arg)
{
printf(“pre\n”);
rb_yield(arg);
printf(“post\n”);
return Qnil;
}
static VALUE
cleanup(VALUE arg)
{
printf(“ensure\n”);
return Qnil;
}
EOC
builder.c <<-EOC
void my_func()
{
rb_ensure(func, Qnil, cleanup, Qnil);
}
EOC
end
end

MyTest.new.my_func {break}

$ ruby t.rb
pre
ensure
$


#6

On Apr 7, 2007, at 10:08 , Kent S. wrote:

You should wrap your rb_yield call with rb_ensure. From README.EXT:

VALUE rb_ensure(VALUE (*func1)(), void *arg1, void (*func2)(), void
*arg2)

Calls the function func1 with arg1 as the argument, then calls func2
with arg2 if execution terminated. The return value from
rb_ensure() is that of func1.

Right, for an example of this, take a look at the inline’d code in
image_science. I just had to wrap all this stuff up in the same way.


#7

On 4/7/07, Kent S. removed_email_address@domain.invalid wrote:

end

test {break}

$ ruby t.rb
pre
ensure

Yes, this is better than my suggestion to wrap the block in a lambda
since it covers not only returns but exceptions as well.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#8

On 4/7/07, Noah E. removed_email_address@domain.invalid wrote:

rb_ensure() is just for handling exceptions, not breaks.
So it seems that rb_ensure is just for exceptions, not ‘break’. I
cleanup:
{
YYY.new.meth(100) { break } # outputs “before callback…\n”
Thanks for the idea, though!
It sounds like you want to do the equivalent of turning the given
block into a lambda. The break is causing a return from the block.

Here’s some ruby code which shows the problem and the solution:

rick@frodo:/public/rubyscripts$ cat lambda.rb
def with_yield
puts “before yield”
yield
puts “got back”
end

def with_call(&block)
puts “before call”
block.call
puts “got back”
end

def with_lambda(&block)
puts “before lambda call”
(lambda &block).call
puts “got back”
end

with_yield {puts “in block”; break}
puts
with_call {puts “in block”; break}
puts
with_lambda {puts “in block”; break}
rick@frodo:/public/rubyscripts$ ruby lambda.rb
before yield
in block

before call
in block

before lambda call
in block
got back

Now just turn that into C code.

You need rb_scan_args with a format of “&” on the last argument to get
the block. rb_funcall2 to call the lambda method which is private

And you probably also want to use rb_ensure in case the block raises
and exception as well.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/


#9

On Apr 7, 3:39 pm, “Kent S.” removed_email_address@domain.invalid wrote:

OK, maybe this code will clean things up:
[…]

Now I understand. So, the rb_ensure() only makes sure that it’s third
argument is called, not that anything after that in the function
containing rb_ensure() is run. That’s what I thought it was doing -
which was leading me astray.

So the original definition of func (which allocated memory, used the
callback, freed memory, and didn’t use ruby.h) won’t work, I need to
divide that into 3 parts (one to alloc, one that uses the callback,
and one to free), and do something like:

static VALUE
my_meth(VALUE self)
{
MyType * my_type = AllocMyType();

rb_ensure(DoStuff, my_type, FreeMyType, my_type);
return Qnil;
}

Thanks for bearing with me and explaining.


#10

“N” == Noah E. removed_email_address@domain.invalid writes:

N> So the original definition of func (which allocated memory, used the
N> callback, freed memory, and didn’t use ruby.h) won’t work, I need to
N> divide that into 3 parts (one to alloc, one that uses the callback,
N> and one to free), and do something like:

Well, the best is to let ruby manage the memory (with its GC), but if
you
have an original C function that you can’t modify and you need to
give
it a callback, you can still write something like this

moulon% cat a.c
#include <ruby.h>

void func( int n, int (*callback)(void) )
{
int i;

for (i = 0; i < n; i++) { if (callback() < 0) { goto cleanup; } }
rb_warn("normal exit");

cleanup:
rb_warn(“cleanup”);
return;
}

static int
meth_callback(void)
{
int state;

rb_protect(rb_yield, 0, &state);
if (state) {

rb_thread_local_aset(rb_thread_current(), rb_intern("_test"),
INT2NUM(state));
return -1;
}
return 0;
}

static VALUE
yyy_meth(VALUE self, VALUE count)
{
VALUE res;

rb_thread_local_aset(rb_thread_current(), rb_intern("_test"), 

INT2NUM(0));
func(FIX2INT(count), meth_callback);
res = rb_thread_local_aref(rb_thread_current(), rb_intern("_test"));
if (FIXNUM_P(res) && NUM2INT(res) != 0) {
rb_thread_local_aset(rb_thread_current(), rb_intern("_test"),
INT2NUM(0));
rb_warn(“rb_jump_tag”);
rb_jump_tag(NUM2INT(res));
}
return Qnil;
}

void Init_a()
{
VALUE a_cM;

a_cM = rb_define_class("YYY", rb_cObject);
rb_define_method(a_cM, "meth", yyy_meth, 1);

}
moulon%

moulon% cat b.rb
#!/usr/bin/ruby
require ‘a’

def m(a)
puts “before”
a.meth(6) { return }
puts “after”
end

puts “before m”
m(YYY.new)
puts “after m”
moulon%

moulon% ./b.rb
before m
before
./b.rb:6: warning: cleanup
./b.rb:6: warning: rb_jump_tag
after m
moulon%

Guy Decoux


#11

Hi,

At Mon, 9 Apr 2007 09:40:07 +0900,
Noah E. wrote in [ruby-talk:247204]:

So the original definition of func (which allocated memory, used the
callback, freed memory, and didn’t use ruby.h) won’t work, I need to
divide that into 3 parts (one to alloc, one that uses the callback,
and one to free), and do something like:

You can use rb_protect() and rb_jump_tag() too.

static VALUE
my_meth(VALUE self)
{
MyType * my_type = AllocMyType();
int status;
VALUE result = rb_protect(DoStuff, (VALUE)my_type, &status);
FreeMyType(my_type);
if (status) rb_jump_tag(status);
return result;
}

The way to do all in one function is not provieded.