Spawning an Independent Thread or Process in Ruby

How do you spawn a child process in Ruby?

You can use the fork kernel method. Here is an example:

#!/usr/bin/env ruby
puts "This is the master process."

child_pid = fork do
puts "This is the child process"
exit
end

puts "The PID of the child process is #{child_pid}"

The fork method returns the PID of the process it forks and executes any code in the block passed. Like regular Ruby blocks it keeps the bindings of the parent process.

It is a good idea to make your forked process exit.

Getting process status and exit code from process id in ruby

I believe you could use the win32-api gem

You can then use the GetExitCodeProcess function from the win32 API.

Rake task for running a server in an independent thread then killing the thread when the task is complete?

You want Process.spawn, not a thread. It's a new process, not a thread of execution within an existing process. You get the PID back, so just send Process.kill(:QUIT, pid) or whatever method you want to use to kill the spawned processed.

pid = Process.spawn(
"ejekyll", "--server",
out: "/dev/null",
err: "/dev/null"
)

# you may need to add a short sleep() here

# other stuff

Process.kill(:QUIT, pid) && Process.wait

If ejekyll has a command line option to run in the foreground, it would be better to use that, otherwise if it self-daemonizes you need to know where it stores its PID file, in order to identify and kill the daemon.

Ruby spawn process, capturing STDOUT/STDERR, while behaving as if it were spawned regularly

[edited: see the bottom for the amended update]

Figured it out :)

To really understand the problem I read up a lot on how a PTY works. I don't think I really understood it properly until I drew it out. Basically PTY could be used for a Terminal emulator, and that was the simplest way to think of the data flow for it:

keyboard -> OS -> terminal -> master pty -> termios -> slave pty -> shell
|
v
monitor <- OS <- terminal <- master pty <- termios

(note: this might not be 100% correct, I'm definitely no expert on the subject, just posting it incase it helps anybody else understand it)

So the important bit in the diagram that I hadn't really realised was that when you type, the only reason you see your input on screen is because it's passed back (left-wards) to the master.

So first thing's first - this ruby script should first set the tty to raw (IO.console.raw!), it can restore it after execution is finished (IO.console.cooked!). This'll make sure the keyboard inputs aren't printed by this parent Ruby script.

Second thing is the slave itself should not be raw, so the slave.raw! call is removed. To explain this, I originally added this because it removes extra return carriages from the output: running echo hello results in "hello\r\n". What I missed was that this return carriage is a key instruction to the terminal emulator (whoops).

Third thing, the process should only be talking to the slave. Passing STDIN felt convenient, but it upsets the flow shown in the diagram.

This brings up a new problem on how to pass user input through, so I tried this. So we basically pass STDIN to the master:

  input_thread = Thread.new do
STDIN.each_char do |char|
master.putc(char) rescue nil
end
end

that kind of worked, but it has its own issues in terms of some interactive processes weren't receiving a key some of the time. Time will tell, but using IO.copy_stream instead appears to solve that issue (and reads much nicer of course).

input_thread = Thread.new { IO.copy_stream(STDIN, master) }

update 21st Aug:

So the above example mostly worked, but for some reason keys like CTRL+c still wouldn't behave correctly. I even looked up other people's approach to see what I could be doing wrong, and effectively it seemed the same approach - as IO.copy_stream(STDIN, master) was successfully sending 3 to the master. None of the following seemed to help at all:

master.putc 3
master.putc "\x03"
master.putc "\003"

Before I went and delved into trying to achieve this in a lower level language I tried out 1 more thing - the block syntax. Apparently the block syntax magically fixes this problem.

To prevent this answer getting a bit too verbose, the following appears to work:

require 'pty'
require 'io/console'

def run
output = ""

IO.console.raw!

input_thread = nil

PTY.spawn('bash') do |read, write, pid|
Signal.trap(:WINCH) { write.winsize = STDOUT.winsize }
input_thread = Thread.new { IO.copy_stream(STDIN, write) }

read.each_char do |char|
STDOUT.print char
output.concat(char)
end

Process.wait(pid)
end

input_thread.kill if input_thread

IO.console.cooked!
end

Bundler.send(:with_env, Bundler.clean_env) do
run
end

Fork a process and send data to it inside Rails

Can the additional data go into the database, and the process checks there for the "new data"?

EventMachine and Ruby Threads - what's really going on here?

First of all, there's only one EventMachine instance per Ruby process, so no matter what you'll always reference the same EM instance, independent of the thread your currently in.

You run the reactor in a new, separate thread so that it doesn't block the main thread (whose purpose it is to serve the web request). EM.run would otherwise take over control, entering its run loop, not leaving the EM.run block anymore. EM.reactor_running? returns true, well, if an EM loop is running somewhere. As there is only one per Ruby process it's easy enough for the method to figure out if EM is running or not.

The setup you have here is the simplest way to use EM inside a normal Ruby process without interfering with everything else that's running. I'm assuming you're pushing messages to an AMQP broker from your web app. Whenever you send a message, it will go into EM's run loop in the separate thread, that part is pretty transparent to you, and not affect the main loop, which can continue handling the Rails web request. Be careful though to always push things onto the EM loop using EM.next_tick. Trying to handle sockets opened by EM in different threads could result in bad things happening, which I have seen in production, incidentally by using and building a library called happening ;)

Stopping the EM loop before starting a new one takes care of an EM loop that may be left over from a parent process, which could result in problems with file descriptors opened, using EM, in the parent process. In custom code this can be circumvented by using EM.fork_reactor, but as the parent process is out of your control, it's safest to check if a reactor exists and stop it before starting a new instance.



Related Topics



Leave a reply



Submit