-
+
+

The best is the enemy of the good.

+

—Voltaire

+
+

Thread Safe Attributes

-

If you use a statechart, your program is multi-threaded.

+

If you use a miros statechart, your program is multi-threaded.

Sometimes, you will want to access an attribute of your statechart from another thread, like the main part of your program. When you do this, you are trying to access memory that could be changed in one thread while it is being read in by @@ -99,7 +103,7 @@ non-atomic “+=”, “-=” … “//=” statements using thread-safe attributes were also wrapped within locks. For more complex situations, the thread-safety features provided by the ThreadSafeAttributes class can be -used get to get the thread lock explicitly.

+used to get the thread lock explicitly.

I will introduce these ideas gradually through a set of examples. Let’s begin by looking at four interacting threads (possible race conditions are highlighted):

@@ -737,15 +741,34 @@

You can download the above code here

The lock can be obtained by calling _, _lock = <thread_safe_attribute>.

-

This is a little nasty piece of metaprogramming that could baffle a beginner or -anyone who looks at the thread safe attribute. Most of the time your thread -safe attribute acts as an attribute, but other times it acts as an iterable, -what is going on? It only acts as an interable when proceeded by _, _lock. -If you use this technique in one of your threads, you must use it in all of your -threads.

-

Once again I recommend against performing calculations directly on your shared -attributes. Instead, copy their variable into a temp, perform a calculation -then assign the results into them.

+

This nasty little piece of metaprogramming could baffle a beginner or anyone who +looks at the thread safe attribute: Most of the time your thread-safe attribute +acts as an attribute, but other times it acts as an iterable, what is going on? +It only acts as an iterable when proceeded by _, _lock. If you use this +technique in one of your threads, you must also explicitly get the lock in all +other threads that share the attribute.

+

This lock-access feature was added for difficult situations, where the client +code absolutely needs the lock, maybe for advanced database calls or that kind +of thing.

+

I recommend against explicitely getting a lock and performing calculations +directly on your shared attributes.

+

Instead, copy their contents into a local variable (automatically locked) , +perform a calculation using local variables, then assign the results back into +the shared attribute (automatically locked).

+

In our example, we don’t need to use shared attribute at all, so we shouldn’t. +The example was arbitrary, a better way to perform the calculation can be seen +in the following code listing. If we needed to place the 0.3 back into the +shared-attribute, we can do that, but we keep the shared-attribute out of our +equation. The equation will use non-shared, thread-safe, local variables which +are placed on the stack during a thread’s context switch.

+
# code which doesn't require an explicit lock
+temp = 0.30
+b = temp * math.cos(0.45) + 3 * temp ** 1.2
+print("thr2: ", b)
+# this code will be implicitly locked by ThreadSafeAttributes
+self.gl1.a = temp
+
+

Note

The ThreadSafeAttributes feature actually reads the last line of code you @@ -769,8 +792,8 @@

Related Topics

diff --git a/examples/broken_thread_foo.py b/examples/broken_thread_foo.py index a62c04f..24ecbd1 100644 --- a/examples/broken_thread_foo.py +++ b/examples/broken_thread_foo.py @@ -1,31 +1,31 @@ -import dis -import threading -n = 0 - -def foo(): - global n - n += 1 - -def thread_test(): - global n - n = 0 - threads = [] - for i in range(1000): - t = threading.Thread(target=foo) - threads.append(t) - return threads - - -print(dis.dis(foo)) - -for i in range(1000): - threads = thread_test() - # start all of the threads - for t in threads: - t.start() - - # wait for all of the threads - for t in threads: - t.join() - - print(n) +import dis +import threading +n = 0 + +def foo(): + global n + n += 1 + +def thread_test(): + global n + n = 0 + threads = [] + for i in range(1000): + t = threading.Thread(target=foo) + threads.append(t) + return threads + + +print(dis.dis(foo)) + +for i in range(1000): + threads = thread_test() + # start all of the threads + for t in threads: + t.start() + + # wait for all of the threads + for t in threads: + t.join() + + print(n) diff --git a/examples/simple_state_15.log b/examples/simple_state_15.log index aee4506..73b9934 100644 --- a/examples/simple_state_15.log +++ b/examples/simple_state_15.log @@ -1,112 +1,112 @@ -2019-08-06 06:42:04,571 DEBUG:T: [f1] e->start_at() top->middle_state -2019-08-06 06:42:04,571 DEBUG:S: [f1] START -2019-08-06 06:42:04,571 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:outer_state -2019-08-06 06:42:04,571 DEBUG:S: [f1] ENTRY_SIGNAL:outer_state -2019-08-06 06:42:04,571 DEBUG:S: [f1] hello from outer_state -2019-08-06 06:42:04,572 DEBUG:S: [f1] INIT_SIGNAL:outer_state -2019-08-06 06:42:04,572 DEBUG:S: [f1] init -2019-08-06 06:42:04,572 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:04,572 DEBUG:S: [f1] ENTRY_SIGNAL:middle_state -2019-08-06 06:42:04,572 DEBUG:S: [f1] arming one-shot -2019-08-06 06:42:04,572 DEBUG:S: [f1] INIT_SIGNAL:middle_state -2019-08-06 06:42:04,572 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:04,573 DEBUG:S: [f1] Hook:middle_state -2019-08-06 06:42:04,573 DEBUG:S: [f1] Hook:outer_state -2019-08-06 06:42:04,573 DEBUG:S: [f1] run some code, but don't transition -2019-08-06 06:42:04,574 DEBUG:S: [f1] Hook:outer_state:HOOK -2019-08-06 06:42:04,574 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:04,576 DEBUG:T: [f2] e->start_at() top->inner_state -2019-08-06 06:42:04,576 DEBUG:S: [f2] START -2019-08-06 06:42:04,576 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:04,576 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:04,576 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:outer_state -2019-08-06 06:42:04,576 DEBUG:S: [f2] ENTRY_SIGNAL:outer_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] hello from outer_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] ENTRY_SIGNAL:middle_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] arming one-shot -2019-08-06 06:42:04,577 DEBUG:S: [f2] ENTRY_SIGNAL:inner_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] hello from new inner_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] INIT_SIGNAL:inner_state -2019-08-06 06:42:04,577 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:04,578 DEBUG:S: [f1] Send_Broadcast:middle_state -2019-08-06 06:42:04,579 DEBUG:S: [f2] Hook:inner_state -2019-08-06 06:42:04,579 DEBUG:S: [f1] Send_Broadcast:outer_state -2019-08-06 06:42:04,579 DEBUG:S: [f2] Hook:middle_state -2019-08-06 06:42:04,579 DEBUG:S: [f1] PUBLISH:(BROADCAST, PRIORITY:1000) -2019-08-06 06:42:04,579 DEBUG:S: [f2] Hook:outer_state -2019-08-06 06:42:04,580 DEBUG:S: [f1] Send_Broadcast:outer_state:HOOK -2019-08-06 06:42:04,580 DEBUG:S: [f2] run some code, but don't transition -2019-08-06 06:42:04,580 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:04,580 DEBUG:S: [f2] Hook:outer_state:HOOK -2019-08-06 06:42:04,581 DEBUG:S: [f1] BROADCAST:middle_state -2019-08-06 06:42:04,581 DEBUG:S: [f2] <- Queued:(1) Deferred:(0) -2019-08-06 06:42:04,581 DEBUG:S: [f1] BROADCAST:outer_state -2019-08-06 06:42:04,584 DEBUG:T: [f2] e->Reset() inner_state->middle_state -2019-08-06 06:42:04,584 DEBUG:S: [f1] received broadcast -2019-08-06 06:42:04,584 DEBUG:S: [f2] Reset:inner_state -2019-08-06 06:42:04,584 DEBUG:S: [f1] BROADCAST:outer_state:HOOK -2019-08-06 06:42:04,584 DEBUG:S: [f2] Reset:middle_state -2019-08-06 06:42:04,584 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:04,585 DEBUG:S: [f2] Reset:outer_state -2019-08-06 06:42:04,585 DEBUG:S: [f2] EXIT_SIGNAL:inner_state -2019-08-06 06:42:04,585 DEBUG:S: [f2] exiting new inner_state -2019-08-06 06:42:04,585 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:04,585 DEBUG:S: [f2] EXIT_SIGNAL:middle_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] EXIT_SIGNAL:outer_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] exiting the outer_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] ENTRY_SIGNAL:outer_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] hello from outer_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] INIT_SIGNAL:outer_state -2019-08-06 06:42:04,586 DEBUG:S: [f2] init -2019-08-06 06:42:04,587 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:04,587 DEBUG:S: [f2] ENTRY_SIGNAL:middle_state -2019-08-06 06:42:04,587 DEBUG:S: [f2] arming one-shot -2019-08-06 06:42:04,587 DEBUG:S: [f2] INIT_SIGNAL:middle_state -2019-08-06 06:42:04,587 DEBUG:S: [f2] <- Queued:(1) Deferred:(0) -2019-08-06 06:42:04,588 DEBUG:S: [f2] BROADCAST:middle_state -2019-08-06 06:42:04,588 DEBUG:S: [f2] BROADCAST:outer_state -2019-08-06 06:42:04,588 DEBUG:S: [f2] received broadcast -2019-08-06 06:42:04,588 DEBUG:S: [f2] BROADCAST:outer_state:HOOK -2019-08-06 06:42:04,588 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:05,572 DEBUG:T: [f1] e->Ready() middle_state->inner_state -2019-08-06 06:42:05,572 DEBUG:S: [f1] Ready:middle_state -2019-08-06 06:42:05,572 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:05,572 DEBUG:S: [f1] ENTRY_SIGNAL:inner_state -2019-08-06 06:42:05,572 DEBUG:S: [f1] hello from inner_state 1 -2019-08-06 06:42:05,572 DEBUG:S: [f1] INIT_SIGNAL:inner_state -2019-08-06 06:42:05,573 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:05,584 DEBUG:T: [f2] e->Ready() middle_state->inner_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] Ready:middle_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] ENTRY_SIGNAL:inner_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] hello from new inner_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] INIT_SIGNAL:inner_state -2019-08-06 06:42:05,585 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:06,575 DEBUG:T: [f1] e->Reset() inner_state->middle_state -2019-08-06 06:42:06,575 DEBUG:S: [f1] Reset:inner_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] Reset:middle_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] Reset:outer_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] EXIT_SIGNAL:inner_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] exiting inner_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] EXIT_SIGNAL:middle_state -2019-08-06 06:42:06,576 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] EXIT_SIGNAL:outer_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] exiting the outer_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] ENTRY_SIGNAL:outer_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] hello from outer_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] INIT_SIGNAL:outer_state -2019-08-06 06:42:06,577 DEBUG:S: [f1] init -2019-08-06 06:42:06,577 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state -2019-08-06 06:42:06,578 DEBUG:S: [f1] ENTRY_SIGNAL:middle_state -2019-08-06 06:42:06,578 DEBUG:S: [f1] arming one-shot -2019-08-06 06:42:06,578 DEBUG:S: [f1] INIT_SIGNAL:middle_state -2019-08-06 06:42:06,578 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) -2019-08-06 06:42:07,576 DEBUG:T: [f1] e->Ready() middle_state->inner_state -2019-08-06 06:42:07,576 DEBUG:S: [f1] Ready:middle_state -2019-08-06 06:42:07,576 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state -2019-08-06 06:42:07,577 DEBUG:S: [f1] ENTRY_SIGNAL:inner_state -2019-08-06 06:42:07,577 DEBUG:S: [f1] hello from inner_state 2 -2019-08-06 06:42:07,577 DEBUG:S: [f1] INIT_SIGNAL:inner_state -2019-08-06 06:42:07,577 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,701 DEBUG:T: [f1] e->start_at() top->middle_state +2019-09-21 09:53:01,701 DEBUG:S: [f1] START +2019-09-21 09:53:01,701 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:outer_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] ENTRY_SIGNAL:outer_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] hello from outer_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] INIT_SIGNAL:outer_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] init +2019-09-21 09:53:01,702 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] ENTRY_SIGNAL:middle_state +2019-09-21 09:53:01,702 DEBUG:S: [f1] arming one-shot +2019-09-21 09:53:01,703 DEBUG:S: [f1] INIT_SIGNAL:middle_state +2019-09-21 09:53:01,703 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,703 DEBUG:S: [f1] Hook:middle_state +2019-09-21 09:53:01,704 DEBUG:S: [f1] Hook:outer_state +2019-09-21 09:53:01,704 DEBUG:S: [f1] run some code, but don't transition +2019-09-21 09:53:01,704 DEBUG:S: [f1] Hook:outer_state:HOOK +2019-09-21 09:53:01,704 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,706 DEBUG:T: [f2] e->start_at() top->inner_state +2019-09-21 09:53:01,706 DEBUG:S: [f2] START +2019-09-21 09:53:01,706 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:outer_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] ENTRY_SIGNAL:outer_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] hello from outer_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] ENTRY_SIGNAL:middle_state +2019-09-21 09:53:01,707 DEBUG:S: [f2] arming one-shot +2019-09-21 09:53:01,707 DEBUG:S: [f2] ENTRY_SIGNAL:inner_state +2019-09-21 09:53:01,708 DEBUG:S: [f2] hello from new inner_state +2019-09-21 09:53:01,708 DEBUG:S: [f2] INIT_SIGNAL:inner_state +2019-09-21 09:53:01,708 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,708 DEBUG:S: [f1] Send_Broadcast:middle_state +2019-09-21 09:53:01,709 DEBUG:S: [f1] Send_Broadcast:outer_state +2019-09-21 09:53:01,709 DEBUG:S: [f1] PUBLISH:(BROADCAST, PRIORITY:1000) +2019-09-21 09:53:01,709 DEBUG:S: [f1] Send_Broadcast:outer_state:HOOK +2019-09-21 09:53:01,709 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,710 DEBUG:S: [f1] BROADCAST:middle_state +2019-09-21 09:53:01,710 DEBUG:S: [f1] BROADCAST:outer_state +2019-09-21 09:53:01,710 DEBUG:S: [f1] received broadcast +2019-09-21 09:53:01,710 DEBUG:S: [f1] BROADCAST:outer_state:HOOK +2019-09-21 09:53:01,710 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:01,711 DEBUG:S: [f2] Hook:inner_state +2019-09-21 09:53:01,711 DEBUG:S: [f2] Hook:middle_state +2019-09-21 09:53:01,711 DEBUG:S: [f2] Hook:outer_state +2019-09-21 09:53:01,711 DEBUG:S: [f2] run some code, but don't transition +2019-09-21 09:53:01,712 DEBUG:S: [f2] Hook:outer_state:HOOK +2019-09-21 09:53:01,712 DEBUG:S: [f2] <- Queued:(2) Deferred:(0) +2019-09-21 09:53:01,714 DEBUG:T: [f2] e->Reset() inner_state->middle_state +2019-09-21 09:53:01,714 DEBUG:S: [f2] Reset:inner_state +2019-09-21 09:53:01,714 DEBUG:S: [f2] Reset:middle_state +2019-09-21 09:53:01,714 DEBUG:S: [f2] Reset:outer_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] EXIT_SIGNAL:inner_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] exiting new inner_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] EXIT_SIGNAL:middle_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] EXIT_SIGNAL:outer_state +2019-09-21 09:53:01,715 DEBUG:S: [f2] exiting the outer_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] ENTRY_SIGNAL:outer_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] hello from outer_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] INIT_SIGNAL:outer_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] init +2019-09-21 09:53:01,716 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] ENTRY_SIGNAL:middle_state +2019-09-21 09:53:01,716 DEBUG:S: [f2] arming one-shot +2019-09-21 09:53:01,717 DEBUG:S: [f2] INIT_SIGNAL:middle_state +2019-09-21 09:53:01,717 DEBUG:S: [f2] <- Queued:(1) Deferred:(0) +2019-09-21 09:53:01,717 DEBUG:S: [f2] BROADCAST:middle_state +2019-09-21 09:53:01,717 DEBUG:S: [f2] BROADCAST:outer_state +2019-09-21 09:53:01,718 DEBUG:S: [f2] received broadcast +2019-09-21 09:53:01,718 DEBUG:S: [f2] BROADCAST:outer_state:HOOK +2019-09-21 09:53:01,718 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:02,709 DEBUG:T: [f1] e->Ready() middle_state->inner_state +2019-09-21 09:53:02,709 DEBUG:S: [f1] Ready:middle_state +2019-09-21 09:53:02,709 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:02,709 DEBUG:S: [f1] ENTRY_SIGNAL:inner_state +2019-09-21 09:53:02,709 DEBUG:S: [f1] hello from inner_state 1 +2019-09-21 09:53:02,710 DEBUG:S: [f1] INIT_SIGNAL:inner_state +2019-09-21 09:53:02,710 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:02,715 DEBUG:T: [f2] e->Ready() middle_state->inner_state +2019-09-21 09:53:02,715 DEBUG:S: [f2] Ready:middle_state +2019-09-21 09:53:02,715 DEBUG:S: [f2] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:02,715 DEBUG:S: [f2] ENTRY_SIGNAL:inner_state +2019-09-21 09:53:02,715 DEBUG:S: [f2] hello from new inner_state +2019-09-21 09:53:02,715 DEBUG:S: [f2] INIT_SIGNAL:inner_state +2019-09-21 09:53:02,716 DEBUG:S: [f2] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:03,707 DEBUG:T: [f1] e->Reset() inner_state->middle_state +2019-09-21 09:53:03,707 DEBUG:S: [f1] Reset:inner_state +2019-09-21 09:53:03,707 DEBUG:S: [f1] Reset:middle_state +2019-09-21 09:53:03,707 DEBUG:S: [f1] Reset:outer_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] EXIT_SIGNAL:inner_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] exiting inner_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] EXIT_SIGNAL:middle_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] EXIT_SIGNAL:outer_state +2019-09-21 09:53:03,708 DEBUG:S: [f1] exiting the outer_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] ENTRY_SIGNAL:outer_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] hello from outer_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] INIT_SIGNAL:outer_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] init +2019-09-21 09:53:03,709 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:middle_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] ENTRY_SIGNAL:middle_state +2019-09-21 09:53:03,709 DEBUG:S: [f1] arming one-shot +2019-09-21 09:53:03,710 DEBUG:S: [f1] INIT_SIGNAL:middle_state +2019-09-21 09:53:03,710 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) +2019-09-21 09:53:04,708 DEBUG:T: [f1] e->Ready() middle_state->inner_state +2019-09-21 09:53:04,709 DEBUG:S: [f1] Ready:middle_state +2019-09-21 09:53:04,709 DEBUG:S: [f1] SEARCH_FOR_SUPER_SIGNAL:inner_state +2019-09-21 09:53:04,709 DEBUG:S: [f1] ENTRY_SIGNAL:inner_state +2019-09-21 09:53:04,709 DEBUG:S: [f1] hello from inner_state 2 +2019-09-21 09:53:04,709 DEBUG:S: [f1] INIT_SIGNAL:inner_state +2019-09-21 09:53:04,709 DEBUG:S: [f1] <- Queued:(0) Deferred:(0) diff --git a/examples/simple_state_15.py b/examples/simple_state_15.py index 5f5c67b..16f91b3 100644 --- a/examples/simple_state_15.py +++ b/examples/simple_state_15.py @@ -3,15 +3,16 @@ import time import logging from functools import partial -from collections import deque from miros import Event from miros import signals from miros import Factory from miros import return_status +from miros import ThreadSafeAttributes +class F1(Factory, ThreadSafeAttributes): -class F1(Factory): + _attributes = ['times_in_inner'] def __init__(self, name, log_file_name=None, live_trace=None, live_spy=None): @@ -23,10 +24,6 @@ def __init__(self, name, log_file_name=None, self.live_spy = \ False if live_spy == None else live_spy - # set up a thread safe ring buffer of size 1 - self._times_in_inner = deque(maxlen=1) - self._times_in_inner.append(0) - self.log_file_name = \ 'simple_state_15.log' if log_file_name == None else log_file_name @@ -79,14 +76,6 @@ def __init__(self, name, log_file_name=None, nest(self.middle_state, parent=self.outer_state). \ nest(self.inner_state, parent=self.middle_state) - @property - def times_in_inner(self): - return self._times_in_inner[-1] - - @times_in_inner.setter - def times_in_inner(self, value): - self._times_in_inner.append(value) - def trace_callback(self, trace): '''trace without datetime-stamp''' trace_without_datetime = re.search(r'(\[.+\]) (\[.+\].+)', trace).group(2) diff --git a/examples/thread_safe_attributes_1.py b/examples/thread_safe_attributes_1.py index 513085d..2a09164 100644 --- a/examples/thread_safe_attributes_1.py +++ b/examples/thread_safe_attributes_1.py @@ -1,64 +1,64 @@ -import time -from threading import Thread -from threading import Event as ThreadEvent - -from miros import ThreadSafeAttributes - -class GetLock1(ThreadSafeAttributes): - _attributes = ['thread_safe_attr_1'] - - def __init__(self, evt): - '''running within main thread''' - self.evt = evt - self.thread_safe_attr_1 = 0 - - def thread_method_1(self): - '''running within th1 thread''' - while(self.evt.is_set()): - self.thread_safe_attr_1 += 1 - print("th1: ", self.thread_safe_attr_1) - time.sleep(0.020) - -class GetLock2(): - def __init__(self, evt, gl1): - '''running within main thread''' - self.evt = evt - self.gl1 = gl1 - - def thread_method_2(self): - '''running within th2 thread''' - while(self.evt.is_set()): - self.gl1.thread_safe_attr_1 -= 1 - print("th2: ", self.gl1.thread_safe_attr_1) - time.sleep(0.020) - -class ThreadKiller(): - def __init__(self, evt, count_down): - '''running within main thread''' - self.evt = evt - self.kill_time = count_down - - def thread_stopper(self): - '''running within killer thread''' - time.sleep(self.kill_time) - self.evt.clear() - -# main thread: -evt = ThreadEvent() -evt.set() - -gl1 = GetLock1(evt) -gl2 = GetLock2(evt, gl1=gl1) -killer = ThreadKiller(evt, count_down=0.1) - -threads = [] -threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) -threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) - -for thread in threads: - thread.start() - -thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) -thread_stopper.start() -thread_stopper.join() - +import time +from threading import Thread +from threading import Event as ThreadEvent + +from miros import ThreadSafeAttributes + +class GetLock1(ThreadSafeAttributes): + _attributes = ['thread_safe_attr_1'] + + def __init__(self, evt): + '''running within main thread''' + self.evt = evt + self.thread_safe_attr_1 = 0 + + def thread_method_1(self): + '''running within th1 thread''' + while(self.evt.is_set()): + self.thread_safe_attr_1 += 1 + print("th1: ", self.thread_safe_attr_1) + time.sleep(0.020) + +class GetLock2(): + def __init__(self, evt, gl1): + '''running within main thread''' + self.evt = evt + self.gl1 = gl1 + + def thread_method_2(self): + '''running within th2 thread''' + while(self.evt.is_set()): + self.gl1.thread_safe_attr_1 -= 1 + print("th2: ", self.gl1.thread_safe_attr_1) + time.sleep(0.020) + +class ThreadKiller(): + def __init__(self, evt, count_down): + '''running within main thread''' + self.evt = evt + self.kill_time = count_down + + def thread_stopper(self): + '''running within killer thread''' + time.sleep(self.kill_time) + self.evt.clear() + +# main thread: +evt = ThreadEvent() +evt.set() + +gl1 = GetLock1(evt) +gl2 = GetLock2(evt, gl1=gl1) +killer = ThreadKiller(evt, count_down=0.1) + +threads = [] +threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) +threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) + +for thread in threads: + thread.start() + +thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) +thread_stopper.start() +thread_stopper.join() + diff --git a/examples/thread_safe_attributes_2.py b/examples/thread_safe_attributes_2.py index 1ef819c..e5c3997 100644 --- a/examples/thread_safe_attributes_2.py +++ b/examples/thread_safe_attributes_2.py @@ -1,66 +1,66 @@ -import time -from threading import RLock -from threading import Thread -from threading import Event as ThreadEvent - -class GetLock1(): - - def __init__(self, evt): - '''running within main thread''' - self._rlock = RLock() - self.evt = evt - self.thread_safe_attr_1 = 0 - - def thread_method_1(self): - '''running within th1 thread''' - while(self.evt.is_set()): - with self._rlock: - self.thread_safe_attr_1 += 1 - with self._rlock: - print("th1: ", self.thread_safe_attr_1) - time.sleep(0.020) - -class GetLock2(): - def __init__(self, evt, gl1): - '''running within main thread''' - self.evt = evt - self.gl1 = gl1 - - def thread_method_2(self): - '''running within th2 thread''' - while(self.evt.is_set()): - with self.gl1._rlock: - self.gl1.thread_safe_attr_1 -= 1 - with self.gl1._rlock: - print("th2: ", self.gl1.thread_safe_attr_1) - time.sleep(0.020) - -class ThreadKiller(): - def __init__(self, evt, count_down): - '''running within main thread''' - self.evt = evt - self.kill_time = count_down - - def thread_stopper(self): - '''running within killer thread''' - time.sleep(self.kill_time) - self.evt.clear() - -evt = ThreadEvent() -evt.set() - -gl1 = GetLock1(evt) -gl2 = GetLock2(evt, gl1=gl1) -killer = ThreadKiller(evt, count_down=0.1) - -threads = [] -threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) -threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) - -for thread in threads: - thread.start() - -thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) -thread_stopper.start() -thread_stopper.join() - +import time +from threading import RLock +from threading import Thread +from threading import Event as ThreadEvent + +class GetLock1(): + + def __init__(self, evt): + '''running within main thread''' + self._rlock = RLock() + self.evt = evt + self.thread_safe_attr_1 = 0 + + def thread_method_1(self): + '''running within th1 thread''' + while(self.evt.is_set()): + with self._rlock: + self.thread_safe_attr_1 += 1 + with self._rlock: + print("th1: ", self.thread_safe_attr_1) + time.sleep(0.020) + +class GetLock2(): + def __init__(self, evt, gl1): + '''running within main thread''' + self.evt = evt + self.gl1 = gl1 + + def thread_method_2(self): + '''running within th2 thread''' + while(self.evt.is_set()): + with self.gl1._rlock: + self.gl1.thread_safe_attr_1 -= 1 + with self.gl1._rlock: + print("th2: ", self.gl1.thread_safe_attr_1) + time.sleep(0.020) + +class ThreadKiller(): + def __init__(self, evt, count_down): + '''running within main thread''' + self.evt = evt + self.kill_time = count_down + + def thread_stopper(self): + '''running within killer thread''' + time.sleep(self.kill_time) + self.evt.clear() + +evt = ThreadEvent() +evt.set() + +gl1 = GetLock1(evt) +gl2 = GetLock2(evt, gl1=gl1) +killer = ThreadKiller(evt, count_down=0.1) + +threads = [] +threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) +threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) + +for thread in threads: + thread.start() + +thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) +thread_stopper.start() +thread_stopper.join() + diff --git a/examples/thread_safe_attributes_3_unsafe.py b/examples/thread_safe_attributes_3_unsafe.py index 32afc7a..a7616ee 100644 --- a/examples/thread_safe_attributes_3_unsafe.py +++ b/examples/thread_safe_attributes_3_unsafe.py @@ -1,60 +1,60 @@ -import time -from threading import Thread -from threading import Event as ThreadEvent - -class GetLock1(): - - def __init__(self, evt): - '''running within main thread''' - self.evt = evt - self.thread_safe_attr_1 = 0 - - def thread_method_1(self): - '''running within th1 thread''' - while(self.evt.is_set()): - self.thread_safe_attr_1 += 1 - print("th1: ", self.thread_safe_attr_1) - time.sleep(0.020) - -class GetLock2(): - def __init__(self, evt, gl1): - '''running within main thread''' - self.evt = evt - self.gl1 = gl1 - - def thread_method_2(self): - '''running within th2 thread''' - while(self.evt.is_set()): - self.gl1.thread_safe_attr_1 -= 1 - print("th2: ", self.gl1.thread_safe_attr_1) - time.sleep(0.020) - -class ThreadKiller(): - def __init__(self, evt, count_down): - '''running within main thread''' - self.evt = evt - self.kill_time = count_down - - def thread_stopper(self): - '''running within killer thread''' - time.sleep(self.kill_time) - self.evt.clear() - -evt = ThreadEvent() -evt.set() - -gl1 = GetLock1(evt) -gl2 = GetLock2(evt, gl1=gl1) -killer = ThreadKiller(evt, count_down=0.1) - -threads = [] -threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) -threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) - -for thread in threads: - thread.start() - -thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) -thread_stopper.start() -thread_stopper.join() - +import time +from threading import Thread +from threading import Event as ThreadEvent + +class GetLock1(): + + def __init__(self, evt): + '''running within main thread''' + self.evt = evt + self.thread_safe_attr_1 = 0 + + def thread_method_1(self): + '''running within th1 thread''' + while(self.evt.is_set()): + self.thread_safe_attr_1 += 1 + print("th1: ", self.thread_safe_attr_1) + time.sleep(0.020) + +class GetLock2(): + def __init__(self, evt, gl1): + '''running within main thread''' + self.evt = evt + self.gl1 = gl1 + + def thread_method_2(self): + '''running within th2 thread''' + while(self.evt.is_set()): + self.gl1.thread_safe_attr_1 -= 1 + print("th2: ", self.gl1.thread_safe_attr_1) + time.sleep(0.020) + +class ThreadKiller(): + def __init__(self, evt, count_down): + '''running within main thread''' + self.evt = evt + self.kill_time = count_down + + def thread_stopper(self): + '''running within killer thread''' + time.sleep(self.kill_time) + self.evt.clear() + +evt = ThreadEvent() +evt.set() + +gl1 = GetLock1(evt) +gl2 = GetLock2(evt, gl1=gl1) +killer = ThreadKiller(evt, count_down=0.1) + +threads = [] +threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) +threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) + +for thread in threads: + thread.start() + +thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) +thread_stopper.start() +thread_stopper.join() + diff --git a/examples/thread_safe_attributes_4.py b/examples/thread_safe_attributes_4.py index c2c2a27..bfd8ec9 100644 --- a/examples/thread_safe_attributes_4.py +++ b/examples/thread_safe_attributes_4.py @@ -1,71 +1,71 @@ -import math -import time -from threading import Thread -from threading import Event as ThreadEvent - -from miros import ThreadSafeAttributes - -class GetLock1(ThreadSafeAttributes): - _attributes = ['a'] - - def __init__(self, evt): - '''running within main thread''' - self.evt = evt - self.a = 0 - - def thread_method_1(self): - '''running within th1 thread''' - _, _lock = self.a - while(self.evt.is_set()): - with _lock: - self.a = 0.35 - b = self.a * math.cos(0.45) + 3 * self.a ** 1.2 - print("th1: ", b) - time.sleep(0.020) - -class GetLock2(): - def __init__(self, evt, gl1): - '''running within main thread''' - self.evt = evt - self.gl1 = gl1 - - def thread_method_2(self): - '''running within th2 thread''' - _, _lock = self.gl1.a - while(self.evt.is_set()): - with _lock: - self.gl1.a = 0.30 - b = self.gl1.a * math.cos(0.45) + 3 * self.gl1.a ** 1.2 - print("th2: ", b) - time.sleep(0.020) - -class ThreadKiller(): - def __init__(self, evt, count_down): - '''running within main thread''' - self.evt = evt - self.kill_time = count_down - - def thread_stopper(self): - '''running within killer thread''' - time.sleep(self.kill_time) - self.evt.clear() - -# main thread: -evt = ThreadEvent() -evt.set() - -gl1 = GetLock1(evt) -gl2 = GetLock2(evt, gl1=gl1) -killer = ThreadKiller(evt, count_down=0.1) - -threads = [] -threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) -threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) - -for thread in threads: - thread.start() - -thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) -thread_stopper.start() -thread_stopper.join() - +import math +import time +from threading import Thread +from threading import Event as ThreadEvent + +from miros import ThreadSafeAttributes + +class GetLock1(ThreadSafeAttributes): + _attributes = ['a'] + + def __init__(self, evt): + '''running within main thread''' + self.evt = evt + self.a = 0 + + def thread_method_1(self): + '''running within th1 thread''' + _, _lock = self.a + while(self.evt.is_set()): + with _lock: + self.a = 0.35 + b = self.a * math.cos(0.45) + 3 * self.a ** 1.2 + print("th1: ", b) + time.sleep(0.020) + +class GetLock2(): + def __init__(self, evt, gl1): + '''running within main thread''' + self.evt = evt + self.gl1 = gl1 + + def thread_method_2(self): + '''running within th2 thread''' + _, _lock = self.gl1.a + while(self.evt.is_set()): + with _lock: + self.gl1.a = 0.30 + b = self.gl1.a * math.cos(0.45) + 3 * self.gl1.a ** 1.2 + print("th2: ", b) + time.sleep(0.020) + +class ThreadKiller(): + def __init__(self, evt, count_down): + '''running within main thread''' + self.evt = evt + self.kill_time = count_down + + def thread_stopper(self): + '''running within killer thread''' + time.sleep(self.kill_time) + self.evt.clear() + +# main thread: +evt = ThreadEvent() +evt.set() + +gl1 = GetLock1(evt) +gl2 = GetLock2(evt, gl1=gl1) +killer = ThreadKiller(evt, count_down=0.1) + +threads = [] +threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) +threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) + +for thread in threads: + thread.start() + +thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) +thread_stopper.start() +thread_stopper.join() + diff --git a/examples/thread_safe_attributes_5.py b/examples/thread_safe_attributes_5.py index 4965e3d..a2f910a 100644 --- a/examples/thread_safe_attributes_5.py +++ b/examples/thread_safe_attributes_5.py @@ -1,69 +1,69 @@ -import math -import time -from threading import Thread -from threading import Event as ThreadEvent - -from miros import ThreadSafeAttributes - -def equation(a): - return a * math.cos(0.45) + 3 * a ** 1.2 - -class GetLock1(ThreadSafeAttributes): - - def __init__(self, evt): - '''running within main thread''' - self.evt = evt - self.thread_race_attr_1 = 0 - - def thread_method_1(self): - '''running within th1 thread''' - while(self.evt.is_set()): - self.thread_race_attr_1 = 0.35 - self.thread_race_attr_1 = self.thread_race_attr_1 * math.cos(0.45) - self.thread_race_attr_1 = 3 * self.thread_race_attr_1 ** 1.2 - print("th1: ", equation(self.thread_race_attr_1)) - -class GetLock2(): - def __init__(self, evt, gl1): - '''running within main thread''' - self.evt = evt - self.gl1 = gl1 - - def thread_method_2(self): - '''running within th2 thread''' - while(self.evt.is_set()): - self.gl1.thread_race_attr_1 = 0.30 - self.gl1.hread_race_attr_1 = self.gl1.thread_race_attr_1 * math.cos(0.45) - self.gl1.hread_race_attr_1 = 3 * self.gl1.thread_race_attr_1 ** 1.2 - print("th2: ", equation(self.gl1.thread_race_attr_1)) - -class ThreadKiller(): - def __init__(self, evt, count_down): - '''running within main thread''' - self.evt = evt - self.kill_time = count_down - - def thread_stopper(self): - '''running within killer thread''' - time.sleep(self.kill_time) - self.evt.clear() - -# main thread: -evt = ThreadEvent() -evt.set() - -gl1 = GetLock1(evt) -gl2 = GetLock2(evt, gl1=gl1) -killer = ThreadKiller(evt, count_down=0.1) - -threads = [] -threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) -threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) - -for thread in threads: - thread.start() - -thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) -thread_stopper.start() -thread_stopper.join() - +import math +import time +from threading import Thread +from threading import Event as ThreadEvent + +from miros import ThreadSafeAttributes + +def equation(a): + return a * math.cos(0.45) + 3 * a ** 1.2 + +class GetLock1(ThreadSafeAttributes): + + def __init__(self, evt): + '''running within main thread''' + self.evt = evt + self.thread_race_attr_1 = 0 + + def thread_method_1(self): + '''running within th1 thread''' + while(self.evt.is_set()): + self.thread_race_attr_1 = 0.35 + self.thread_race_attr_1 = self.thread_race_attr_1 * math.cos(0.45) + self.thread_race_attr_1 = 3 * self.thread_race_attr_1 ** 1.2 + print("th1: ", equation(self.thread_race_attr_1)) + +class GetLock2(): + def __init__(self, evt, gl1): + '''running within main thread''' + self.evt = evt + self.gl1 = gl1 + + def thread_method_2(self): + '''running within th2 thread''' + while(self.evt.is_set()): + self.gl1.thread_race_attr_1 = 0.30 + self.gl1.hread_race_attr_1 = self.gl1.thread_race_attr_1 * math.cos(0.45) + self.gl1.hread_race_attr_1 = 3 * self.gl1.thread_race_attr_1 ** 1.2 + print("th2: ", equation(self.gl1.thread_race_attr_1)) + +class ThreadKiller(): + def __init__(self, evt, count_down): + '''running within main thread''' + self.evt = evt + self.kill_time = count_down + + def thread_stopper(self): + '''running within killer thread''' + time.sleep(self.kill_time) + self.evt.clear() + +# main thread: +evt = ThreadEvent() +evt.set() + +gl1 = GetLock1(evt) +gl2 = GetLock2(evt, gl1=gl1) +killer = ThreadKiller(evt, count_down=0.1) + +threads = [] +threads.append(Thread(target=gl1.thread_method_1, name='th1', args=())) +threads.append(Thread(target=gl2.thread_method_2, name='th2', args=())) + +for thread in threads: + thread.start() + +thread_stopper = Thread(target=killer.thread_stopper, name='stopper', args=()) +thread_stopper.start() +thread_stopper.join() + diff --git a/miros/thread_safe_attributes_broken.py b/miros/thread_safe_attributes_broken.py deleted file mode 100644 index 62fd687..0000000 --- a/miros/thread_safe_attributes_broken.py +++ /dev/null @@ -1,25 +0,0 @@ -# to test this: -# pytest -s -m thread_safe_attributes_broken -from collections import deque - -class ThreadSafeAttribute: - - def __init__(self, initial_value=None): - self._initial_value = initial_value - self._value = deque(maxlen=1) - self._value.append(self._initial_value) - - def __get__(self, obj, objtype): - return self._value[-1] - - def __set__(self, obj, val): - self._value.append(val) - -class MetaThreadSafeAttributes(type): - - def __init__(cls, *args, **kwargs): - '''Build thread safe attributes''' - if hasattr(cls, '_attributes'): - for name in list(set(cls._attributes)): - setattr(cls, name, ThreadSafeAttribute(initial_value=0)) - super().__init__(*args, **kwargs) diff --git a/plan/Explain_how_the_thing_works.wiki b/plan/Explain_how_the_thing_works.wiki index 8fb67e5..6ce1236 100644 --- a/plan/Explain_how_the_thing_works.wiki +++ b/plan/Explain_how_the_thing_works.wiki @@ -1,62 +1,62 @@ -[[subgoals.thread_safe_attributes|back]] -*Explain How thread safe attribute Work* -= Observer ([[local:C:/Vim8.1/.vim/ooda.pdf|diagram]]) = -== Evidence ([[evidence.Explain_how_the_thing_works|evidence]]) == - - *What defines Success?:* ([[subgoals.Explain_how_the_thing_works|subgoals]]) - "The best is the enemy of the good" - Voltaire - - A simple well written description of how to use the thread-safe attribute - features of miros. Explain why it should be used sparingly, and explain how - to deal with difficult situations (where _locks are required). - - Place the way that it was designed into the examples folder. It should be - explained since there could be issues with it, if there are invite critique. - - *What is the problem?* - They need to know why they would use it in the first place (race conditions) - If they use the feature they will slow down their code. - It breaks the spirit of why you would use a statechart in the first place. - It uses weird syntax. - It's still very useful, and if used sparingly will keep the code clean. - There is an example of how to do things right (philosopher's problem), this - could serve as a nice point to jump into the example, and another shout out - to Miro Samek. - -= Orient = - *Symptoms:* [[symptoms.Explain_how_the_thing_works|symptoms]] - -== Questions == - *Questions for Explain How thread safe attribute Work: >= 5* - Can you think of anyway to speed up your feedback cycle? - Do you have enough information? - Is your information organized? - Can you reproduce the problem? - Can you simplify your test? - Do you have something to baseline from? - What recently changed? - What don't you know that you need to know? - - *Assumptions* - -= Decide = - [[#Questions]] - *Research Needed For?:* - - - [[#Questions]] - *Idea/Hypotheses for Explain How thread safe attribute Work: >=5* - * [ ] - * [ ] - * [ ] - * [ ] - * [ ] - - *Chosen Research-Goal/Hypothesis* - - -== Plan ([[plan.Explain_how_the_thing_works|plan]]) == - -= Act = - - +[[subgoals.thread_safe_attributes|back]] +*Explain How thread safe attribute Work* += Observer ([[local:C:/Vim8.1/.vim/ooda.pdf|diagram]]) = +== Evidence ([[evidence.Explain_how_the_thing_works|evidence]]) == + + *What defines Success?:* ([[subgoals.Explain_how_the_thing_works|subgoals]]) + "The best is the enemy of the good" - Voltaire + + A simple well written description of how to use the thread-safe attribute + features of miros. Explain why it should be used sparingly, and explain how + to deal with difficult situations (where _locks are required). + + Place the way that it was designed into the examples folder. It should be + explained since there could be issues with it, if there are invite critique. + + *What is the problem?* + They need to know why they would use it in the first place (race conditions) + If they use the feature they will slow down their code. + It breaks the spirit of why you would use a statechart in the first place. + It uses weird syntax. + It's still very useful, and if used sparingly will keep the code clean. + There is an example of how to do things right (philosopher's problem), this + could serve as a nice point to jump into the example, and another shout out + to Miro Samek. + += Orient = + *Symptoms:* [[symptoms.Explain_how_the_thing_works|symptoms]] + +== Questions == + *Questions for Explain How thread safe attribute Work: >= 5* + Can you think of anyway to speed up your feedback cycle? + Do you have enough information? + Is your information organized? + Can you reproduce the problem? + Can you simplify your test? + Do you have something to baseline from? + What recently changed? + What don't you know that you need to know? + + *Assumptions* + += Decide = + [[#Questions]] + *Research Needed For?:* + + + [[#Questions]] + *Idea/Hypotheses for Explain How thread safe attribute Work: >=5* + * [ ] + * [ ] + * [ ] + * [ ] + * [ ] + + *Chosen Research-Goal/Hypothesis* + + +== Plan ([[plan.Explain_how_the_thing_works|plan]]) == + += Act = + + diff --git a/plan/subgoals.Explain_how_the_thing_works.wiki b/plan/subgoals.Explain_how_the_thing_works.wiki index 6084b0c..774dc71 100644 --- a/plan/subgoals.Explain_how_the_thing_works.wiki +++ b/plan/subgoals.Explain_how_the_thing_works.wiki @@ -1,3 +1,3 @@ -[[Explain_how_the_thing_works|back]] -= Subgoals = -* [ ] +[[Explain_how_the_thing_works|back]] += Subgoals = +* [ ] diff --git a/plan/subgoals.Make_a_test_that_can_break_the_old_method.wiki b/plan/subgoals.Make_a_test_that_can_break_the_old_method.wiki index 8df9abb..68daf17 100644 --- a/plan/subgoals.Make_a_test_that_can_break_the_old_method.wiki +++ b/plan/subgoals.Make_a_test_that_can_break_the_old_method.wiki @@ -1,3 +1,3 @@ -[[Make_a_test_that_can_break_the_old_method|back]] -= Subgoals = -* [ ] +[[Make_a_test_that_can_break_the_old_method|back]] += Subgoals = +* [ ] diff --git a/setup.py b/setup.py index 037f4dd..f078039 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ py_modules=['miros'], # https://packaging.python.org/en/latest/single_source_version.html - version='4.1.3', + version='4.1.4', description='A statechart library for Python', long_description=long_description, diff --git a/test/valgrind-python.supp b/test/valgrind-python.supp index d616f69..30a26c4 100644 --- a/test/valgrind-python.supp +++ b/test/valgrind-python.supp @@ -1,390 +1,390 @@ -# -# This is a valgrind suppression file that should be used when using valgrind. -# -# Here's an example of running valgrind: -# -# cd python/dist/src -# valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ -# ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network -# -# You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER -# to use the preferred suppressions with Py_ADDRESS_IN_RANGE. -# -# If you do not want to recompile Python, you can uncomment -# suppressions for PyObject_Free and PyObject_Realloc. -# -# See Misc/README.valgrind for more information. - -# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Addr4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 4 - Memcheck:Value4 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) - Memcheck:Value8 - fun:Py_ADDRESS_IN_RANGE -} - -{ - ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value - Memcheck:Cond - fun:Py_ADDRESS_IN_RANGE -} - -# -# Leaks (including possible leaks) -# Hmmm, I wonder if this masks some real leaks. I think it does. -# Will need to fix that. -# - -{ - Suppress leaking the GIL. Happens once per process, see comment in ceval.c. - Memcheck:Leak - fun:malloc - fun:PyThread_allocate_lock - fun:PyEval_InitThreads -} - -{ - Suppress leaking the GIL after a fork. - Memcheck:Leak - fun:malloc - fun:PyThread_allocate_lock - fun:PyEval_ReInitThreads -} - -{ - Suppress leaking the autoTLSkey. This looks like it shouldn't leak though. - Memcheck:Leak - fun:malloc - fun:PyThread_create_key - fun:_PyGILState_Init - fun:Py_InitializeEx - fun:Py_Main -} - -{ - Hmmm, is this a real leak or like the GIL? - Memcheck:Leak - fun:malloc - fun:PyThread_ReInitTLS -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:realloc - fun:_PyObject_GC_Resize - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_New - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -{ - Handle PyMalloc confusing valgrind (possibly leaked) - Memcheck:Leak - fun:malloc - fun:_PyObject_GC_NewVar - fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING -} - -# -# Non-python specific leaks -# - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:calloc - fun:allocate_dtv - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -{ - Handle pthread issue (possibly leaked) - Memcheck:Leak - fun:memalign - fun:_dl_allocate_tls_storage - fun:_dl_allocate_tls -} - -###{ -### ADDRESS_IN_RANGE/Invalid read of size 4 -### Memcheck:Addr4 -### fun:PyObject_Free -###} -### -###{ -### ADDRESS_IN_RANGE/Invalid read of size 4 -### Memcheck:Value4 -### fun:PyObject_Free -###} -### -###{ -### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value -### Memcheck:Cond -### fun:PyObject_Free -###} - -###{ -### ADDRESS_IN_RANGE/Invalid read of size 4 -### Memcheck:Addr4 -### fun:PyObject_Realloc -###} -### -###{ -### ADDRESS_IN_RANGE/Invalid read of size 4 -### Memcheck:Value4 -### fun:PyObject_Realloc -###} -### -###{ -### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value -### Memcheck:Cond -### fun:PyObject_Realloc -###} - -### -### All the suppressions below are for errors that occur within libraries -### that Python uses. The problems to not appear to be related to Python's -### use of the libraries. -### - -{ - Generic ubuntu ld problems - Memcheck:Addr8 - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so - obj:/lib/ld-2.4.so -} - -{ - Generic gentoo ld problems - Memcheck:Cond - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so - obj:/lib/ld-2.3.4.so -} - -{ - DBM problems, see test_dbm - Memcheck:Param - write(buf) - fun:write - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_close -} - -{ - DBM problems, see test_dbm - Memcheck:Value8 - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - DBM problems, see test_dbm - Memcheck:Cond - fun:memmove - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - obj:/usr/lib/libdb1.so.2 - fun:dbm_store - fun:dbm_ass_sub -} - -{ - GDBM problems, see test_gdbm - Memcheck:Param - write(buf) - fun:write - fun:gdbm_open - -} - -{ - ZLIB problems, see test_gzip - Memcheck:Cond - obj:/lib/libz.so.1.2.3 - obj:/lib/libz.so.1.2.3 - fun:deflate -} - -{ - Avoid problems w/readline doing a putenv and leaking on exit - Memcheck:Leak - fun:malloc - fun:xmalloc - fun:sh_set_lines_and_columns - fun:_rl_get_screen_size - fun:_rl_init_terminal_io - obj:/lib/libreadline.so.4.3 - fun:rl_initialize -} - -### -### These occur from somewhere within the SSL, when running -### test_socket_sll. They are too general to leave on by default. -### -###{ -### somewhere in SSL stuff -### Memcheck:Cond -### fun:memset -###} -###{ -### somewhere in SSL stuff -### Memcheck:Value4 -### fun:memset -###} -### -###{ -### somewhere in SSL stuff -### Memcheck:Cond -### fun:MD5_Update -###} -### -###{ -### somewhere in SSL stuff -### Memcheck:Value4 -### fun:MD5_Update -###} - -# -# All of these problems come from using test_socket_ssl -# -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_bin2bn -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_num_bits_word -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:BN_num_bits_word -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_mod_exp_mont_word -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BN_mod_exp_mont -} - -{ - from test_socket_ssl - Memcheck:Param - write(buf) - fun:write - obj:/usr/lib/libcrypto.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:RSA_verify -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:RSA_verify -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:DES_set_key_unchecked -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:DES_encrypt2 -} - -{ - from test_socket_ssl - Memcheck:Cond - obj:/usr/lib/libssl.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Value4 - obj:/usr/lib/libssl.so.0.9.7 -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:BUF_MEM_grow_clean -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:memcpy - fun:ssl3_read_bytes -} - -{ - from test_socket_ssl - Memcheck:Cond - fun:SHA1_Update -} - -{ - from test_socket_ssl - Memcheck:Value4 - fun:SHA1_Update -} - +# +# This is a valgrind suppression file that should be used when using valgrind. +# +# Here's an example of running valgrind: +# +# cd python/dist/src +# valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ +# ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network +# +# You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER +# to use the preferred suppressions with Py_ADDRESS_IN_RANGE. +# +# If you do not want to recompile Python, you can uncomment +# suppressions for PyObject_Free and PyObject_Realloc. +# +# See Misc/README.valgrind for more information. + +# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Addr4 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Value4 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) + Memcheck:Value8 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value + Memcheck:Cond + fun:Py_ADDRESS_IN_RANGE +} + +# +# Leaks (including possible leaks) +# Hmmm, I wonder if this masks some real leaks. I think it does. +# Will need to fix that. +# + +{ + Suppress leaking the GIL. Happens once per process, see comment in ceval.c. + Memcheck:Leak + fun:malloc + fun:PyThread_allocate_lock + fun:PyEval_InitThreads +} + +{ + Suppress leaking the GIL after a fork. + Memcheck:Leak + fun:malloc + fun:PyThread_allocate_lock + fun:PyEval_ReInitThreads +} + +{ + Suppress leaking the autoTLSkey. This looks like it shouldn't leak though. + Memcheck:Leak + fun:malloc + fun:PyThread_create_key + fun:_PyGILState_Init + fun:Py_InitializeEx + fun:Py_Main +} + +{ + Hmmm, is this a real leak or like the GIL? + Memcheck:Leak + fun:malloc + fun:PyThread_ReInitTLS +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:realloc + fun:_PyObject_GC_Resize + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:malloc + fun:_PyObject_GC_New + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:malloc + fun:_PyObject_GC_NewVar + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +# +# Non-python specific leaks +# + +{ + Handle pthread issue (possibly leaked) + Memcheck:Leak + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls_storage + fun:_dl_allocate_tls +} + +{ + Handle pthread issue (possibly leaked) + Memcheck:Leak + fun:memalign + fun:_dl_allocate_tls_storage + fun:_dl_allocate_tls +} + +###{ +### ADDRESS_IN_RANGE/Invalid read of size 4 +### Memcheck:Addr4 +### fun:PyObject_Free +###} +### +###{ +### ADDRESS_IN_RANGE/Invalid read of size 4 +### Memcheck:Value4 +### fun:PyObject_Free +###} +### +###{ +### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value +### Memcheck:Cond +### fun:PyObject_Free +###} + +###{ +### ADDRESS_IN_RANGE/Invalid read of size 4 +### Memcheck:Addr4 +### fun:PyObject_Realloc +###} +### +###{ +### ADDRESS_IN_RANGE/Invalid read of size 4 +### Memcheck:Value4 +### fun:PyObject_Realloc +###} +### +###{ +### ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value +### Memcheck:Cond +### fun:PyObject_Realloc +###} + +### +### All the suppressions below are for errors that occur within libraries +### that Python uses. The problems to not appear to be related to Python's +### use of the libraries. +### + +{ + Generic ubuntu ld problems + Memcheck:Addr8 + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so +} + +{ + Generic gentoo ld problems + Memcheck:Cond + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so +} + +{ + DBM problems, see test_dbm + Memcheck:Param + write(buf) + fun:write + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_close +} + +{ + DBM problems, see test_dbm + Memcheck:Value8 + fun:memmove + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + DBM problems, see test_dbm + Memcheck:Cond + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + DBM problems, see test_dbm + Memcheck:Cond + fun:memmove + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + GDBM problems, see test_gdbm + Memcheck:Param + write(buf) + fun:write + fun:gdbm_open + +} + +{ + ZLIB problems, see test_gzip + Memcheck:Cond + obj:/lib/libz.so.1.2.3 + obj:/lib/libz.so.1.2.3 + fun:deflate +} + +{ + Avoid problems w/readline doing a putenv and leaking on exit + Memcheck:Leak + fun:malloc + fun:xmalloc + fun:sh_set_lines_and_columns + fun:_rl_get_screen_size + fun:_rl_init_terminal_io + obj:/lib/libreadline.so.4.3 + fun:rl_initialize +} + +### +### These occur from somewhere within the SSL, when running +### test_socket_sll. They are too general to leave on by default. +### +###{ +### somewhere in SSL stuff +### Memcheck:Cond +### fun:memset +###} +###{ +### somewhere in SSL stuff +### Memcheck:Value4 +### fun:memset +###} +### +###{ +### somewhere in SSL stuff +### Memcheck:Cond +### fun:MD5_Update +###} +### +###{ +### somewhere in SSL stuff +### Memcheck:Value4 +### fun:MD5_Update +###} + +# +# All of these problems come from using test_socket_ssl +# +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_bin2bn +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_num_bits_word +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:BN_num_bits_word +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_mod_exp_mont_word +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_mod_exp_mont +} + +{ + from test_socket_ssl + Memcheck:Param + write(buf) + fun:write + obj:/usr/lib/libcrypto.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:RSA_verify +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:RSA_verify +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:DES_set_key_unchecked +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:DES_encrypt2 +} + +{ + from test_socket_ssl + Memcheck:Cond + obj:/usr/lib/libssl.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Value4 + obj:/usr/lib/libssl.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BUF_MEM_grow_clean +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:memcpy + fun:ssl3_read_bytes +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:SHA1_Update +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:SHA1_Update +} + diff --git a/test/valgrind_simple_1.py b/test/valgrind_simple_1.py index ce8cd52..8c9557d 100644 --- a/test/valgrind_simple_1.py +++ b/test/valgrind_simple_1.py @@ -1,5 +1,5 @@ -# Here is super simple script that shouldn't cause a race condition. -# We will test it with valgrind, if valgrind finds a problem with it we will -# stop using valgrind as a tool for testing race conditions - -print("hello world") +# Here is super simple script that shouldn't cause a race condition. +# We will test it with valgrind, if valgrind finds a problem with it we will +# stop using valgrind as a tool for testing race conditions + +print("hello world") diff --git a/test/valgrind_simple_2.py b/test/valgrind_simple_2.py index 8dc4545..4fbe313 100644 --- a/test/valgrind_simple_2.py +++ b/test/valgrind_simple_2.py @@ -1,147 +1,147 @@ -import re -import time -import logging -from collections import deque -from functools import partial -from collections import namedtuple - -from miros import Event -from miros import signals -from miros import Factory -from miros import return_status - -class InstrumentedFactory(Factory): - def __init__(self, name, *, log_file=None, live_trace=None, live_spy=None): - super().__init__(name) - self.live_trace = False if live_trace == None else live_trace - self.live_spy = False if live_spy == None else live_spy - self.log_file = 'valgrind_simple_2.log' if log_file == None else log_file - - self._output = deque(maxlen=1) - self._output.append(None) - - with open(self.log_file, "w") as fp: - fp.write("") - - logging.basicConfig( - format='%(asctime)s %(levelname)s:%(message)s', - filename=self.log_file, - level=logging.DEBUG) - - self.register_live_spy_callback(partial(self.spy_callback)) - self.register_live_trace_callback(partial(self.trace_callback)) - - @property - def output(self): - return self._output[-1] - - @output.setter - def output(self, item): - self._output.append(item) - - def trace_callback(self, trace): - '''trace without datetimestamp''' - trace_without_datetime = re.search(r'(\[.+\]) (\[.+\].+)', trace).group(2) - logging.debug("T: " + trace_without_datetime) - - def spy_callback(self, spy): - '''spy with machine name pre-pending''' - logging.debug("S: [{}] {}".format(self.name, spy)) - - -class SimpleChartToTestWithValgrind(InstrumentedFactory): - def __init__(self, name, live_trace=None, live_spy=None): - '''Testing a statechart that doesn't share attributes with valgrind - - **Args**: - | ``name`` (str): name of the chart - | ``live_trace=None``: enable live_trace feature? - | ``live_spy=None``: enable live_spy feature? - - **Example(s)**: - - .. code-block:: python - - ao = SimpleChartToTestWithValgrind('valgrind_test') - - ''' - super().__init__(name, live_trace=live_trace, live_spy=live_spy) - - self.c = self.create(state="c"). \ - catch(signal=signals.ENTRY_SIGNAL, - handler=self.c_entry_signal). \ - catch(signal=signals.INIT_SIGNAL, - handler=self.c_init_signal). \ - catch(signal=signals.B, - handler=self.c_b). \ - to_method() - - self.c1 = self.create(state="c1"). \ - catch(signal=signals.ENTRY_SIGNAL, - handler=self.c1_entry_signal). \ - catch(signal=signals.A, - handler=self.c1_a). \ - catch(signal=signals.EXIT_SIGNAL, - handler=self.c1_exit_signal). \ - to_method() - - self.c2 = self.create(state="c2"). \ - catch(signal=signals.ENTRY_SIGNAL, - handler=self.c2_entry_signal). \ - catch(signal=signals.A, - handler=self.c2_a). \ - catch(signal=signals.EXIT_SIGNAL, - handler=self.c2_exit_signal). \ - to_method() - - self.nest(self.c, parent=None). \ - nest(self.c1, parent=self.c). \ - nest(self.c2, parent=self.c) - - self.start_at(self.c) - - def c_entry_signal(self, e): - status = return_status.HANDLED - return status - - def c_init_signal(self, e): - status = self.trans(self.c1) - return status - - def c_b(self, e): - status = self.trans(self.c) - return status - - def c1_entry_signal(self, e): - status = return_status.HANDLED - return status - - def c1_a(self, e): - status = self.trans(self.c2) - return status - - def c1_exit_signal(self, e): - status = return_status.HANDLED - return status - - def c2_entry_signal(self, e): - status = return_status.HANDLED - return status - - def c2_a(self, e): - status = self.trans(self.c1) - return status - - def c2_exit_signal(self, e): - status = return_status.HANDLED - return status - -if __name__ == '__main__': - ao = SimpleChartToTestWithValgrind('valgrind_test', live_trace=True) - ao.post_fifo(Event(signal=signals.A)) - ao.post_fifo(Event(signal=signals.B)) - ao.post_fifo(Event(signal=signals.A)) - ao.post_fifo(Event(signal=signals.B)) - ao.post_fifo(Event(signal=signals.A)) - time.sleep(0.5) - +import re +import time +import logging +from collections import deque +from functools import partial +from collections import namedtuple + +from miros import Event +from miros import signals +from miros import Factory +from miros import return_status + +class InstrumentedFactory(Factory): + def __init__(self, name, *, log_file=None, live_trace=None, live_spy=None): + super().__init__(name) + self.live_trace = False if live_trace == None else live_trace + self.live_spy = False if live_spy == None else live_spy + self.log_file = 'valgrind_simple_2.log' if log_file == None else log_file + + self._output = deque(maxlen=1) + self._output.append(None) + + with open(self.log_file, "w") as fp: + fp.write("") + + logging.basicConfig( + format='%(asctime)s %(levelname)s:%(message)s', + filename=self.log_file, + level=logging.DEBUG) + + self.register_live_spy_callback(partial(self.spy_callback)) + self.register_live_trace_callback(partial(self.trace_callback)) + + @property + def output(self): + return self._output[-1] + + @output.setter + def output(self, item): + self._output.append(item) + + def trace_callback(self, trace): + '''trace without datetimestamp''' + trace_without_datetime = re.search(r'(\[.+\]) (\[.+\].+)', trace).group(2) + logging.debug("T: " + trace_without_datetime) + + def spy_callback(self, spy): + '''spy with machine name pre-pending''' + logging.debug("S: [{}] {}".format(self.name, spy)) + + +class SimpleChartToTestWithValgrind(InstrumentedFactory): + def __init__(self, name, live_trace=None, live_spy=None): + '''Testing a statechart that doesn't share attributes with valgrind + + **Args**: + | ``name`` (str): name of the chart + | ``live_trace=None``: enable live_trace feature? + | ``live_spy=None``: enable live_spy feature? + + **Example(s)**: + + .. code-block:: python + + ao = SimpleChartToTestWithValgrind('valgrind_test') + + ''' + super().__init__(name, live_trace=live_trace, live_spy=live_spy) + + self.c = self.create(state="c"). \ + catch(signal=signals.ENTRY_SIGNAL, + handler=self.c_entry_signal). \ + catch(signal=signals.INIT_SIGNAL, + handler=self.c_init_signal). \ + catch(signal=signals.B, + handler=self.c_b). \ + to_method() + + self.c1 = self.create(state="c1"). \ + catch(signal=signals.ENTRY_SIGNAL, + handler=self.c1_entry_signal). \ + catch(signal=signals.A, + handler=self.c1_a). \ + catch(signal=signals.EXIT_SIGNAL, + handler=self.c1_exit_signal). \ + to_method() + + self.c2 = self.create(state="c2"). \ + catch(signal=signals.ENTRY_SIGNAL, + handler=self.c2_entry_signal). \ + catch(signal=signals.A, + handler=self.c2_a). \ + catch(signal=signals.EXIT_SIGNAL, + handler=self.c2_exit_signal). \ + to_method() + + self.nest(self.c, parent=None). \ + nest(self.c1, parent=self.c). \ + nest(self.c2, parent=self.c) + + self.start_at(self.c) + + def c_entry_signal(self, e): + status = return_status.HANDLED + return status + + def c_init_signal(self, e): + status = self.trans(self.c1) + return status + + def c_b(self, e): + status = self.trans(self.c) + return status + + def c1_entry_signal(self, e): + status = return_status.HANDLED + return status + + def c1_a(self, e): + status = self.trans(self.c2) + return status + + def c1_exit_signal(self, e): + status = return_status.HANDLED + return status + + def c2_entry_signal(self, e): + status = return_status.HANDLED + return status + + def c2_a(self, e): + status = self.trans(self.c1) + return status + + def c2_exit_signal(self, e): + status = return_status.HANDLED + return status + +if __name__ == '__main__': + ao = SimpleChartToTestWithValgrind('valgrind_test', live_trace=True) + ao.post_fifo(Event(signal=signals.A)) + ao.post_fifo(Event(signal=signals.B)) + ao.post_fifo(Event(signal=signals.A)) + ao.post_fifo(Event(signal=signals.B)) + ao.post_fifo(Event(signal=signals.A)) + time.sleep(0.5) + diff --git a/test/valgrind_simple_3.py b/test/valgrind_simple_3.py index b16f3dc..8b00a83 100644 --- a/test/valgrind_simple_3.py +++ b/test/valgrind_simple_3.py @@ -1,34 +1,34 @@ -import time -import random -import logging -from threading import Event -from threading import Thread - -logging.basicConfig( - format='%(asctime)s %(levelname)s:%(message)s', - filename='valgrind_simple_3.log', - level=logging.DEBUG) - -def make_test_thread1(name, thread_event): - def thread_runner(name, e): - while e.is_set(): - if random.choice(list(['get', 'set'])) == 'get': - logging.debug(name + str(' get')) - else: - logging.debug(name + str(' set')) - time.sleep(random.uniform(0, 0.5)) - return Thread(target=thread_runner, name=name, daemon=True, args=(name, thread_event)) - -# begin the multithreaded tests -# make an event that can turn off all threads -event = Event() -event.set() - -number_of_threads = 10 - -# create and start the thread -for i in range(number_of_threads): - thread = make_test_thread1("thrd_" + "{0:02}:".format(i), event) - thread.start() - -time.sleep(3) +import time +import random +import logging +from threading import Event +from threading import Thread + +logging.basicConfig( + format='%(asctime)s %(levelname)s:%(message)s', + filename='valgrind_simple_3.log', + level=logging.DEBUG) + +def make_test_thread1(name, thread_event): + def thread_runner(name, e): + while e.is_set(): + if random.choice(list(['get', 'set'])) == 'get': + logging.debug(name + str(' get')) + else: + logging.debug(name + str(' set')) + time.sleep(random.uniform(0, 0.5)) + return Thread(target=thread_runner, name=name, daemon=True, args=(name, thread_event)) + +# begin the multithreaded tests +# make an event that can turn off all threads +event = Event() +event.set() + +number_of_threads = 10 + +# create and start the thread +for i in range(number_of_threads): + thread = make_test_thread1("thrd_" + "{0:02}:".format(i), event) + thread.start() + +time.sleep(3)