Bug #11350
closedWhen Process.exec failed, redirections were still changed and not restored
Description
When Process.exec failed, redirections of file descriptors were changed and not restored.
When redirecting fd 3 or 5, ASYNC BUG occurred as below.
$ ruby -e 'begin; Process.exec("/does_not_exist", "arg", { 3=>["/dev/null", "w"]}); rescue Errno::ENOENT => e; $stdout.print "stdout: ", e.inspect, "\n"; $stderr.print "stderr: ", e.inspect, "\n"; end'
[ASYNC BUG] consume_communication_pipe: read
EBADF
ruby 2.3.0dev (2015-07-13) [sparc64-solaris2.10]
[NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html
stdout: #<Errno::ENOENT: No such file or directory - /does_not_exist>
stderr: #<Errno::ENOENT: No such file or directory - /does_not_exist>Abort
$ ruby -e 'begin; Process.exec("/does_not_exist", "arg", { 5=>["/dev/null", "w"]}); rescue Errno::ENOENT => e; $stdout.print "stdout: ", e.inspect, "\n"; $stderr.print "stderr: ", e.inspect, "\n"; end'
[ASYNC BUG] consume_communication_pipe: read
EBADF
ruby 2.3.0dev (2015-07-13) [sparc64-solaris2.10]
stdout: [NOTE]
You may have encountered a bug in the Ruby interpreter or extension libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html
#<Errno::ENOENT: No such file or directory - /does_not_exist>
stderr: #<Errno::ENOENT: No such file or directory - /does_not_exist>Abort
When redirecting fd 1 or 2 to /dev/null, stdout or stderr is still redirected to /dev/null and the printed contents are not shown.
% ruby -e 'begin; Process.exec("/does_not_exist", "arg", { 1=>["/dev/null", "w"]}); rescue Errno::ENOENT => e; $stdout.print "stdout: ", e.inspect, "\n"; $stderr.print "stderr: ", e.inspect, "\n"; end'
stderr: #<Errno::ENOENT: No such file or directory - /does_not_exist>
% ruby -e 'begin; Process.exec("/does_not_exist", "arg", { 2=>["/dev/null", "w"]}); rescue Errno::ENOENT => e; $stdout.print "stdout: ", e.inspect, "\n"; $stderr.print "stderr: ", e.inspect, "\n"; end'
stdout: #<Errno::ENOENT: No such file or directory - /does_not_exist>
All of the above are observed both on x86_64 Linux and sparc Solaris.
Is this spec or bug?
Updated by ngoto (Naohisa Goto) over 9 years ago
- Related to Bug #11336: TestProcess#test_exec_fd_3_redirect failed on Solaris 10 added
Updated by akr (Akira Tanaka) over 9 years ago
- Status changed from Open to Feedback
It is an example of the documented behavior described as follows.
* The modified attributes may be retained when <code>exec(2)</code> system
* call fails.
*
* For example, hard resource limits are not restorable.
*
* Consider to create a child process using ::spawn or Kernel#system if this
* is not acceptable.
It is possible to restore FDs if there are free FDs enough.
But It is a tired task.
Updated by ngoto (Naohisa Goto) over 9 years ago
- Status changed from Feedback to Open
- Assignee deleted (
nobu (Nobuyoshi Nakada))
Consider to create a child process using ::spawn or Kernel#system if this is
not acceptable.
I think so, and I agree this is a spec.
I think the documentation should be added about the risk of ASYNC BUG when redirecting FDs that Ruby timer thread internally uses.
PS.
It seems that Japanese documentation of Process.exec does not describe about the limitation. (but this is not a bug of Ruby itself)
http://docs.ruby-lang.org/ja/2.2.0/class/Process.html#S_EXEC
Updated by ngoto (Naohisa Goto) over 9 years ago
To avoid ASYNC BUG, is it possible to close timer-thread pipe after stopping the timer thread, and when Process.exec fails, to open the timer-thread pipe again before re-starting the timer thread?
Updated by normalperson (Eric Wong) over 9 years ago
ngotogenome@gmail.com wrote:
To avoid ASYNC BUG, is it possible to close timer-thread pipe after
stopping the timer thread, and when Process.exec fails, to open the
timer-thread pipe again before re-starting the timer thread?
Yes. And we should always lazy start the timer thread to avoid
wasting resources on single-threaded scripts.
If nobody else does this soon, I will try it in a few weeks (currently
busy with other stuff).
Updated by ngoto (Naohisa Goto) over 9 years ago
On UNIX-like systems, native_stop_timer_thread(int close_anyway) in thread_pthread.c stops the timer thread, and if "int close_anyway" is true, it should close the communication pipe. However, currently, it does not close the pipe even if close_anyway is true.
TODO is described in the comment lines of the source.
/* close communication pipe */
if (close_anyway) {
/* TODO: Uninstall all signal handlers or mask all signals.
* This pass is cleaning phase (terminate ruby process).
* To avoid such race, we skip to close communication
* pipe. OS will close it at process termination.
* It may not good practice, but pragmatic.
* We remain it is TODO.
*/
/* close_communication_pipe(); */
}
Updated by ngoto (Naohisa Goto) over 9 years ago
- Related to Bug #11353: ASYNC BUG after failure of Process.exec when closing FD 3 (or 4 or 5) added
Updated by ngoto (Naohisa Goto) over 9 years ago
- Status changed from Open to Closed
Applied in changeset r51268.
-
process.c (redirect_dup2): when the new FD of dup2() coflicts
with one of the timer thread FDs, the internal FD is diverted.
[Bug #11336] [ruby-core:69886] [Bug #11350] [ruby-core:69961] -
process.c (dup2_with_divert): new function for the above purpose.
-
thread_pthread.c (rb_divert_reserved_fd): new function for
diverting reserved FD. If the given FD is the same as one of the
reserved FDs, the reserved FD number is internally changed.
It returns -1 when error. Otherwise, returns 0. It also returns
0 if there is no need to change reserved FD number. -
thread_win32.c (rb_divert_reserved_fd): always returns 0 because
of no reserved FDs. -
internal.h (rb_divert_reserved_fd): prototype declaration.
It is Ruby internal use only.