Graceful restart also for imapd.conf
Julien Coloos
julien.coloos at gmail.com
Thu Oct 6 10:14:03 EDT 2011
As proposed by Olivier, there is the 'sighup' branch available on
git://github.com/worldline-messaging/cyrus-imapd.git. It is based on
current official cyrus 'master' branch.
Details about the commits:
20a7e4b32c01e7c8dd7aeb8eae41d35e4c630369 - Recycle running services upon
SIGHUP on master.
Currently when SIGHUP is sent to the master process, only new and legacy
services are being specifically processed:
- new ones are being started
- legacy ones are stopped by transmitting SIGHUP to the concerned
children (to stop them once the client - if connected - leaves)
What we propose is to extend the use of SIGHUP to also recycle remaining
services so that any change to imapd.conf is taken into account as fast
as possible.
5b55aab917d6686c3ba74dc8abad1a3eb906cd12 - Send message to master when
service is exiting.
This commit is useful to make recycling smoother. Commit log:
Master process is notified about children exiting by a SIGCHLD
signal, which are taken into account but processed separately (not right
when receiving the signal).
The master process uses a 'select' call to wait for messages from its
children, and manage other events.
Signal handlers in the master process are set with the SA_RESTART
flag. But the POSIX specifications let the implementation decide whether
'select' restarts or returns EINTR in this case. Thus it may take some
time ('select' timeout) before the master process actually reap children
(and fork new ones when necessary). It can also happen due to race
conditions.
Provided that letting children send back a message when they are
exiting is not too much time consuming (for both child and master),
removing the static MESSAGE_MASTER_ON_EXIT configuration variable is
actually useful for smoother janitoring, and faster services recycling
when sending SIGHUP to master process.
I actually wonder why 'MESSAGE_MASTER_ON_EXIT' was used and set to 0 up
to now. Compared to what services and master are already doing, I
believe that it should not consume a significant amount of resources to
send this 'exit' message. But maybe someone can prove me wrong here ?
4578a0268a1e1c2c1f0b33d68e547fc864dcca4b - Remain root on master process.
Currently master process is usually started as root to later become cyrus.
With SIGHUP it reloads its configuration and can start newly added
services. Sometimes this is however problematic because non privileged
users (as cyrus) are not allowed to bind ports below 1024: this results
in the added service being unabled to start.
To be fair I don't know if there are - for all platforms targeted by
cyrus - ways to circumvent this security limitation. At least I tried
setcap'ing the exe on Ubuntu, but it did not work as expected ...
Now, as far as security is concerned it is best to not be root. But
after thinking about it, two things make us think that maybe the master
process could stay root:
- it does not discuss with external clients as IMAP/POP/etc services
do; so no remote exploits, right ?
- many daemon services do run as root by default - ok, ok, that does
not mean it's the right thing to do :p
Or maybe it could be root by default, and the configuration would allow
to use a given user/group, as some other daemon services do.
What do you think ?
As usual, comments are welcomed :)
Now, a side note about something I observed while messing with signals
in cyrus: from time to time I did get deadlocked processes upon SIGTERM;
for example, when sending it to the master process right after starting
it (yeah I know, who in their sane mind would want to do that ? - except
me).
Stacktraces of deadlocked processes:
#0 0xf7777430 in __kernel_vsyscall ()
#1 0xf7388753 in __lll_lock_wait_private () at
../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/lowlevellock.S:95
#2 0xf731bbac in _L_lock_10489 () from /lib/i386-linux-gnu/libc.so.6
#3 0xf731a553 in __libc_realloc (oldmem=0x97929e0, bytes=62) at
malloc.c:3813
#4 0xf7309cea in _IO_mem_finish (fp=0x9792868, dummy=0) at memstream.c:132
#5 0xf7305949 in _IO_new_fclose (fp=0x9792868) at iofclose.c:66
#6 0xf73764bc in __vsyslog_chk (pri=<value optimized out>, flag=1,
fmt=0x805482e "exiting on SIGTERM/SIGINT", ap=0xffb7f43c " o+\367\023\t")
at ../misc/syslog.c:228
#7 0xf7376896 in __syslog_chk (pri=6, flag=1, fmt=0x805482e "exiting on
SIGTERM/SIGINT") at ../misc/syslog.c:131
#8 0x08049b7f in syslog (sig=15) at /usr/include/bits/syslog.h:32
#9 sigterm_handler (sig=15) at master.c:1067
#10 <signal handler called>
#11 0xf7777430 in __kernel_vsyscall ()
#12 0xf73430d7 in __libc_fork () at
../nptl/sysdeps/unix/sysv/linux/i386/../fork.c:130
#13 0x0804afb5 in spawn_service (si=2) at master.c:633
#14 0x0804cce4 in main (argc=10, argv=0xffb80db4) at master.c:2069
#0 0xf7777430 in __kernel_vsyscall ()
#1 0xf7388753 in __lll_lock_wait_private () at
../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/lowlevellock.S:95
... (same backtrace)
#13 0x0804afb5 in spawn_service (si=3) at master.c:633
#14 0x0804cce4 in main (argc=10, argv=0xffb80db4) at master.c:2069
strace of deadlocked a master daemon:
... (startup, children forking, etc)
12:07:33.299382 --- SIGTERM (Terminated) @ 0 (0) ---
12:07:33.299466 --- SIGCHLD (Child exited) @ 0 (0) ---
12:07:33.299503 sigreturn() = ? (mask now [TERM]) <0.000183>
12:07:33.299799 --- SIGCHLD (Child exited) @ 0 (0) ---
12:07:33.299818 sigreturn() = ? (mask now [TERM]) <0.000005>
12:07:33.299861 rt_sigaction(SIGTERM, {SIG_IGN, [], 0}, NULL, 8) = 0
<0.003123>
12:07:33.303060 kill(0, SIGTERM) = 0 <0.000013>
12:07:33.303124 time(NULL) = 1317816453 <0.000007>
12:07:33.303163 getpid() = 21549 <0.000005>
12:07:33.303197 futex(0xf73ad3c0, FUTEX_WAIT_PRIVATE, 2, NULL
<unfinished ...>
13:48:01.724288 +++ killed by SIGKILL +++
strace of a deadlocked child:
12:07:33.298775 --- SIGTERM (Terminated) @ 0 (0) ---
12:07:33.298871 rt_sigaction(SIGTERM, {SIG_IGN, [], 0}, NULL, 8) = 0
<0.000018>
12:07:33.298986 kill(0, SIGTERM) = 0 <0.000013>
12:07:33.299067 time(NULL) = 1317816453 <0.000044>
12:07:33.299340 getpid() = 21557 <0.000016>
12:07:33.299423 futex(0xf73ad3c0, FUTEX_WAIT_PRIVATE, 2,
NULL13:49:12.294353 +++ killed by SIGKILL +++
So, according to that, here is what may have happened:
During a short lapse of time the child has just been forked and have not
yet 'exec'ed the service binary. According to the POSIX specs, the
signal handlers set by the parent are not resetted here - but they do
after 'exec'.
So now the master process receives SIGTERM: it enters the signal
callback function 'sigterm_handler', and thus propagates the signal to
its group before exiting. Now if one of the forks has not yet reached
'exec', it will also callback 'sigterm_handler' upon SIGTERM.
But right before exiting this function, 'syslog' is called (to say it
received the signal and is exiting). And according to the gdb
backtraces, it is where is gets locked.
Actually POSIX has a list of functions that shall be safe to call (and
said async-signal-safe) while handling a signal. And syslog is not part
of it.
That would mean that something syslog do - at least on the
implementation I have here - is not safe to do as far as signal handling
is concerned. But that would be a pity if one cannot syslog anything in
that case :(
Note that I observed the deadlock upon startup, but I guess that on
heavy load (platforms where there are a lot of service instances) it may
happen even if SIGTERM is sent later.
Regards
Julien
More information about the Cyrus-devel
mailing list