--- date: 2017-09-28 15:11:43 tags: Tutorial Perl6 Programming Raku description: > 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. --- = Perl 6 - Creating a background service :toc: preamble I've recently made some progress on https://github.com/scriptkitties/perl6-Shinrin[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. [NOTE] ==== If you just want to get to the solution and don't care for the details, just head straight to link:#the-final-solution[the full script]. ==== == It's not possible! After a lot of trying and talking with the folks at irc://chat.freenode.net:6697/#perl6[#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: [quote, jnthn] ____ (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. ____ [quote, geekosaur] ____ 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. ____ Not much later, `jnthn` https://github.com/perl6/doc/commit/8f9443c3ac[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: [source,sh] ---- #! /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: [source,sh] ---- 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. [source,sh] ---- 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: [source,sh] ---- > /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: [source,sh] ---- > /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: [source,sh] ---- #! /usr/bin/env sh readonly DIR=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd) main() { cd -- "${DIR}/.." || exit perl6 -Ilib bin/shinrind "$@" > /dev/null >2&1 & } main "$@" ----