Commits

Robert Brewer committed 4f5646e

Test and fix for #826 (SignalHandler needs an unsubscribe). Need nix testing.

  • Participants
  • Parent commits 63b5a11

Comments (0)

Files changed (3)

File cherrypy/process/plugins.py

                          'SIGHUP': self.handle_SIGHUP,
                          'SIGUSR1': self.bus.graceful,
                          }
+        
+        self._previous_handlers = {}
     
     def subscribe(self):
         for sig, func in self.handlers.iteritems():
             except ValueError:
                 pass
     
+    def unsubscribe(self):
+        for sig, handler in self._previous_handlers.iteritems():
+            signame = self.signals[sig]
+            
+            if handler is None:
+                self.bus.log("Restoring %s handler to SIG_DFL." % signame)
+                handler = _signal.SIG_DFL
+            else:
+                self.bus.log("Restoring %s handler %r." % (signame, handler))
+            
+            try:
+                _signal.signal(sig, handler)
+            except ValueError:
+                self.bus.log("Unable to restore %s handler %r." %
+                             (signame, handler))
+    
     def set_handler(self, signal, listener=None):
         """Subscribe a handler for the given signal (number or name).
         
                 raise ValueError("No such signal: %r" % signal)
             signum = signal
         
-        # Should we do something with existing signal handlers?
-        # cur = _signal.getsignal(signum)
-        _signal.signal(signum, self._handle_signal)
+        prev = _signal.signal(signum, self._handle_signal)
+        self._previous_handlers[signum] = prev
+        
         if listener is not None:
             self.bus.log("Listening for %s." % signame)
             self.bus.subscribe(signame, listener)

File cherrypy/test/test_states.py

         result = os.spawnl(os.P_NOWAIT, sys.executable, *args)
         cherrypy._cpserver.wait_for_occupied_port(host, port)
     
+    # Give the engine a wee bit more time to finish STARTING
+    if daemonize:
+        time.sleep(2)
+    else:
+        time.sleep(1)
+    
     return result
 
 def wait(pid):
 db_connection.subscribe()
 
 
+
+# ------------ Enough helpers. Time for real live test cases. ------------ #
+
+
 from cherrypy.test import helper
 
 class ServerStateTests(helper.CPWebCase):
         write_conf(scheme=self.scheme)
         exit_code = spawn_cp(wait=True, daemonize=True)
         
-        # Give the server some time to start up
-        time.sleep(2)
-        
         # Get the PID from the file.
         pid = int(open(PID_file_path).read())
         try:
         # Spawn the process.
         write_conf(scheme=self.scheme)
         pid = spawn_cp(wait=False, daemonize=False)
-        # Give the engine a wee bit more time to finish STARTING
-        time.sleep(1)
         # Send a SIGHUP
         os.kill(pid, SIGHUP)
         # This might hang if things aren't working right, but meh.
         write_conf(scheme=self.scheme)
         exit_code = spawn_cp(wait=True, daemonize=True)
         
-        # Give the server some time to start up
-        time.sleep(2)
-        
         # Get the PID from the file.
         pid = int(open(PID_file_path).read())
         try:
             # Shut down the spawned process
             self.getPage("/exit")
         wait(new_pid)
-
+    
+    def test_SIGTERM(self):
+        # SIGTERM should shut down the server whether daemonized or not.
+        if not self.server_class:
+            print "skipped (no server) ",
+            return
+        
+        try:
+            from signal import SIGTERM
+        except ImportError:
+            print "skipped (no SIGTERM) ",
+            return
+        
+        try:
+            from os import kill
+        except ImportError:
+            print "skipped (no os.kill) ",
+            return
+        
+        # Spawn a normal, undaemonized process.
+        write_conf(scheme=self.scheme)
+        pid = spawn_cp(wait=False, daemonize=False)
+        # Send a SIGTERM
+        os.kill(pid, SIGTERM)
+        # This might hang if things aren't working right, but meh.
+        wait(pid)
+        
+        if os.name in ['posix']: 
+            # Spawn a daemonized process and test again.
+            exit_code = spawn_cp(wait=True, daemonize=True)
+            # Send a SIGTERM
+            pid = int(open(PID_file_path).read())
+            os.kill(pid, SIGTERM)
+            # This might hang if things aren't working right, but meh.
+            wait(pid)
+    
+    def test_signal_handler_unsubscribe(self):
+        if not self.server_class:
+            print "skipped (no server) ",
+            return
+        
+        try:
+            from signal import SIGTERM
+        except ImportError:
+            print "skipped (no SIGTERM) ",
+            return
+        
+        try:
+            from os import kill
+        except ImportError:
+            print "skipped (no os.kill) ",
+            return
+        
+        # Spawn a normal, undaemonized process.
+        write_conf(scheme=self.scheme)
+        pid = spawn_cp(wait=False, daemonize=False)
+        self.getPage("/unsub_sig")
+        self.assertBody("OK")
+        os.kill(pid, SIGTERM)
+        wait(pid)
+        # Assert the old handler ran.
+        errlog = os.path.join(thisdir, 'test_states_demo.error.log')
+        self.assertEqual(open(errlog, 'rb').readlines()[-1],
+                         "I am an old SIGTERM handler.")
 
 
 cases = [v for v in globals().values()

File cherrypy/test/test_states_demo.py

         cherrypy.engine.wait(state=cherrypy.engine.states.STARTED)
         cherrypy.engine.exit()
     exit.exposed = True
+    
+    def unsub_sig(self):
+        cherrypy.engine.signal_handler.unsubscribe()
+        return "OK"
+    unsub_sig.exposed = True
+
+try:
+    from signal import SIGTERM
+except ImportError:
+    pass
+else:
+    def old_term_handler(signum=None, frame=None):
+        cherrypy.log("I am an old SIGTERM handler.")
+    _signal.signal(SIGTERM, old_term_handler)
+
 
 def starterror():
     if cherrypy.config.get('starterror', False):