def would_block(function): """ Run the specified function in a nested transaction, abort the nested transaction to avoid any side effects being persisted, then return True if the function attempted to retry. """ try: # Create a function to pass to or_else that runs the function, then # raises an exception to abort the nested transaction. def run_and_raise(): function() # We need to abort this nested transaction to avoid the function's # side effects being persisted, and we don't differentiate between # a function that runs successfully and a function that raises an # exception (neither would retry), so just raise Exception here. raise Exception() # The call to stm.atomically isn't strictly necessary right now, but # I'm considering changing or_else to not revert the effects of a # function that ends up throwing an exception, in which case it would # be necessary. stm.atomically(lambda: stm.or_else(run_and_raise, lambda: None)) # Function tried to retry return True except Exception: # Function didn't retry (either it threw an exception itself or it # succeeded, in which case we threw the exception for it) return False
def run(self): # There's no platform agnostic way to sleep in such a way that one can # be interrupted half-way through (indeed, threading.Condition # basically uses a busywait with exponentially increasing delays), so # we'll do the next best thing: check every second to see if we've lost # our reference to the variable to change and return if we did. # # The main upside of this is that we guarantee that the transaction # will be interrupted precisely when the timeout expires, not up to # 50 milliseconds later as is possible with threading.Condition, but it # does mean that spare threads can hang around for up to a second if # a transaction returns on its own without a timeout happening. # # At some point I'd like to write a ctypes wrapper around pthread, # which is used on every platform except Windows and which does have # proper timeout support, that's used on platforms that support it to # get rid of this delay, but it's not top priority right now. while time.time() < self.stop_time: # Sleep up to 1 second or the amount of time remaining until we're # supposed to time out, whichever is less time.sleep(min(1, max(0, self.stop_time - time.time()))) if not self.var_ref(): # Variable was garbage collected since a second ago, so return return var = self.var_ref() if var: # Still have a reference to the variable, so set it to True. stm.atomically(lambda: var.set(True))
def main(): with Bus() as bus: server = atomically(Server) ConsoleSink(server).start() autobus_service = SixjetService() atomically(lambda: server.provider_list.append(autobus_service)) bus.create_service({"type": "sixjet"}, autobus_service) wait_for_interrupt() @atomically def _(): server.running = False
def with_timeout(stop_time, function): """ A variant of with_delay that accepts its timeout as a number of seconds since the epoch at which the transaction should raise Timeout. """ var = make_timeout(stop_time) return stm.atomically(lambda: stm.or_else(function, lambda: wait_then_raise(var)))
def run(self): while True: try: new_state = atomically(self.wait_for_new_state) self.write(new_state) except StopException: self.write({}) return
def __init__(self, server, map=default_map, names=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16], levels=".123456789^"): self.server = server self.map = map self.names = names self.levels = levels self.last_state = atomically(TDict)
def run(self): self.write([[0 for _ in g] for g in self.state_names]) while True: try: new_state = atomically(self.wait_for_new_state) self.write(new_state) except StopException: self.write([[0 for _ in g] for g in self.state_names]) return
def flash(self, *jets): """ Turns the specified jets on, then turns them off after the number of seconds specified by the flash_time object on this service. The flash time can be adjusted by calling set_flash_time or update_flash_time. I'll probably add a mechanism later for specifying a custom flash time when calling flash. """ flasher = atomically(lambda: Flasher(jets, self.flash_time, self.provider_list)) flasher.schedule()
def make_timeout(stop_time): """ A variant of make_delay that sets the returned TVar to True when time.time() becomes greater than stop_time. """ if stm._stm_state.current: raise Exception("Timeout vars cannot be created inside a transaction") var = stm.atomically(lambda: stm.TVar(False)) _TimeoutThread(var, stop_time).start() return var
def __init__(self, server, state_names=[[1, 2, 3, 4, 5, 6, 7, 8], [9, 10, 11, 12, 13, 14, 15, 16]], data_pins=[DATA_A, DATA_B], strobe_pin=STROBE, clock_pin=CLOCK): self.server = server self.state_names = state_names self.flat_state_names = [name for l in state_names for name in l] self.data_pins = data_pins self.strobe_pin = strobe_pin self.clock_pin = clock_pin self.port = Parallel() self.write_function = self.port.setData self.last_state = atomically(TDict)
def atomically_watch(function, callback=None): """ A wrapper around stm.watch that automatically runs the call inside a transaction. This is essentially equivalent to:: stm.atomically(lambda: stm.watch(function, callback)) but, as with stm.watch, atomically_watch can be used as a decorator by omitting the callback parameter. For example, the following could be used outside of a transaction to place a new watch:: @atomically_watch(some_tvar.get) def _(result): ...do something... This would be equivalent to: @stm.atomically def _(): @stm.watch(some_tvar.get) def _(result): ..do something.. Note that the callback will (as callbacks always are) still be run inside a transaction. If you need to perform I/O in the callback, use stm.eventloop.scheduled_function to decorate the callback such that it will be run by the event loop outside of the scope of STM:: @atomically_watch(some_tvar.get) @stm.eventloop.scheduled_function def _(result): print "Changed to " + str(result) # Or any other I/O """ if callback is None: def decorator(actual_callback): atomically_watch(function, actual_callback) return decorator stm.atomically(lambda: stm.watch(function, callback))
def __init__(self): """ Creates a new sixjet server. backend is the instance of backends.Backend that will be used to write jets. service_extra is an optionally-empty set of values that will be added to the Autobus 2's service info dictionary. (Keys such as type will be added automatically, but such keys present in service_extra will override the ones added automatically.) bus is the Autobus bus to use. You can usually just use: from autobus2 import Bus with Bus() as bus: server = SixjetServer(..., bus, ...) ... and things will work. """ PyServiceProvider.__init__(self, True) ComplexProvider.__init__(self) self.flash_time = 0.25 self.fixed_provider = atomically(FixedProvider) atomically(lambda: self.provider_list.append(self.fixed_provider))
def run(self): # Outside of STM only if stm._stm_state.current: raise Exception("This must be called outside of a transaction.") while True: next_event = stm.atomically(self._endpoint.get) if next_event is None: print "Event loop exiting" return try: next_event() except: print "Event threw an exception, which will be ignored." print "For reference, the exception is:" traceback.print_exc()
def state(self): """ The state this thread is currently in. This will be one of: * NEW: This thread has been created but start() has not yet been called * STARTED: start() has been called but the thread has not yet commenced execution. Threads started from within a transaction will remain in STARTED until shortly after the transaction commits. * RUNNING: The thread has commenced execution. * FINISHED: The thread has died. """ return stm.atomically(lambda: self._state)
def changes_only(callback=None, according_to=None): """ A decorator that can be used to decorate callbacks that are to be passed to stm.watch to filter out duplicate invocations with the same result value. It can be used either as:: @changes_only def callback(result): ... or as:: @changes_only(according_to=some_predicate) def callback(result): ... with the latter allowing a custom two-argument function to be used to compare the equality of the value passed to a given invocation with the value passed to the previous invocation; the former compares values using the "is" operator. Note that the resulting callback will keep a reference around to the last value with which it was called, so make sure you're not counting on this value's being garbage collected immediately after the callback is invoked. """ if callback and according_to: last = stm.atomically(lambda: stm.TVar((False, None))) @functools.wraps(callback) def actual_callback(*args): # We use *args here to permit @changes_only to be used to decorate # methods on objects; in such cases, we'll be passed two arguments, # self and the actual result. result = args[-1] has_run, last_value = last.value if not has_run or not according_to(last_value, result): last.value = True, result callback(*args) return actual_callback elif callback: return changes_only(callback, operator.is_) elif according_to: def decorator(callback): return changes_only(callback, according_to) return decorator else: raise ValueError("Either callback or according_to must be specified")
def run(self): # Outside of STM only if stm._stm_state.current: raise Exception("This must be called outside of a transaction.") while True: next_event = stm.atomically(self._endpoint.get) if next_event is None: print "Event loop exiting" return try: next_event() except Exception: print "Event threw an exception, which will be ignored." print "For reference, the exception is:" traceback.print_exc() except: print "Event threw a non-standard exception:" traceback.print_exc() raise
def schedule(self, function): # In or out of STM if function is None: raise ValueError("function cannot be None") stm.atomically(lambda: self._queue.put(function))
if stm._stm_state.current: raise Exception("This must be called outside of a transaction.") while True: next_event = stm.atomically(self._endpoint.get) if next_event is None: print "Event loop exiting" return try: next_event() except: print "Event threw an exception, which will be ignored." print "For reference, the exception is:" traceback.print_exc() default_event_loop = stm.atomically(EventLoop) default_event_loop.start() def schedule(function): """ Schedule a function to be run later. This can be used from within a transaction to schedule a function to be called outside of the transaction, after it commits. The specified function will be run outside of the context of a transaction, so it can do things like I/O that transactions normally aren't allowed to do. Note that such functions are run synchronously and in the order they were scheduled. They should therefore complete quickly, or spawn a new thread (or make use of a stm.threadutils.ThreadPool) to do their work. """
def __str__(self): return "TDict(%r)" % stm.atomically(lambda: dict(self))
def __str__(self): return "TList(%r)" % stm.atomically(lambda: list(self))
def run(self): target = stm.atomically(lambda: self._target) target()
def wrapper(*args, **kwargs): return stm.atomically(lambda: function(*args, **kwargs))
def stop(self): # In or out of STM stm.atomically(lambda: self._queue.put(None))