rails icon indicating copy to clipboard operation
rails copied to clipboard

Ensure pipe is closed after test run

Open sambostock opened this issue 1 year ago • 0 comments

Summary

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.

Other Information

I hope my explanation above is correct. Multiple processes, pipes, and file descriptors are outside my usual area of expertise.

I noticed this while working on #45664. While trying to run tests, I would inevitably run into an error whereby the test suite would terminate abruptly part way through due to Too many open files (Errno::EMFILE), requiring me to re-run the command repeatedly until the randomized test order meant that the test I cared about had run before the crash.

Example error output

Note the following output is somewhat interleaved, due to coming from multiple processes.

sambostock@sams-m1-mbp railties % bundle exec rake test:regular TEST=test/application/configuration_test.rb TESTOPTS="--verbose"
...
ApplicationTests::ConfigurationTest#test_Rails.application_is_nil_until_app_is_initialized = 0.55 s = .
ApplicationTests::ConfigurationTest#test_Digest::UUID.use_rfc4122_namespaced_uuids_is_disabled_by_default_for_upgraded_apps = 0.59 s = .
ApplicationTests::ConfigurationTest#test_config.active_record.use_yaml_unsafe_load_is_false_by_default = 0.56 s = .
#<Thread:0x000000010a134560 /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:28 run> terminated with exception (report_on_exception is true):
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:28:in `pipe': Too many open files (Errno::EMFILE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:28:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
ApplicationTests::ConfigurationTest#test_application_verifier_can_build_different_verifiers = 0.31 s = .
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:28:in `pipe': Too many open files (Errno::EMFILE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:28:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
rake aborted!
Command failed with status (1): [ruby -w -I"lib:test:/Users/sambostock/src/github.com/sambostock/rails/railties/../activesupport/lib" /opt/rubies/3.1/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/rake_test_loader.rb "test/application/configuration_test.rb" --verbose]
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli/exec.rb:58:in `load'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli/exec.rb:58:in `kernel_load'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli/exec.rb:23:in `run'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli.rb:483:in `exec'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli.rb:31:in `dispatch'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/cli.rb:25:in `start'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/exe/bundle:48:in `block in <top (required)>'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'
/Users/sambostock/.gem/ruby/3.1.0/gems/bundler-2.3.14/exe/bundle:36:in `<top (required)>'
/Users/sambostock/.gem/ruby/3.1.0/bin/bundle:25:in `load'
/Users/sambostock/.gem/ruby/3.1.0/bin/bundle:25:in `<main>'
Tasks: TOP => test:regular
(See full trace by running task with --trace)
sambostock@sams-m1-mbp railties % /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'
/Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `write': Broken pipe (Errno::EPIPE)
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:52:in `block in run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:32:in `run_in_isolation'
	from /Users/sambostock/src/github.com/sambostock/rails/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest.rb:1059:in `run_one_method'
	from /Users/sambostock/.gem/ruby/3.1.0/gems/minitest-5.16.1/lib/minitest/parallel.rb:33:in `block (2 levels) in start'

Having failed to figure out how to run individual tests, I eventually got frustrated enough to investigate... 😅

So far, with this change, I have been unable to reproduce the error. :tada:

sambostock avatar Jul 27 '22 04:07 sambostock