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

Fix out_of_band hook #2218

Merged
merged 2 commits into from Apr 16, 2020
Merged

Fix out_of_band hook #2218

merged 2 commits into from Apr 16, 2020

Conversation

wjordan
Copy link
Contributor

@wjordan wjordan commented Apr 7, 2020

Description

Fixes #2177. (Thanks @GuiTeK for tracking down this bug!)

Modifies the out_of_band hook implementation in the Server class to ensure that hooks are fired as expected when the server is configured with >1 max threads.

Compare to #2178, #2180. This implementation is pretty simple. It changes behavior slightly by executing the hooks on the same thread that finished processing the last request (instead of the main acceptor thread), so the server can continue accepting incoming requests if any arrive while the out-of-band hooks are still executing.

I've also added a couple of server tests to ensure out-of-band hooks fire as expected in two scenarios (sequential and parallel requests).

Your checklist for this pull request

  • I have reviewed the guidelines for contributing to this repository.
  • I have added an entry to History.md if this PR fixes a bug or adds a feature. If it doesn't need an entry to HISTORY.md, I have added [changelog skip] the pull request title.
  • I have added appropriate tests if this PR fixes a bug or adds a feature.
  • My pull request is 100 lines added/removed or less so that it can be easily reviewed.
  • If this PR doesn't need tests (docs change), I added [ci skip] to the title of the PR.
  • If this closes any issues, I have added "Closes #issue" to the PR description or my commit messages.
  • I have updated the documentation accordingly.
  • All new and existing tests passed, including Rubocop.

@MSP-Greg
Copy link
Member

MSP-Greg commented Apr 7, 2020

I just switched to a different 'apt-get' server, so Ubuntu should consistently install ragel, I think.

@nateberkopec nateberkopec added bug waiting-for-review Waiting on review from anyone labels Apr 8, 2020
@@ -79,6 +79,10 @@ def pool_capacity
waiting + (@max - spawned)
end

def busy_threads
@spawned - @waiting + @todo.size
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to wait for the @mutex here if not already held. This will work on MRI but won't work on JRuby/etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in ff28636 - not 100% sure if this approach (calling @mutex.owned? in a with_mutex wrapper method) has any small perf implications we care about. Alternative would be to not use a method at all, or introduce a in_mutex argument.

lib/puma/dsl.rb Outdated
@@ -494,8 +494,9 @@ def after_worker_fork(&block)

# Code to run out-of-band when the worker is idle.
# These hooks run immediately after a request has finished
# processing and there are no busy threads on the worker.
# The worker doesn't accept new requests until this code finishes.
Copy link
Member

Choose a reason for hiding this comment

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

Whoops, that wasn't ever true, was it?

Copy link
Member

Choose a reason for hiding this comment

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

Also, Q: should this be the actual behavior? Is it really OOB otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, that wasn't ever true, was it?

The oob hook previously ran from the acceptor thread, so I think it was true at least for the case where it was working correctly (max threads 1).

should this be the actual behavior? Is it really OOB otherwise?

Hmm, I went back and forth on whether it's better to keep accepting new requests while an OOB hook is running. (Might depend on the hook's use case?) For GC optimization at least, I thought continuing to accept requests could potentially be better under high load (though the MRI GVL could make it a wash). But on second thought, not accepting new requests is probably safer for light loads especially while request-balancing is still uneven. Plus it is 'really OOB' for other use-cases. I can change this to run on the acceptor thread as before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 7b36391

@nateberkopec
Copy link
Member

Only Q for me is: should the OOB hook work the way it was advertised? "No other requests will be processed until the hook finishes".

Both approaches have the potential to add latency to requests.

@nateberkopec nateberkopec added waiting-for-changes Waiting on changes from the requestor and removed waiting-for-review Waiting on review from anyone labels Apr 8, 2020
@wjordan wjordan force-pushed the out_of_band_fix branch 2 times, most recently from c1092b3 to ff28636 Compare April 8, 2020 06:06
@nateberkopec
Copy link
Member

Hm. I think that if the method is called oob, it needs to actually be oob - so:

  1. Grab the threadpool mutex. If we are the only busy thread, then:
  2. Stop accepting requests.
  3. Run callbacks, return.
  4. Start accepting requests.

@nateberkopec
Copy link
Member

@wjordan thoughts on #2218 (comment)?

Add server tests to ensure oob hooks fire as expected.
Prevent overlapping requests from being processed during
out of band hook.
@nateberkopec
Copy link
Member

Oh OK, that's definitely better than interacting with the threadpool 👍

@nateberkopec nateberkopec merged commit df9cdc7 into puma:master Apr 16, 2020
@@ -439,6 +436,12 @@ def process_client(client, buffer)
rescue StandardError => e
@events.unknown_error self, e, "Client"
end

if @options[:out_of_band]
@thread_pool.with_mutex do
Copy link
Contributor

@ayufan ayufan Apr 23, 2020

Choose a reason for hiding this comment

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

Does this actually create a mutex now on thread_pool? The OOB hook is executed within a mutex, rather than dispatched to be executed. Does it mean that it creates a contention now due to mutex, till OOB is executed, pretty much similar to before, stopping acceptance thread.

This also, connected with a test #test_out_of_band_stream creates a very strong dependency on execution: The test assumes that all connections are in fact processed concurrently, but this might not always be true, because the test does fail if we assume that puma runs with threads=1. The test does fail in this condition, as this is being executed on down failing edge of the event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug waiting-for-changes Waiting on changes from the requestor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

out_of_band hook not working when using multiple threads
4 participants