Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code runs fine on JRuby but crashes after compilation to .class files (Rails / Grape Entity API) #4088

Closed
tom-mayer opened this issue Aug 18, 2016 · 5 comments

Comments

@tom-mayer
Copy link

Environment

Provide at least:

  • JRuby version: jruby 9.1.2.0 (2.3.0) 2016-05-26 7357c8f Java HotSpot(TM) 64-Bit Server VM 25.101-b13 on 1.8.0_101-b13 +jit [linux-x86_64]
  • OS: Debian Docker Container: 4.4.16-moby

Other relevant info you may wish to add:

  • Gems: Rails 4.2.6, Grape 0.10.1, Grape-Entity 0.5.1, Warbler 2.0.3
  • Tomcat 8.5

Expected Behavior

When writing a grape entity (basically a mapping file of what attributes of you JSON you want to expose in the API) different syntaxes are supported:

class MyEntity < Grape::Entity
  expose :attribute_a, proc: lambda { |data, options|
    data[:attr_a]
  }

  expose :attribute_a do |data, options|
    data[:attr_a]
  end
end

Both of these should do exactly the same thing.

Actual Behavior

The second (block syntax) dies since inside the block, data and options are nil.

It works fine when I run the code on the jRuby interpreter but fails when I compile it into a class file and run it again.

What I found so far

I did a bit of digging into the grape source and it feels a bit strange since the block and the lambda function are treated the same way:

https://github.com/ruby-grape/grape-entity/blob/master/lib/grape_entity/entity.rb#L162

is where the block is just copied into the options array under the :proc key. From the ruby side these feel pretty similar so it's maybe a deeper issue on how the compiler handles block vs lambdas (although my understanding is they are pretty much the same under the hood).

@headius
Copy link
Member

headius commented Aug 18, 2016

Very strange.

The current JRuby .class compiler basically just compiles the code as our internal IR format, serializes it to a byte array, and stuffs that into the class. It is then loaded on boot and goes through normal interpretation/JIT cycle. So if this is failing it could be a problem with how we're serializing the code in question.

@headius
Copy link
Member

headius commented Aug 18, 2016

Here's the IR for the main class body (sans Grape reference and extension):

2016-08-18T12:48:14.231-05:00 [main] INFO InterpretedIRMethod : Printing simple IR for MyEntity:
begin CLASS_BODY<MyEntity>
flags: [BINDING_HAS_ESCAPED, CAN_CAPTURE_CALLERS_BINDING, REQUIRES_DYNSCOPE, USES_BACKREF_OR_LASTLINE, USES_EVAL, USES_ZSUPER, REQUIRES_FRAME]
signature(pre=0,opt=0,post=0,rest=NONE,kwargs=0,kwreq=0,kwrest=false)
  00:           %self := recv_self
  01:            %v_0 := load_frame_closure
  02:  %current_scope := copy(scope<0>)
  03: %current_module := copy(mod<0>)
  04:                    line_num(lineNumber: 1)
  05:            %v_3 := call(self<%self>, block<MyEntity_CLOSURE_1>, callType: FUNCTIONAL, name: lambda, potentiallyRefined: false)
  06:            %v_4 := copy(hash<sym<>=>%v_3>)
  07:            %v_5 := call_2o(self<%self>, sym<>, %v_4, callType: FUNCTIONAL, name: expose, potentiallyRefined: false)
  08:                    line_num(lineNumber: 5)
  09:            %v_6 := call_1ob(self<%self>, sym<>, block<MyEntity_CLOSURE_2>, callType: FUNCTIONAL, name: expose, potentiallyRefined: false)
  10:                    return(%v_6)

The two closures constructed here differ in that for lambda, we see a separate call to the lambda function to create it.

The closures themselves are pretty much identitical:

begin CLOSURE<MyEntity_CLOSURE_1>
flags: [BINDING_HAS_ESCAPED, CAN_CAPTURE_CALLERS_BINDING, REQUIRES_DYNSCOPE, USES_BACKREF_OR_LASTLINE, USES_EVAL, USES_ZSUPER, REQUIRES_FRAME]
signature(pre=2,opt=0,post=0,rest=NONE,kwargs=0,kwreq=0,kwrest=false)
declared variables
  data(0:0:local=true)
  options(0:1:local=true)
  00:                    exc_region_start(ipc<_GLOBAL_ENSURE_BLOCK__0:13>)
  01:           %self := recv_self
  02:         %cl_1_0 := load_frame_closure
  03:  %current_scope := copy(scope<0>)
  04: %current_module := copy(mod<0>)
  05:           *data := recv_pre_reqd_arg(argIndex: 0)
  06:        *options := recv_pre_reqd_arg(argIndex: 1)
  07:                    label(ipc<_CLOSURE_START_0:8>)
  08:                    line_num(lineNumber: 2)
  09:         %cl_1_3 := call_1o(*data, sym<>, callType: NORMAL, name: [], potentiallyRefined: false)
  10:                    return(%cl_1_3)
  11:                    exc_region_end
  12:                    label(ipc<_GLOBAL_ENSURE_BLOCK__0:13>)
  13:         %cl_1_4 := recv_jruby_exc
  14:         %cl_1_5 := runtime_helper(%cl_1_4, helperMethod: HANDLE_BREAK_AND_RETURNS_IN_LAMBDA)
  15:                    return_or_rethrow_saved_exc(%cl_1_5)
  16:                    label(ipc<CL1_LBL_2:17>)
begin CLOSURE<MyEntity_CLOSURE_2>
flags: [BINDING_HAS_ESCAPED, CAN_CAPTURE_CALLERS_BINDING, REQUIRES_DYNSCOPE, USES_BACKREF_OR_LASTLINE, USES_EVAL, USES_ZSUPER, REQUIRES_FRAME]
signature(pre=2,opt=0,post=0,rest=NONE,kwargs=0,kwreq=0,kwrest=false)
declared variables
  data(0:0:local=true)
  options(0:1:local=true)
  00:                    exc_region_start(ipc<_GLOBAL_ENSURE_BLOCK__0:13>)
  01:           %self := recv_self
  02:         %cl_2_0 := load_frame_closure
  03:  %current_scope := copy(scope<0>)
  04: %current_module := copy(mod<0>)
  05:           *data := recv_pre_reqd_arg(argIndex: 0)
  06:        *options := recv_pre_reqd_arg(argIndex: 1)
  07:                    label(ipc<_CLOSURE_START_0:8>)
  08:                    line_num(lineNumber: 6)
  09:         %cl_2_3 := call_1o(*data, sym<>, callType: NORMAL, name: [], potentiallyRefined: false)
  10:                    return(%cl_2_3)
  11:                    exc_region_end
  12:                    label(ipc<_GLOBAL_ENSURE_BLOCK__0:13>)
  13:         %cl_2_4 := recv_jruby_exc
  14:         %cl_2_5 := runtime_helper(%cl_2_4, helperMethod: HANDLE_BREAK_AND_RETURNS_IN_LAMBDA)
  15:                    return_or_rethrow_saved_exc(%cl_2_5)
  16:                    label(ipc<CL2_LBL_2:17>)

The only difference between the two closures would be that one is executed as a lambda -- which checks arity -- and the other is a normal non-lambda block. I'll look along those lines and see if I find anything.

@headius
Copy link
Member

headius commented Aug 18, 2016

Hmm. I'm trying to come up with a reproduction that doesn't depend on Grape, and I'm not succeeding so far. Here's the script I have, based on yours:

class MyEntity
  def self.expose(a, proc: nil)
    if block_given?
      yield a, a.to_s.reverse
    else
      proc.call(a, a.to_s.reverse)
    end
  end
  expose :attribute_a, proc: lambda { |a, b|
    p [a,b]
  }

  expose :attribute_a do |a, b|
    p [a,b]
  end
end

And the results of running it, then precompiling it and running it:

[] ~/projects/jruby $ jruby blah.rb
[:attribute_a, "a_etubirtta"]
[:attribute_a, "a_etubirtta"]

[] ~/projects/jruby $ jrubyc blah.rb

[] ~/projects/jruby $ mv blah.rb /tmp

[] ~/projects/jruby $ jruby blah.class
[:attribute_a, "a_etubirtta"]
[:attribute_a, "a_etubirtta"]

[] ~/projects/jruby $ jruby -e 'load "./blah.class"'
[:attribute_a, "a_etubirtta"]
[:attribute_a, "a_etubirtta"]

Can you perhaps put together a script or small project repo that we can use to reproduce your issue?

@enebo
Copy link
Member

enebo commented Mar 14, 2017

@tom-mayer can you still test this to see if it is broken? We have fixed a number of fairly random AOT bugs (some of which were us not dumping some instrs properly).

@enebo enebo added this to the Invalid or Duplicate milestone Oct 13, 2021
@enebo
Copy link
Member

enebo commented Oct 13, 2021

If we get someone to test this again we should re-open but no details in 4+ years with no repro we can run locally.

@enebo enebo closed this as completed Oct 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants