summaryrefslogtreecommitdiff
path: root/src/_posts/2017-09-28-perl6-creating-a-background-service.adoc
blob: 4cc759d0d52fe984c3c207ada37d59cf0ac55f78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
---
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 "$@"
----