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

[JRuby] Padrino Thread-Safety Issue #863

Closed
sgonyea opened this issue Jun 19, 2012 · 42 comments
Closed

[JRuby] Padrino Thread-Safety Issue #863

sgonyea opened this issue Jun 19, 2012 · 42 comments
Assignees
Milestone

Comments

@sgonyea
Copy link

sgonyea commented Jun 19, 2012

I've come across a thread-safety issue in Padrino, running on JRuby (1.6.7.2). I don't know if this affects MRI or Rubinius. I run into the issue using WEBrick, Puma, and Trinidad. This issue seems to occur right after boot up. Perhaps Padrino is lazily compiling routes, and should instead do so on boot-up? That's just a guess; I have not dived deep enough into Padrino.

Anyway, if I boot up my app server and, once it's listening, I immediately run a script like the following:

curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &

Note that the '&' basically causes them to happen at nearly the same time (I know you probably know).

This blows up for me every time. If I update the script to look like:

curl "http://localhost:50018/foo/bar"
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &
curl "http://localhost:50018/foo/bar" &

Then the first curl will block the rest of the script, until the app server completes its request. The rest of the script then executes in parallel, with no problems.

I was able to fix this thread-safety issue by wrapping Padrino::Routing::ClassMethods#compiled_router in a Mutex, such that it now becomes:

      # Compiles the routes including deferred routes.
      @@compiled_router_mutex = Mutex.new
      def compiled_router
        @@compiled_router_mutex.synchronize {
          if deferred_routes.empty?
            return router
          else
            deferred_routes.each { |_, routes| routes.each { |(route, dest)| route.to(dest) } }
            @deferred_routes = nil
            return router
          end
        }
      end

I don't believe this is the correct solution, so I'm not submitting this as a pull-request. I'd wager that this mutex should live somewhere deeper.

@sgonyea
Copy link
Author

sgonyea commented Jun 20, 2012

If anyone needs a monkey patch for the time being, go ahead and stick this somewhere:

require 'padrino-core/application/routing'

# @see https://github.com/padrino/padrino-framework/issues/863
module Padrino
  module Routing
    module ClassMethods

      # Compiles the routes including deferred routes.
      @@compiled_router_mutex = Mutex.new
      def compiled_router
        @@compiled_router_mutex.synchronize {
          if deferred_routes.empty?
            return router
          else
            deferred_routes.each { |_, routes| routes.each { |(route, dest)| route.to(dest) } }
            @deferred_routes = nil
            return router
          end
        }
      end

    end
  end
end

@nesquena
Copy link
Member

Thanks for bringing up this issue @sgonyea we hope to resolve this in the next release (0.10.8)

@sgonyea
Copy link
Author

sgonyea commented Jun 21, 2012

Np. I should note that placing this mutex around the compiled_router method exposed another thread safety issue in the HttpRouter library. It exists in HttpRouter::Node::Root#compile, but I eventually gave up trying to dig it out as it's encumbered with some insane meta-programming.

Thankfully this was changed in HttpRouter 0.11.x, but I have no idea how compatible it is with the current release of Padrino.

Ideally, if routes need to be compiled (or anything), that would happen during application bootup if reload is set to false. I simply did not have enough insight into Padrino's internals to understand how to force this to happen (other than perhaps making a request immediately on app bootup).

@nesquena
Copy link
Member

@joshbuddy Any thoughts on this?

@sgonyea
Copy link
Author

sgonyea commented Jun 21, 2012

For reference, the backtrace that I got when I hit the thread-safety issue is pasted below.

It bars at "Line 44", which is actually Line 42... But line 42 has "\n" chars that are getting instance_eval'd.

https://github.com/joshbuddy/http_router/blob/29e41c98cb806a952a05354cbb9a350b79e08170/lib/http_router/node/root.rb

It also barfs twice at the #[] method. This is because #[] gets re-defined dynamically and called again. Pretty much impossible to debug in a time-box. I'd say upgrade to HttpRouter 0.11.x asap, as it no longer does any of that.

You'll also note the "Line 34" from my own app, in the backtrace. That's the monkey patch that I pasted above.

NoMethodError - undefined method `key?' for #<HttpRouter::Node::Path:0x25b9abb4>:
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/http_router-0.10.2/lib/http_router/node/root.rb:44:in `[]'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/http_router-0.10.2/lib/http_router/node/root.rb:13:in `[]'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/http_router-0.10.2/lib/http_router.rb:119:in `call'
  org/jruby/RubyKernel.java:1197:in `catch'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/http_router-0.10.2/lib/http_router.rb:119:in `call'
  /usr/local/share/workspace/my_app/lib/ace/core_ext/padrino/routing.rb:34:in `route!'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/padrino-core-0.10.5/lib/padrino-core/application/routing.rb:900:in `dispatch!'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:706:in `call!'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:871:in `invoke'
  org/jruby/RubyKernel.java:1197:in `catch'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:871:in `invoke'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:706:in `call!'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:692:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/airbrake-3.0.9/lib/airbrake/rack.rb:27:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.3.5/lib/rack/head.rb:9:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.3.5/lib/rack/methodoverride.rb:24:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/padrino-core-0.10.5/lib/padrino-core/reloader.rb:250:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/padrino-core-0.10.5/lib/padrino-core/logger.rb:306:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:1334:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:1403:in `synchronize'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/sinatra-1.3.1/lib/sinatra/base.rb:1334:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/padrino-core-0.10.5/lib/padrino-core/router.rb:85:in `call'
  org/jruby/RubyArray.java:1615:in `each'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/padrino-core-0.10.5/lib/padrino-core/router.rb:76:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.3.5/lib/rack/commonlogger.rb:20:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:420:in `handle_request'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:288:in `process_client'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:197:in `run'
  org/jruby/RubyProc.java:258:in `call'
  /Users/me/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/thread_pool.rb:92:in `spawn_thread'

@DAddYE
Copy link
Member

DAddYE commented Jun 25, 2012

@joshbuddy can you look at this?

@yogi
Copy link

yogi commented Aug 13, 2012

We've gotten around this for the moment using rack-middleware to ensure serial initialization of the routes. Its holding up well for the moment in production. Will observe for a few more days and see if this puts the issue to rest.

Do let me know if you see any concerns with this approach.

module Rack
  # Ensures only 1 thread passes through the stack the first time (subsequent requests are allowed through concurrently).
  # This is needed to work-around the thread-safety issue in http_router gem used by Padrino which results in a
  # DoubleCompileError when concurrent requests occur on server startup.
  #
  class SerialInitializer
    @@mutex = Mutex.new
    @@initialized = false

    def initialize(app)
      logger.info "SerialInitializer: #{Thread.current} created"
      @app = app
    end

    def call(env)
      if !@@initialized
        logger.info "SerialInitializer: #{Thread.current} not initialized, attempting lock"
        @@mutex.synchronize do
          logger.info "SerialInitializer: #{Thread.current} acquired lock!"
          if !@@initialized
            begin
              logger.info "SerialInitializer: #{Thread.current} within lock, app not initialized, going ahead with call"
              @app.call(env)
              @@initialized = true
              logger.info "SerialInitializer: #{Thread.current} app initialized successfully - this should only happen once!"
            rescue Exception => e
              logger.error "SerialInitializer: #{Thread.current} error initializing app, logging and re-raising (subsequent threads will re-attempt initialization): #{e.class} #{e.message} \n #{e.backtrace.join("\n")}"
              raise
            end
          else
            logger.info "SerialInitializer: #{Thread.current} acquired lock, but app was already initialized, calling it"
            @app.call(env)
          end
        end
      else
        @app.call(env)
      end
    end
  end
end

@sgonyea
Copy link
Author

sgonyea commented Aug 13, 2012

I like your approach, it's what I attempted to setup but couldn't get it all the way there in my time-box :). How are you setting it up to use SerialInitializer? I'll be happy to try this out in my stuff.

@yogi
Copy link

yogi commented Aug 13, 2012

In config.ru:

use SerialInitializer
run Padrino.application

On Monday, August 13, 2012, Scott Gonyea wrote:

I like your approach, it's what I attempted to setup but couldn't get it
all the way there in my time-box :). How are you setting it up to use
SerialInitializer? I'll be happy to try this out in my stuff.


Reply to this email directly or view it on GitHubhttps://github.com//issues/863#issuecomment-7701827.

@sgonyea
Copy link
Author

sgonyea commented Aug 13, 2012

Gotcha, cool. I'll give it a shot.

@sgonyea
Copy link
Author

sgonyea commented Aug 13, 2012

Hmmm, on the first request I get a stacktrace. I'm using Puma as my development web server.

Puma caught this error: undefined method `each' for nil:NilClass
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.4.1/lib/rack/utils.rb:375:in `initialize'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.4.1/lib/rack/utils.rb:369:in `new'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/rack-1.4.1/lib/rack/commonlogger.rb:21:in `call'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:420:in `handle_request'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:288:in `process_client'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/server.rb:197:in `run'
org/jruby/RubyProc.java:258:in `call'
/Users/sgonyea/.rbenv/versions/jruby-1.6.7.2/lib/ruby/gems/1.8/gems/puma-1.4.0-java/lib/puma/thread_pool.rb:92:in `spawn_thread'

In the log I get:

   INFO - [2012-08-13 12:06:54] SerialInitializer: #<Thread:0x7b64769> created
   INFO - [2012-08-13 12:07:02] SerialInitializer: #<Thread:0xc5c5f3b> not initialized, attempting lock
   INFO - [2012-08-13 12:07:02] SerialInitializer: #<Thread:0xc5c5f3b> acquired lock!
   INFO - [2012-08-13 12:07:02] SerialInitializer: #<Thread:0xc5c5f3b> within lock, app not initialized, going ahead with call
  DEBUG - [2012-08-13 12:07:03]      GET (0.2350ms) /qa/ - 200 OK
   INFO - [2012-08-13 12:07:03] SerialInitializer: #<Thread:0xc5c5f3b> app initialized successfully - this should only happen once!

Subsequent requests work. I'll dig around to see what's up. Also, I'll try a different web server.

@yogi
Copy link

yogi commented Aug 14, 2012

INFO - [2012-08-13 12:06:54] SerialInitializer: #Thread:0x7b64769 created
INFO - [2012-08-13 12:07:02] SerialInitializer: #Thread:0xc5c5f3b not
initialized, attempting lock
INFO - [2012-08-13 12:07:02] SerialInitializer: #Thread:0xc5c5f3bacquired lock!
INFO - [2012-08-13 12:07:02] SerialInitializer: #Thread:0xc5c5f3b within
lock, app not initialized, going ahead with call
DEBUG - [2012-08-13 12:07:03] GET (0.2350ms) /qa/ - 200 OK
INFO - [2012-08-13 12:07:03] SerialInitializer: #Thread:0xc5c5f3b app
initialized successfully - this should only happen once!

Not sure about this error.

However if it happened on the first request it would have been logged by
SerialInitializer... logs show it to have initialized correctly on the
first request.

How many JRuby runtimes is Puma creating?

FYI I'm using Trinidad 1.3.4 with jruby-rack 1.1.4 and rack 1.3.6.

-Yogi

@yogi
Copy link

yogi commented Aug 21, 2012

Scott,

There was a bug in the SerialInitializer where the first request's response was not getting returned due to the log statement. Here's the fixed version:

module Rack
  # Ensures only 1 thread passes through the stack the first time (subsequent requests are allowed through concurrently).
  # This is needed to work-around the thread-safety issue in http_router gem used by Padrino which results in a
  # DoubleCompileError when concurrent requests occur on server startup.
  #
  class SerialInitializer
    @@mutex = Mutex.new
    @@initialized = false

    def initialize(app)
      logger.info "SerialInitializer: #{Thread.current} created"
      @app = app
    end

    def call(env)
      if !@@initialized
        logger.info "SerialInitializer: #{Thread.current} not initialized, attempting lock"
        @@mutex.synchronize do
          logger.info "SerialInitializer: #{Thread.current} acquired lock!"
          if !@@initialized
            begin
              logger.info "SerialInitializer: #{Thread.current} within lock, app not initialized, going ahead with call"
              res = @app.call(env)
              @@initialized = true
              logger.info "SerialInitializer: #{Thread.current} app initialized successfully - this should only happen once!"
              return res # <------ this was missing!
            rescue Exception => e
              logger.error "SerialInitializer: #{Thread.current} error initializing app, logging and re-raising (subsequent threads will re-attempt initialization): #{e.class} #{e.message} \n #{e.backtrace.join("\n")}"
              raise
            end
          else
            logger.info "SerialInitializer: #{Thread.current} acquired lock, but app was already initialized, calling it"
            return @app.call(env)
          end
        end
      else
        return @app.call(env)
      end
    end
  end
end

@dariocravero
Copy link

@DAddYE as @sgonyea suggests, upgrading to 0.11.0 actually solves the issue. Let's keep this into account when finally upgrading to it so we close this issue afterwards.

@DAddYE
Copy link
Member

DAddYE commented Jan 31, 2013

@dariocravero this #863 (comment) is perfect, we can add this patch ;)

@dariocravero
Copy link

@DAddYE, yes we could but is there really a point if we're actually upgrading to http_router 0.11?

@netoneko
Copy link

netoneko commented Feb 3, 2013

Yesterday I've checked out the Padrino branch for 0.11 and it worked fine at MRI, but in JRuby concurrency was totally broken because of http_router. I could track it down to HttpRouter#compile which contains a lot of def, undef and alias stuff.

I figured that http_router is still not thread safe in JRuby. If tried to fix the issue with

if respond_to(:method)
  undef :method
end
alias :method :raw_method

and it fixed a problem that said :method was not defined. But things kept falling apart after that, because I got a lot of ArrayIndexOutOfBound exceptions from JRuby.

@mungler
Copy link

mungler commented Feb 25, 2013

FWIW, i hit this issue last night using Ruby 1.9.3-p194 - the (fixed version of the) SerialInitializer above fixed it for me.

@nesquena
Copy link
Member

What should we do here for a quick 0.11.0 release? I want to try to release by next weekend. Should we push these thread issues to 0.11.1, apply a quick fix? Please advise @DAddYE @dariocravero @ujifgc @achiu

FWIW, this does seem like a serious issue. So this may be worth holding off the release for? /cc @skade

@dariocravero
Copy link

IMHO it's something we should fix for 0.11.0, http_router 0.11.0 does fix it so I guess the efforts should be put in upgrading Padrino to it

@sgonyea
Copy link
Author

sgonyea commented Mar 11, 2013

To me it's a serious-enough issue to hold off until the dependency is upgraded. I'm a semver pansy, so I hate to see minor version bumps for dependencies in a point release. I believe the other benefit is that the newer HttpRouter releases no longer do the awful eval of a big, impenetrable string. Debugging it down to the source of the issue in HttpRouter was not my fondest hour.

Anyone who runs Padrino in a multi-threaded environment (as I did) will likely slam into this issue. Especially if they use JRuby, though MRI is certainly affected as well.

Lucky for me, it was an internal app and I used Padrino as an interface for driving batch tasks. So it was easy for me to figure out the cause/effect. For anyone else, it'll probably be more of a frustrating phantom.

@nesquena
Copy link
Member

Agreed, will mark as blocker. Thanks for your feedback.

@nesquena
Copy link
Member

@netoneko @sgonyea We have upgraded padrino to http_router 0.11. Looks like that should fix this issue as well? /cc @DAddYE @dariocravero @skade thoughts?

@DAddYE
Copy link
Member

DAddYE commented Mar 17, 2013

Confirmed solved!

@DAddYE DAddYE closed this as completed Mar 17, 2013
@nesquena
Copy link
Member

Amazing, another issue down 👍 Thanks @DAddYE @sgonyea

@azell
Copy link

azell commented Apr 25, 2013

Sad to say, this is not fixed in http_router 0.11.0. I can still trigger Java::JavaLang::ArrayIndexOutOfBoundsException by launching an ab session.

$ JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom" trinidad --env production --port 8888 --threadsafe &
$ ab -n 15000 -c 20 http://localhost:8888/admin
...
2013-04-25 12:27:02 -0700 INFO: Java::JavaLang::ArrayIndexOutOfBoundsException - 33:

rubyjit.HttpRouter::Node$$to_code_4C97EF26DD96755123BC66BB2CBBE6572F8F20CA1934056750.file(gems/http_router-0.11.0/lib/http_router/node.rb:113)

rubyjit.HttpRouter::Node$$to_code_4C97EF26DD96755123BC66BB2CBBE6572F8F20CA1934056750.file(gems/http_router-0.11.0/lib/http_router/node.rb)

Running the Rack::SerialInitializer code fixed the error,

@dariocravero
Copy link

Too bad to hear that @azell reopening this issue then /cc @DAddYE @nesquena

@dariocravero dariocravero reopened this Apr 25, 2013
@yogi
Copy link

yogi commented May 8, 2013

FYI, we've started migrating our services from JRuby to Ruby.

The lack of thread-safety in ruby libs is turning out to be a larger issue
than we expected. While there are hacks to work around most of them, these
don't give you any reassurance that concurrency issues won't crop up again.

So far it seems like most ruby libraries treat concurrency as an after
thought. This is a fundamental philosophical difference and unfortunately
severely limits the possibilities for JRuby.

-Yogi

@DAddYE
Copy link
Member

DAddYE commented May 8, 2013

This will be our priority ONE. Stay tuned, I'll fix soon!

@sgonyea
Copy link
Author

sgonyea commented May 8, 2013

@yogi - Developing on JRuby does require a heightened awareness that Thread safety issues may exist in some obscure library you're using. But I built a very thread-heavy application on JRuby and found it to be well worth the effort. Your best bet is to stay on the beaten path and to use libraries that are actively maintained.

One option may be to just use less Padrino; I believe it's not all that difficult to use plain old Sinatra for your routes and to use the Padrino helpers elsewhere.

@ghost ghost assigned DAddYE May 16, 2013
@ghost
Copy link

ghost commented Aug 25, 2013

I seem to be running into the same issue where padrino will crash with multiple incoming requests right after startup. The stack trace I see:

Padrino/0.11.2 has taken the stage development at http://0.0.0.0:3000
Puma 2.4.0 starting…
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000
NoMethodError - undefined method `add_destination' for nil:NilClass:
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/node/root.rb:135:in `add_non_path_to_tree'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/node/root.rb:84:in `add_route'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/generator.rb:71:in `each_path'
    org/jruby/RubyArray.java:1617:in `each'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/generator.rb:71:in `each_path'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/node/root.rb:72:in `add_route'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/node/root.rb:34:in `compile'
    org/jruby/RubyArray.java:1617:in `each'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router/node/root.rb:34:in `compile'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router.rb:314:in `compile'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/http_router-0.11.0/lib/http_router.rb:160:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/application/routing.rb:1068:in `route!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/application/routing.rb:1053:in `dispatch!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1041:in `invoke'
    org/jruby/RubyKernel.java:1254:in `catch'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1041:in `invoke'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/application/routing.rb:1051:in `dispatch!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:882:in `call!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1041:in `invoke'
    org/jruby/RubyKernel.java:1254:in `catch'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1041:in `invoke'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:882:in `call!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:870:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-less-3.0.2/lib/rack/less/base.rb:40:in `call!'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-less-3.0.2/lib/rack/less/base.rb:24:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/base.rb:49:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/xss_header.rb:18:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/base.rb:49:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/path_traversal.rb:16:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/json_csrf.rb:18:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/base.rb:49:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/base.rb:49:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-protection-1.5.0/lib/rack/protection/frame_options.rb:31:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-1.5.2/lib/rack/head.rb:11:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-1.5.2/lib/rack/methodoverride.rb:21:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/reloader.rb:337:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/logger.rb:400:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/showexceptions.rb:21:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:225:in `context'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:220:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1949:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1449:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1726:in `synchronize'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/sinatra-1.4.3/lib/sinatra/base.rb:1449:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/router.rb:85:in `call'
    org/jruby/RubyArray.java:1617:in `each'
    /Users/slawo/.rvm/gems/jruby-1.7.4/bundler/gems/padrino-framework-77beaa470af2/padrino-core/lib/padrino-core/router.rb:76:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/rack-1.5.2/lib/rack/deflater.rb:25:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/puma-2.4.0-java/lib/puma/server.rb:381:in `handle_request'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/puma-2.4.0-java/lib/puma/server.rb:252:in `process_client'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/puma-2.4.0-java/lib/puma/server.rb:151:in `run'
    org/jruby/RubyProc.java:255:in `call'
    /Users/slawo/.rvm/gems/jruby-1.7.4/gems/puma-2.4.0-java/lib/puma/thread_pool.rb:92:in `spawn_thread'

@dariocravero
Copy link

I'm sorry to hear that @type-exit. Another thing to take into account in #1339. @DAddYE any clues on this?

@ghost
Copy link

ghost commented Oct 11, 2013

Hey guys, it turns out the concurrent serialization is necessary for each mounted app. I've modified the above SerialInitializer to account for individual apps. This works for me. Would be so awesome if we could get rid of it in the next padrino release :)

module Rack
  # Ensures only 1 thread passes through the stack the first time (subsequent requests are allowed through concurrently).
  # This is needed to work-around the thread-safety issue in http_router gem used by Padrino which results in a
  # DoubleCompileError when concurrent requests occur on server startup.
  #
  class SerialInitializer

    @@apps_initialized = {}
    @@mutexes = {}

    def initialize(app)
      logger.info "SerialInitializer: #{Thread.current} created"
      @app = app

      mapping = @app.instance_variable_get("@mapping")

      mapping.each do |host, path, match, app|
        @@mutexes[path] = Mutex.new
        @@apps_initialized[path] = false
      end
    end

    def call(env)

      path_info = env["PATH_INFO"].to_s
      mapping = @app.instance_variable_get("@mapping")

      matching_app = mapping.find do |routing_def|
        host, path, match, app = *routing_def

        matches = path_info =~ match && rest = $1
        matches && (rest.empty? || rest[0] == ?/)
      end

      path = matching_app[1]

      if !@@apps_initialized[path]
        app_name = matching_app[3].to_s
        logger.info "SerialInitializer: #{Thread.current} not initialized, attempting lock on #{app_name}"
        @@mutexes[path].synchronize do
          logger.info "SerialInitializer: #{Thread.current} acquired lock on #{app_name}!"
          if !@@apps_initialized[path]
            begin
              logger.info "SerialInitializer: #{Thread.current} within lock on #{app_name}, app not initialized, going ahead with call"
              res = @app.call(env)
              @@apps_initialized[path] = true
              logger.info "SerialInitializer: #{Thread.current} app #{app_name} initialized successfully - this should only happen once!"
              return res
            rescue Exception => e
              logger.error "SerialInitializer: #{Thread.current} error initializing app #{app_name}, logging and re-raising (subsequent threads will re-attempt initialization): #{e.class} #{e.message} \n #{e.backtrace.join("\n")}"
              raise
            end
          else
            logger.info "SerialInitializer: #{Thread.current} acquired lock, but app #{app_name} was already initialized, calling it"
            return @app.call(env)
          end
        end
      else
        return @app.call(env)
      end
    end
  end
end

@ghost ghost assigned ujifgc Nov 6, 2013
@ujifgc
Copy link
Member

ujifgc commented Nov 6, 2013

I understand it looks meta, but it's temporary until we retire problematic http_router.

@ghost
Copy link

ghost commented Nov 6, 2013

looks good!
I'll see if I can disable the middleware workadround and if the commit fixes it for me

@ujifgc ujifgc closed this as completed in 0d1083f Nov 8, 2013
ujifgc added a commit that referenced this issue Nov 8, 2013
Ortuna pushed a commit to Ortuna/padrino-framework that referenced this issue Jan 17, 2014
@dariocravero
Copy link

Are we good to remove this patch now that we're on the new router? @namusyaka @ujifgc

@ujifgc ujifgc reopened this Jan 9, 2015
@namusyaka
Copy link
Contributor

I'm going to confirm that in a few days.

@ujifgc ujifgc closed this as completed in 2a2803a Jan 11, 2015
@ujifgc
Copy link
Member

ujifgc commented Jan 11, 2015

I checked on jruby 1.7.18. Padrino 0.12.4 without the patch still fails, Padrino 0.13.0.rc1 without the patch works fine.

@dariocravero
Copy link

Happy days! :)

On Sun, Jan 11, 2015 at 11:20 AM, Igor Bochkariov notifications@github.com
wrote:

I checked on jruby 1.7.18. Padrino 0.12.4 without the patch still fails,
Padrino 0.13.0.rc1 without the patch works fine.


Reply to this email directly or view it on GitHub
#863 (comment)
.

@namusyaka
Copy link
Contributor

@ujifgc Great! Thank you!!

@mitchellhenke
Copy link

I'm trying to use jruby on padrino, but it doesn't look like there is a Padrino 0.13.0.rc1 branch or a release for it in rubygems?

@nesquena
Copy link
Member

Not yet, I plan on putting together a 0.13.0.beta1 soon though.

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