Description
There seems to be a deadlock issue with ExecJS -> therubyracer and using a threaded server like Puma.
See the issue here rubyjs/therubyracer#270 for some discussion.
I was able to reproduce this deadlock consistently on my local machine using the following setup:
Using the latest released version of
Rails
Puma
ExecJS
therubyracer
libv8
Puma configured to run 8 Worker processes with 8 threads each.
Server side rendering configured to use a connection_pool of size 8, since each process will only have 8 worker threads.
Using a tool like JMeter to generate significant load/traffic to the Puma workers eventually every forked process that Puma spawns deadlocks and halts request processing.
There are 2 different traces exhibited by the 8 different worker processes, but always end up being deadlocked in the same place inside of ExecJS.
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/rails/view_helper.rb:12:in `block in react_component'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:15:in `render'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:60:in `with'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:60:in `handle_interrupt'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:61:in `block in with'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:89:in `checkout'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:76:in `pop'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:76:in `synchronize'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:77:in `block in pop'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:77:in `loop'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:81:in `block (2 levels) in pop'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:169:in `try_create'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool/timed_stack.rb:169:in `call'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:11:in `block in reset_pool'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:21:in `create_renderer'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:21:in `new'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering/sprockets_renderer.rb:14:in `initialize'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/module.rb:27:in `compile'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/runtime.rb:44:in `compile'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/runtime.rb:44:in `new'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:9:in `initialize'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:73:in `lock'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:73:in `Locker'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/rails/view_helper.rb:12:in `block in react_component'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:15:in `render'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:60:in `with'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:60:in `handle_interrupt'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:63:in `block in with'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:63:in `handle_interrupt'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/connection_pool-2.2.0/lib/connection_pool.rb:64:in `block (2 levels) in with'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering.rb:16:in `block in render'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/bundler/gems/react-rails-2ac01d93eee6/lib/react/server_rendering/sprockets_renderer.rb:46:in `render'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:32:in `eval'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:73:in `lock'
from /home/matt/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/execjs-2.5.2/lib/execjs/ruby_racer_runtime.rb:73:in `Locker'
Using the exact same setup, but setting the react server_rendering pool size to be 1 I am able to avoid the deadlock and performance doesn't seem to change much. Perhaps using a connection_pool is a premature optimization that comes with some potential fatal side effects from underlying thread safety bugs.
The only big change I notice about performance is that latency seems to have a higher deviation now, but requests/minute hasn't really changed much when running at full capacity.