Giter Site home page Giter Site logo

Comments (3)

JoshCheek avatar JoshCheek commented on July 20, 2024

Update: This program, when run will hang indefinitely. When interrupted from the shell (both fish and bash do this), it kills the process, its child, and its grandchild. When interrupted with SIGINT, from another process, it kills the parent and the child, but not the grandchild. The documentation assumes a deeper knowledge of Unix than I have. Trying to read explanations of processes / process groups / sessions / etc has not improved clarity. It may be possible to prevent it from hanging, though, which would be something, at least.

require 'timeout'
require 'rbconfig'

# helper methods
def annotate(description, *allowed_exceptions, &block)
  success = false
  puts "\e[33mPRE #{description}\e[39m"
  block.call
  success = true
rescue *allowed_exceptions
  success = true
ensure
  puts "\e[3#{success ? 2 : 1}mPOST #{description}\e[39m"
end

def close_all(*streams)
  streams.each { |io| io && !io.closed? && io.close }
end

# setup streams
parentout, childout = IO.pipe
parenterr, childerr = IO.pipe
childin,   parentin = IO.pipe

# clean up when exiting the program
at_exit do
  annotate("ensure streams closed") { close_all parentout, childout, parenterr, childerr, childin, parentin }
end

# the child program has its own child, both of which will hang waiting on the pipe to be closed
parentin.write <<-RUBY
  puts "hello, world"
  read, write = IO.pipe
  if fork
    write.puts "parent"
  else
    write.puts "child"
  end
  read.read
RUBY

# evaluate the code in a child process
child_pid = Kernel.spawn(RbConfig.ruby, '-', in: childin, out: childout, err: childerr)

# close streams talking to the child so that it could exit
[parentin, childout, childerr, childin].each(&:close)

# print anything we see the child print
consumer_thread = Thread.new do
  parentout.each_line { |line| p stdout: line }
  parenterr.each_line { |line| p stderr: line }
end

# wait for child to complete, timing out if it does not
begin
  Timeout.timeout 2 do
    annotate("wait for the child we started")        { Process.wait child_pid }
    annotate("wait for all children", Errno::ECHILD) { Process.wait }
    annotate("joining consumer thread")              { consumer_thread.join }
  end
rescue Timeout::Error
  annotate("killing child")           { Process.kill "KILL", child_pid }
  annotate("joining consumer thread") { consumer_thread.join }
end

from seeing_is_believing.

JoshCheek avatar JoshCheek commented on July 20, 2024

While debugging, I changed the way the child is invoked. IDK if this is relevant, but it took a bit to figure out, so here's the diff (print statements were because I was trying to understand why it didn't resolve this issue)

diff --git a/lib/seeing_is_believing/evaluate_by_moving_files.rb b/lib/seeing_is_believing/evaluate_by_moving_files.rb
index a48e69e..fa07d29 100644
--- a/lib/seeing_is_believing/evaluate_by_moving_files.rb
+++ b/lib/seeing_is_believing/evaluate_by_moving_files.rb
@@ -108,7 +108,7 @@ class SeeingIsBelieving
                out:                 child_stdout,
                err:                 child_stderr,
                child_eventstream => child_eventstream }
-      child = Process.detach Kernel.spawn(env, *popen_args, opts)
+      child = Kernel.spawn(env, *popen_args, opts)

       # close child streams b/c they won't emit EOF
       # until both child and parent references are closed
@@ -130,15 +130,26 @@ class SeeingIsBelieving

       # wait for completion
       Timeout.timeout timeout_seconds do
-        exitstatus = child.value.exitstatus
-        consumer.process_exitstatus exitstatus
+        puts "pre wait"
+        Process.wait child # for the child we started
+        status = $?.exitstatus
+        puts "status: #{status.inspect}"
+        begin  Process.wait       # for any children it may have started
+        rescue Errno::ECHILD
+        end
+        puts "post wait"
+        consumer.process_exitstatus status
         consumer_thread.join
       end
     rescue Timeout::Error
-      Process.kill "KILL", child.pid
+      puts "rescue timeout"
+      Process.kill "SIGKILL", child
       consumer.process_timeout timeout_seconds
+      puts "pre join"
       consumer_thread.join # finish consuming events
+      puts "post join"
     ensure
+      puts "ensure streams closed"
       [stdin, stdout, stderr, eventstream].each { |io| io.close unless io.closed? }
     end

from seeing_is_believing.

JoshCheek avatar JoshCheek commented on July 20, 2024

Ugh, I think the solution I have kills parents, too -.-
Here's a diff of where I'm at:

diff --git a/lib/seeing_is_believing/evaluate_by_moving_files.rb b/lib/seeing_is_believing/evaluate_by_moving_files.rb
index 9f8a5eb..6d12abe 100644
--- a/lib/seeing_is_believing/evaluate_by_moving_files.rb
+++ b/lib/seeing_is_believing/evaluate_by_moving_files.rb
@@ -104,11 +104,14 @@ class SeeingIsBelieving
                                 )].pack('m0')

       # evaluate the code in a child process
-      opts = { in:                  child_stdin,
+      opts = {
+        child_eventstream => child_eventstream,
+        in:                  child_stdin,
         out:                 child_stdout,
         err:                 child_stderr,
-               child_eventstream => child_eventstream }
-      child = Process.detach Kernel.spawn(env, *popen_args, opts)
+        pgroup:              true, # run in its own process group so we can kill any children it spawned
+      }
+      child_pid  = Kernel.spawn(env, *popen_args, opts)

       # close child streams b/c they won't emit EOF
       # until both child and parent references are closed
@@ -130,17 +133,22 @@ class SeeingIsBelieving

       # wait for completion
       Timeout.timeout timeout_seconds do
-        exitstatus = child.value.exitstatus
-        consumer.process_exitstatus exitstatus
+        Process.wait child_pid
+        consumer.process_exitstatus $?.exitstatus
+        # begin  Process.wait # for any grandchildren
+        # rescue Errno::ECHILD
+        # end
         consumer_thread.join
       end
     rescue Timeout::Error
-      pgid = Process.getpgid(child.pid)
-      handler = trap("INT") { trap("INT", handler) } # noop
-      Process.kill "-INT", pgid
       consumer.process_timeout timeout_seconds
-      consumer_thread.join # finish consuming events
     ensure
+      begin
+        child_pgid = Process.getpgid(child_pid)
+        Process.kill "-INT", child_pgid
+      rescue Errno::ESRCH
+      end
+      consumer_thread.join
       [stdin, stdout, stderr, eventstream].each { |io| io.close unless io.closed? }
     end

from seeing_is_believing.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.