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

Remove executor/reloader for less interlocking #97

Merged
merged 7 commits into from
Aug 26, 2020

Conversation

sj26
Copy link
Contributor

@sj26 sj26 commented Aug 25, 2020

I believe this will fix a bunch of dependency interlocking issues. The only thing good_job needs from the executor and/or reloader is activerecord connection management, and considering how tightly intertwined with activerecord it is, it's very reasonable to own the connection management in the same way actioncable does.

ActiveJob takes care of the interface to user rails code which requires reloading, and makes sure the executor wraps user code in the right places:
https://github.com/rails/rails/blob/acaa546026471354ae1ff15e96c6bf5d39b0e34a/activejob/lib/active_job/railtie.rb#L39-L47

The only reason Sidekiq etc have to get involved with the executor is because they execute user code outside of ActiveJob.

Fixes #95

Take a leaf from actioncable's book and explicitly checkout and own a
connection so we don't have to worry about locking.

See: https://github.com/rails/rails/blob/acaa546026471354ae1ff15e96c6bf5d39b0e34a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb#L36-L49
I've observed swallowed errors here.
The instrumentation is hairy, but they have their own interlocks during
dispatch.
ActiveJob takes care of invoking a full executor around the actual piece
of work which will invoke code reloading etc.
lib/good_job/scheduler.rb:140:76: C: Style/SymbolProc: Pass &:next as an argument to new instead of a block.
      future = Concurrent::Future.new(args: [@Performer], executor: @pool) do |performer| ...
                                                                           ^^^^^^^^^^^^^^

Although I'm not sure I find this cleaner.
Copy link
Owner

@bensheldon bensheldon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ ✨ This is fantastic ✨ ✨

I'm going to do a little bit more poking locally and will merge it if I don't run into any issues.

Rails.application.executor.wrap { output = performer.next }
output
end
future = Concurrent::Future.new(executor: @pool, &@performer.method(:next))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very clean, but I think I may regret it in the future because it's easy to overlook this is where the work gets done.

But I'm happy to roll with it for now and just make a mental note that if it causes confusion in the future, to go to the more expansive version.

And just while I'm noodling:

  • I think it would be possible to cache the proc so it doesn't have to be recreated everytime this method is called
  • I wonder if the performer should alias call to next so it is directly callable (I'm imagining there was a reason why that wasn't done originally but I'm not remembering it... though it was likely that I wanted explicitness)


def with_listen_connection
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
ActiveRecord::Base.connection_pool.remove(conn)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but I am curious: this will have the effect of using one additional database connection than the pool size?

Copy link
Contributor Author

@sj26 sj26 Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will, good spot. It should probably be documented. But I don't think this is unreasonable — the total number of ideal connections will be one for the notifier, and then one for each worker thread.

It's the same approach as actioncable, and I think they have the same caveat documented somewhere...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I don't think they do — I can't find it. I'd expect it to be here:

https://edgeguides.rubyonrails.org/action_cable_overview.html#postgresql-adapter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bensheldon bensheldon changed the title Less interlocking Remove executor/reloader for less interlocking Aug 26, 2020
@bensheldon bensheldon merged commit eea7066 into bensheldon:main Aug 26, 2020
@sj26
Copy link
Contributor Author

sj26 commented Aug 26, 2020

🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Code changes that do not introduce new features
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Freezes puma on code change
2 participants