[Zope-Checkins] SVN: Zope/branches/2.12/src/nt_svcutils/service.py Another big reshuffle of this module, hopefully to make things more readable:

Chris Withers chris at simplistix.co.uk
Mon Oct 5 18:37:18 EDT 2009


Log message for revision 104814:
  Another big reshuffle of this module, hopefully to make things more readable:
  - re-installed some comments that Jeremy obfuscated
  - renamed the crazily named "redirectCaptureThread" to a name that matches what it does
  - highlight the likely cause of #443005: 
  
  The magic named event is no longer found even though Zope is still running.
  I suspect this is because runzope is now a setuptools .exe.

Changed:
  U   Zope/branches/2.12/src/nt_svcutils/service.py

-=-
Modified: Zope/branches/2.12/src/nt_svcutils/service.py
===================================================================
--- Zope/branches/2.12/src/nt_svcutils/service.py	2009-10-05 22:26:47 UTC (rev 104813)
+++ Zope/branches/2.12/src/nt_svcutils/service.py	2009-10-05 22:37:17 UTC (rev 104814)
@@ -63,103 +63,24 @@
         self._svc_command_ = self.getReg('Command',keyname='PythonClass')
         
         win32serviceutil.ServiceFramework.__init__(self, args)
+
+        # Don't use the service name as the event source name:        
+        servicemanager.SetEventSourceName(self.evtlog_name)
         
-        servicemanager.SetEventSourceName(self.evtlog_name)
         # Create an event which we will use to wait on.
         # The "service stop" request will set this event.
         # We create it inheritable so we can pass it to the child process, so
         # it too can act on the stop event.
         sa = win32security.SECURITY_ATTRIBUTES()
         sa.bInheritHandle = True
-
         self.hWaitStop = win32event.CreateEvent(sa, 0, 0, None)
-        self.redirect_thread = None
 
-    @classmethod
-    def openKey(cls,serviceName,keyname=None):
-        keypath = "System\\CurrentControlSet\\Services\\"+serviceName
-        if keyname:
-            keypath += ('\\'+keyname)
-        return win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE,keypath,0,win32con.KEY_ALL_ACCESS)
-    
-    @classmethod
-    def setReg(cls,name,value,serviceName=None,keyname='PythonClass'):
-        if not serviceName:
-            serviceName = cls._svc_name_
-        key = cls.openKey(serviceName,keyname)
-        try:
-            win32api.RegSetValueEx(key, name, 0, win32con.REG_SZ, value)
-        finally:
-            win32api.RegCloseKey(key)
-
-    def getReg(self,name,keyname=None):
-        key = self.openKey(self._svc_name_,keyname)
-        return win32api.RegQueryValueEx(key,name)[0]
-    
-    def SvcStop(self):
-        # Before we do anything, tell the SCM we are starting the stop process.
-        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-        self.onStop()
-        # Set the stop event - the main loop takes care of termination.
-        win32event.SetEvent(self.hWaitStop)
-
-    # SvcStop only gets triggered when the user explictly stops (or restarts)
-    # the service.  To shut the service down cleanly when Windows is shutting
-    # down, we also need to hook SvcShutdown.
-    SvcShutdown = SvcStop
-
-    def onStop(self):
-        # A hook for subclasses to override
-        pass
-
-    def logmsg(self, event):
-        # log a service event using servicemanager.LogMsg
-        try:
-            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
-                                  event,
-                                  (self._svc_name_,
-                                   " (%s)" % self._svc_display_name_))
-        except win32api.error, details:
-            # Failed to write a log entry - most likely problem is
-            # that the event log is full.  We don't want this to kill us
-            try:
-                print "FAILED to write INFO event", event, ":", details
-            except IOError:
-                pass
-
-    def _dolog(self, func, msg):
-        try:
-            fullmsg = "%s (%s): %s" % \
-                      (self._svc_name_, self._svc_display_name_, msg)
-            func(fullmsg)
-        except win32api.error, details:
-            # Failed to write a log entry - most likely problem is
-            # that the event log is full.  We don't want this to kill us
-            try:
-                print "FAILED to write event log entry:", details
-                print msg
-            except IOError:
-                # And if running as a service, its likely our sys.stdout
-                # is invalid
-                pass
-
-    def info(self, s):
-        self._dolog(servicemanager.LogInfoMsg, s)
-
-    def warning(self, s):
-        self._dolog(servicemanager.LogWarningMsg, s)
-
-    def error(self, s):
-        self._dolog(servicemanager.LogErrorMsg, s)
-
+    ### ServiceFramework methods
+        
     def SvcDoRun(self):
         # indicate to Zope that the process is daemon managed (restartable)
         os.environ['ZMANAGED'] = '1'
 
-        # XXX the restart behavior is different here than it is for
-        # zdaemon.zdrun.  we should probably do the same thing in both
-        # places.
-
         # daemon behavior:  we want to to restart the process if it
         # dies, but if it dies too many times, we need to give up.
 
@@ -177,75 +98,35 @@
         # the cumulative backoff seconds counter
         self.backoff_cumulative = 0
 
+        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
         self.logmsg(servicemanager.PYS_SERVICE_STARTED)
-
         while 1:
-            info = self.createProcess(self._svc_command_)
-            # info is (hProcess, hThread, pid, tid)
-            self.hZope = info[0] # process handle
-            if self.backoff_interval > BACKOFF_INITIAL_INTERVAL:
-                # make a note that we've created a process after backing
-                # off?
-                self.info("created process")
-            if not (self.run() and self.checkRestart()):
+            self.hZope, hThread, pid, tid = self.createProcess(self._svc_command_)
+            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
+            keep_running = self.run()
+            if not keep_running:
+                # The daemon process has asked to stop
                 break
-
+            # should we attempt a restart?
+            if not self.checkRestart():
+                # No, we should not
+                break
         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-        # Stop the child process by opening the special named event.
-        # We give it 90 seconds to shutdown normally.  If that doesn't
-        # stop things, we give it 30 seconds to do a "fast" shutdown.
-        # After that, we just knock it on the head.
-        winver = sys.getwindowsversion()
-        for sig, timeout in ((signal.SIGINT, 30), (signal.SIGTERM, 10)):
-            event_name = "Zope-%d-%d" % (info[2], sig)
-            # sys.getwindowsversion() -> major, minor, build, platform_id, ver_string
-            # for platform_id, 2==VER_PLATFORM_WIN32_NT
-            if winver[0] >= 5 and winver[3] == 2:
-                event_name = "Global\\" + event_name
-            try:
-                he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0,
-                                          event_name)
-            except win32event.error, details:
-                if details[0] == winerror.ERROR_FILE_NOT_FOUND:
-                    # process already dead!
-                    break
-                # no other expected error - report it.
-                self.warning("Failed to open child shutdown event %s"
-                             % (event_name,))
-                continue
+        self.stop(pid)
+        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
+        self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
 
-            win32event.SetEvent(he)
-            # It should be shutting down now - wait for termination, reporting
-            # progress as we go.
-            for i in range(timeout):
-                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-                rc = win32event.WaitForSingleObject(self.hZope, 3000)
-                if rc == win32event.WAIT_OBJECT_0:
-                    break
-            # Process terminated - no need to try harder.
-            if rc == win32event.WAIT_OBJECT_0:
-                break
+    def SvcStop(self):
+        # Set the stop event - the main loop takes care of termination.
+        win32event.SetEvent(self.hWaitStop)
 
-        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-        # If necessary, kill it
-        if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE:
-            win32api.TerminateProcess(self.hZope, 3)
-        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+    # SvcStop only gets triggered when the user explictly stops (or restarts)
+    # the service.  To shut the service down cleanly when Windows is shutting
+    # down, we also need to hook SvcShutdown.
+    SvcShutdown = SvcStop
 
-        # Wait for the redirect thread - it should have died as the remote
-        # process terminated.
-        # As we are shutting down, we do the join with a little more care,
-        # reporting progress as we wait (even though we never will <wink>)
-        if self.redirect_thread is not None:
-            for i in range(5):
-                self.redirect_thread.join(1)
-                self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
-                if not self.redirect_thread.isAlive():
-                    break
-            else:
-                self.warning("Redirect thread did not stop!")
-        self.logmsg(servicemanager.PYS_SERVICE_STOPPED)
-
+    ### Helper methods
+    
     def run(self):
         """Monitor the daemon process.
 
@@ -259,24 +140,19 @@
                                                0, # bWaitAll
                                                win32event.INFINITE)
         if rc == win32event.WAIT_OBJECT_0:
-            # user sent a stop service request
+            # a stop service request was recieved
             keep_running = False
         elif rc == win32event.WAIT_OBJECT_0 + 1:
-            # user did not send a service stop request, but
             # the process died; this may be an error condition
             status = win32process.GetExitCodeProcess(self.hZope)
-            # exit status 0 means the user caused a clean shutdown,
-            # presumably via the web interface.  Any other status
-            # is an error that gets written to the event log.
-            if status != 0:
-                # This should never block - the child process terminating
-                # has closed the redirection pipe, so our thread dies.
-                self.redirect_thread.join(5)
-                if self.redirect_thread.isAlive():
-                    self.warning("Redirect thread did not stop!")
-                self.warning("process terminated with exit code %d.\n%s" \
-                             % (status, "".join(self.captured_blocks)))
+            # exit status 0 means a clean shutdown,
+            # presumably via the web interface.
             keep_running = status != 0
+            if keep_running:
+                # Any other status is an error so we write it and
+                # any output to the event log
+                self.warning("Process terminated with exit code %d.\n%s" \
+                             % (status, self.getCapturedOutput()))
         else:
             # No other valid return codes.
             assert 0, rc
@@ -285,10 +161,13 @@
     def checkRestart(self):
         # this was an abormal shutdown.
         if self.backoff_cumulative > BACKOFF_MAX:
-            self.error("restarting too frequently; quit")
+            self.error("Attempted restarting more than %s times, aborting."
+                       % BACKOFF_MAX)
             return False
-        self.warning("sleep %s to avoid rapid restarts"
-                     % self.backoff_interval)
+        self.warning(
+            "Process died unexpectedly, will attempt restart after %s seconds."
+            % self.backoff_interval
+            )
         if time.time() - self.start_time > BACKOFF_CLEAR_TIME:
             self.backoff_interval = BACKOFF_INITIAL_INTERVAL
             self.backoff_cumulative = 0
@@ -339,18 +218,19 @@
         hInputWrite.Close()
 
         # start a thread collecting output
-        t = threading.Thread(target=self.redirectCaptureThread,
-                             args = (hOutRead,))
+        t = threading.Thread(
+            target=self.outputCaptureThread,
+            args = (hOutRead,)
+            )
         t.start()
-        self.redirect_thread = t
+        self.output_thread = t
         return info
 
-    def redirectCaptureThread(self, handle):
+    def outputCaptureThread(self, handle):
         # Only one of these running at a time, and handling both stdout and
         # stderr on a single handle.  The read data is never referenced until
         # the thread dies - so no need for locks around self.captured_blocks.
         self.captured_blocks = []
-        #self.info("Redirect thread starting")
         while 1:
             try:
                 ec, data = win32file.ReadFile(handle, CHILDCAPTURE_BLOCK_SIZE)
@@ -363,8 +243,13 @@
             self.captured_blocks.append(data)
             del self.captured_blocks[CHILDCAPTURE_MAX_BLOCKS:]
         handle.Close()
-        #self.info("Redirect capture thread terminating")
 
+    def getCapturedOutput(self):
+        self.output_thread.join(5)
+        if self.output_thread.isAlive():
+            self.warning("Output capturing thread failed to terminate!")
+        return "".join(self.captured_blocks)
+
     def newPipe(self):
         sa = win32security.SECURITY_ATTRIBUTES()
         sa.bInheritHandle = True
@@ -380,8 +265,131 @@
         pipe.Close()
         return dup
 
-# Real __main__ bootstrap code is in the instance's service module.
-if __name__ == '__main__':
-    print "This is a framework module - you don't run it directly."
-    print "See your installation directory for the service script."
-    sys.exit(1)
+    def stop(self,pid):
+        # call the method that any subclasses out there may implement:
+        self.onStop()
+        # Stop the child process by sending signals to the special named event.
+        
+          
+        
+        winver = sys.getwindowsversion()
+        
+        for sig, timeout in (
+            (signal.SIGINT, 30), # We give it 90 seconds to shutdown normally.
+            (signal.SIGTERM, 10) # If that doesn't stop things, we give it 30
+                                 # seconds to do a "fast" shutdown.
+            ):
+            event_name = "Zope-%d-%d" % (pid, sig)
+            # sys.getwindowsversion() -> major, minor, build, platform_id, ver_string
+            # for platform_id, 2==VER_PLATFORM_WIN32_NT
+            if winver[0] >= 5 and winver[3] == 2:
+                event_name = "Global\\" + event_name
+            try:
+                # XXX This no longer works, see bug #443005 on Launchpad
+                # This is likely because cmd in now a setuptools-generated .exe
+                # Any ideas?
+                he = win32event.OpenEvent(win32event.EVENT_MODIFY_STATE, 0,
+                                          event_name)
+            except win32event.error, details:
+                # no other expected error - report it.
+                self.warning("Failed to open child shutdown event %s"
+                             % (event_name,))
+                continue
+
+            win32event.SetEvent(he)
+            # It should be shutting down now - wait for termination, reporting
+            # progress as we go.
+            for i in range(timeout):
+                # wait for one second
+                rc = win32event.WaitForSingleObject(self.hZope, 1000)
+                if rc == win32event.WAIT_OBJECT_0:
+                    break
+            # Process terminated - no need to try harder.
+            if rc == win32event.WAIT_OBJECT_0:
+                break
+
+        if win32process.GetExitCodeProcess(self.hZope)==win32con.STILL_ACTIVE:
+            # None of the signals worked, so kill the process
+            self.warning(
+                "Terminating process as it could not be gracefully ended"
+                )
+            win32api.TerminateProcess(self.hZope, 3)
+
+        output = self.getCapturedOutput()
+        if output:
+            self.info("Process terminated with output:\n"+output)
+                
+    ### Overridable subclass methods
+
+    def onStop(self):
+        # A hook for subclasses to override.
+        # Called just before the service is stopped.
+        pass
+
+    ### Registry interaction methods
+    
+    @classmethod
+    def openKey(cls,serviceName,keyname=None):
+        keypath = "System\\CurrentControlSet\\Services\\"+serviceName
+        if keyname:
+            keypath += ('\\'+keyname)
+        return win32api.RegOpenKey(
+            win32con.HKEY_LOCAL_MACHINE,keypath,0,win32con.KEY_ALL_ACCESS
+            )
+    
+    @classmethod
+    def setReg(cls,name,value,serviceName=None,keyname='PythonClass'):
+        if not serviceName:
+            serviceName = cls._svc_name_
+        key = cls.openKey(serviceName,keyname)
+        try:
+            win32api.RegSetValueEx(key, name, 0, win32con.REG_SZ, value)
+        finally:
+            win32api.RegCloseKey(key)
+
+    def getReg(self,name,keyname=None):
+        key = self.openKey(self._svc_name_,keyname)
+        return win32api.RegQueryValueEx(key,name)[0]
+    
+    ### Logging methods
+        
+    def logmsg(self, event):
+        # log a service event using servicemanager.LogMsg
+        try:
+            servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,
+                                  event,
+                                  (self._svc_name_,
+                                   " (%s)" % self._svc_display_name_))
+        except win32api.error, details:
+            # Failed to write a log entry - most likely problem is
+            # that the event log is full.  We don't want this to kill us
+            try:
+                print "FAILED to write INFO event", event, ":", details
+            except IOError:
+                pass
+
+    def _dolog(self, func, msg):
+        try:
+            fullmsg = "%s (%s): %s" % \
+                      (self._svc_name_, self._svc_display_name_, msg)
+            func(fullmsg)
+        except win32api.error, details:
+            # Failed to write a log entry - most likely problem is
+            # that the event log is full.  We don't want this to kill us
+            try:
+                print "FAILED to write event log entry:", details
+                print msg
+            except IOError:
+                # And if running as a service, its likely our sys.stdout
+                # is invalid
+                pass
+
+    def info(self, s):
+        self._dolog(servicemanager.LogInfoMsg, s)
+
+    def warning(self, s):
+        self._dolog(servicemanager.LogWarningMsg, s)
+
+    def error(self, s):
+        self._dolog(servicemanager.LogErrorMsg, s)
+



More information about the Zope-Checkins mailing list