[jruby-user] Re: How to capture and call a block in an extension?

Theo Hultberg <[email protected]> Wed, 18 Mar 2015 15:16:14 +0100
Newsgroups gmane.comp.lang.jruby.user
Message-ID <CAMR1f-erKv6jZycESzYi_02tgNNmOZ=7G7N8OM6q8xc9g9hdSA@mail.gmail.com>
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#

On Wed, Mar 18, 2015 at 3:05 PM, Theo Hultberg <[email protected]> wrote:

> 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:
> https://github.com/iconara/multimeter/blob/8de05828912f892e55f7631dce37f6df3aa64207/ext/java/multimeter/MetricRegistry.java#L87-L97
>
> 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
>