@@ -169,13 +169,7 @@ def __init__(self, transitions=None, errors=None,
169169 id = hex (random .randint (0 , sys .maxint ))[- 8 :]
170170 self .id = id
171171 self ._priorities = {}
172-
173- if select :
174- (self ._state_transition_pipe_read ,
175- self ._state_transition_pipe_write ) = os .pipe ()
176- else :
177- (self ._state_transition_pipe_read ,
178- self ._state_transition_pipe_write ) = (None , None )
172+ self ._state_transition_pipes = set ()
179173
180174 @property
181175 def states (self ):
@@ -204,8 +198,11 @@ def _transition(self, newstate, *args, **kwargs):
204198 """
205199 try :
206200 self .state = newstate
207- if self ._state_transition_pipe_write is not None :
208- os .write (self ._state_transition_pipe_write , '1' )
201+
202+ # Write to any pipes created by threads calling self.wait().
203+ # Use list() to avoid "Set changed size during iteration" errors.
204+ for read_fd , write_fd in list (self ._state_transition_pipes ):
205+ os .write (write_fd , "1" )
209206
210207 # Note: logging here means 1) the initial transition
211208 # will not be logged if loggers are set up in the initial
@@ -294,28 +291,52 @@ def publish(self, channel, *args, **kwargs):
294291 raise exc
295292 return output
296293
297- def wait (self , state , interval = 0.1 , channel = None ):
298- """Poll for the given state(s) at intervals; publish to channel."""
294+ def wait (self , state , interval = 0.1 , channel = None , sleep = False ):
295+ """Poll for the given state(s) at intervals; publish to channel.
296+
297+ If sleep is True, the calling thread loops, sleeping for the given
298+ interval each time, then returning only when the bus state is
299+ one of the given states to wait for.
300+
301+ If sleep is False (the default) and the operating system supports
302+ I/O multiplexing via the 'select' module, then an anonymous pipe
303+ will be used to signal the waiting thread to wake up whenever
304+ the state transitions. This allows the waiting thread to return
305+ when the bus shuts down, for example, rather than waiting for
306+ the sleep interval to elapse first. Each thread that calls wait()
307+ creates a new pipe, so if file descriptors are in short supply
308+ on your system you might need to use sleep instead.
309+ """
299310 if isinstance (state , (tuple , list )):
300311 _states_to_wait_for = state
301312 else :
302313 _states_to_wait_for = [state ]
303314
315+ if select :
316+ pipe = os .pipe ()
317+ read_fd , write_fd = pipe
318+ self ._state_transition_pipes .add (pipe )
319+
304320 def _wait ():
305- while self .state not in _states_to_wait_for :
306- if self ._state_transition_pipe_read is not None :
307- try :
308- r , w , x = select .select ([self ._state_transition_pipe_read ], [], [], interval )
309- if r :
310- os .read (self ._state_transition_pipe_read , 1 )
311- except (select .error , OSError ):
312- # Interrupted due to a signal (being handled by some
313- # other thread). No need to panic, here, just check
314- # the new state and proceed/return.
315- pass
316- else :
317- time .sleep (interval )
318- self .publish (channel )
321+ try :
322+ while self .state not in _states_to_wait_for :
323+ if select :
324+ try :
325+ r , w , x = select .select ([read_fd ], [], [], interval )
326+ if r :
327+ os .read (read_fd , 1 )
328+ except (select .error , OSError ):
329+ # Interrupted due to a signal (being handled by some
330+ # other thread). No need to panic, here, just check
331+ # the new state and proceed/return.
332+ pass
333+ else :
334+ time .sleep (interval )
335+ self .publish (channel )
336+ finally :
337+ self ._state_transition_pipes .discard (pipe )
338+ os .close (read_fd )
339+ os .close (write_fd )
319340
320341 # From http://psyco.sourceforge.net/psycoguide/bugs.html:
321342 # "The compiled machine code does not include the regular polling
0 commit comments