How to capture and call a block in an extension?

Hello,

I’m struggling with how to capture and call a block from an extension
written in Java. What I want to do is roughly equivalent to the
following
Ruby code:

def register_thing(name, &block)
@things[name] = block
end

def call_things
@things.each_value { |callback| callback.call }
end

At first what I did was this:

public IRubyObject register_thing(ThreadContext ctx, IRubyObject name,
Block block) {
things.put(name, block);
}

private void call_things(ThreadContext ctx) {
for (Block callback : things.entrySet()) {
callback.yieldSpecific(ctx);
}
}

However, I got very strange errors after running that code for a while
(see
below). I looked at how Hash#initialize captures its block and rewrote
the
code like this:

public IRubyObject register_thing(ThreadContext ctx, IRubyObject name,
Block block) {
RubyProc callback = ctx.runtime.newProc(Block.Type.PROC, block);
things.put(name, callback);
}

private void call_things(ThreadContext ctx) {
for (Block callback : things.entrySet()) {
callback.call(ctx, IRubyObject.NULL_ARRAY)
}
}

No luck, though, the problem is still there. I get different errors, but
they seem to be related to the blocks and how they get called. Here is
one:

java.lang.RuntimeException: NoVarsDynamicScope does not support scopes
with
one or more variables
at
org.jruby.runtime.scope.NoVarsDynamicScope.setValueZeroDepthZero(NoVarsDynamicScope.java:94)
at
rubyjit.MyApplication$$jvm_metric_fe000f11d0fb78e6a1e327b8436522a071de2476185645490.file(my-application.jar!/META-INF/app.home/lib/my_application.rb)
at
rubyjit.MyApplication$$jvm_metric_fe000f11d0fb78e6a1e327b8436522a071de2476185645490.file(my-application.jar!/META-INF/app.home/lib/my_application.rb)
at
org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:221)
at
org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:202)
at org.jruby.ast.FCallTwoArgNode.interpret(FCallTwoArgNode.java:38)
at org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105)
at
org.jruby.evaluator.ASTInterpreter.INTERPRET_BLOCK(ASTInterpreter.java:112)
at
org.jruby.runtime.Interpreted19Block.evalBlockBody(Interpreted19Block.java:206)
at
org.jruby.runtime.Interpreted19Block.yield(Interpreted19Block.java:194)
at
org.jruby.runtime.Interpreted19Block.call(Interpreted19Block.java:125)
at org.jruby.runtime.Block.call(Block.java:101)
at org.jruby.RubyProc.call(RubyProc.java:290)
at org.jruby.RubyProc.call19(RubyProc.java:271)
at multimeter.MetricRegistry$2.getValue(MetricRegistry.java:93)
at multimeter.MetricRegistry$2.getValue(MetricRegistry.java:90)
at
com.codahale.metrics.graphite.GraphiteReporter.reportGauge(GraphiteReporter.java:281)
at
com.codahale.metrics.graphite.GraphiteReporter.report(GraphiteReporter.java:171)

The error is raised from my Ruby code, but it goes through the block
calling in my extension.

Another error that is quite frequent is this one:

java.lang.ArrayIndexOutOfBoundsException: -1
at
org.jruby.runtime.ThreadContext.pushBacktrace(ThreadContext.java:565)
at
org.jruby.evaluator.ASTInterpreter.INTERPRET_BLOCK(ASTInterpreter.java:110)
at
org.jruby.runtime.Interpreted19Block.evalBlockBody(Interpreted19Block.java:206)
at
org.jruby.runtime.Interpreted19Block.yield(Interpreted19Block.java:194)
at
org.jruby.runtime.Interpreted19Block.call(Interpreted19Block.java:125)
at org.jruby.runtime.Block.call(Block.java:101)
at org.jruby.RubyProc.call(RubyProc.java:290)
at org.jruby.RubyProc.call19(RubyProc.java:271)
at multimeter.MetricRegistry$2.getValue(MetricRegistry.java:93)
at multimeter.MetricRegistry$2.getValue(MetricRegistry.java:90)
at com.codahale.metrics.CsvReporter.reportGauge(CsvReporter.java:234)
at com.codahale.metrics.CsvReporter.report(CsvReporter.java:150)
at
com.codahale.metrics.ScheduledReporter.report(ScheduledReporter.java:162)
at
com.codahale.metrics.ScheduledReporter$1.run(ScheduledReporter.java:117)

I’ve also seen really inexplicable things happen in the blocks
themselves,
variables containing nil when they absolutely couldn’t (they are
assigned
once outside the block and then never changed), that make me think these
blocks aren’t called the right way.

You’ve probably figured out that the code examples above are complete
fabrications, the actual code doesn’t look exactly like that, but would
be
harder to show as an example. The real implementation is here:

Unfortunately I can’t show the code of the application that uses this,
but
it also doesn’t look like the problem is there.

I should also add that it worked fine in an earlier implementation that
called the Java library directly from Ruby, so there’s no problem in the
underlying Java library.

It all runs in JRuby 1.7.19.

yours,
Theo

I realized that one difference from the stripped down example I posted
and
the actual code is that the actual code captures the ThreadContext in a
final variable and uses it when calling the callback, which most often
happens in another thread. I assume that’s significant, and that you
should
always use the ThreadContext of the thread you’re running on. How do you
do
that when you’re responding to a call from Java (like say in
Callable#call)
and you don’t get passed a ThreadContext? Is it safe to capture the
runtime
in a final variable instead, and then call #getCurrentContext to get a
thread context?

T#