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

[Proposal] Replace ConnectionReset to Rails executor #134

Open
le0pard opened this issue Sep 6, 2021 · 4 comments
Open

[Proposal] Replace ConnectionReset to Rails executor #134

le0pard opened this issue Sep 6, 2021 · 4 comments

Comments

@le0pard
Copy link

le0pard commented Sep 6, 2021

Based on rails docs: https://guides.rubyonrails.org/threading_and_code_execution.html#executor

The Rails Executor separates application code from framework code: any time the framework invokes code you've written in your application, it will be wrapped by the Executor.

Here how it can be done in gruf:

app = Rails.application
executor = app.config.reload_classes_only_on_change ? app.reloader : app.executor

Gruf.configure do |c|
  c.interceptors.use(Gruf::Interceptors::RailsExecutor, executor: executor)
end

Interseptor itself (not tested - code provided as example):

module Gruf
  module Interceptors
    # Executor runs Rails executor for each call
    # See https://guides.rubyonrails.org/v5.2.0/threading_and_code_execution.html#framework-behavior
    class RailsExecutor < ::Gruf::Interceptors::ServerInterceptor

      def call
        options.executor.wrap { yield }
      end

    end
  end
end

In this case no need care about close connections for Activerecord and also fixing issues to other databases (like release connections to redis), which not covered by gruf, but covered by rails.

Also

Before entering the wrapped block, the Reloader will check whether the running application needs to be reloaded

So it may help to reach this #115 and #86

@splittingred
Copy link
Member

@le0pard Thanks! This is interesting. It seems this is only supported in Rails 5.0+, do we have an idea of Rails version compatibility here? It does seem more promising than the current interceptor approach.

Some questions I have from the docs:

This operation will block temporarily if another thread is currently either autoloading a constant or unloading/reloading the application.

Would this potentially lock incoming gRPC requests during constant autoloading? I wonder how that would impact tail latencies on boot (potentially affecting "healthy status" checks in container schedulers).

Definitely worth an investigation. Thanks for the proposal.

@le0pard
Copy link
Author

le0pard commented Sep 7, 2021

Thanks @splittingred

It seems this is only supported in Rails 5.0+, do we have an idea of Rails version compatibility here?

Looks like yes. For old rails we can leave ConnectionReset interseptor.

Would this potentially lock incoming gRPC requests during constant autoloading?

Maybe, this need to be checked. That is why I pointed to anycable, which have internal grpc server and found how they resolving such issue.

@ximus
Copy link
Contributor

ximus commented Jun 21, 2022

I did similar work on my fork to enable code reloading.
The work from Anycable is likely better inspiration than mine as I worked quickly without much domain experience.

However I had to do one extra thing not mentioned here to get my controllers to reload. Currently, Gruf's ServiceBinder setups up a reference to controller class instances when the server initializes. Given how code reload works, this reference is never refreshed to point to newer (reloaded) class instances, this needs to be done explicitly.

There is probably a few ways to refresh this reference. I went the dumb simple way of doing a constant lookup. see here

reloader.to_prepare do
  controller = controller.name.constantize
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants