tagomoris / right_speed

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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