Updated 2017-02-02 09:53:16 by Ethouris

Fork is one of the primitives used for process creation in Unixy systems. It creates a copy of the process that calls it, and the only difference in internal state between the original and the copy is in the return value from the fork call (0 in the copy, but the pid of the copy in the parent).

Expect includes a fork. So does TclX.

Example:
    for {set i 0} {$i < 100} {incr i} {
        set pid [fork]
        switch $pid {
            -1 {
                puts "Fork attempt #$i failed."
            }
            0 {
                puts "I am child process #$i."
                exit
            }
            default {
                puts "The parent just spawned child process #$i."
            }
        }
    }

An other example script using fork is a unix style daemon.

In most cases though, one is not interested in spawning a copy of the process one already has, but rather wants a different process. When using POSIX APIs, this has to be done by first forking and then having the child use the exec system call to replace itself with a different program. The Tcl exec command does this fork&exec combination — in part because non-Unix OSs typicallly don't have "make a copy of parent process" as an intermediate step when spawning new processes.

stevel offers a version in Critcl
    package provide fork 1.0
    package require critcl

    critcl::cproc fork {} int {
        return fork();
    }

MG wonders if those package statements should be the other way around, so it only reports the package being available if critcl is too?

RS: True - I've even learnt somewhere that it's best to put the package provide at the very end of the code, so if any error occurs during development, the package isn't provided (and can be tried to reload, after fixing).

Fork will apparently not work unless threads are disabled when building TCL. See this thread on comp.lang.tcl: http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/e62ae9431acbbeee

[Also add links to more technical discussions held on tcl-core mailing list.]

Simplest example of incompatibility between fork and threads:
    package require Tclx
    if {[fork]} {
            puts "parent"
    } else {
            puts "child"
    }

gets you with ActiveTcl-8.5 (threaded):
    child
    parent
    Tcl_FinalizeNotifier: notifier pipe not initialized
    Aborted

Conclusion: don't mix fork and threads.

... and this is not a Tcl-only problem: threads and fork really do not match! At least as explained in [1] focussing on go-language (adds MS without really understanding much)

JJM 2015-03-17 It seems that this particular problem (using the Tclx fork script fragment above) may now be fixed on some operating systems (e.g. FreeBSD)?

JJM 2015-06-18 Once TIP #435 is approved and merged, I think this issue will be fixed permanently. In theory, it could be minimally "fixed" without a TIP; however, TIP #435 also fixes a very subtle, but fundamental design flaw with Tcl's mutex subsystem.

(2016-11-23) the 'fork' snippet works with Tcl 8.6.6.

[Ethouris] There's one important problem concerning processes that do fork() in order to continue in background, but the parent process might first printed something that the Tcl script running it would like to read and interpret.

Remember that when you do exec, then normally the process would return the standard output. However, the child process continues to run and as a forked process it usually derives the output channel from the parent. In result, the channel is only closed when the child is finished, no matter that the parent has exited.

In result, the parent turns into zombie and exec does't return until the child process is finished.

You can redirect the output of the started process to a file or to /dev/null, but not when you would like to read and interpret it - provided that you are still able to extract what was printed by the parent even though the output would be intermixed with what the kid printed.

Solution (at least "half solution"):
lassign [chan pipe] input output
chan configure $input -blocking no -buffering line ;# just for a case
exec $process 2>@stderr >@$output
# The process is running. The [exec] call returns.
set line [get $input] ;# will return empty in worst case; can do it in a loop with sleep, if waiting for parent to finally print it
exec /bin/true ;# force Tcl to clean up the zombie
exit 0

It would be nice if Tcl had kinda wait command, which will block until the process exits (with an option to return immediately with status value). Just to recognize the fact that the parent process exited and therefore it's not goint to print anything more.