Sinatra can't run on Ractor
tagomoris opened this issue · comments
Because all routes/filters/middlewares/extensions/errors(error handlers) are stored as class instance variable. And those values are sometimes Proc objects or unbound methods dynamically created with contexts.
We need to rewrite Sinatra entirely if we want to run it on Ractor.
errors = Sinatra::Base.errors
errors.keys.each do |key|
key.freeze
errors[key] = Ractor.make_shareable(errors[key])
end
RightSpeed::RactorHelper.overwrite_method(Sinatra::Base.singleton_class, :errors, errors)
<internal:ractor>:816:in `make_shareable': can not make shareable Proc because it can refer unshareable object #<UnboundMethod: Sinatra::Base#ERROR (?-mix:.*)() /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/sinatra-2.1.0/lib/sinatra/base.rb:1872> from variable `unbound_method' (Ractor::IsolationError)
from /Users/tagomoris/gh/demo-webapps/sinatra/config.ru:27:in `block (3 levels) in <main>'
from /Users/tagomoris/gh/demo-webapps/sinatra/config.ru:25:in `each'
from /Users/tagomoris/gh/demo-webapps/sinatra/config.ru:25:in `block (2 levels) in <main>'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/right_speed-0.1.0/lib/right_speed/server.rb:54:in `block in run'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/right_speed-0.1.0/lib/right_speed/server.rb:52:in `each'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/right_speed-0.1.0/lib/right_speed/server.rb:52:in `run'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/right_speed-0.1.0/lib/rack/handler/right_speed.rb:19:in `run'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:327:in `start'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/rack-2.2.3/lib/rack/server.rb:168:in `start'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/lib/ruby/gems/3.1.0/gems/rack-2.2.3/bin/rackup:5:in `<top (required)>'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/bin/rackup:23:in `load'
from /Users/tagomoris/.rbenv/versions/3.1.0-dev/bin/rackup:23:in `<main>'
The method to register error handlers: https://github.com/sinatra/sinatra/blob/v2.1.0/lib/sinatra/base.rb#L1310-L1316
def error(*codes, &block)
args = compile! "ERROR", /.*/, block
codes = codes.flat_map(&method(:Array))
codes << Exception if codes.empty?
codes << Sinatra::NotFound if codes.include?(404)
codes.each { |c| (@errors[c] ||= []) << args }
end
The compiler of handlers: https://github.com/sinatra/sinatra/blob/v2.1.0/lib/sinatra/base.rb#L1661
def compile!(verb, path, block, **options)
# Because of self.options.host
host_name(options.delete(:host)) if options.key?(:host)
# Pass Mustermann opts to compile()
route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
options.each_pair { |option, args| send(option, *args) }
pattern = compile(path, route_mustermann_opts)
method_name = "#{verb} #{path}"
unbound_method = generate_method(method_name, &block)
conditions, @conditions = @conditions, []
wrapper = block.arity != 0 ?
proc { |a, p| unbound_method.bind(a).call(*p) } :
proc { |a, p| unbound_method.bind(a).call }
[ pattern, conditions, wrapper ]
end