[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 >