tagomoris / right_speed

MIT License
15 stars 1 forks source link

Sinatra can't run on Ractor #10

Open tagomoris opened 3 years ago

tagomoris commented 3 years ago

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>'
tagomoris commented 3 years ago

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