Author: gstein
Date: Wed Mar  7 00:50:52 2012
New Revision: 1297815

URL: http://svn.apache.org/viewvc?rev=1297815&view=rev
Log:
We need to turn svnwcsub into a daemon, rather than only running in
the foreground. Add this module to provide that functionality.

Copied from gstein.googlecode.com/svn/trunk/python/daemonize.py, r15.

* tools/server-side/svnpubsub/daemonize.py: new file

Added:
    subversion/trunk/tools/server-side/svnpubsub/daemonize.py

Added: subversion/trunk/tools/server-side/svnpubsub/daemonize.py
URL: 
http://svn.apache.org/viewvc/subversion/trunk/tools/server-side/svnpubsub/daemonize.py?rev=1297815&view=auto
==============================================================================
--- subversion/trunk/tools/server-side/svnpubsub/daemonize.py (added)
+++ subversion/trunk/tools/server-side/svnpubsub/daemonize.py Wed Mar  7 
00:50:52 2012
@@ -0,0 +1,262 @@
+# ---------------------------------------------------------------------------
+#
+# Copyright (c) 2005, Greg Stein
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+# ---------------------------------------------------------------------------
+#
+# This software lives at:
+#    http://gstein.googlecode.com/svn/trunk/python/daemonize.py
+#
+
+import os
+import signal
+import sys
+import time
+
+
+# possible return values from Daemon.daemonize()
+DAEMON_RUNNING = 'The daemon is running'
+DAEMON_NOT_RUNNING = 'The daemon is not running'
+DAEMON_COMPLETE = 'The daemon has completed its operations'
+DAEMON_STARTED = 'The daemon has been started'
+
+
+class Daemon(object):
+
+  def __init__(self, logfile, pidfile):
+    self.logfile = logfile
+    self.pidfile = pidfile
+
+  def foreground(self):
+    "Run in the foreground."
+    ### we should probably create a pidfile. other systems may try to detect
+    ### the pidfile to see if this "daemon" is running.
+    self.setup()
+    self.run()
+    ### remove the pidfile
+
+  def daemonize_exit(self):
+    try:
+      result = self.daemonize()
+    except (ChildFailed, DaemonFailed), e:
+      # duplicate the exit code
+      sys.exit(e.code)
+    except (ChildTerminatedAbnormally, ChildForkFailed,
+            DaemonTerminatedAbnormally, DaemonForkFailed), e:
+      sys.stderr.write('ERROR: %s\n' % e)
+      sys.exit(1)
+    except ChildResumedIncorrectly:
+      sys.stderr.write('ERROR: continued after receiving unknown signal.\n')
+      sys.exit(1)
+
+    if result == DAEMON_STARTED or result == DAEMON_COMPLETE:
+      sys.exit(0)
+    elif result == DAEMON_NOT_RUNNING:
+      sys.stderr.write('ERROR: the daemon exited with a success code '
+                       'without signalling its startup.\n')
+      sys.exit(1)
+
+    # in original process. daemon is up and running. we're done.
+
+  def daemonize(self):
+    # fork off a child that can detach itself from this process.
+    try:
+      pid = os.fork()
+    except OSError, e:
+      raise ChildForkFailed(e.errno, e.strerror)
+
+    if pid > 0:
+      # we're in the parent. let's wait for the child to finish setting
+      # things up -- on our exit, we want to ensure the child is accepting
+      # connections.
+      cpid, status = os.waitpid(pid, 0)
+      assert pid == cpid
+      if os.WIFEXITED(status):
+        code = os.WEXITSTATUS(status)
+        if code:
+          raise ChildFailed(code)
+        return DAEMON_RUNNING
+
+      # the child did not exit cleanly.
+      raise ChildTerminatedAbnormally(status)
+
+    # we're in the child.
+
+    # decouple from the parent process
+    os.chdir('/')
+    os.umask(0)
+    os.setsid()
+
+    # remember this pid so the second child can signal it.
+    thispid = os.getpid()
+
+    # register a signal handler so the SIGUSR1 doesn't stop the process.
+    # this object will also record whether if got signalled.
+    daemon_accepting = SignalCatcher(signal.SIGUSR1)
+
+    # if the daemon process exits before sending SIGUSR1, then we need to see
+    # the problem. trap SIGCHLD with a SignalCatcher.
+    daemon_exit = SignalCatcher(signal.SIGCHLD)
+
+    # perform the second fork
+    try:
+      pid = os.fork()
+    except OSError, e:
+      raise DaemonForkFailed(e.errno, e.strerror)
+
+    if pid > 0:
+      # in the parent.
+
+      # we want to wait for the daemon to signal that it has created and
+      # bound the socket, and is (thus) ready for connections. if the
+      # daemon improperly exits before serving, we'll see SIGCHLD and the
+      # .pause will return.
+      ### we should add a timeout to this. allow an optional parameter to
+      ### specify the timeout, in case it takes a long time to start up.
+      signal.pause()
+
+      if daemon_exit.signalled:
+        # reap the daemon process, getting its exit code. bubble it up.
+        cpid, status = os.waitpid(pid, 0)
+        assert pid == cpid
+        if os.WIFEXITED(status):
+          code = os.WEXITSTATUS(status)
+          if code:
+            raise DaemonFailed(code)
+          return DAEMON_NOT_RUNNING
+
+        # the daemon did not exit cleanly.
+        raise DaemonTerminatedAbnormally(status)
+
+      if daemon_accepting.signalled:
+        # the daemon is up and running, so save the pid and return success.
+        if self.pidfile:
+          open(self.pidfile, 'w').write('%d\n' % pid)
+        return DAEMON_STARTED
+
+      # some other signal popped us out of the pause. the daemon might not
+      # be running.
+      raise ChildResumedIncorrectly()
+
+    # we're a deamon now. get rid of the final remnants of the parent.
+    sys.stdout.flush()
+    sys.stderr.flush()
+    si = open('/dev/null', 'r')
+    so = open(self.logfile, 'a+')
+    se = open(self.logfile, 'a+', 0)  # unbuffered
+    os.dup2(si.fileno(), sys.stdin.fileno())
+    os.dup2(so.fileno(), sys.stdout.fileno())
+    os.dup2(se.fileno(), sys.stderr.fileno())
+    # note: we could not inline the open() calls. after the fileno() completed,
+    # the file would be closed, making the fileno invalid. gotta hold them
+    # open until now:
+    si.close()
+    so.close()
+    se.close()
+
+    # TEST: don't release the parent immediately. the whole parent stack
+    #       should pause along with this sleep.
+    #time.sleep(10)
+
+    # everything is set up. call the initialization function.
+    self.setup()
+
+    # sleep for one second before signalling. we want to make sure the
+    # parent has called signal.pause()
+    ### we should think of a better wait around the race condition.
+    time.sleep(1)
+
+    # okay. the daemon is ready. signal the parent to tell it we're set.
+    os.kill(thispid, signal.SIGUSR1)
+
+    # start the daemon now.
+    self.run()
+
+    # The daemon is shutting down, so toss the pidfile.
+    try:
+      os.remove(self.pidfile)
+    except OSError:
+      pass
+
+    return DAEMON_COMPLETE
+
+  def setup(self):
+    raise NotImplementedError
+
+  def run(self):
+    raise NotImplementedError
+
+
+class SignalCatcher(object):
+  def __init__(self, signum):
+    self.signalled = False
+    signal.signal(signum, self.sig_handler)
+
+  def sig_handler(self, signum, frame):
+    self.signalled = True
+
+
+class ChildTerminatedAbnormally(Exception):
+  "The child process terminated abnormally."
+  def __init__(self, status):
+    Exception.__init__(self, status)
+    self.status = status
+  def __str__(self):
+    return 'child terminated abnormally (0x%04x)' % self.status
+
+class ChildFailed(Exception):
+  "The child process exited with a failure code."
+  def __init__(self, code):
+    Exception.__init__(self, code)
+    self.code = code
+  def __str__(self):
+    return 'child failed with exit code %d' % self.code
+
+class ChildForkFailed(Exception):
+  "The child process could not be forked."
+  def __init__(self, errno, strerror):
+    Exception.__init__(self, errno, strerror)
+    self.errno = errno
+    self.strerror = strerror
+  def __str__(self):
+    return 'child fork failed with error %d (%s)' % self.args
+
+class ChildResumedIncorrectly(Exception):
+  "The child resumed its operation incorrectly."
+
+class DaemonTerminatedAbnormally(Exception):
+  "The daemon process terminated abnormally."
+  def __init__(self, status):
+    Exception.__init__(self, status)
+    self.status = status
+  def __str__(self):
+    return 'daemon terminated abnormally (0x%04x)' % self.status
+
+class DaemonFailed(Exception):
+  "The daemon process exited with a failure code."
+  def __init__(self, code):
+    Exception.__init__(self, code)
+    self.code = code
+  def __str__(self):
+    return 'daemon failed with exit code %d' % self.code
+
+class DaemonForkFailed(Exception):
+  "The daemon process could not be forked."
+  def __init__(self, errno, strerror):
+    Exception.__init__(self, errno, strerror)
+    self.errno = errno
+    self.strerror = strerror
+  def __str__(self):
+    return 'daemon fork failed with error %d (%s)' % self.args


Reply via email to