Class: Bootsnap::CLI::WorkerPool
- Inherits:
-
Object
- Object
- Bootsnap::CLI::WorkerPool
- Defined in:
- lib/bootsnap/cli/worker_pool.rb
Defined Under Namespace
Class Method Summary collapse
Instance Method Summary collapse
- #dispatch_loop ⇒ Object
- #free_worker ⇒ Object
-
#initialize(size:, jobs: {}) ⇒ WorkerPool
constructor
A new instance of WorkerPool.
- #push(*args) ⇒ Object
- #shutdown ⇒ Object
- #spawn ⇒ Object
Constructor Details
#initialize(size:, jobs: {}) ⇒ WorkerPool
156 157 158 159 160 161 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 156 def initialize(size:, jobs: {}) @size = size @jobs = jobs @queue = Queue.new @pids = [] end |
Class Method Details
.cpu_quota ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 37 def cpu_quota if RbConfig::CONFIG["target_os"].include?("linux") if File.exist?("/sys/fs/cgroup/cpu.max") # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files cpu_max = File.read("/sys/fs/cgroup/cpu.max") return nil if cpu_max.start_with?("max ") # no limit max, period = cpu_max.split.map(&:to_f) max / period elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us") # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions # https://docs.kernel.org/scheduler/sched-bwc.html#management return nil if max <= 0 period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f max / period end end end |
.create(size:, jobs:) ⇒ Object
11 12 13 14 15 16 17 18 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 11 def create(size:, jobs:) size ||= default_size if size > 0 && Process.respond_to?(:fork) new(size: size, jobs: jobs) else Inline.new(jobs: jobs) end end |
.default_size ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 20 def default_size nprocessors = Etc.nprocessors size = [nprocessors, cpu_quota&.to_i || nprocessors].min case size when 0, 1 0 else if fork_defunct? $stderr.puts "warning: faulty fork(2) detected, probably in cross platform docker builds. " \ "Disabling parallel compilation." 0 else size end end end |
.fork_defunct? ⇒ Boolean
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 85 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 59 def fork_defunct? return true unless ::Process.respond_to?(:fork) # Ref: https://github.com/rails/bootsnap/issues/495 # The second forked process will hang on some QEMU environments r, w = IO.pipe pids = 2.times.map do ::Process.fork do exit!(true) end end w.close r.wait_readable(1) # Wait at most 1s defunct = false pids.each do |pid| _pid, status = ::Process.wait2(pid, ::Process::WNOHANG) if status.nil? # Didn't exit in 1s defunct = true Process.kill(:KILL, pid) ::Process.wait2(pid) end end defunct end |
Instance Method Details
#dispatch_loop ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 171 def dispatch_loop loop do case job = @queue.pop when nil @workers.each do |worker| worker.write([:exit]) worker.close end return true else unless @workers.sample.write(job, block: false) free_worker.write(job) end end end end |
#free_worker ⇒ Object
188 189 190 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 188 def free_worker IO.select(nil, @workers)[1].sample end |
#push(*args) ⇒ Object
192 193 194 195 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 192 def push(*args) @queue.push(args) nil end |
#shutdown ⇒ Object
197 198 199 200 201 202 203 204 205 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 197 def shutdown @queue.close @dispatcher_thread.join @workers.each do |worker| _pid, status = Process.wait2(worker.pid) return status.exitstatus unless status.success? end nil end |
#spawn ⇒ Object
163 164 165 166 167 168 169 |
# File 'lib/bootsnap/cli/worker_pool.rb', line 163 def spawn @workers = @size.times.map { Worker.new(@jobs) } @workers.each(&:spawn) @dispatcher_thread = Thread.new { dispatch_loop } @dispatcher_thread.abort_on_exception = true true end |