Browse Source

Merge http://svn.supervisord.org/supervisor/branches/simpler_process_state_changes/ (we can revert if this breaks stuff for folks, but I'm pretty confident it does the right thing).

Chris McDonough 17 years ago
parent
commit
cd8ed8e2bc

+ 0 - 31
TODO.txt

@@ -1,34 +1,3 @@
-- Reconsider the wisdom of sending an event for every process state
-  change.  It's easiest to code this way, but many of the events are
-  useless in a process monitoring system.  Particlarly, nobody will
-  care that a process moved from STARTING to STOPPING or from BACKOFF
-  to STARTING or from STARTING to BACKOFF if we've already notified
-  once.  Nor does anyone care what the starting state is, just the
-  ending state.  Also, the current process state change regime
-  provides no indication of whether the state change that gets
-  reported is "expected".
-
-  The state changes we care about in a monitoring system are these:
-
-  PROCESS_STATE_CHANGE_STOPPED_OR_EXITED (-> STOPPED or -> EXITED)
-
-      PROCESS_STATE_CHANGE_STOPPED (-> STOPPED)
-
-      PROCESS_STATE_CHANGE_EXITED (-> EXITED)
-
-          PROCESS_STATE_CHANGE_EXITED_UNEXPECTED (-> EXITED with an
-          unexpected exit code)
-
-          PROCESS_STATE_CHANGE_EXITED_EXPECTED (-> EXITED with an
-          expected exit code)
-
-  PROCESS_STATE_CHANGE_FATAL (-> FATAL [supervisor gave up trying to start])
-
-  PROCESS_STATE_CHANGE_BACKOFF (-> BACKOFF [supervisor retrying a
-  failed start], but once only probably)
-
-  PROCESS_STATE_CHANGE_RUNNING Process started successfully (-> RUNNING)
-
 - Supervisorctl tab completion and history.
 
 - Supervisorctl "debug" command (communicate with a process over its stdin).

+ 71 - 115
src/supervisor/events.py

@@ -12,9 +12,6 @@
 #
 ##############################################################################
 
-import types
-
-from supervisor.states import ProcessStates
 from supervisor.states import getProcessStateDescription
 
 callbacks = []
@@ -38,6 +35,7 @@ class ProcessCommunicationEvent(Event):
     # event mode tokens
     BEGIN_TOKEN = '<!--XSUPERVISOR:BEGIN-->'
     END_TOKEN   = '<!--XSUPERVISOR:END-->'
+
     def __init__(self, process, pid, data):
         self.process = process
         self.pid = pid
@@ -59,114 +57,96 @@ class ProcessCommunicationStdoutEvent(ProcessCommunicationEvent):
 class ProcessCommunicationStderrEvent(ProcessCommunicationEvent):
     channel = 'stderr'
 
-class ProcessStateChangeEvent(Event):
-    """ Abstract class, never raised directly """
-    frm = None
-    to = None
-    def __init__(self, process, pid):
-        self.process = process
-        self.pid = pid
-
+class SupervisorStateChangeEvent(Event):
+    """ Abstract class """
     def __str__(self):
-        groupname = ''
-        if self.process.group is not None:
-            groupname = self.process.group.config.name
-        return 'processname:%s groupname:%s pid:%s' % (
-            self.process.config.name,
-            groupname,
-            self.pid)
-
-class StartingFromStoppedEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.STOPPED
-    to = ProcessStates.STARTING
-
-class StartingFromBackoffEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.BACKOFF
-    to = ProcessStates.STARTING
-
-class StartingFromExitedEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.EXITED
-    to = ProcessStates.STARTING
-
-class StartingFromFatalEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.FATAL
-    to = ProcessStates.STARTING
-
-class RunningFromStartingEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.STARTING
-    to = ProcessStates.RUNNING
+        return ''
 
-class BackoffFromStartingEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.STARTING
-    to = ProcessStates.BACKOFF
+class SupervisorRunningEvent(SupervisorStateChangeEvent):
+    pass
 
-class StoppingFromRunningEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.RUNNING
-    to = ProcessStates.STOPPING
+class SupervisorStoppingEvent(SupervisorStateChangeEvent):
+    pass
 
-class StoppingFromStartingEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.STARTING
-    to = ProcessStates.STOPPING
+class EventRejectedEvent:
+    def __init__(self, process, event):
+        self.process = process
+        self.event = event
 
-class ExitedOrStoppedEvent(ProcessStateChangeEvent):
+class ProcessStateEvent(Event):
     """ Abstract class, never raised directly """
     frm = None
     to = None
+    def __init__(self, process, from_state, expected=True):
+        self.process = process
+        self.from_state = from_state
+        self.expected = expected
+        # we eagerly render these so if the process pid, etc changes beneath
+        # us, we stash the values at the time the event was sent
+        self.extra_values = self.get_extra_values()
 
-class ExitedFromRunningEvent(ExitedOrStoppedEvent):
-    frm = ProcessStates.RUNNING
-    to = ProcessStates.EXITED
+    def __str__(self):
+        groupname = ''
+        if self.process.group is not None:
+            groupname = self.process.group.config.name
+        L = []
+        L.append(('processname',  self.process.config.name))
+        L.append(('groupname', groupname))
+        L.append(('from_state', getProcessStateDescription(self.from_state)))
+        L.extend(self.extra_values)
+        s = ' '.join( [ '%s:%s' % (name, val) for (name, val) in L ] )
+        return s
+
+    def get_extra_values(self):
+        return []
+
+class ProcessStateFatalEvent(ProcessStateEvent):
+    pass
 
-class StoppedFromStoppingEvent(ExitedOrStoppedEvent):
-    frm = ProcessStates.STOPPING
-    to = ProcessStates.STOPPED
+class ProcessStateUnknownEvent(ProcessStateEvent):
+    pass
 
-class FatalFromBackoffEvent(ProcessStateChangeEvent):
-    frm = ProcessStates.BACKOFF
-    to = ProcessStates.FATAL
+class ProcessStateStartingOrBackoffEvent(ProcessStateEvent):
+    def get_extra_values(self):
+        return [('tries', int(self.process.backoff))]
 
-_ANY = ()
+class ProcessStateBackoffEvent(ProcessStateStartingOrBackoffEvent):
+    pass
 
-class ToUnknownEvent(ProcessStateChangeEvent):
-    frm = _ANY
-    to = ProcessStates.UNKNOWN
+class ProcessStateStartingEvent(ProcessStateStartingOrBackoffEvent):
+    pass
 
-class SupervisorStateChangeEvent(Event):
-    """ Abstract class """
-    def __str__(self):
-        return ''
+class ProcessStateExitedEvent(ProcessStateEvent):
+    def get_extra_values(self):
+        return [('expected', int(self.expected)), ('pid', self.process.pid)]
 
-class SupervisorRunningEvent(SupervisorStateChangeEvent):
-    pass
+class ProcessStateRunningEvent(ProcessStateEvent):
+    def get_extra_values(self):
+        return [('pid', self.process.pid)]
 
-class SupervisorStoppingEvent(SupervisorStateChangeEvent):
-    pass
+class ProcessStateStoppingEvent(ProcessStateEvent):
+    def get_extra_values(self):
+        return [('pid', self.process.pid)]
 
-class EventRejectedEvent:
-    def __init__(self, process, event):
-        self.process = process
-        self.event = event
+class ProcessStateStoppedEvent(ProcessStateEvent):
+    def get_extra_values(self):
+        return [('pid', self.process.pid)]
 
 class EventTypes:
-    EVENT = Event
-    PROCESS_STATE_CHANGE = ProcessStateChangeEvent
-    PROCESS_STATE_CHANGE_STARTING_FROM_STOPPED = StartingFromStoppedEvent
-    PROCESS_STATE_CHANGE_STARTING_FROM_BACKOFF = StartingFromBackoffEvent
-    PROCESS_STATE_CHANGE_STARTING_FROM_EXITED = StartingFromExitedEvent
-    PROCESS_STATE_CHANGE_STARTING_FROM_FATAL = StartingFromFatalEvent
-    PROCESS_STATE_CHANGE_RUNNING_FROM_STARTING = RunningFromStartingEvent
-    PROCESS_STATE_CHANGE_BACKOFF_FROM_STARTING = BackoffFromStartingEvent
-    PROCESS_STATE_CHANGE_STOPPING_FROM_RUNNING = StoppingFromRunningEvent
-    PROCESS_STATE_CHANGE_STOPPING_FROM_STARTING = StoppingFromStartingEvent
-    PROCESS_STATE_CHANGE_EXITED_OR_STOPPED = ExitedOrStoppedEvent
-    PROCESS_STATE_CHANGE_EXITED_FROM_RUNNING = ExitedFromRunningEvent
-    PROCESS_STATE_CHANGE_STOPPED_FROM_STOPPING = StoppedFromStoppingEvent
-    PROCESS_STATE_CHANGE_FATAL_FROM_BACKOFF = FatalFromBackoffEvent
-    PROCESS_STATE_CHANGE_TO_UNKNOWN = ToUnknownEvent
-    PROCESS_COMMUNICATION = ProcessCommunicationEvent
+    EVENT = Event # abstract
+    PROCESS_STATE = ProcessStateEvent # abstract
+    PROCESS_STATE_STOPPED = ProcessStateStoppedEvent
+    PROCESS_STATE_EXITED = ProcessStateExitedEvent
+    PROCESS_STATE_STARTING = ProcessStateStartingEvent
+    PROCESS_STATE_STOPPING = ProcessStateStoppingEvent
+    PROCESS_STATE_BACKOFF = ProcessStateBackoffEvent
+    PROCESS_STATE_FATAL = ProcessStateFatalEvent
+    PROCESS_STATE_RUNNING = ProcessStateRunningEvent
+    PROCESS_STATE_UNKNOWN = ProcessStateUnknownEvent
+    PROCESS_COMMUNICATION = ProcessCommunicationEvent # abstract
     PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent
     PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent
-    SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent
+    SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent # abstract
     SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent
     SUPERVISOR_STATE_CHANGE_STOPPING = SupervisorStoppingEvent
 
@@ -175,31 +155,7 @@ def getEventNameByType(requested):
         if typ is requested:
             return name
 
-_map = {}
-
-def _makeProcessStateChangeMap():
-    states = ProcessStates.__dict__.values()
-    for old_state in states:
-        for new_state in states:
-            for name, typ in EventTypes.__dict__.items():
-                if type(typ) is types.ClassType:
-                    if issubclass(typ, ProcessStateChangeEvent):
-                        if typ.frm == old_state or typ.frm is _ANY:
-                            if typ.to == new_state or typ.to is _ANY:
-                                _map[(old_state, new_state)] = typ
-
-_makeProcessStateChangeMap()
-
-def getProcessStateChangeEventType(old_state, new_state):
-    typ = _map.get((old_state, new_state))
-    if typ is None:
-        old_desc = getProcessStateDescription(old_state)
-        new_desc = getProcessStateDescription(new_state)
-        raise NotImplementedError(
-            'Unknown transition (%s (%s) -> %s (%s))' % (
-            old_desc, old_state, new_desc, new_state)
-            )
-    return typ
+
         
         
 

+ 52 - 35
src/supervisor/process.py

@@ -146,12 +146,33 @@ class Subprocess:
 
         return filename, commandargs
 
-    def change_state(self, new_state):
+    event_map = {
+        ProcessStates.BACKOFF: events.ProcessStateBackoffEvent,
+        ProcessStates.FATAL:   events.ProcessStateFatalEvent,
+        ProcessStates.UNKNOWN: events.ProcessStateUnknownEvent,
+        ProcessStates.STOPPED: events.ProcessStateStoppedEvent,
+        ProcessStates.EXITED:  events.ProcessStateExitedEvent,
+        ProcessStates.RUNNING: events.ProcessStateRunningEvent,
+        ProcessStates.STARTING: events.ProcessStateStartingEvent,
+        ProcessStates.STOPPING: events.ProcessStateStoppingEvent,
+        }
+
+    def change_state(self, new_state, expected=True):
         old_state = self.state
         if new_state is old_state:
+            # exists for unit tests
             return False
-        event_type = events.getProcessStateChangeEventType(old_state, new_state)
-        events.notify(event_type(self, self.pid))
+
+        event_class = self.event_map.get(new_state)
+        if event_class is not None:
+            event = event_class(self, old_state, expected)
+            events.notify(event)
+
+        if new_state == ProcessStates.BACKOFF:
+            now = time.time()
+            self.backoff = self.backoff + 1
+            self.delay = now + self.backoff
+
         self.state = new_state
 
     def _assertInState(self, *states):
@@ -162,11 +183,8 @@ class Subprocess:
                 self.config.name, current_state, allowable_states))
 
     def record_spawnerr(self, msg):
-        now = time.time()
         self.spawnerr = msg
         self.config.options.logger.info("spawnerr: %s" % msg)
-        self.backoff = self.backoff + 1
-        self.delay = now + self.backoff
 
     def spawn(self):
         """Start the subprocess.  It must not be running already.
@@ -340,24 +358,24 @@ class Subprocess:
                    (self.config.name, signame(sig)))
             options.logger.debug(msg)
             return msg
+
+        options.logger.debug('killing %s (pid %s) with signal %s'
+                             % (self.config.name,
+                                self.pid,
+                                signame(sig))
+                             )
+
+        # RUNNING/STARTING/STOPPING -> STOPPING
+        self.killing = 1
+        self.delay = now + self.config.stopwaitsecs
+        # we will already be in the STOPPING state if we're doing a
+        # SIGKILL as a result of overrunning stopwaitsecs
+        self._assertInState(ProcessStates.RUNNING,ProcessStates.STARTING,
+                            ProcessStates.STOPPING)
+        self.change_state(ProcessStates.STOPPING)
+
         try:
-            options.logger.debug('killing %s (pid %s) with signal %s'
-                                 % (self.config.name,
-                                    self.pid,
-                                    signame(sig)))
-            # RUNNING -> STOPPING
-            self.killing = 1
-            self.delay = now + self.config.stopwaitsecs
-            # we will already be in the STOPPING state if we're doing a
-            # SIGKILL as a result of overrunning stopwaitsecs
-            self._assertInState(ProcessStates.RUNNING,ProcessStates.STARTING,
-                                ProcessStates.STOPPING)
-            self.change_state(ProcessStates.STOPPING)
             options.kill(self.pid, sig)
-        except (AssertionError, NotImplementedError):
-            # AssertionError may be raised by _assertInState,
-            # NotImplementedError potentially raised by change_state
-            raise
         except:
             io = StringIO.StringIO()
             traceback.print_exc(file=io)
@@ -402,39 +420,38 @@ class Subprocess:
             # the program did not stay up long enough to make it to RUNNING
             # implies STARTING -> BACKOFF
             self.exitstatus = None
-            self.backoff = self.backoff + 1
-            self.delay = now + self.backoff
-
             self.spawnerr = 'Exited too quickly (process log may have details)'
             msg = "exited: %s (%s)" % (processname, msg + "; not expected")
             self._assertInState(ProcessStates.STARTING)
             self.change_state(ProcessStates.BACKOFF)
 
         else:
-            # this finish was not the result of a stop request,
-            # the program was in the RUNNING state but exited
-            # implies RUNNING -> EXITED
+            # this finish was not the result of a stop request, the
+            # program was in the RUNNING state but exited implies
+            # RUNNING -> EXITED
             self.delay = 0
             self.backoff = 0
             self.exitstatus = es
 
             if self.state == ProcessStates.STARTING:
-                # XXX I dont know under which circumstances this happens,
-                # but in the wild, there is a transition that subverts
-                # the RUNNING state (directly from STARTING to EXITED),
-                # so we perform the correct transition here.
+                # XXX I dont know under which circumstances this
+                # happens, but in the wild, there is a transition that
+                # subverts the RUNNING state (directly from STARTING
+                # to EXITED), so we perform the correct transition
+                # here.
                 self.change_state(ProcessStates.RUNNING)
 
+            self._assertInState(ProcessStates.RUNNING)
+
             if exit_expected:
                 # expected exit code
                 msg = "exited: %s (%s)" % (processname, msg + "; expected")
+                self.change_state(ProcessStates.EXITED, expected=True)
             else:
                 # unexpected exit code
                 self.spawnerr = 'Bad exit code %s' % es
                 msg = "exited: %s (%s)" % (processname, msg + "; not expected")
-
-            self._assertInState(ProcessStates.RUNNING)
-            self.change_state(ProcessStates.EXITED)
+                self.change_state(ProcessStates.EXITED, expected=False)
 
         self.config.options.logger.info(msg)
 

+ 7 - 2
src/supervisor/tests/base.py

@@ -896,8 +896,13 @@ class DummyStream:
         return len(self.written)
         
 class DummyEvent:
-    serial = 'abc'
-        
+    def __init__(self, serial='abc'):
+        if serial is not None:
+            self.serial = serial
+
+    def __str__(self):
+        return 'dummy event'
+
 def lstrip(s):
     strings = [x.strip() for x in s.split('\n')]
     return '\n'.join(strings)

+ 149 - 72
src/supervisor/tests/test_events.py

@@ -4,6 +4,7 @@ import unittest
 from supervisor.tests.base import DummyOptions
 from supervisor.tests.base import DummyPConfig
 from supervisor.tests.base import DummyProcess
+from supervisor.tests.base import DummyEvent
 
 class EventSubscriptionNotificationTests(unittest.TestCase):
     def setUp(self):
@@ -87,11 +88,42 @@ class TestEventTypes(unittest.TestCase):
         self.assertEqual(inst.data, 3)
         self.assertEqual(inst.channel, 'stderr')
 
-    def test_ProcessStateChangeEvent(self):
-        from supervisor.events import ProcessStateChangeEvent
-        inst = ProcessStateChangeEvent(1, 2)
-        self.assertEqual(inst.process, 1)
-        self.assertEqual(inst.pid, 2)
+    # nothing to test for SupervisorStateChangeEvent and subtypes
+
+    def test_EventRejectedEvent(self):
+        from supervisor.events import EventRejectedEvent
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process = DummyProcess(pconfig1)
+        rejected_event = DummyEvent()
+        event = EventRejectedEvent(process, rejected_event)
+        self.assertEqual(event.process, process)
+        self.assertEqual(event.event, rejected_event)
+
+    def _test_ProcessStateEvent(self, klass):
+        from supervisor.states import ProcessStates
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process = DummyProcess(pconfig1)
+        inst = klass(process, ProcessStates.STARTING)
+        self.assertEqual(inst.process, process)
+        self.assertEqual(inst.from_state, ProcessStates.STARTING)
+        self.assertEqual(inst.expected, True)
+
+    def test_ProcessStateEvents(self):
+        from supervisor import events
+        for klass in (
+            events.ProcessStateEvent,
+            events.ProcessStateStoppedEvent,
+            events.ProcessStateExitedEvent,
+            events.ProcessStateFatalEvent,
+            events.ProcessStateBackoffEvent,
+            events.ProcessStateRunningEvent,
+            events.ProcessStateUnknownEvent,
+            events.ProcessStateStoppingEvent,
+            events.ProcessStateStartingEvent,
+            ):
+            self._test_ProcessStateEvent(klass)
         
 class TestSerializations(unittest.TestCase):
     def _deserialize(self, serialization):
@@ -138,26 +170,136 @@ class TestSerializations(unittest.TestCase):
         self.assertEqual(headers['processname'], 'process1', headers)
         self.assertEqual(headers['groupname'], 'process1', headers)
         self.assertEqual(headers['pid'], '1', headers)
+        self.assertEqual(payload, 'yo')
+
+    def test_process_state_events_without_extra_values(self):
+        from supervisor.states import ProcessStates
+        from supervisor import events
+        for klass in (
+            events.ProcessStateFatalEvent,
+            events.ProcessStateUnknownEvent,
+            ):
+            options = DummyOptions()
+            pconfig1 = DummyPConfig(options, 'process1', 'process1',
+                                    '/bin/process1')
+            class DummyGroup:
+                config = pconfig1
+            process1 = DummyProcess(pconfig1)
+            process1.group = DummyGroup
+            event = klass(process1, ProcessStates.STARTING)
+            headers, payload = self._deserialize(str(event))
+            self.assertEqual(len(headers), 3)
+            self.assertEqual(headers['processname'], 'process1')
+            self.assertEqual(headers['groupname'], 'process1')
+            self.assertEqual(headers['from_state'], 'STARTING')
+            self.assertEqual(payload, '')
+
+    def test_process_state_events_with_pid(self):
+        from supervisor.states import ProcessStates
+        from supervisor import events
+        for klass in (
+            events.ProcessStateRunningEvent,
+            events.ProcessStateStoppedEvent,
+            events.ProcessStateStoppingEvent,
+            ):
+            options = DummyOptions()
+            pconfig1 = DummyPConfig(options, 'process1', 'process1',
+                                    '/bin/process1')
+            class DummyGroup:
+                config = pconfig1
+            process1 = DummyProcess(pconfig1)
+            process1.group = DummyGroup
+            process1.pid = 1
+            event = klass(process1, ProcessStates.STARTING)
+            headers, payload = self._deserialize(str(event))
+            self.assertEqual(len(headers), 4)
+            self.assertEqual(headers['processname'], 'process1')
+            self.assertEqual(headers['groupname'], 'process1')
+            self.assertEqual(headers['from_state'], 'STARTING')
+            self.assertEqual(headers['pid'], '1')
+            self.assertEqual(payload, '')
 
-    def test_process_sc_event(self):
+    def test_process_state_events_starting_and_backoff(self):
+        from supervisor.states import ProcessStates
         from supervisor import events
+        for klass in (
+            events.ProcessStateStartingEvent,
+            events.ProcessStateBackoffEvent,
+            ):
+            options = DummyOptions()
+            pconfig1 = DummyPConfig(options, 'process1', 'process1',
+                                    '/bin/process1')
+            class DummyGroup:
+                config = pconfig1
+            process1 = DummyProcess(pconfig1)
+            process1.group = DummyGroup
+            event = klass(process1, ProcessStates.STARTING)
+            headers, payload = self._deserialize(str(event))
+            self.assertEqual(len(headers), 4)
+            self.assertEqual(headers['processname'], 'process1')
+            self.assertEqual(headers['groupname'], 'process1')
+            self.assertEqual(headers['from_state'], 'STARTING')
+            self.assertEqual(headers['tries'], '0')
+            self.assertEqual(payload, '')
+            process1.backoff = 1
+            event = klass(process1, ProcessStates.STARTING)
+            headers, payload = self._deserialize(str(event))
+            self.assertEqual(headers['tries'], '1')
+            process1.backoff = 2
+            event = klass(process1, ProcessStates.STARTING)
+            headers, payload = self._deserialize(str(event))
+            self.assertEqual(headers['tries'], '2')
+        
+    def test_process_state_exited_event_expected(self):
+        from supervisor import events
+        from supervisor.states import ProcessStates
         options = DummyOptions()
         pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
         process1 = DummyProcess(pconfig1)
         class DummyGroup:
             config = pconfig1
         process1.group = DummyGroup
-        event = events.StartingFromStoppedEvent(process1, 1)
+        process1.pid = 1
+        event = events.ProcessStateExitedEvent(process1,
+                                               ProcessStates.STARTING,
+                                               expected=True)
         headers, payload = self._deserialize(str(event))
+        self.assertEqual(len(headers), 5)
         self.assertEqual(headers['processname'], 'process1')
         self.assertEqual(headers['groupname'], 'process1')
         self.assertEqual(headers['pid'], '1')
+        self.assertEqual(headers['from_state'], 'STARTING')
+        self.assertEqual(headers['expected'], '1')
+        self.assertEqual(payload, '')
+
+    def test_process_state_exited_event_unexpected(self):
+        from supervisor import events
+        from supervisor.states import ProcessStates
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process1 = DummyProcess(pconfig1)
+        class DummyGroup:
+            config = pconfig1
+        process1.group = DummyGroup
+        process1.pid = 1
+        event = events.ProcessStateExitedEvent(process1,
+                                               ProcessStates.STARTING,
+                                               expected=False)
+        headers, payload = self._deserialize(str(event))
+        self.assertEqual(len(headers), 5)
+        self.assertEqual(headers['processname'], 'process1')
+        self.assertEqual(headers['groupname'], 'process1')
+        self.assertEqual(headers['pid'], '1')
+        self.assertEqual(headers['from_state'], 'STARTING')
+        self.assertEqual(headers['expected'], '0')
+        self.assertEqual(payload, '')
 
     def test_supervisor_sc_event(self):
         from supervisor import events
         event = events.SupervisorRunningEvent()
         headers, payload = self._deserialize(str(event))
         self.assertEqual(headers, {})
+        self.assertEqual(payload, '')
 
 class TestUtilityFunctions(unittest.TestCase):
     def test_getEventNameByType(self):
@@ -170,71 +312,6 @@ class TestUtilityFunctions(unittest.TestCase):
         klass = getProcessStateChangeEventType(old, new)
         self.assertEqual(expected, klass)
 
-    def test_getProcessStateChangeEventType_STOPPED_TO_STARTING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.STOPPED, ProcessStates.STARTING,
-                                events.StartingFromStoppedEvent)
-        
-    def test_getProcessStateChangeEventType_STARTING_TO_RUNNING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.STARTING, ProcessStates.RUNNING,
-                                events.RunningFromStartingEvent)
-
-    def test_getProcessStateChangeEventType_STARTING_TO_BACKOFF(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.STARTING, ProcessStates.BACKOFF,
-                                events.BackoffFromStartingEvent)
-
-    def test_getProcessStateChangeEventType_BACKOFF_TO_STARTING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.BACKOFF, ProcessStates.STARTING,
-                                events.StartingFromBackoffEvent)
-
-    def test_getProcessStateChangeEventType_BACKOFF_TO_FATAL(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.BACKOFF, ProcessStates.FATAL,
-                                events.FatalFromBackoffEvent)
-
-    def test_getProcessStateChangeEventType_FATAL_TO_STARTING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.FATAL, ProcessStates.STARTING,
-                                events.StartingFromFatalEvent)
-
-    def test_getProcessStateChangeEventType_STARTING_TO_RUNNING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.STARTING, ProcessStates.RUNNING,
-                                events.RunningFromStartingEvent)
-
-    def test_getProcessStateChangeEventType_RUNNING_TO_EXITED(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.RUNNING, ProcessStates.EXITED,
-                                events.ExitedFromRunningEvent)
-
-    def test_getProcessStateChangeEventType_EXITED_TO_STARTING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.EXITED, ProcessStates.STARTING,
-                                events.StartingFromExitedEvent)
-
-    def test_getProcessStateChangeEventType_RUNNING_TO_STOPPING(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.RUNNING, ProcessStates.STOPPING,
-                                events.StoppingFromRunningEvent)
-
-    def test_getProcessStateChangeEventType_STOPPING_TO_STOPPED(self):
-        from supervisor.states import ProcessStates
-        from supervisor import events
-        self._assertStateChange(ProcessStates.STOPPING, ProcessStates.STOPPED,
-                                events.StoppedFromStoppingEvent)
 
 def test_suite():
     return unittest.findTestCases(sys.modules[__name__])

+ 126 - 82
src/supervisor/tests/test_process.py

@@ -145,8 +145,6 @@ class SubprocessTests(unittest.TestCase):
         instance.record_spawnerr('foo')
         self.assertEqual(instance.spawnerr, 'foo')
         self.assertEqual(options.logger.data[0], 'spawnerr: foo')
-        self.assertEqual(instance.backoff, 1)
-        self.failUnless(instance.delay)
 
     def test_spawn_already_running(self):
         options = DummyOptions()
@@ -168,7 +166,7 @@ class SubprocessTests(unittest.TestCase):
         instance.state = ProcessStates.BACKOFF
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         result = instance.spawn()
         self.assertEqual(result, None)
         self.assertEqual(instance.spawnerr, 'bad filename')
@@ -177,8 +175,11 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(instance.backoff)
         from supervisor.states import ProcessStates
         self.assertEqual(instance.state, ProcessStates.BACKOFF)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
-        self.assertEqual(L[1].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 2)
+        event1 = L[0]
+        event2 = L[1]
+        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
 
     def test_spawn_fail_make_pipes_emfile(self):
         options = DummyOptions()
@@ -190,7 +191,7 @@ class SubprocessTests(unittest.TestCase):
         instance.state = ProcessStates.BACKOFF
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         result = instance.spawn()
         self.assertEqual(result, None)
         self.assertEqual(instance.spawnerr,
@@ -201,8 +202,10 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(instance.backoff)
         from supervisor.states import ProcessStates
         self.assertEqual(instance.state, ProcessStates.BACKOFF)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
-        self.assertEqual(L[1].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 2)
+        event1, event2 = L
+        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
 
     def test_spawn_fail_make_pipes_other(self):
         options = DummyOptions()
@@ -213,7 +216,7 @@ class SubprocessTests(unittest.TestCase):
         instance.state = ProcessStates.BACKOFF
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         result = instance.spawn()
         self.assertEqual(result, None)
         self.assertEqual(instance.spawnerr, 'unknown error: EPERM')
@@ -223,8 +226,10 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(instance.backoff)
         from supervisor.states import ProcessStates
         self.assertEqual(instance.state, ProcessStates.BACKOFF)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
-        self.assertEqual(L[1].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 2)
+        event1, event2 = L
+        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
 
     def test_spawn_fork_fail_eagain(self):
         options = DummyOptions()
@@ -236,7 +241,7 @@ class SubprocessTests(unittest.TestCase):
         instance.state = ProcessStates.BACKOFF
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         result = instance.spawn()
         self.assertEqual(result, None)
         self.assertEqual(instance.spawnerr,
@@ -249,8 +254,10 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(instance.backoff)
         from supervisor.states import ProcessStates
         self.assertEqual(instance.state, ProcessStates.BACKOFF)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
-        self.assertEqual(L[1].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 2)
+        event1, event2 = L
+        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
 
     def test_spawn_fork_fail_other(self):
         options = DummyOptions()
@@ -261,7 +268,7 @@ class SubprocessTests(unittest.TestCase):
         instance.state = ProcessStates.BACKOFF
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         result = instance.spawn()
         self.assertEqual(result, None)
         self.assertEqual(instance.spawnerr, 'unknown error: EPERM')
@@ -273,8 +280,10 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(instance.backoff)
         from supervisor.states import ProcessStates
         self.assertEqual(instance.state, ProcessStates.BACKOFF)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
-        self.assertEqual(L[1].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 2)
+        event1, event2 = L
+        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
 
     def test_spawn_as_child_setuid_ok(self):
         options = DummyOptions()
@@ -600,14 +609,16 @@ class SubprocessTests(unittest.TestCase):
         L = []
         from supervisor.states import ProcessStates
         from supervisor import events
-        events.subscribe(events.FatalFromBackoffEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         instance.state = ProcessStates.BACKOFF
         instance.give_up()
         self.assertEqual(instance.system_stop, 1)
         self.assertFalse(instance.delay)
         self.assertFalse(instance.backoff)
         self.assertEqual(instance.state, ProcessStates.FATAL)
-        self.assertEqual(L[0].__class__, events.FatalFromBackoffEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateFatalEvent)
 
     def test_kill_nopid(self):
         options = DummyOptions()
@@ -626,7 +637,7 @@ class SubprocessTests(unittest.TestCase):
         L = []
         from supervisor.states import ProcessStates
         from supervisor import events
-        events.subscribe(events.ProcessStateChangeEvent,
+        events.subscribe(events.ProcessStateEvent,
                          lambda x: L.append(x))
         instance.pid = 11
         instance.state = ProcessStates.RUNNING
@@ -636,8 +647,11 @@ class SubprocessTests(unittest.TestCase):
         self.failUnless(options.logger.data[1].startswith(
             'unknown problem killing test'))
         self.assertEqual(instance.killing, 0)
-        self.assertEqual(L[0].__class__, events.StoppingFromRunningEvent)
-        self.assertEqual(L[1].__class__, events.ToUnknownEvent)
+        self.assertEqual(len(L), 2)
+        event1 = L[0]
+        event2 = L[1]
+        self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)
+        self.assertEqual(event2.__class__, events.ProcessStateUnknownEvent)
 
     def test_kill_from_starting(self):
         options = DummyOptions()
@@ -647,15 +661,16 @@ class SubprocessTests(unittest.TestCase):
         L = []
         from supervisor.states import ProcessStates
         from supervisor import events
-        events.subscribe(events.StoppingFromStartingEvent,lambda x: L.append(x))
-        from supervisor.states import ProcessStates
+        events.subscribe(events.ProcessStateEvent,lambda x: L.append(x))
         instance.state = ProcessStates.STARTING
         instance.kill(signal.SIGTERM)
         self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
                          'signal SIGTERM')
         self.assertEqual(instance.killing, 1)
         self.assertEqual(options.kills[11], signal.SIGTERM)
-        self.assertEqual(L[0].__class__, events.StoppingFromStartingEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)
 
     def test_kill_from_running(self):
         options = DummyOptions()
@@ -665,15 +680,16 @@ class SubprocessTests(unittest.TestCase):
         L = []
         from supervisor.states import ProcessStates
         from supervisor import events
-        events.subscribe(events.StoppingFromRunningEvent, lambda x: L.append(x))
-        from supervisor.states import ProcessStates
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         instance.state = ProcessStates.RUNNING
         instance.kill(signal.SIGTERM)
         self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
                          'signal SIGTERM')
         self.assertEqual(instance.killing, 1)
         self.assertEqual(options.kills[11], signal.SIGTERM)
-        self.assertEqual(L[0].__class__, events.StoppingFromRunningEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)
 
     def test_kill_from_stopping(self):
         options = DummyOptions()
@@ -684,7 +700,6 @@ class SubprocessTests(unittest.TestCase):
         from supervisor.states import ProcessStates
         from supervisor import events
         events.subscribe(events.Event,lambda x: L.append(x))
-        from supervisor.states import ProcessStates
         instance.state = ProcessStates.STOPPING
         instance.kill(signal.SIGKILL)
         self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
@@ -707,7 +722,8 @@ class SubprocessTests(unittest.TestCase):
         from supervisor import events
         instance.state = ProcessStates.STOPPING
         L = []
-        events.subscribe(events.StoppedFromStoppingEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateStoppedEvent, lambda x: L.append(x))
+        instance.pid = 123
         instance.finish(123, 1)
         self.assertEqual(instance.killing, 0)
         self.assertEqual(instance.pid, 0)
@@ -717,7 +733,11 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.logger.data[0], 'stopped: notthere '
                          '(terminated by SIGHUP)')
         self.assertEqual(instance.exitstatus, -1)
-        self.assertEqual(L[0].__class__, events.StoppedFromStoppingEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStoppedEvent)
+        self.assertEqual(event.extra_values, [('pid', 123)])
+        self.assertEqual(event.from_state, ProcessStates.STOPPING)
 
     def test_finish_expected(self):
         options = DummyOptions()
@@ -732,7 +752,8 @@ class SubprocessTests(unittest.TestCase):
         from supervisor import events
         instance.state = ProcessStates.RUNNING
         L = []
-        events.subscribe(events.ExitedFromRunningEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateExitedEvent, lambda x: L.append(x))
+        instance.pid = 123
         instance.finish(123, 1)
         self.assertEqual(instance.killing, 0)
         self.assertEqual(instance.pid, 0)
@@ -742,9 +763,15 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.logger.data[0],
                          'exited: notthere (terminated by SIGHUP; expected)')
         self.assertEqual(instance.exitstatus, -1)
-        self.assertEqual(L[0].__class__, events.ExitedFromRunningEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__,
+                         events.ProcessStateExitedEvent)
+        self.assertEqual(event.expected, True)
+        self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])
+        self.assertEqual(event.from_state, ProcessStates.RUNNING)
 
-    def test_finish_unexpected(self):
+    def test_finish_tooquickly(self):
         options = DummyOptions()
         config = DummyPConfig(options, 'notthere', '/notthere',
                               stdout_logfile='/tmp/foo', startsecs=10)
@@ -759,7 +786,8 @@ class SubprocessTests(unittest.TestCase):
         from supervisor import events
         instance.state = ProcessStates.STARTING
         L = []
-        events.subscribe(events.BackoffFromStartingEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
+        instance.pid = 123
         instance.finish(123, 1)
         self.assertEqual(instance.killing, 0)
         self.assertEqual(instance.pid, 0)
@@ -769,12 +797,15 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.logger.data[0],
                       'exited: notthere (terminated by SIGHUP; not expected)')
         self.assertEqual(instance.exitstatus, None)
-        self.assertEqual(L[0].__class__, events.BackoffFromStartingEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateBackoffEvent)
+        self.assertEqual(event.from_state, ProcessStates.STARTING)
 
     def test_finish_with_current_event_sends_rejected(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ExitedFromRunningEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         events.subscribe(events.EventRejectedEvent, lambda x: L.append(x))
         options = DummyOptions()
         config = DummyPConfig(options, 'notthere', '/notthere',
@@ -785,10 +816,13 @@ class SubprocessTests(unittest.TestCase):
         event = DummyEvent()
         instance.event = event
         instance.finish(123, 1)
-        self.assertEqual(L[0].__class__, events.ExitedFromRunningEvent)
-        self.assertEqual(L[1].__class__, events.EventRejectedEvent)
-        self.assertEqual(L[1].process, instance)
-        self.assertEqual(L[1].event, event)
+        self.assertEqual(len(L), 2)
+        event1, event2 = L
+        self.assertEqual(event1.__class__,
+                         events.ProcessStateExitedEvent)
+        self.assertEqual(event2.__class__, events.EventRejectedEvent)
+        self.assertEqual(event2.process, instance)
+        self.assertEqual(event2.event, event)
         self.assertEqual(instance.event, None)
 
     def test_set_uid_no_uid(self):
@@ -831,7 +865,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_stopped_to_starting_supervisor_stopping(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.SHUTDOWN
@@ -848,7 +882,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_stopped_to_starting_supervisor_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.RUNNING
@@ -859,12 +893,14 @@ class SubprocessTests(unittest.TestCase):
         process.state = ProcessStates.STOPPED
         process.transition()
         self.assertEqual(process.state, ProcessStates.STARTING)
-        self.assertEqual(L[0].__class__, events.StartingFromStoppedEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
 
     def test_transition_exited_to_starting_supervisor_stopping(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.SHUTDOWN
@@ -885,8 +921,8 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_exited_to_starting_uncond_supervisor_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
-        from supervisor.states import ProcessStates, SupervisorStates
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
+        from supervisor.states import ProcessStates
         options = DummyOptions()
 
         pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
@@ -897,13 +933,15 @@ class SubprocessTests(unittest.TestCase):
         process.state = ProcessStates.EXITED
         process.transition()
         self.assertEqual(process.state, ProcessStates.STARTING)
-        self.assertEqual(L[0].__class__, events.StartingFromExitedEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
 
     def test_transition_exited_to_starting_condit_supervisor_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
-        from supervisor.states import ProcessStates, SupervisorStates
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
+        from supervisor.states import ProcessStates
         options = DummyOptions()
 
         pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
@@ -915,13 +953,15 @@ class SubprocessTests(unittest.TestCase):
         process.exitstatus = 'bogus'
         process.transition()
         self.assertEqual(process.state, ProcessStates.STARTING)
-        self.assertEqual(L[0].__class__, events.StartingFromExitedEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
 
     def test_transition_exited_to_starting_condit_fls_supervisor_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
-        from supervisor.states import ProcessStates, SupervisorStates
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
+        from supervisor.states import ProcessStates
         options = DummyOptions()
 
         pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
@@ -938,7 +978,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_backoff_to_starting_supervisor_stopping(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.SHUTDOWN
@@ -956,7 +996,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_backoff_to_starting_supervisor_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.RUNNING
@@ -969,12 +1009,13 @@ class SubprocessTests(unittest.TestCase):
         process.state = ProcessStates.BACKOFF
         process.transition()
         self.assertEqual(process.state, ProcessStates.STARTING)
-        self.assertEqual(L[0].__class__, events.StartingFromBackoffEvent)
+        self.assertEqual(len(L), 1)
+        self.assertEqual(L[0].__class__, events.ProcessStateStartingEvent)
 
     def test_transition_backoff_to_starting_supervisor_running_notyet(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates, SupervisorStates
         options = DummyOptions()
         options.mood = SupervisorStates.RUNNING
@@ -992,7 +1033,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_starting_to_running(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates
 
         options = DummyOptions()
@@ -1017,12 +1058,14 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.logger.data[0],
                          'success: process entered RUNNING state, process has '
                          'stayed up for > than 10 seconds (startsecs)')
-        self.assertEqual(L[0].__class__, events.RunningFromStartingEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateRunningEvent)
 
     def test_transition_backoff_to_fatal(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates
         options = DummyOptions()
 
@@ -1046,12 +1089,14 @@ class SubprocessTests(unittest.TestCase):
         self.assertEqual(options.logger.data[0],
                          'gave up: process entered FATAL state, too many start'
                          ' retries too quickly')
-        self.assertEqual(L[0].__class__, events.FatalFromBackoffEvent)
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.__class__, events.ProcessStateFatalEvent)
 
     def test_transition_stops_unkillable_notyet(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates
         options = DummyOptions()
 
@@ -1067,7 +1112,7 @@ class SubprocessTests(unittest.TestCase):
     def test_transition_stops_unkillable(self):
         from supervisor import events
         L = []
-        events.subscribe(events.ProcessStateChangeEvent, lambda x: L.append(x))
+        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
         from supervisor.states import ProcessStates
         options = DummyOptions()
 
@@ -1095,6 +1140,16 @@ class SubprocessTests(unittest.TestCase):
         instance.state = 10
         self.assertEqual(instance.change_state(10), False)
 
+    def test_change_state_sets_backoff_and_delay(self):
+        from supervisor.states import ProcessStates
+        options = DummyOptions()
+        config = DummyPConfig(options, 'test', '/test')
+        instance = self._makeOne(config)
+        instance.state = 10
+        instance.change_state(ProcessStates.BACKOFF)
+        self.assertEqual(instance.backoff, 1)
+        self.failUnless(instance.delay > 0)
+
 class ProcessGroupBaseTests(unittest.TestCase):
     def _getTargetClass(self):
         from supervisor.process import ProcessGroupBase
@@ -1328,14 +1383,13 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         import errno
         process1.write_error = errno.EPIPE
         process1.listener_state = EventListenerStates.READY
-        from supervisor.events import StartingFromStoppedEvent
-        event = StartingFromStoppedEvent(process1, 1)
+        event = DummyEvent()
         pool._acceptEvent(event)
         pool.dispatch()
         self.assertEqual(process1.listener_state, EventListenerStates.READY)
         self.assertEqual(pool.event_buffer, [event])
         self.assertEqual(options.logger.data[0],
-                         'rebuffering event 1 for pool whatever (bufsize 0)')
+                         'rebuffering event abc for pool whatever (bufsize 0)')
 
     def test__acceptEvent_attaches_pool_serial_and_serial(self):
         from supervisor.process import GlobalSerial
@@ -1347,8 +1401,7 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         process1 = pool.processes['process1']
         from supervisor.states import EventListenerStates
         process1.listener_state = EventListenerStates.READY
-        from supervisor.events import StartingFromStoppedEvent
-        event = StartingFromStoppedEvent(process1, 1)
+        event = DummyEvent(None)
         pool._acceptEvent(event)
         self.assertEqual(event.serial, GlobalSerial.serial)
         self.assertEqual(event.pool_serials['whatever'], pool.serial)
@@ -1370,10 +1423,9 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
         pool = self._makeOne(gconfig)
         pool.processes = {'process1': process1}
-        from supervisor.events import StartingFromStoppedEvent
-        from supervisor.states import EventListenerStates
-        event = StartingFromStoppedEvent(process1, 1)
+        event = DummyEvent()
         event.serial = 'a'
+        from supervisor.states import EventListenerStates
         process1.listener_state = EventListenerStates.BUSY
         pool._acceptEvent(event)
         pool.transition()
@@ -1389,9 +1441,8 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
         pool = self._makeOne(gconfig)
         pool.processes = {'process1': process1}
-        from supervisor.events import StartingFromStoppedEvent
+        event = DummyEvent()
         from supervisor.states import EventListenerStates
-        event = StartingFromStoppedEvent(process1, 1)
         event.serial = 1
         process1.listener_state = EventListenerStates.READY
         pool._acceptEvent(event)
@@ -1409,9 +1460,8 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
         pool = self._makeOne(gconfig)
         pool.processes = {'process1': process1}
-        from supervisor.events import StartingFromStoppedEvent
+        event = DummyEvent()
         from supervisor.states import EventListenerStates
-        event = StartingFromStoppedEvent(process1, 1)
         process1.listener_state = EventListenerStates.READY
         class DummyGroup:
             config = gconfig
@@ -1421,13 +1471,7 @@ class EventListenerPoolTests(ProcessGroupBaseTests):
         self.assertEqual(process1.transitioned, True)
         self.assertEqual(pool.event_buffer, [])
         header, payload = process1.stdin_buffer.split('\n', 1)
-        
-        self.assertEquals(payload,
-            'processname:process1 groupname:whatever pid:1', payload)
-        headers = header.split()
-        self.assertEqual(
-            headers[5],
-            'eventname:PROCESS_STATE_CHANGE_STARTING_FROM_STOPPED')
+        self.assertEquals(payload, 'dummy event', payload)
         self.assertEqual(process1.listener_state, EventListenerStates.BUSY)
         self.assertEqual(process1.event, event)