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

Asynchronous pruning for RubyThreadPoolExecutor #1082

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

joshuay03
Copy link

@joshuay03 joshuay03 commented Feb 8, 2025

Closes #1066
Closes #1075

Alternative to #1079

Implementation is based on the discussion in the linked issues.

@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 15 times, most recently from b6e5656 to f90d46d Compare February 9, 2025 04:23
@@ -114,9 +120,9 @@ def worker_task_completed
synchronize { @completed_task_count += 1 }
end

# @!macro thread_pool_executor_method_prune_pool
def prune_pool
Copy link
Author

Choose a reason for hiding this comment

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

This public API no longer makes sense now that the pruning is automatic. It seems it was only added because of the Ruby thread pool's synchronous pruning, which the Java version didn't suffer from, hence why it's a no-op there.

@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 14 times, most recently from c5eca7d to 89b67f4 Compare February 14, 2025 15:54
@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 2 times, most recently from 57ae7ae to 9164d29 Compare February 14, 2025 23:57
@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 2 times, most recently from 10f5833 to 76cd713 Compare February 15, 2025 00:22
@joshuay03 joshuay03 changed the title Better pruning for RubyThreadPoolExecutor Asynchronous pruning for RubyThreadPoolExecutor Feb 16, 2025
@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 13 times, most recently from 547428b to a675844 Compare February 22, 2025 01:33
@joshuay03 joshuay03 marked this pull request as ready for review February 22, 2025 01:40

if non_block
super(true)
elsif @mutex.synchronize { empty? && timed_out?(timeout) { @cond_var.wait(@mutex, timeout) } }
Copy link
Contributor

@bensheldon bensheldon Mar 10, 2025

Choose a reason for hiding this comment

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

Just flagging that there could be a spurious wake up signal on the Condition Variable that prematurely invokes this condition. If that happened, I think you'd accidentally end up in a blocking pop.

I think the recommended design is to set an additional variable flag in addition to the signal (in push) to disambiguate between the meaningful signal and the spurious ones. The primitive for this is a Concurrent::Event.

Copy link
Author

Choose a reason for hiding this comment

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

Ah good catch, I'll address, thanks!

Copy link
Author

Choose a reason for hiding this comment

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

I think the recommended design is to set an additional variable flag in addition to the signal (in push) to disambiguate between the meaningful signal and the spurious ones. The primitive for this is a Concurrent::Event.

I think we can rely on #empty? instead? - it can only be true if there was a spurious wakeup.

Copy link
Author

Choose a reason for hiding this comment

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

I've addressed, let me know what you think.

Copy link
Contributor

Choose a reason for hiding this comment

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

I imagine that's sufficient 👍🏻 Thank you!

I think the only thing is there could be a race condition on empty? between a non-timeout pop and a timeout pop on the same Queue instance, because the non-timeout call to super is not synchronized with the same mutex. ...but we're building this for a very specific use case where that wouldn't happen.

Copy link
Author

@joshuay03 joshuay03 Mar 17, 2025

Choose a reason for hiding this comment

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

Good point! Although I think it’s best to ensure the implementation is robust in case it gets relied on more heavily in future work.

I've pushed a slightly different approach where we rely on the non-blocking super call instead.

@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch 6 times, most recently from 1c208c9 to fd02056 Compare March 16, 2025 22:10
@joshuay03 joshuay03 force-pushed the better-ruby-thread-pool-executor-pruning branch from fd02056 to 6cedac3 Compare March 17, 2025 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CachedThreadPool does not spin down idle threads Unexpected pruning behaviour with consecutive task batches
2 participants