Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spawned Ruby process on Windows dies when shell is terminated

I am trying to spawn a Ruby process on Windows using something like this:

p1 = spawn('ruby', 'loop.rb', [:out, :err] => ['process.log', "w"],  :new_pgroup => true)

I also then detach from the process via:

p1.detach

This should as far I understand create a new process which is independent of the parent. I am even using new_pgroup parameter to make sure that new process gets its own process group.

When I execute my script, the sub-process gets started and keeps running. The execution of the script spawning the sub process also completes. However, when I now close the shell, the sub-process dies. I would expect it to continue running (it does on OS X and Linux). I cannot figure out whether this is a bug in the Ruby runtime on Windows or whether this is a limitation of Windows and how it handles processes.

For completeness the full Ruby code of what I am trying to do:


spawner.rb: can be executed via ruby spawner.rb and just spawns a new sub-process. The process creates is loop.rb which is just an endless loop. Depending on the OS it specifies a different parameter for the process group creation.

require "tempfile"
require 'rbconfig'

class SpawnTest

  def self.spawn_process

    if os == :windows
      p1 = spawn('ruby', 'loop.rb', [:out, :err] => ['process.log', "w"],  :new_pgroup => true)
    else
      p1 = spawn('ruby', 'loop.rb', [:out, :err] => ['process.log', "w"],  :pgroup => true)
    end

    # Detach from the processes so they will keep running
    puts p1
    Process.detach(p1)
  end

  def self.os
    @os ||= (
    host_os = RbConfig::CONFIG['host_os']
    case host_os
      when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
        :windows
      when /darwin|mac os/
        :macosx
      when /linux/
        :linux
      when /solaris|bsd/
        :unix
      else
        raise Error::WebDriverError, "unknown os: #{host_os.inspect}"
    end
    )
  end
end

if __FILE__ == $0
  SpawnTest.spawn_process
end

loop.rb

$stdout.sync = true
$stderr.sync = true

i = 0
while i < 10000
  $stdout.puts "Iteration #{i}"
  sleep 1
  i = i + 1
end

$stdout.puts "Bye from #{Process.pid}"

I found the win32-process gem during my investigations. It seems to be using win32 API calls to spawn processes. Does anyone know whether this library would fix the problem?

Any help appreciated.

like image 490
Hardy Avatar asked Jun 17 '16 12:06

Hardy


1 Answers

I couldn't find any use of process groups in Windows except for dispatching CTRL events to multiple processes as it states in Process Creation Flags MSDN docs:

Process groups are used by the GenerateConsoleCtrlEvent function to enable sending a CTRL+BREAK signal to a group of console processes.

What actually happens when CREATE_NEW_PROCESS_GROUP flag is specified is...

...an implicit call to SetConsoleCtrlHandler(NULL,TRUE) is made on behalf of the new process; this means that the new process has CTRL+C disabled. This lets shells handle CTRL+C themselves, and selectively pass that signal on to sub-processes. CTRL+BREAK is not disabled, and may be used to interrupt the process/process group.

From CreateProcess MSDN docs.

Note that this is not the same as closing the console window with the x button. In the latter case the process receives CTRL_CLOSE_EVENT,

A signal that the system sends to all processes attached to a console when the user closes the console (either by clicking Close on the console window's window menu, or by clicking the End Task button command from Task Manager).

From HandlerRoutine callback MSDN docs.

Handling of this event is not affected by SetConsoleCtrlHandler(NULL,TRUE) set by CREATE_NEW_PROCESS_GROUP flag when creating the process.

All above means CREATE_NEW_PROCESS_GROUP flag has nothing to do with the behaviour you are observing.

By default a child process inherits parent's console window and IO handles. Therefore when you close the shell by clicking on the x button, it receives CTRL_CLOSE_EVENT and does not have an option to not terminate.

To avoid that, the child process should not be attached to the parent console. This is done by supplying DETACHED_PROCESS flag to CreateProcess Windows API.

By default, a console process inherits its parent's console ... Console processes are not attached to a console if they are created using CreateProcess with DETACHED_PROCESS.

From Creation of a Console MSDN docs.

This is what I believe Ruby's spawn and win32-process gem do differently. I haven't try to debug both in order to see the specific difference in the flags they supply to CreateProcess although that would be an interesting thing to do.

There are other ways to create a child process without inheriting the console or IO handles of the parent. Examples are CREATE_NO_WINDOW or CREATE_NEW_CONSOLE flags, Job Objects, etc.

like image 197
Nic Nilov Avatar answered Oct 14 '22 01:10

Nic Nilov