I’ve recently made some progress on Shinrin, a centralized logging system in Perl 6. This has to run as service, which means that for most service managers it has to be able to run in the background.

If you just want to get to the solution and don’t care for the details, just head straight to the full script.

It’s not possible!

After a lot of trying and talking with the folks at #perl6 I was told that it is not possible to do this in pure Perl 6, explained by people with more knowledge than I have on the internals:

(jnthn suspects fork + multi-threaded VM = pain) Since fork only clones one thread - the one that called it. So suddenly you’ve got an instance of the VM missing most of its threads.

— jnthn

The most common failure mode is that some thread is holding e.g. a mutex (or a userspace lock) during the fork. The thread goes away but the lock is process level and remains, with nothing around to know to unlock it. So then things work until something else needs that lock and suddenly you deadlock.

— geekosaur

Not much later, jnthn pushed a commit to update the docs to clarify that a fork call through NativeCall will probably not give the result you were hoping for.

Or is it?

Luckily, the same people were able to think up of a work-around, which can be made in POSIX sh, so it’s usable on any decent OS. The workaround is to let a little shell script fork into the background, and let that run the Perl application.

A first example

This is fairly simple to create, as in this example to launch shinrind in the background:

#! /usr/bin/env sh

main()
{
    perl6 -Ilib bin/shinrind "$@"
}

main "$@" &

This works just fine if the working directory is correct. This means you need to be in the parent directory to lib and bin of the program to make it work.

Improving the forking script

While that short script works fine to show a proof of concept, in order to make it viable for real-world scenarios, it can use some improvements. After all, it would be annoying if you’d have to cd to a specific directory any time you want to start your application.

Ensure you are in the directory you should be in

So for starters, let’s make sure that you can run it from anywhere on your system. For this, you should set the working directory for the script, so you don’t have to do it manually. Because the script runs in its own subshell, the shell you’re working from remains unaffected.

A POSIX compliant way to get the directory the script is stored in is as follows:

DIR=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)

This will set $DIR to the path of the directory the shell script is stored in. You can simply cd to that and be assured you’re in the right directory.

In Perl 6, it is expected for executable files to live in the bin directory of your project repository. So you should actually be in the parent of the directory holding your script. Furthermore, you should check the cd command executed correctly, just to be safe.

cd -- "${DIR}/.." || exit

Disable STDOUT and STDERR

A started service should not be polluting your interactive shell, so you should disable (or otherwise redirect) STDOUT and STDERR. This is done in the shell using a small bit of code behind whatever you want to redirect:

> /dev/null 2>&1

This will set STDOUT to /dev/null, and set STDERR to the same stream as STDOUT, which in effect will make all output go to /dev/null. If you want to log everything to a single file, you can replace /dev/null with another file of your choice. If you don’t want logs to be overwritten on each start, use a >> instead of a single > at the start.

If you want to log errors and output in different files, you can use the following:

> /var/log/service.log 2> /var/log/service.err

This will put standard output in /var/log/service.log and errors in /var/log/service.err.

Fork just the Perl 6 program

In the initial example, I put the & behind the main call, at the bottom of the script. While this works just fine for most simple usage, if you want to do additional chores, like creating a pidfile after starting the Perl 6 program, you’re out of luck. If you were to only fork the Perl 6 application, you could handle some other cases in the shell script.

The final solution

For those eager to just get going with this, here is the complete example script to just fork your Perl program into the background:

#! /usr/bin/env sh

readonly DIR=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)

main()
{
    cd -- "${DIR}/.." || exit

    perl6 -Ilib bin/shinrind "$@" > /dev/null >2&1 &
}

main "$@"