Class: Bootsnap::CLI::WorkerPool

Inherits:
Object
  • Object
show all
Defined in:
lib/bootsnap/cli/worker_pool.rb

Defined Under Namespace

Classes: Inline, Worker

Class Method Summary collapse

Instance Method Summary collapse

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_quotaObject



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_sizeObject



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_loopObject



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_workerObject



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

#shutdownObject



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

#spawnObject



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