[Zodb-checkins] CVS: Zope/lib/python/zdaemon - SignalPasser.py:1.2 Daemon.py:1.9

Chris McDonough chrism@zope.com
Tue, 11 Jun 2002 18:02:06 -0400


Update of /cvs-repository/Zope/lib/python/zdaemon
In directory cvs.zope.org:/tmp/cvs-serv24742

Modified Files:
	Daemon.py 
Added Files:
	SignalPasser.py 
Log Message:
These patches provide clean signal handling and logfile rotation to Zope.

All Zope process will respond to signals in the specified manner:

  SIGHUP -  close open database connections and sockets, then restart the
            process

  SIGTERM - close open database connections and sockets, then shut down.

  SIGINT  - same as SIGTERM

  SIGUSR2 - rotate all Zope log files (z2.log, event log, detailed log)

The common idiom for doing automated logfile rotation will become:

kill -USR2 `cat /path/to/var/z2.pid`

The common idiom for doing "prophylactic" restarts will become:

kill -HUP `cat /path/to/var/z2.pid`

When a process is interrupted via ctrl-C or via a TERM signal (INT, TERM),
all open database connections and sockets will be closed before
the process dies.  This will speed up restart time for sites that
use a FileStorage as its index will be written to the filesystem before
shutdown. 

Unspecified signals kill the process without doing cleanup.


=== Zope/lib/python/zdaemon/SignalPasser.py 1.1 => 1.2 ===
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+# 
+##############################################################################
+
+""" A module used for passing signals to children """
+
+class SignalPasser:
+    def __init__(self, pid):
+        self.pid = pid
+
+    def __call__(self, signum, frame):
+        import os, sys, signal
+        os.kill(self.pid, signum)
+        if signum in [signal.SIGTERM, signal.SIGINT]:
+            sys.exit(0)
+
+def pass_signals_to_process(pid, signals):
+    import signal
+    for s in signals:
+        signal.signal(s, SignalPasser(pid))


=== Zope/lib/python/zdaemon/Daemon.py 1.8 => 1.9 ===
 ##############################################################################
 
-import os, sys, time, signal
-
+import os, sys, time, posix, signal
 from ZDaemonLogging import pstamp
 import Heartbeat
 import zLOG
@@ -74,7 +73,9 @@
     pstamp('Aiieee! Process %s %s' % (p, msg),
            zLOG.ERROR)
     
-def run(argv, pidfile=''):
+def run(argv, pidfile='', signals=None):
+    if signals is None:
+        signals = []
     if os.environ.has_key('ZDAEMON_MANAGED'):
         # We're the child at this point.
         return
@@ -100,6 +101,17 @@
                 raise ForkError
 
             elif pid:
+                # the process we're daemoning for can signify that it
+                # wants us to notify it when we get specific signals
+                #
+                #
+                # we always register TERM and INT so we can reap our child.
+                signals = signals + [signal.SIGTERM, signal.SIGINT]
+                # TERM happens on normal kill
+                # INT happens on Ctrl-C (debug mode)
+                import SignalPasser
+                SignalPasser.pass_signals_to_process(pid, signals)
+
                 # Parent 
                 pstamp(('Hi, I just forked off a kid: %s' % pid), zLOG.INFO)
                 # here we want the pid of the parent
@@ -110,9 +122,21 @@
 
                 while 1: 
                     if not Heartbeat.BEAT_DELAY:
-                        p, s = os.waitpid(pid, 0)
+                        try:
+                            p,s = os.waitpid(pid, 0)
+                        except OSError:
+                            # catch EINTR, it's raised as a result of
+                            # interrupting waitpid with a signal
+                            # and we don't care about it.
+                            continue
                     else:
-                        p, s = os.waitpid(pid, os.WNOHANG)
+                        try:
+                            p,s = os.waitpid(pid, os.WNOHANG)
+                        except OSError:
+                            # catch EINTR, it's raised as a result of
+                            # interrupting waitpid with a signal
+                            # and we don't care about it.
+                            p, s = None, None
                         if not p:
                             time.sleep(Heartbeat.BEAT_DELAY)
                             Heartbeat.heartbeat()