Releases: aleph2c/miros
Thread-safe live instrumentation, print and signal catching glob support
Added four new internal signals: STOP_FABRIC_SIGNAL
, STOP_ACTIVE_OBJECT_SIGNAL
, SUBSCRIBE_META_SIGNAL
, PUBLISH_META_SIGNAL
. These signals used to be created by the active object, but they have been moved into the event.py
class, so that signal catching globs can be supported by miros.
Writes to Python print
are not thread safe. Before this release the default behavior of live_spy
and live_trace
wrote out to python's print
function directly. This live instrumentation could cause an io-block and a resulting run time error. I fixed this threading issue by changing the miros active objects to write their live stream messages through a dedicated instrumentation thread. This thread ensures that only one thing gets to print at a time. Also, the active object now provides a thread safe version of print
which uses the instrumentation thread.
Miros provides the user with a way to over-write how their live trace
and live spy
streams are written. The thread managing the live stream watches a queue, which contains 0 or more Instrumentation
named tuples. An Instrumentation
named tuple has a fn
and a content
element. The fn
describes the way the content
is intended to be printed, so that the register_live_spy_callback
and register_live_trace_callback
features will continue to work. The user can change "how" their instrumentation stream will be represented, but the function-they-provide-to-do-this-with: fn
, must be protected within a thread.
Since there can be many active objects which all want to print their live instrumentation information at the same time, but only one instrumentation thread handler, I made the class which provides the instrumentation threading a singleton. The class which provides the instrumentation threading is called InstrumentationWriterClass,
and it is turned into the singleton InstrumentationWriter
by the SingletonDectorator
class. When an active object is being constructed it creates an InstrumentationWriter
. If a previously build active object has already done this, a reference to the previously created InstrumentedWriter
is returned. Then the instrumentation writing thread is started if it hasn't been started already.
Fixed task start race condition
When the start_at
method is called, the statechart climbs into its initial state, then the active object starts a thread.
But, prior to this release, if the start_at
method caused calls to other methods which needed a task to run, the task would be started to honor the request of the call. This meant that the task would be started too soon, and the active object could send events to the HSM dispatcher, while it was still trying to climb into its initial state. Statecharts only run one event at a time, and starting a chart while having it react to events breaks the RTC protocol meaning that the statechart could behave non-deterministically .
Specifically, the bug fixed in this release would happen when a call to subscribe
, or publish
, or post_fifo
, or post_lifo
where made during the start-up process. The start_thread_if_not_running
decorator wrapped these calls so that they could work because they needed a thread to work. However it was a mistake to allow the thread to be started in more than one location in the code base. This bug has been fixed in this release and the start_thread_if_not_running
decorator has been removed so that the same mistake won't be made again.
There are situations when the subscribe
, or publish
, or post_fifo
, or post_lifo
calls will be made before the thread is started. These requests must be honored but they can be deferred till after the thread is started. To grant the client access to these features without a running thread, the post_fifo
and post_lifo
calls put their requests directly into the Active Object's queues so that they will be acted upon when the thread is started. To do the same for the publish
and subscribe
calls, two new meta signals (a signal which carries another signal) were invented. If a publish
method is called before a thread was started, an event with the signal named PUBLISH_META_SIGNAL
is posted using the post_lifo
technique into the Active Object's lifo queue. Inside of this event is enough information to publish an event with the required priority once the Active Object's thread started at a later time. A similar technique was followed to honor calls to subscribe
, it's signal name is SUBSCRIBE_META_SIGNAL
.
The ActiveObject's self.top
state was overloaded to catch the PUBLISH_META_SIGNAL
and SUBSCRIBE_META_SIGNAL
and dispatch them to private functions which work once the thread is running. To improve performance the publish
and subscribe
methods only publish events if no thread is running. If the thread is running they call thread related private functions directly, as they did before.
In addition to this change, a subscribed
method was added to the ActiveObject and ActiveFabric class. This method could be used to determine if a signal_name
or event
had been subscribed to for a given queue type (lifo/fifo).
Also, the stop_fabric
and stop_active_object
internal signals were renamed to STOP_FABRIC_SIGNAL
and STOP_ACTIVE_OBJECT_SIGNAL
to make their names consistent with other internal signal names.
Switching ThreadSafeAttributes code to use a threading.RLock
The previous, v4.1.3, version of miros ThreadSafeAttributes
class would not protect thread-safe-attributes from race conditions when they were used with the non-atomic "+="
, "-="
... "<<="
family of operations. (I could not get the broken code to fail in my tests, it was a review that brought the error to my attention; thank you to Ljubomir Kaludjercic for pointing this out to me).
To fix this issue, the deque was removed and a threading.Rlock was used with the inspect module, within a descriptor. Upon access, the descriptor checks to see if the previous line of code contains a "+="
, "-="
, ... "<<="
and if so it will leave the lock active until the value is set, see the code here. The usage of this limited protection was explained and documented in the example section of miros.ca, you can see it here.
Thread Safe Attributes (attributes which can be shared across threads)
Added a ThreadSafeAttributes class which when inherited from will provide new
class level syntax for building thread safe attributes. At the class level you
would write _attributes = ['attr1', 'attr2', 'attr3']
to produce the thread
safe attributes, attr1
, attr2
and attr3
. (Under the hood these attributes
are actually properties of a small deque ring buffer of size 1.) To see more
about this feature reference the recipes section of the miros project at tag
v4.1.3 or later.
Allow simple methods to be registered using the "catch" method in the Factory class.
The Factory class lets a miros user build state machines within a class. Previous to this release only static methods could be registered to signals during the state construction process.
- static methods are still permitted as catch handler inputs (backwards
compatible) - standard methods are now also permitted as catch handlers too.
Allowing stop to be called within ActiveObject or Factory
- the stop method can now be called from within an ActiveObject or a Factory.
Fixed stop method of ActiveObject
- removed code which hides exceptions within your statechart (this might unmask
bugs in your design) - changed how miros stops active objects.
- a call to the ActiveObject stop method will not stop the active fabric
- a call to the ActiveObject stop method will kill all slave threads which
are posting events into that ActiveObject. - after the stop method is called on an ActiveObject, the ActiveFabric will
not post events into that ActiveObject's input queue, since it won't exist
anymore.
- removed unnecessary files from pip package (doc/test...)