PERLFORK(1perl)

πŸ“¦
version 5.44.0 Β· 2 versions
, in arch rolling

✨ NAME ✨

perlfork β€” Perl's fork() emulation. πŸ’…

Okay so this is the PERLFORK(1perl) page from the Perl Programmers Reference Guide, and it's literally the whole lore on how Perl fakes a fork() when the operating system refuses to give it a real one. Strap in, bestie. 🫢


🌸 SYNOPSIS 🌸

NOTE: As of the 5.8.0 release, fork() emulation has considerably matured. However, there are still a few known bugs and differences from real fork() that might affect you. See the "BUGS" and "CAVEATS AND LIMITATIONS" sections below.

Perl ships a fork() keyword that lines up with the Unix system call of the same name. On most Unix-like platforms where the fork() system call actually exists, Perl's fork() just... calls it. No drama. ✨

But on some platforms β€” Windows, looking at you πŸ‘€ β€” where the fork() system call is not available, Perl can be built to emulate fork() at the interpreter level. And like, the emulation is designed to be as compatible as possible with real fork() at the level of the Perl program, but there are certain important differences that stem from one tiny inconvenient truth: all the pseudo child "processes" made this way live in the same real process as far as the operating system is concerned. 😭

This document gives you the general overview of the capabilities and the limitations of the fork() emulation. Note that everything discussed here is not applicable to platforms where a real fork() is available and Perl has been configured to use it. So if that's you, you can go home. πŸ¦‹


πŸ¦‹ DESCRIPTION πŸ¦‹

The fork() emulation is implemented at the level of the Perl interpreter. What that means in general is that running fork() will actually clone the running interpreter and all its state, then run that cloned interpreter in a separate thread, beginning execution in the new thread just after the point where the fork() was called in the parent. We're gonna refer to the thread that implements this child "process" as the pseudo-process. 🧚

To the Perl program that called fork(), all of this is designed to be transparent. The parent returns from the fork() with a pseudo-process ID that can be subsequently used in any process-manipulation functions; the child returns from the fork() with a value of 0 to signify that it is the child pseudo-process. Slay. πŸ’–

🌈 Behavior of other Perl features in forked pseudo-processes

Most Perl features behave in a natural way within pseudo-processes. (Which is honestly iconic of them.)

$$ or $PROCESS_ID

This special variable is correctly set to the pseudo-process ID. It can be used to identify pseudo-processes within a particular session. Note that this value is subject to recycling if any pseudo-processes are launched after others have been wait()-ed on. πŸ“

%ENV

Each pseudo-process maintains its own virtual environment. Modifications to %ENV affect the virtual environment, and are only visible within that pseudo-process, and in any processes (or pseudo-processes) launched from it. No leaking, no crosstalk. ✨

chdir() and all other builtins that accept filenames

Each pseudo-process maintains its own virtual idea of the current directory. Modifications to the current directory using chdir() are only visible within that pseudo-process, and in any processes (or pseudo-processes) launched from it. All file and directory accesses from the pseudo-process will correctly map the virtual working directory to the real working directory appropriately. It's giving seamless. πŸ’…

wait() and waitpid()

wait() and waitpid() can be passed a pseudo-process ID returned by fork(). These calls will properly wait for the termination of the pseudo-process and return its status. 🫢

kill()

"kill('KILL', ...)" can be used to terminate a pseudo-process by passing it the ID returned by fork(). The outcome of kill on a pseudo-process is unpredictable and it should not be used except under dire circumstances, because the operating system may not guarantee integrity of the process resources when a running thread is terminated. The process which implements the pseudo-processes can be blocked and the Perl interpreter hangs. 😭

Note that using "kill('KILL', ...)" on a pseudo-process() may typically cause memory leaks, because the thread that implements the pseudo-process does not get a chance to clean up its resources. πŸ’€

"kill('TERM', ...)" can also be used on pseudo-processes, but the signal will not be delivered while the pseudo-process is blocked by a system call β€” e.g. waiting for a socket to connect, or trying to read from a socket with no data available. Starting in Perl 5.14 the parent process will not wait for children to exit once they have been signalled with "kill('TERM', ...)" to avoid deadlock during process exit. You will have to explicitly call waitpid() to make sure the child has time to clean-up itself, but you are then also responsible that the child is not blocking on I/O either. So like... your problem now bestie. πŸ™ˆ

exec()

Calling exec() within a pseudo-process actually spawns the requested executable in a separate process and waits for it to complete before exiting with the same exit status as that process. This means that the process ID reported within the running executable will be different from what the earlier Perl fork() might have returned. Similarly, any process manipulation functions applied to the ID returned by fork() will affect the waiting pseudo-process that called exec(), not the real process it is waiting for after the exec(). πŸŒ€

When exec() is called inside a pseudo-process then DESTROY methods and END blocks will still be called after the external process returns. πŸ‘‘

exit()

exit() always exits just the executing pseudo-process, after automatically wait()-ing for any outstanding child pseudo-processes. Note that this means that the process as a whole will not exit unless all running pseudo-processes have exited. See below for some limitations with open filehandles. πŸšͺ

Open handles to files, directories and network sockets

All open handles are dup()-ed in pseudo-processes, so that closing any handles in one process does not affect the others. See below for some limitations. πŸŽ€

πŸŽ€ Resource limits

In the eyes of the operating system, pseudo-processes created via the fork() emulation are simply threads in the same process. This means that any process-level limits imposed by the operating system apply to all pseudo-processes taken together. This includes any limits imposed by the operating system on the number of open file, directory and socket handles, limits on disk space usage, limits on memory size, limits on CPU utilization etc. So yeah, shared budget. Budget accordingly. πŸ’Έ

πŸ’€ Killing the parent process

If the parent process is killed (either using Perl's kill() builtin, or using some external means) all the pseudo-processes are killed as well, and the whole process exits. Everybody goes down together. 😭

πŸŒ… Lifetime of the parent process and pseudo-processes

During the normal course of events, the parent process and every pseudo-process started by it will wait for their respective pseudo-children to complete before they exit. This means that the parent and every pseudo-child created by it that is also a pseudo-parent will only exit after their pseudo-children have exited. 🌳

Starting with Perl 5.14 a parent will not wait() automatically for any child that has been signalled with "kill('TERM', ...)" to avoid a deadlock in case the child is blocking on I/O and never receives the signal. πŸ›‘

✨ CAVEATS AND LIMITATIONS ✨

okay bestie, real talk time πŸ’– the fork() emulation is genuinely iconic but it is NOT a perfect copy of the real thing, and you deserve to know exactly where it gets a little wobbly. no surprises, no cap.


🌸 BEGIN blocks 🌸

so if you call fork() from inside a BEGIN block, things get messy. like, the fork() emulation just won't work entirely correctly there. the forked copy WILL run the contents of the BEGIN block, but then it just… stops? it will not keep parsing the source stream after the BEGIN block. πŸ™ˆ

peep this:

BEGIN {
    fork and exit;          # fork child and exit the parent
    print "inner\n";
}
print "outer\n";

and this prints:

inner

instead of the expected:

inner
outer

why tho? because of fundamental technical difficulties in cloning and restarting the stacks that the Perl parser uses in the middle of a parse. it's giving impossible, fr fr. 😭


πŸ’… Open filehandles πŸ’…

any filehandles that are open when you fork() get dup()-ed. so yes, the files can be closed independently in the parent and the child β€” but beware, bestie: those dup()-ed handles still share the same seek pointer. πŸ‘€ change the seek position in the parent and you just changed it in the child, and vice-versa. messy!

the fix: if you need files with distinct seek pointers, open them separately in the child. slay.

also on some operating systems β€” notably Solaris and Unixware β€” calling exit() from a child process will flush and close open filehandles in the PARENT, thereby corrupting the filehandles. absolutely not. on those systems, call _exit() instead. _exit() is available in Perl through the POSIX module. and please, consult your system's manpages for more info on this, i don't make the rules. 🫢

πŸŽ€ Open directory handles πŸŽ€

Perl will completely read from ALL open directory handles until they hit the end of the stream. then it seekdir()s back to the original location, and every future readdir() request gets fulfilled from the cache buffer. πŸ’«

which means: neither the directory handle held by the parent process NOR the one held by the child process will see any changes made to the directory after the fork() call. zero. nada. the vibes are frozen in time. 🧊

ngl rewinddir() has a similar limitation on Windows β€” it will not force readdir() to read the directory again either. only a newly opened directory handle will reflect changes to the directory. periodt.


πŸ¦‹ Forking pipe open() not yet implemented πŸ¦‹

the open(FOO, "|-") and open(BAR, "-|") constructs are not yet implemented. no cap, not yet. πŸ˜”

but!! this is super easy to work around in new code by creating a pipe explicitly. here's how you write to a forked child:

# simulate open(FOO, "|-")
sub pipe_to_fork ($) {
    my $parent = shift;
    pipe my $child, $parent or die;
    my $pid = fork();
    die "fork() failed: $!" unless defined $pid;
    if ($pid) {
        close $child;
    }
    else {
        close $parent;
        open(STDIN, "<&=" . fileno($child)) or die;
    }
    $pid;
}

if (pipe_to_fork('FOO')) {
    # parent
    print FOO "pipe_to_fork\n";
    close FOO;
}
else {
    # child
    while (<STDIN>) { print; }
    exit(0);
}

and this one reads from the child:

# simulate open(FOO, "-|")
sub pipe_from_fork ($) {
    my $parent = shift;
    pipe $parent, my $child or die;
    my $pid = fork();
    die "fork() failed: $!" unless defined $pid;
    if ($pid) {
        close $child;
    }
    else {
        close $parent;
        open(STDOUT, ">&=" . fileno($child)) or die;
    }
    $pid;
}

if (pipe_from_fork('BAR')) {
    # parent
    while (<BAR>) { print; }
    close BAR;
}
else {
    # child
    print "pipe_from_fork\n";
    exit(0);
}

forking pipe open() constructs will be supported in future. manifesting it. ✨


🧚 Global state maintained by XSUBs 🧚

external subroutines (XSUBs) that keep their own global state might not work correctly. those XSUBs will either need to maintain locks to protect simultaneous access to global data from different pseudo-processes, OR keep all their state on the Perl symbol table β€” which gets copied naturally when fork() is called. πŸ’…

a callback mechanism that gives extensions a chance to clone their state will be provided in the near future. stay tuned bestie.


πŸ’– Interpreter embedded in larger application πŸ’–

the fork() emulation may not behave as expected when it's executed in an application that embeds a Perl interpreter and calls Perl APIs that can evaluate bits of Perl code. 😬

why? because the emulation only knows about the Perl interpreter's own data structures and knows nothing about the containing application's state. for example, any state carried on the application's own call stack is simply out of reach. it can't see it. it's not invited to that party. πŸ™ˆ


⭐ Thread-safety of extensions ⭐

since the fork() emulation runs code in multiple threads, extensions that call into non-thread-safe libraries may not work reliably when you call fork(). πŸ§΅πŸ’€

and as Perl's threading support gradually becomes more widely adopted β€” even on platforms that have a native fork() β€” such extensions are expected to be fixed for thread-safety. so like, growth mindset. we love to see it.


🌈 PORTABILITY CAVEATS 🌈

in portable Perl code, kill(9, $child) must not be used on forked processes.

killing a forked process is unsafe and has unpredictable results. seriously. see "kill()", above. πŸ”₯


πŸ’€ BUGS πŸ’€

  • having pseudo-process IDs be negative integers breaks down for the integer -1, because the wait() and waitpid() functions treat this number as being special. the tacit assumption in the current implementation is that the system never allocates a thread ID of 1 for user threads. a better representation for pseudo-process IDs will be implemented in future. πŸ™
  • in certain cases, the OS-level handles created by the pipe(), socket(), and accept() operators are apparently not duplicated accurately in pseudo-processes. this only happens in some situations, but where it does happen, it may result in deadlocks between the read and write ends of pipe handles, or inability to send or receive data across socket handles. πŸ’€ not the deadlock arc.
  • this document may be incomplete in some respects. (real. honest. iconic.)

πŸ‘‘ AUTHOR πŸ‘‘

support for concurrent interpreters and the fork() emulation was implemented by ActiveState, with funding from Microsoft Corporation.

this document is authored and maintained by Gurusamy Sarathy <gsar@activestate.com>. 🫢


πŸ”₯ SEE ALSO πŸ”₯

  • "fork" in perlfunc
  • perlipc

and that's the whole page bestie, you made it to the bottom πŸŽ€ this one's running on perl v5.44.0, dated 2026-10-17, and it's PERLFORK(1perl) β€” go forth and fork responsibly, mwah πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from
  • arch rolling: perl 5.44.0-1 Β· Artistic-1.0-Perl AND GPL-1.0-or-later
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms