See the discussion in https://github.com/rabbitmq/rabbitmq-server/pull/573.
Some testing reveals that there is a problem with guessing MAINPID.
Currently the first clause executes under systemd. But when it's done that way systemd is actually unhappy with:
os:cmd("systemd-notify --ready MAINPID=" ++ os:getpid()),
because it's the top-level shell process PID that should be passed to systemd.
Maybe additional argument for scripts/rabbitmq-server should be introduced - -systemd, in addition to already existing -detached?
Hi!
So I'm testing your pull request on Debian Jessie (8.3, systemd), freshly upgraded from Wheezy (sysvinit). As this is the first time I play with systemd, I'm going to post comments which will probably seem obvious to you, @binarin. I just want to do it because it helps me to understand the problem and correctly test the PR. Feel free to correct me what I say doesn't make any sense :-)
```
root@rabbit-jpedron-debian1# service rabbitmq-server status
β rabbitmq-server.service - LSB: Manages RabbitMQ server
Loaded: loaded (/etc/init.d/rabbitmq-server)
Active: active (running) since Thu 2016-03-10 11:04:43 GMT; 58s ago
CGroup: /system.slice/rabbitmq-server.service
ββ2693 /usr/lib/erlang/erts-7.2/bin/epmd -daemon
ββ2723 /bin/sh -e /usr/lib/rabbitmq/bin/rabbitmq-server
ββ2997 /usr/lib/erlang/erts-7.2/bin/beam.smp ...
ββ3115 inet_gethost 4
ββ3116 inet_gethost 4
root@rabbit-jpedron-debian1# rabbitmqctl status
Status of node 'rabbit@rabbit-jpedron-debian1' ...
[{pid,2997},
{running_applications,[{rabbit,"RabbitMQ","3.6.1"},
...
```
A unit file was automatically generated by systemd-sysv-generator and written to /run/systemd/generator.late/rabbitmq-server.service:
```
# Automatically generated by systemd-sysv-generator
[Unit]
SourcePath=/etc/init.d/rabbitmq-server
Description=LSB: Manages RabbitMQ server
Before=runlevel2.target runlevel3.target runlevel4.target runlevel5.target shutdown.target
After=remote-fs.target network-online.target
Wants=network-online.target
Conflicts=shutdown.target
[Service]
Type=forking
Restart=no
TimeoutSec=5min
IgnoreSIGPIPE=no
KillMode=process
GuessMainPID=no
RemainAfterExit=yes
SysVStartPriority=16
ExecStart=/etc/init.d/rabbitmq-server start
ExecStop=/etc/init.d/rabbitmq-server stop
ExecReload=/etc/init.d/rabbitmq-server reload
```
The service type defaults to forking, which means systemd expects the ExecStart command to fork a process and exit. The main PID could be derived from the PIDFile but this option is not set. If I understand correctly, systemd doesn't care about the notification we send with systemd-notify because the service type is not notify. See https://www.freedesktop.org/software/systemd/man/systemd.service.html#Options.
I guess that's the reason systemd is unable to track the service processes correctly and therefore, issueing rabbitmctl stop (ie. bypassing systemd) doesn't tell systemd the service is stopped:
```
root@rabbit-jpedron-debian1# rabbitmqctl stop
Stopping and halting node 'rabbit@rabbit-jpedron-debian1' ...
root@rabbit-jpedron-debian1# service rabbitmq-server status
β rabbitmq-server.service - LSB: Manages RabbitMQ server
Loaded: loaded (/etc/init.d/rabbitmq-server)
Active: active (running) since Thu 2016-03-10 11:04:43 GMT; 16min ago
CGroup: /system.slice/rabbitmq-server.service
ββ2693 /usr/lib/erlang/erts-7.2/bin/epmd -daemon
```
In the example above, we see that systemd has no idea which process is the main one. After rabbitmqctl stop, epmd is still running and the service status is active (running). If I kill epmd, the service status becomes active (exited). Any service rabbitmq-server start does nothing. Only service rabbitmq-server stop sets the status correctly, allowing one to restart the service. But as discussed internally, this is acceptable in this situation: the user is not supposed to mess with the service with a mix of official (service(8)) and unofficial (rabbitmqctl) tools.
Now, I'm going to install the unit file and test further. In the end, the probable conclusion is that our RPM and Debian packages should install this unit file out-of-the-box, but I need to see how to handle both init systems with a single package if possible.
Just a note while I'm on it: Debian official package of erlang-base for Jessie includes a unit file for epmd. However, Erlang Solutions' one doesn't. We may have a service dependency issue here.
epmd dependency isn't a problem - it's implicit because jessie uses socket activation for epmd.
service rabbitmq-server stop.I copied rabbitmq-server.service.example to /lib/systemd/system/rabbitmq-server.service. Now, the service is listed when I query all unit files (including disabled ones):
root@rabbit-jpedron-debian1# systemctl list-unit-files | grep rabbitmq-server
rabbitmq-server.service disabled
I double-checked that this entry doesn't come from the one generated by systemd-sysv-generator: before I copied the unit file, there was no rabbitmq-server entry in the list.
service rabbitmq-server start directly doesn't improve the situation because the generated unit file is still being used (ie. the Loaded: line in the status still points to the SysV init script).Doing systemctl daemon-reload is enough to make the real unit file override the generated one (which is removed by the way):
root@rabbit-jpedron-debian1# systemctl daemon-reload
root@rabbit-jpedron-debian1# service rabbitmq-server status
β rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/lib/systemd/system/rabbitmq-server.service; disabled)
Active: inactive (dead)
The service points to the copied unit file.
Let's start it:
root@rabbit-jpedron-debian1# service rabbitmq-server start
root@rabbit-jpedron-debian1# service rabbitmq-server status
β rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/lib/systemd/system/rabbitmq-server.service; disabled)
Active: active (running) since Thu 2016-03-10 12:07:32 GMT; 2s ago
Main PID: 13532 (beam.smp)
CGroup: /system.slice/rabbitmq-server.service
ββ13532 /usr/lib/erlang/erts-7.2/bin/beam.smp ...
ββ13611 /usr/lib/erlang/erts-7.2/bin/epmd -daemon
ββ13719 inet_gethost 4
ββ13720 inet_gethost 4
It works and systemd has the correct main PID.
Stopping the service works correctly too. However, starting it again doesn't: service rabbitmq-server start hangs and the service status is activating, even though RabbitMQ finished to start:
root@rabbit-jpedron-debian1# service rabbitmq-server status
β rabbitmq-server.service - RabbitMQ broker
Loaded: loaded (/lib/systemd/system/rabbitmq-server.service; disabled)
Active: activating (start) since Thu 2016-03-10 12:10:34 GMT; 1min 6s ago
Main PID: 13943 (beam.smp)
CGroup: /system.slice/rabbitmq-server.service
ββ13943 /usr/lib/erlang/erts-7.2/bin/beam.smp ...
ββ14025 /usr/lib/erlang/erts-7.2/bin/epmd -daemon
ββ14133 inet_gethost 4
ββ14134 inet_gethost 4
=INFO REPORT==== 10-Mar-2016::12:10:36 ===
Server startup complete; 0 plugins started.
Mar 10 12:10:35 rabbit-jpedron-debian1 rabbitmq-server[13943]: ###### ## /var/log/rabbitmq/[email protected]
Mar 10 12:10:35 rabbit-jpedron-debian1 rabbitmq-server[13943]: ##########
Mar 10 12:10:36 rabbit-jpedron-debian1 systemd[1]: Cannot find unit for notify message of PID 14136.
Mar 10 12:10:36 rabbit-jpedron-debian1 rabbitmq-server[13943]: Starting broker... completed with 0 plugins.
Mar 10 12:12:04 rabbit-jpedron-debian1 systemd[1]: rabbitmq-server.service start operation timed out. Terminating.
Mar 10 12:12:04 rabbit-jpedron-debian1 systemd[1]: Failed to start RabbitMQ broker.
If I understand systemd logs correctly, the READY notification came from PID 14136, which is incorrect. This number looks like a process worked by Erlang though. systemd eventually killed the service.
I will continue to debug this and learn more about systemd.
epmd dependency isn't a problem - it's implicit because jessie uses socket activation for epmd.
Ok, perfect :-) Thanks!
Mar 10 12:10:36 rabbit-jpedron-debian1 systemd[1]: Cannot find unit for notify message of PID 14136.
So looks --pid should be used to prevent this, and it'll set 'MAINPID=%s' automatically. Probably my problem with --pid was that I was testing from a user unit.
It's some sort of permission problems with systemd-notify - looks like sd-notify daemon ignores pid passed. But if I invoke the same command from root shell, startup ends up successfully. I hope there is some permission-related option that'll allow to fix it.
systemd-notify is broken for non-root users - https://github.com/systemd/systemd/issues/2739
So I think NIF dependency should be restored in the master branch for a time being.
Good job finding the reported issue. I tried to debug this with strace(1), but the problem never occurred with. This led me in the direction of a race with the exit of the shell, but I couldn't anything else.
I'm not a fan of the sd_notify NIF because we can't ship it easily in our binary packages, and if we don't ship it, it will be painful to end-users.
Here are two comments:
$RABBITMQ_RUNNING_UNDER_SYSTEMD, I believe we can rely on the existing $NOTIFY_SOCKET variable which is required by sd_pid_notify(3).sd_pid_notify(3) would do: write to the Unix socket pointed to by $NOTIFY_SOCKET. However, in addition, it waits for the service state to not be activating anymore. Here is the script:``` perl
#!/usr/bin/env perl
# vim:ft=perl
use strict;
use warnings;
use IO::Socket::UNIX;
my $SERVICE = 'rabbitmq-server';
my $MAINPID = $ARGV[0];
my $SOCK_PATH = $ENV{'NOTIFY_SOCKET'};
my $sock = IO::Socket::UNIX->new(
Type => SOCK_DGRAM(),
Peer => $SOCK_PATH,
);
die "Could not create socket: $!\n" unless $sock;
print $sock "READY=1\nMAINPID=$MAINPID\n";
close($sock);
sub is_activating($) {
my ($service) = @_;
my $cmd = 'systemctl show --property=ActiveState '.$service;
my $state = `$cmd`;
chomp($state);
$state eq 'ActiveState=activating';
}
exit 0 unless is_activating($SERVICE);
do {
sleep(1);
} while (is_activating($SERVICE));
exit 0;
```
I was thinking about Perl myself, but wasn't sure it would be acceptable solution :smile:
Should I amend this PR with your suggestions from above or are you going to do it yourself?
Another alternative to perl is using socat and erlang:open_port.
I'm using following implementation of systemd-notify and running start/stop in a loop works completely fine:
echo -en "MAINPID=$PPID\nREADY=1\n" | socat unix-sendto:$NOTIFY_SOCKET STDIO
@binarin is socat available out of the box on popular systemd-based distros? I assume that's the case in practice with Perl?
socat is readily available on centos7 and on every supported version of debian and ubuntu. At least on centos7 perl is not a mandatory package, and the difference in package size between perl and socat is 30-fold. I think we should stick with socat.
@binarin I agree.
I tried socat(1) too yesterday and it worked fine in my tests, but I guess we could still hit the problem. Here is the paragraph from the systemd issue (systemd/systemd#2737) I'm referring:
This is because the the manager decides which units it applies to based on the cgroup string. And it decides the cgroup string by looking at
/proc/${sending_pid}/cgroup, which won't exist anymore if the sending process gets cleaned up before systemd gets to handling the message.
About the availability, socat is obviously available as a package on all distributions, but it's not installed by default. Our RPM/Debian package can depend on it of course.
About the footprint on disk: Perl is already installed on the Debian Jessie VM I used to test.
By the way, we may have another issue waiting for us after this systemd bug: systemd kills the service if it didn't report readyness in time. However, RabbitMQ may take several dozen minutes to load queue data or sync mirrored queues, all this before reporting "I'm ready!" to systemd.
I don't know if we can disable this timeout from systemd for rabbitmq-server (probably not). I saw that the notify protocol allows regular status report in the form of STATUS=<arbitrary text> on the same Unix socket. I have no idea yet if this resets systemd timers, but it's worth investigating.
Here is my glorified systemd-notify :smile: using socat - it avoids problem of early process exit
#!/bin/bash -eux
TEMP=$(getopt -o "" --long ready,pid: -n "$0" -- "$@")
eval set -- "$TEMP"
while true; do
case "$1" in
--ready) shift;;
--pid) PID=$2; shift 2 ;;
--) shift; break;;
*) echo "Internal error on arg $1"; exit 1 ;
esac
done
(
echo -en "MAINPID=$PID\nREADY=1\n"
while [[ $(systemctl show --property=ActiveState rabbitmq-server) == 'ActiveState=activating' ]]; do
sleep 1
done
) | socat unix-sendto:$NOTIFY_SOCKET STDIO
I'm going to test whether the same approach will work with erlang:open_port.
socat and more than 10M of perl.TimeoutStartSec=3600Nice! Your solution is better. I didn't think of making the state check in the pipe itself to maintain socat up.
A few comments:
[[ ... ]]?-e and -n are not portable; I know this script is Linux-specific, but it's just to keep all scripts consistent.set flags on the shebang line, I prefer to have an explicit set -eux line in the script itself: if I run the script using /bin/sh <script> instead of ./script, the flags are still set.About the startup timeout, I saw the TimeoutStartSec and TimeoutStopSec service options. On this Debian, they both defaults to 90 seconds. It's possible to set them to infinity to effectively disable the timeout. It may be the sanest choice for RabbitMQ.
Next funny finding - if epmd was not running initially, it'll send acknowledgement to systemd itself. It will result in marking rabbitmq as running prematurely.

Nice patch, thank you! I didn't test it yet, but is there any benefit from keeping support for the NIF?
Compatibility. The NIF was never a requirement but lets keep it around for another feature release or so.
On 11 mar 2016, at 18:30, Jean-SΓ©bastien PΓ©dron [email protected] wrote:
Nice patch, thank you! I didn't test it yet, but is there any benefit from keeping support for the NIF?
β
Reply to this email directly or view it on GitHub.
I'm still debugging one more issue with this patch, so please don't merge even if it passes your tests )
Initially idea was to add systemd-notify as a fallback for NIF in stable branch, and to get rid of NIF completely only in master. So as not to cause any pains for users of the current stable version.
Now the patch is ready. Unless I've missed something this time )
Added port_close calld and more explanations about usage of socat.
Testing uncovered some more issues, fixed them also.
I merged the pull request. Thank you!
Most helpful comment
Another alternative to perl is using
socatanderlang:open_port.I'm using following implementation of
systemd-notifyand running start/stop in a loop works completely fine: