[Zope-Checkins] CVS: Zope/lib/python/nt_svcutils - service.py:1.4.2.4

Jeremy Hylton jeremy at zope.com
Mon Jan 26 22:02:51 EST 2004


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

Modified Files:
      Tag: jeremy-windows-service-branch
	service.py 
Log Message:
Finish off first cut at redirecting I/O.

Add redirect() method that reads using ReadFile() and writes to the log file.  redirect() also catches I/O errors and logs ones we don't understand.

Replace CloseHandle() calls with Close() calls.  Seems more Pythonic.

Add a bunch of comments and revise doc string.


=== Zope/lib/python/nt_svcutils/service.py 1.4.2.3 => 1.4.2.4 ===
--- Zope/lib/python/nt_svcutils/service.py:1.4.2.3	Mon Jan 26 15:57:18 2004
+++ Zope/lib/python/nt_svcutils/service.py	Mon Jan 26 22:02:49 2004
@@ -38,8 +38,13 @@
 BACKOFF_INITIAL_INTERVAL = 5
 
 class Service(win32serviceutil.ServiceFramework):
-    """ A class representing a Windows NT service that can manage an
-    instance-home-based Zope/ZEO/ZRS processes """
+    """Base class for a Windows Server to manage an external process.
+
+    Subclasses can be used to managed an instance home-based Zope or
+    ZEO process.  The win32 Python service module registers a specific
+    file and class for a service.  To manage an instance, a subclass
+    should be created in the instance home.
+    """
 
     # The PythonService model requires that an actual on-disk class declaration
     # represent a single service.  Thus, the below definition of start_cmd,
@@ -55,7 +60,14 @@
         r'"C:\Program Files\Zope-2.7.0-a1\lib\python\Zope\Startup\run.py" '
         r'-C "C:\Zope-Instance\etc\zope.conf"'
         )
-    
+
+    # If capture_io is True, then log_file must be the path of a file
+    # that the controlled process's stdout and stderr will be written to.
+    # The I/O capture is immature.  It does not handle buffering in the
+    # controlled process or sensible interleaving of output between
+    # stdout and stderr.  It is intended primarily as a stopgap when
+    # the controlled process produces critical output that can't be
+    # written to a log file using mechanism inside that process.
     capture_io = False
     log_file = None
 
@@ -80,6 +92,7 @@
     def createProcess(self, cmd):
         self.start_time = time.time()
         if self.capture_io:
+            self.log = open(self.log_file, "ab")
             return self.createProcessCaptureIO(cmd)
         else:
             return win32process.CreateProcess(
@@ -137,8 +150,6 @@
         
         while 1:
             info, handles = self.createProcess(self.start_cmd)
-            # XXX integrate handles into the wait and make a loop
-            # that reads data and writes it into a logfile
             self.hZope = info[0] # process handle
             # XXX why the test before the log message?
             if self.backoff_interval > BACKOFF_INITIAL_INTERVAL:
@@ -157,12 +168,14 @@
         """
 
         keep_running = True
-        # ignore stdin
+        # Assume that the controlled program isn't expecting anything
+        # on stdin.
         handles[0].Close()
 
+        waitfor = [self.hWaitStop, self.hZope, handles[1], handles[2]]
         while 1:
-            rc = win32event.WaitForMultipleObjects(
-                (self.hWaitStop, self.hZope) + handles[1:], 0, win32event.INFINITE)
+            rc = win32event.WaitForMultipleObjects(waitfor, 0,
+                                                   win32event.INFINITE)
             if rc == win32event.WAIT_OBJECT_0:
                 # user sent a stop service request
                 self.SvcStop()
@@ -174,31 +187,37 @@
                 status = win32process.GetExitCodeProcess(self.hZope)
                 # exit status 0 means the user caused a clean shutdown,
                 # presumably via the web interface
-                print "exit status", status
                 keep_running = status != 0
                 break
             else:
-                i = rc - (win32event.WAIT_OBJECT_0 + 2)
-                if i == 0:
-                    try:
-                        ec, data = win32file.ReadFile(handles[1], 8192)
-                    except pywintypes.error, err:
-                        print err
-                        continue
-                    if data:
-                        self.info("stdout: %s" % data)
-                elif i == 1:
-                    try:
-                        ec, data = win32file.ReadFile(handles[2], 8192)
-                    except pywintypes.error, err:
-                        print err
-                        continue
-                    if data:
-                        self.info("stderr: %s" % data)
+                i = rc - win32event.WAIT_OBJECT_0
+                if not self.redirect(waitfor[i]):
+                    del waitfor[i]
         handles[1].Close()
         handles[2].Close()
         return keep_running
 
+    def redirect(self, handle):
+        # This call will block until 80 bytes of output are ready.
+        # If the controlled program is buffering its I/O, it's
+        # possible for this to take a long time.  Don't know if
+        # there is a better solution.
+        try:
+            ec, data = win32file.ReadFile(handle, 80)
+        except pywintypes.error, err:
+            # 109 means that the pipe was closed by the controlled
+            # process.  Other errors might have similarly inocuous
+            # explanations, but we haven't run into them yet.
+            if err[0] != 109:
+                self.warning("Error reading output from process: %s" % err)
+            return False
+        # In the absence of overlapped I/O, the Python win32api
+        # turns all error codes into exceptions.
+        assert ec == 0
+        self.log.write(data)
+        self.log.flush()
+        return True
+
     def checkRestart(self):
         # this was an abormal shutdown.
         if self.backoff_cumulative > BACKOFF_MAX:
@@ -241,10 +260,9 @@
         # circumstances of a service process.
         info = win32process.CreateProcess(None, cmd, None, None, True, 0,
                                           None, None, si)
-
-        win32file.CloseHandle(stdin[0])
-        win32file.CloseHandle(stdout[1])
-        win32file.CloseHandle(stderr[1])
+        stdin[0].Close()
+        stdout[1].Close()
+        stderr[1].Close()
 
         return info, (c_stdin, c_stdout, c_stderr)
 
@@ -260,7 +278,7 @@
         pid = win32api.GetCurrentProcess()
         dup = win32api.DuplicateHandle(pid, pipe, pid, 0, 0,
                                        win32con.DUPLICATE_SAME_ACCESS)
-        win32file.CloseHandle(pipe)
+        pipe.Close()
         return dup
 
 if __name__ == '__main__':




More information about the Zope-Checkins mailing list