Skip to content

Commit 1bd5220

Browse files
committed
Ensure pipe is closed after test run
`Forking.run_in_isolation` opens two ends of a pipe. The fork process closes the read end, writes to it, and then terminates (which presumably closes the file descriptors on its end). The parent process closes the write end, reads from it, and returns, never closing the read end. This results in an accumulation of open file descriptors, which can cause errors if the limit is reached. One approach to fixing this would be to simply close the read end of the pipe in the parent process. However, it is more idiomatic to open the pipe given a block, which automatically closes the pipe after the block exits.
1 parent 8a67a6d commit 1bd5220

File tree

2 files changed

+41
-27
lines changed

2 files changed

+41
-27
lines changed

activesupport/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
* Ensure `ActiveSupport::Testing::Isolation::Forking` closes pipes
2+
3+
Previously, `Forking.run_in_isolation` opened two ends of a pipe. The fork
4+
process closed the read end, wrote to it, and then terminated (which
5+
presumably closed the file descriptors on its end). The parent process
6+
closed the write end, read from it, and returned, never closing the read
7+
end.
8+
9+
This resulted in an accumulation of open file descriptors, which could
10+
cause errors if the limit is reached.
11+
12+
*Sam Bostock*
13+
114
* Fix `Time#change` and `Time#advance` for times around the end of Daylight
215
Saving Time.
316

activesupport/lib/active_support/testing/isolation.rb

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,38 +25,39 @@ def run
2525

2626
module Forking
2727
def run_in_isolation(&blk)
28-
read, write = IO.pipe
29-
read.binmode
30-
write.binmode
28+
IO.pipe do |read, write|
29+
read.binmode
30+
write.binmode
3131

32-
pid = fork do
33-
read.close
34-
yield
35-
begin
36-
if error?
37-
failures.map! { |e|
38-
begin
39-
Marshal.dump e
40-
e
41-
rescue TypeError
42-
ex = Exception.new e.message
43-
ex.set_backtrace e.backtrace
44-
Minitest::UnexpectedError.new ex
45-
end
46-
}
32+
pid = fork do
33+
read.close
34+
yield
35+
begin
36+
if error?
37+
failures.map! { |e|
38+
begin
39+
Marshal.dump e
40+
e
41+
rescue TypeError
42+
ex = Exception.new e.message
43+
ex.set_backtrace e.backtrace
44+
Minitest::UnexpectedError.new ex
45+
end
46+
}
47+
end
48+
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
49+
result = Marshal.dump(test_result)
4750
end
48-
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
49-
result = Marshal.dump(test_result)
51+
52+
write.puts [result].pack("m")
53+
exit!
5054
end
5155

52-
write.puts [result].pack("m")
53-
exit!
56+
write.close
57+
result = read.read
58+
Process.wait2(pid)
59+
result.unpack1("m")
5460
end
55-
56-
write.close
57-
result = read.read
58-
Process.wait2(pid)
59-
result.unpack1("m")
6061
end
6162
end
6263

0 commit comments

Comments
 (0)