Skip to content

Commit 5cc0f1f

Browse files
Attempt to detect QEMU hangs
When building cross platform images with Docker, QEMU is often used under the hood and can have a bug that cause forked processes to deadlock. Before spawning workers we test for that bug. Fix: #495 Closes: #497 Co-Authored-By: Sarun Rattanasiri <[email protected]>
1 parent 5e87800 commit 5cc0f1f

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
* Attempt to detect a QEMU bug that can cause `bootsnap precompile` to hang forever when building ARM64 docker images
4+
from x86_64 machines. See #495.
35
* Improve CLI to detect cgroup CPU limits and avoid spawning too many worker processes.
46

57
# 1.18.4

lib/bootsnap/cli/worker_pool.rb

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require "etc"
44
require "rbconfig"
5+
require "io/wait" unless IO.method_defined?(:wait_readable)
56

67
module Bootsnap
78
class CLI
@@ -17,12 +18,18 @@ def create(size:, jobs:)
1718
end
1819

1920
def default_size
20-
size = [Etc.nprocessors, cpu_quota || 0].min
21+
nprocessors = Etc.nprocessors
22+
size = [nprocessors, cpu_quota || nprocessors].min
2123
case size
2224
when 0, 1
2325
0
2426
else
25-
size
27+
if fork_defunct?
28+
$stderr.puts "warning: faulty fork(2) detected, probably in cross platform docker builds. Disabling parallel compilation."
29+
0
30+
else
31+
size
32+
end
2633
end
2734
end
2835

@@ -45,6 +52,34 @@ def cpu_quota
4552
end
4653
end
4754
end
55+
56+
def fork_defunct?
57+
return true unless ::Process.respond_to?(:fork)
58+
59+
# Ref: https://github.com/Shopify/bootsnap/issues/495
60+
# The second forked process will hang on some QEMU environments
61+
r, w = IO.pipe
62+
pids = 2.times.map do
63+
::Process.fork do
64+
exit!(true)
65+
end
66+
end
67+
w.close
68+
r.wait_readable(1) # Wait at most 1s
69+
70+
defunct = false
71+
72+
pids.each do |pid|
73+
_pid, status = ::Process.wait2(pid, ::Process::WNOHANG)
74+
if status.nil? # Didn't exit in 1s
75+
defunct = true
76+
Process.kill(:KILL, pid)
77+
::Process.wait2(pid)
78+
end
79+
end
80+
81+
defunct
82+
end
4883
end
4984

5085
class Inline

0 commit comments

Comments
 (0)