Problems with external processes in Ruby

I have huge problems handling external processes through Ruby in a win7/Skp15/16 setting. I want to fire an external process generating meshes for use in renderers and for this I need four methods to work: spawn, waitpid, kill and alive?. The first three methods are from the Ruby Process module and the fourth is a hack polling kill.

The code below is a test script encapsulating all problems. I spawn a process that takes about 10 seconds to finish.

module PROCESS_TEST

    def self.test1
        puts "---=== TEST 1 ===---"

        pid = Process.spawn('start timeout 10')
        puts "Spawned process: #{pid.to_s}"
        t0 = Time.now
        wait_for_process(pid)
        t1 = Time.now
        puts "Waited for #{(t1-t0).to_s} s"
        sleep 2
        puts "Process #{pid} alive?  #{alive?(pid).to_s} "
    end

    def self.test2
        puts "---=== TEST 2 ===---"
        
        pid = Process.spawn('start timeout 10')
        puts "Spawned process: #{pid.to_s}"
        kill_process(pid)
        puts "Process #{pid} killed"
        sleep 2
        puts "Process #{pid} alive?  #{alive?(pid).to_s} "
    end

    def self.wait_for_process(pid)
        begin
            Process.waitpid(pid, 0)
        rescue => e
        end
    end

    def self.kill_process(pid)
        begin
            Process.kill('KILL', pid)
        rescue => e
        end
    end

    def self.alive?(pid)
      !!Process.kill(0, pid) rescue false
    end

    test1
    test2

end

The first test has output:

—=== TEST 1 ===—
Spwaned process: 6276
Waited for 0.17901 s
Process 6276 alive? false

which means that waitpid waits 0.18s for the 10-second process to finish. When asked whether the process is alive we get the answer no even though the process is clearly visible in the cmd-window counting down.

The second test has output:

—=== TEST 2 ===—
Spwaned process: 7472
Process 7472 killed
Process 7472 alive? true

The process is killed, something which is seen by the lack of a cmd window, but ruby says the process is alive. I’ve added some sleeps between the different commands to let the system catch up.

So, does anyone know what’s going on here?

Welcome to an embedded Ruby interpreter loop.

The kinds of things you are doing, using Process or Thread were written for system Ruby running as it’s own process.

In embedded Ruby, the parent application process’ code controls when the interpreter loop runs. Also it is very hard to get only Ruby to wait. Basically everything in the application process will halt if you use Kernel#sleep().


If you are going to create a timer loop, it is best to use the Ruby API’s exposed C++ timer:
id = UI::start_timer( interval, repeat ) { block of code }
with a call to UI::stop_timer( id ) within the timer’s block of code as soon as some tested condition becomes true.

:warning: Do not open any modal windows, messageboxes or inputboxes within a timer code block. (It will cause an endless loop with enumerable windows opening. And you’ll have to kill the entire SketchUp process with the Task Manager.)

Thanks for the anwser.

In this case I want waitpid to block since the renderer needs to be put on hold until all meshes are written (we wait in a render callback). Anyway, I gather it’s not a good idea to use the Process library in an embedded setting. The waitpid isn’t strictly needed since it can be simulated but that requires a rocksolid alive?-method but no matter what I’ve tried, the results are always completely unreliable. So, it seems this approach for doing stuff in the background is a dead end…unless I’ve made an obvious mistake.

Make sure some_spurious_random_temp_folder_file is NOT there…
If it is delete it !
Start your process inside a UI timer.
At the END of the process write a file to some_spurious_random_temp_folder_file
In the UI timer poll every second or so to see if that some_spurious_random_temp_folder_file exists?
When you find it then you can exit the timer loop - knowing that the process has stopped.
To tidy up delete the some_spurious_random_temp_folder_file before doing what you need to do next…

Why the double NOT ?

… and are you aware of the Ruby core Process::Status class?

This will cause the SketchUp application window to go into zombie “Not responding” mode, since the application code is blocked and cannot peek at the window messaging stack.


Are you concerned with only Windows platform, or also Mac ?

The double negation is a type conversion from int to bool. The trouble is that in the first test (from the initial post) alive?(pid) becomes false due to the rescue clause; even though the process is running Ruby doesn’t recognize the pid as a valid process. There seems to be some inherent flakiness here. I’ve tested with status too, but that just another way of extracting the same info and it doesn’t seem solve the core problem.

Regarding blocking, in the current implementation all meshes are written at rendertime (and no input from the user is expected while the scene is being parsed) which makes Sketchup go Zombie. In the threaded version most of the writing will already have happened in the background, but in the end, we have to wait for all meshes to be written and at the same time force the renderer to wait, so blocking Sketchup (and the renderer) is intended behavior.

Finally, yes, both Mac and PC should be supported.

Inter process communication through a common file is indeed possible but I’d rather not go down that path. It’s way to dirty. Instead I’m leaning towards setting up a server running in the background and then communicate through a TCP-socket. This keeps the ruby side clean while all the asynchronous stuff is handled in a more suitable environment.