Project

General

Profile

Bug #11350

When Process.exec failed, redirections were still changed and not restored

Added by ngoto (Naohisa Goto) almost 5 years ago. Updated almost 5 years ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:69961]

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?


Related issues

Related to Ruby master - Bug #11336: TestProcess#test_exec_fd_3_redirect failed on Solaris 10ClosedActions
Related to Ruby master - Bug #11353: ASYNC BUG after failure of Process.exec when closing FD 3 (or 4 or 5)ClosedActions
#1

Updated by ngoto (Naohisa Goto) almost 5 years ago

  • Related to Bug #11336: TestProcess#test_exec_fd_3_redirect failed on Solaris 10 added

Updated by akr (Akira Tanaka) almost 5 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) almost 5 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) almost 5 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) almost 5 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) almost 5 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(); */
    }
#7

Updated by ngoto (Naohisa Goto) almost 5 years ago

  • Related to Bug #11353: ASYNC BUG after failure of Process.exec when closing FD 3 (or 4 or 5) added
#8

Updated by ngoto (Naohisa Goto) almost 5 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.

Also available in: Atom PDF