Class: PumaPlus::Worker
- Inherits:
-
Object
- Object
- PumaPlus::Worker
- Defined in:
- lib/puma_plus/worker.rb
Overview
A Ruby worker process: loads the Rack app once, then runs N threads that each dial the Go server and serve requests.
Phase 1 runs a single worker process directly. Phase 4 puts a shepherd above this that forks several of these and handles SET_SLOTS/QUIESCE.
Instance Method Summary collapse
-
#initialize(socket_path:, app_path:, threads:, worker_id: 0, config_path: nil, hooks: nil, multiprocess: false, logger: $stderr) ⇒ Worker
constructor
A new instance of Worker.
- #run ⇒ Object
-
#run_preloaded(app) ⇒ Object
Serve using an already-loaded app.
Constructor Details
#initialize(socket_path:, app_path:, threads:, worker_id: 0, config_path: nil, hooks: nil, multiprocess: false, logger: $stderr) ⇒ Worker
Returns a new instance of Worker.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/puma_plus/worker.rb', line 16 def initialize(socket_path:, app_path:, threads:, worker_id: 0, config_path: nil, hooks: nil, multiprocess: false, logger: $stderr) @socket_path = socket_path @app_path = app_path @threads = threads @worker_id = worker_id @logger = logger # The shepherd passes hooks it already loaded, so a forked child does not # re-read and re-evaluate the config file once per worker. @hooks = hooks || Hooks.load(config_path, logger: logger) # Matches puma: multithread when more than one thread can call the app in # this process, multiprocess when a shepherd is managing worker processes # (puma/lib/puma/binder.rb:33-34). @multithread = threads > 1 @multiprocess = multiprocess end |
Instance Method Details
#run ⇒ Object
33 34 35 36 37 38 |
# File 'lib/puma_plus/worker.rb', line 33 def run # Load before any thread dials in. A connection appearing on the Go side is # the readiness signal, so it must not appear until the app can actually # serve -- that is the whole reason puma-plus needs no dial-polling. run_preloaded(load_app) end |
#run_preloaded(app) ⇒ Object
Serve using an already-loaded app. Used by the shepherd, which loads once before forking so children inherit a warm heap through copy-on-write.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/puma_plus/worker.rb', line 42 def run_preloaded(app) # Start GVL instrumentation before any thread serves, so the very first # request is measured. A no-op without the native extension. PumaPlus::GVL.start! # One outbound WebSocket control channel per worker process, opened before # any thread serves. The app can then address any connection at any time, # from any thread, rather than only while handling a message. PumaPlus::WS.connect!(@socket_path, logger: @logger) # Before any thread dials in, which is what makes this ordering meaningful: # a connection appearing on the Go side is the readiness signal, so hooks # finishing first means Go cannot dispatch a request to a worker whose # setup has not completed. @hooks.run(:on_worker_boot, @worker_id) threads = @threads.times.map do |i| Thread.new do Thread.current.name = "puma-plus #{@worker_id}/#{i}" WorkerThread.new( socket_path: @socket_path, app: app, worker_id: @worker_id, thread_index: i, multithread: @multithread, multiprocess: @multiprocess, logger: @logger ).run end end @logger.puts "[puma-plus] worker #{@worker_id} pid=#{Process.pid} " \ "serving with #{@threads} threads" trap("TERM") { threads.each(&:kill) } trap("INT") { threads.each(&:kill) } threads.each(&:join) ensure # Not fatal: the process is leaving either way, and refusing to exit # because a cleanup hook raised helps nobody. @hooks.run(:on_worker_shutdown, @worker_id, fatal: false) end |