[Zope-Checkins] SVN: Zope/trunk/ - Applied patch for

Sidnei da Silva sidnei at awkly.org
Thu Mar 17 07:39:01 EST 2005


Log message for revision 29524:
  
        - Applied patch for
          http://www.zope.org/Collectors/Zope/1527. The patch
          installs a windows 'control handler'. Ctrl+C/Break etc are
          delivered asynchronously (notwithstanding the GIL) to a new
          thread. This new thread triggers a shutdown, then tricks
          asyncore into breaking out of its 'select' call.
  

Changed:
  U   Zope/trunk/doc/CHANGES.txt
  U   Zope/trunk/lib/python/Lifetime.py
  U   Zope/trunk/lib/python/ZEO/runzeo.py

-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt	2005-03-17 12:14:32 UTC (rev 29523)
+++ Zope/trunk/doc/CHANGES.txt	2005-03-17 12:39:01 UTC (rev 29524)
@@ -28,6 +28,13 @@
 
     Features added
 
+      - Applied patch for
+        http://www.zope.org/Collectors/Zope/1527. The patch
+        installs a windows "control handler". Ctrl+C/Break etc are
+        delivered asynchronously (notwithstanding the GIL) to a new
+        thread. This new thread triggers a shutdown, then tricks
+        asyncore into breaking out of its 'select' call.
+
       - The ZEO server now records its PID to a file like the ZEO
         client. Defaults to $INSTANCE_HOME/var/ZEO.pid, and its
         configurable in $INSTANCE_HOME/etc/zeo.conf.

Modified: Zope/trunk/lib/python/Lifetime.py
===================================================================
--- Zope/trunk/lib/python/Lifetime.py	2005-03-17 12:14:32 UTC (rev 29523)
+++ Zope/trunk/lib/python/Lifetime.py	2005-03-17 12:39:01 UTC (rev 29524)
@@ -38,6 +38,68 @@
         # enough, but still clean.
         _shutdown_timeout = 1.0
 
+def setup_windows_control_handler():
+    try:
+        from win32api import SetConsoleCtrlHandler, \
+                             GenerateConsoleCtrlEvent
+        from win32con import CTRL_C_EVENT, CTRL_BREAK_EVENT, \
+                             CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, \
+                             CTRL_SHUTDOWN_EVENT
+    except ImportError:
+        pass
+    else:
+        def interrupt_select():
+            """Interrupt a sleeping acyncore 'select' call"""
+            # What is the right thing to do here?  
+            # asyncore.close_all() works, but I fear that would
+            # prevent the poll based graceful cleanup code from working.
+            # This seems to work :)
+            for fd, obj in asyncore.socket_map.items():
+                if hasattr(obj, "pull_trigger"):
+                    obj.pull_trigger()
+
+        def ctrl_handler(ctrlType):
+            """Called by Windows on a new thread whenever a
+               console control event is raised."""
+            result = 0
+            if ctrlType == CTRL_C_EVENT:
+                # user pressed Ctrl+C or someone did 
+                # GenerateConsoleCtrlEvent
+                if _shutdown_phase == 0:
+                    print "Shutting down Zope..."
+                    shutdown(0)
+                    interrupt_select()
+                elif _shutdown_timeout > 1.0:
+                    print "Zope shutdown switching to 'fast'"
+                    shutdown(0, 1)
+                else:
+                    # Third time around - terminate via
+                    # a CTRL_BREAK_EVENT
+                    GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0)
+                result = 1
+            elif ctrlType == CTRL_BREAK_EVENT:
+                # Always let Ctrl+Break force it down.
+                # Default handler terminates process.
+                print "Terminating Zope (press Ctrl+C to shutdown cleanly)"
+            elif ctrlType == CTRL_CLOSE_EVENT:
+                # Console is about to die.
+                # CTRL_CLOSE_EVENT gives us 5 seconds before displaying
+                # the "End process" dialog - so switch directly to 'fast'
+                shutdown(0, 1)
+                interrupt_select()
+                result = 1
+            elif ctrlType in (CTRL_LOGOFF_EVENT, CTRL_SHUTDOWN_EVENT):
+                # MSDN says:
+                # "Note that this signal is received only by services. 
+                # Interactive applications are terminated at logoff, so 
+                # they are not present when the system sends this signal."
+                # We can therefore ignore it (our service framework 
+                # manages shutdown in this case)
+                pass
+            return result
+        # Install our handler.
+        SetConsoleCtrlHandler(ctrl_handler)
+    
 def loop():
     # Run the main loop until someone calls shutdown()
     lifetime_loop()
@@ -46,6 +108,9 @@
     graceful_shutdown_loop()
 
 def lifetime_loop():
+    if sys.platform.startswith("win"):
+        setup_windows_control_handler()
+
     # The main loop. Stay in here until we need to shutdown
     map = asyncore.socket_map
     timeout = 30.0

Modified: Zope/trunk/lib/python/ZEO/runzeo.py
===================================================================
--- Zope/trunk/lib/python/ZEO/runzeo.py	2005-03-17 12:14:32 UTC (rev 29523)
+++ Zope/trunk/lib/python/ZEO/runzeo.py	2005-03-17 12:39:01 UTC (rev 29524)
@@ -186,6 +186,8 @@
         method is called without additional arguments.
         """
         if os.name != "posix":
+            if os.name == "nt":
+                self.setup_win32_signals()
             return
         if hasattr(signal, 'SIGXFSZ'):
             signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case
@@ -197,6 +199,48 @@
                     method()
                 signal.signal(sig, wrapper)
 
+    def setup_win32_signals(self):
+        try:
+            from win32api import SetConsoleCtrlHandler
+            import win32con # our handler uses this
+        except ImportError:
+            warn("no pywin32 extensions - can't install ctrl+c handler")
+        else:
+            SetConsoleCtrlHandler(self._win32_ctrl_handler)
+        # And borrow the Zope Signals module to get a log reopen handler.
+        from Signals.WinSignalHandler import SignalHandler
+        from Signals.Signals import logfileReopenHandler
+        SIGUSR2 = 12
+        SignalHandler.registerHandler(SIGUSR2, logfileReopenHandler)
+
+    def _win32_ctrl_handler(self, ctrlType):
+        """Called by Windows on a new thread whenever a
+           console control event is raised."""
+        from win32con import CTRL_C_EVENT, CTRL_BREAK_EVENT, \
+                             CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, \
+                             CTRL_SHUTDOWN_EVENT
+        import asyncore
+        result = 0
+        # Note we probably don't want to raise SystemExit from
+        # this thread - pywin32-203 at least calls PyErr_Print,
+        # which will still terminate us (but print a message
+        # about the callback failing)
+        if ctrlType == CTRL_C_EVENT:
+            # user pressed Ctrl+C or someone did 
+            # GenerateConsoleCtrlEvent
+            info("terminated by CTRL_C_EVENT")
+            asyncore.close_all()
+            # Default will raise KeyboardInterrupt - we don't need that
+        elif ctrlType == CTRL_BREAK_EVENT:
+            info("terminated by CTRL_BREAK_EVENT")
+            asyncore.close_all()
+            # Default handler terminates process - result remains 0
+        elif ctrlType == CTRL_CLOSE_EVENT:
+            info("terminated by CTRL_CLOSE_EVENT")
+            asyncore.close_all()
+            result = 1
+        return result
+
     def create_server(self):
         from ZEO.StorageServer import StorageServer
         self.server = StorageServer(



More information about the Zope-Checkins mailing list