How to fire and forget a subprocess?

15,318

Solution 1

The fork function separates your process in two.

Both processes then receive the result of the function. The child receives a value of zero/nil (and hence knows that it's the child) and the parent receives the PID of the child.

Hence:

exec("something") if fork.nil?

will make the child process start "something", and the parent process will carry on with where it was.

Note that exec() replaces the current process with "something", so the child process will never execute any subsequent Ruby code.

The call to Process.detach() looks like it might be incorrect. I would have expected it to have the child's PID in it, but if I read your code right it's actually detaching the parent process.

Solution 2

Alnitak is right. Here's a more explicit way to write it, without $$

pid = Process.fork
if pid.nil? then
  # In child
  exec "whatever --take-very-long"
else
  # In parent
  Process.detach(pid)
end

The purpose of detach is just to say, "I don't care when the child terminates" to avoid zombie processes.

Solution 3

The other answers are good if you're sure you want to detach the child process. However, if you either don't mind, or would prefer to keep the child process attached (e.g. you are launching sub-servers/services for a web app), then you can take advantage of the following shorthand

fork do
    exec('whatever --option-flag')
end

Providing a block tells fork to execute that block (and only that block) in the child process, while continuing on in the parent.

Solution 4

Detaching $$ wasn't right. From p. 348 of the Pickaxe (2nd Ed):

$$ Fixnum The process number of the program being executed. [r/o]

This section, "Variables and Constants" in the "Ruby Language" chapter, is very handy for decoding various ruby short $ constants - however the online edition (the first

So what you were actually doing was detaching the program from itself, not from its child. Like others have said, the proper way to detach from the child is to use the child's pid returned from fork().

Share:
15,318

Related videos on Youtube

kch
Author by

kch

Updated on June 30, 2020

Comments

  • kch
    kch almost 4 years

    I have a long running process and I need it to launch another process (that will run for a good while too). I need to only start it, and then completely forget about it.

    I managed to do what I needed by scooping some code from the Programming Ruby book, but I'd like to find the best/right way, and understand what is going on. Here's what I got initially:

    exec("whatever --take-very-long") if fork.nil?
    Process.detach($$)
    


    So, is this the way, or how else should I do it?

    After checking the answers below I ended up with this code, which seems to make more sense:

    (pid = fork) ? Process.detach(pid) : exec("foo")
    


    I'd appreciate some explanation on how fork works. [got that already]

    Was detaching $$ right? I don't know why this works, and I'd really love to have a better grasp of the situation.

  • kch
    kch about 15 years
    Ok, I think I got the .nil? check. In the child it doesn't really return 0, it returns nil. So exec will run if you're in the child. So then, how would I capture the other return of fork, the one that returns the id of the child process? This way I could use it directly instead of relying on $$.
  • Alnitak
    Alnitak about 15 years
    just call fork and capture the result. then if you got nil, do the exec, else you're in the parent.
  • kch
    kch about 15 years
    Hi, I was playing with my newly acquired knowledge, and I basically came to a more succinct version of the same: (pid = fork) ? Process.detach(pid) : exec("foo")
  • kch
    kch about 15 years
    Something borked your answer. Anyway I have the third edition of the pickaxe. So, $$ isn't right, fact. But why did that work anyway?
  • Matthew Flaschen
    Matthew Flaschen about 15 years
    It doesn't work right if you call detach on $$. But again, detach is only to avoid zombie processes. It isn't necessary to make a job run in the background.
  • KomodoDave
    KomodoDave over 11 years
    This is such a brilliantly clear and concise definition of 'fork'. Great job, thank you.
  • oldergod
    oldergod over 10 years
    @MatthewFlaschen In case this does not contain exec, don't I need a exit in the child code for the zombie process not to last?
  • Matthew Flaschen
    Matthew Flaschen over 10 years
    @oldergod, normally a program will terminate naturally at some point. However, if there's a long-running loop in the child, you can certainly put an exit call at an appropriate point. Either way, once you call detach your parent code no longer needs to think about (the Ruby interpreter solves the zombie process problem for you).