Commits

Robert Brewer committed 0440f1f

New select() option in wait. New bus.id.

  • Participants
  • Parent commits 5662169
  • Branches graph_transition

Comments (0)

Files changed (1)

 are free to define their own channels. If a message is sent to a
 channel that has not been defined or has no listeners, there is no effect.
 """
-
+import os
+import random
+try:
+    import select
+except ImportError:
+    select = None
 import sys
 import time
 import traceback as _traceback
 
 
 class Bus(object):
-    """State machine and pub/sub messenger."""
+    """State machine and pub/sub messenger.
+
+    If the 'select' module is present (POSIX systems), then select.select()
+    (on an os.pipe) will be used in self.wait instead of time.sleep().
+    """
 
     publish_exception_class = ChannelFailures
 
     def __init__(self, transitions=None, errors=None,
-                 initial_state=None, extra_channels=None):
+                 initial_state=None, extra_channels=None, id=None):
         if not isinstance(transitions, Graph):
             transitions = Graph.from_edges(transitions)
         self.transitions = transitions
         for c in extra_channels:
             self.listeners[c] = set()
 
+        if id is None:
+            id = hex(random.randint(0, sys.maxint))[-8:]
+        self.id = id
         self._priorities = {}
 
+        if select:
+            (self._state_transition_pipe_read,
+             self._state_transition_pipe_write) = os.pipe()
+        else:
+            (self._state_transition_pipe_read,
+             self._state_transition_pipe_write) = (None, None)
+
     @property
     def states(self):
         return self.transitions.states
         """
         try:
             self.state = newstate
+            if self._state_transition_pipe_write is not None:
+                os.write(self._state_transition_pipe_write, "1")
+
+            # Note: logging here means 1) the initial transition
+            # will not be logged if loggers are set up in the initial
+            # transition! and 2) the final transition will not be logged
+            # if loggers are torn down in the penultimate transition!
+            # This is why, for example, the included loggers are
+            # "always on" rather than listening for start/stop themselves.
             self.log('Bus state: %s' % newstate)
+
             return self.publish(newstate, *args, **kwargs)
         except self.throws:
             raise
 
         def _wait():
             while self.state not in _states_to_wait_for:
-                time.sleep(interval)
+                if self._state_transition_pipe_read is not None:
+                    try:
+                        select.select([self._state_transition_pipe_read], [], [], interval)
+                        os.read(self._state_transition_pipe_read, 1)
+                    except (select.error, OSError):
+                        # Interrupted due to a signal (being handled by some
+                        # other thread). No need to panic, here, just check
+                        # the new state and proceed/return.
+                        pass
+                else:
+                    time.sleep(interval)
                 self.publish(channel)
 
         # From http://psyco.sourceforge.net/psycoguide/bugs.html: