On Wed, May 21, 2014 at 09:50:02AM -0700, Ethan Furman wrote:
> I see two ways around this, both enhancements to daemon.py:
> 
>   - have __init__ accept a parent_callback parameter which will be called
>     after the child is forked but before exiting

The name "parent_callback" is misleading, because the callback must be
executed in the child process. If it were to be executed in the parent
and were to initialize a thread, then the exiting of the parent were to
terminate the newly spawned thread. Not the desired outcome.

>   - have a stub method (such as .finished()) that is always called after
>     the child is forked but before the parent exits, which can then be
>     overridden in subclasses

Would open be calling the .finished() method?

> I would prefer the first option as that would make subclassing unnecessary.

Both variants have the advantage of mostly retaining backwards
compatibility at the cost of being hard to use. Being able to continue
initialization after having called .open() would be far easier to use.
Consider a different change:

The .open() method gains a keyword parameter "exitparent" defaulting to
True. If False is passed, a pipe is created prior to the background
fork. Rather than exiting, the parent reads from that pipe. A new
.exitparent() method is provided and must be called after .open() when
exitparent=False. It closes the write end of the pipe and thereby causes
the parent to exit. Optionally an error message can be given to
.exitparent() in which case the error message is passed through the
pipe, causes the parent to print the error and exit with a failure
status code.

A POC is attached.

(Please keep CCing me.)

Helmut
--- daemon.py.orig	2014-05-21 22:43:26.000000000 +0200
+++ daemon.py	2014-05-21 22:54:13.000000000 +0200
@@ -245,6 +245,7 @@
             signal_map = make_default_signal_map()
         self.signal_map = signal_map
 
+        self._status_pipe = None
         self._is_open = False
 
     @property
@@ -252,7 +253,7 @@
         """ ``True`` if the instance is currently open. """
         return self._is_open
 
-    def open(self):
+    def open(self, exitparent=True):
         """ Become a daemon process.
             :Return: ``None``
 
@@ -328,7 +329,12 @@
         change_process_owner(self.uid, self.gid)
 
         if self.detach_process:
-            detach_process_context()
+            if not exitparent:
+                rpipe, self._status_pipe = os.pipe()
+                detach_process_context(waitpipe=rpipe)
+                os.close(rpipe)
+            else:
+                detach_process_context()
 
         signal_handler_map = self._make_signal_handler_map()
         set_signal_handlers(signal_handler_map)
@@ -347,6 +353,18 @@
 
         register_atexit_function(self.close)
 
+    def exitparent(self, errormessage=None):
+        """Cause the waiting parent process to exit
+
+        If an errormessage is given, make the parent print this message and
+        exit with a failure. Otherwise make it exit cleanly."""
+
+        assert self._is_open
+        assert self._status_pipe is not None
+        os.write(self._status_pipe, errormessage)
+        os.close(self._status_pipe)
+        self._status_pipe = None
+
     def __enter__(self):
         """ Context manager entry point. """
         self.open()
@@ -548,7 +566,7 @@
     resource.setrlimit(core_resource, core_limit)
 
 
-def detach_process_context():
+def detach_process_context(waitpipe=None):
     """ Detach the process context from parent and session.
 
         Detach from the parent process and session group, allowing the
@@ -560,7 +578,7 @@
     
         """
 
-    def fork_then_exit_parent(error_message):
+    def fork_then_exit_parent(error_message, waitpipe=None):
         """ Fork a child process, then exit the parent process.
 
             If the fork fails, raise a ``DaemonProcessDetachError``
@@ -570,6 +588,11 @@
         try:
             pid = os.fork()
             if pid > 0:
+                if waitpipe:
+                    message = os.read(waitpipe, 1024)
+                    if message:
+                        sys.stderr.write(message)
+                        os._exit(1)
                 os._exit(0)
         except OSError, exc:
             exc_errno = exc.errno
@@ -578,7 +601,7 @@
                 "%(error_message)s: [%(exc_errno)d] %(exc_strerror)s" % vars())
             raise error
 
-    fork_then_exit_parent(error_message="Failed first fork")
+    fork_then_exit_parent(error_message="Failed first fork", waitpipe=waitpipe)
     os.setsid()
     fork_then_exit_parent(error_message="Failed second fork")
 
_______________________________________________
python-daemon-devel mailing list
[email protected]
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/python-daemon-devel

Reply via email to