-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Concurrently runs a number of 'Racecar::Runner' instances in a fixed size thread pool. Each thread starts a single `Racecar::Runner` with `Racecar::Consumer` class instance. All threads run the same consumer class, have the same config and consume partitions from the same topic(s). `ThreadPoolRunnerProxy` can be combined with `ParallelRunner`, to run forks and threads. `ParallelRunner` is not used or battle tested (at Zendesk) and so this is still not a recommended thing to do. Other inclusions: - Signal handling has been moved up one level to the CLI - Runner-like object interface standardized to `#run` `#stop` `#running?` - Test can be run locally with Docker, `export LOCAL=1` - Some test bugs have been fixed, connections now always close and orphaned processes raise an exception
- Loading branch information
Showing
14 changed files
with
395 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ PATH | |
remote: . | ||
specs: | ||
racecar (2.8.2) | ||
concurrent-ruby | ||
king_konf (~> 1.0.0) | ||
rdkafka (~> 0.12.0) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
require "concurrent-ruby" | ||
|
||
module Racecar | ||
class ThreadPoolRunnerProxy | ||
def initialize(processor, config:, runner_factory:) | ||
@processor = processor | ||
@config = config | ||
@runner_factory = runner_factory | ||
|
||
@parent_thread = Thread.current | ||
@runners = [] | ||
end | ||
|
||
attr_reader :runner_factory, :processor, :config | ||
private :runner_factory, :processor, :config | ||
|
||
attr_reader :parent_thread, :runners | ||
private :parent_thread, :runners | ||
|
||
def stop | ||
runners.each(&:stop) | ||
end | ||
|
||
def running? | ||
@runners.any?(&:running?) | ||
end | ||
|
||
def run | ||
Thread.current.name = "main" | ||
|
||
logger.debug("Spawning #{thread_count} " + "🧵" * thread_count) | ||
logger.info("ThreadPoolRunnerProxy starting #{thread_count} worker threads") | ||
|
||
thread_count.times do |id| | ||
thread_pool.post do | ||
work_in_thread(id) | ||
end | ||
end | ||
|
||
wait_for_runners_to_start | ||
logger.debug("Threaded runners running 🧵🏃") | ||
wait_for_normal_stop | ||
ensure | ||
ensure_thread_pool_terminates | ||
nil | ||
end | ||
|
||
private | ||
|
||
attr_reader :runners | ||
|
||
def work_in_thread(id) | ||
Thread.current.name = "Racecar worker thread #{Process.pid}-#{id}" | ||
Thread.current.abort_on_exception = true | ||
|
||
runner = runner_factory.call(processor) | ||
runners << runner | ||
logger.debug("ThreadPoolRunnerProxy starting runner #{runner} with consumer #{processor} 🏃🏁") | ||
|
||
runner.run | ||
|
||
logger.debug("ThreadPoolRunnerProxy runner stopped #{runner} 🏃🛑") | ||
rescue Exception => e | ||
logger.error("Error in threadpool, raising in parent thread. #{e.full_message}") | ||
stop | ||
parent_thread.raise(e) | ||
end | ||
|
||
def wait_for_runners_to_start | ||
started_waiting_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) | ||
|
||
until runners.length == thread_count && runners.all?(&:running?) | ||
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_waiting_at | ||
|
||
summary = runners.map { |r| "#{r}: running=#{r.running?}" }.join(", ") | ||
logger.debug("Waiting for runners to start #{summary} 🥱🏃🏃🏃🏁") | ||
|
||
break if elapsed > thread_pool_timeout | ||
sleep(thread_check_interval) | ||
end | ||
end | ||
|
||
def wait_for_normal_stop | ||
until @runners.none?(&:running?) | ||
sleep(thread_check_interval) | ||
end | ||
logger.debug("ThreadPoolRunnerProxy all runners have stopped 🏃🏃🏃 🛑🛑🛑") | ||
end | ||
|
||
def ensure_thread_pool_terminates | ||
logger.debug("ThreadPoolRunnerProxy waiting for thread pool to terminate 🎱💀💀💀") | ||
thread_pool.wait_for_termination(thread_pool_timeout) | ||
end | ||
|
||
def thread_pool | ||
@thread_pool ||= Concurrent::FixedThreadPool.new(thread_count) | ||
end | ||
|
||
def thread_count | ||
@threads ||= (config.threads || 1) | ||
end | ||
|
||
def thread_pool_timeout | ||
config.thread_pool_timeout | ||
end | ||
|
||
def thread_check_interval | ||
1 | ||
end | ||
|
||
def logger | ||
config.logger | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.