[otj-users] Teams, Threads and Within: how to deal with them;
how to coordinate them?
Henry Sudhof
hsudhof at cs.tu-berlin.de
Wed Oct 12 10:32:08 CEST 2005
Hi,
For a change I won't use this list to report a bug, but to discuss a
feature instead. The within.
In fact it is indeed rather harmless, as long as only a single thread
is using it: contrary to what one (or rather: I) might expect from a
block statement, its effect is global and not limited to the enclosed
statements. While any thread is executing code inside a "within" block
the corresponding team is active for _all_ threads.
The language definition leaves no room for speculation about this:
------------------------------------snip
§5.2. Explicit Team activation
(a) Activation block
A Team can be activated by the block construct within (myTeam) { stmts }
If stmts has only one statement this can be abbreviated to within
(myTeam) stmt In these statements, myTeam must denote a Team instance.
For the time of executing this block, this Team instance is activated.
The within block statement guarantees that it leaves the team in exactly
the same activation state as it was in when entering this block. This
includes the cases of exceptions, meaning that deactivation will also
occur if the execution of the block terminates abnormally.
(b) Imperative activation
Each team class inherits the methods activate() and deactivate() from
the predefined class org.objectteams.Team (super class of all team
classes). These methods allow to control Team activation disregarding
the block structure of the program.
Note, that methods activate() and deacivate() make no guarantees with
respect to exceptions.
(c) Multiple and mixed activations
* If activate() is invoked on a team instance that has been
explicitly activated before, this statement has no effect at all (note
the difference in §5.3(a) below).
The same applies to deactivating an inactive team.
* If a team was already active when entering a within block, it will
remain active after leaving the block.
* If the team was active on entry of a within block and if
deactivate() is invoked on the same team instance from within the within
block, leaving the block will re-activate the team.
------------------------------------snap
Note the sentence : "For the time of executing this block, this Team
instance is activated."
Hence, the described behaviour is intended .
I argue that this definition and implementation has serious side-effects
in multi-threaded environments, which are not trivial to workaround.
To make that case, I'll throw in my findings from a row of smaller
experiments (I am aware that this might reach down to the very core of
team activation/deactivation).
A)
Issue #1: §5.2(c)-2 the implementation behaves exactly as it should; but
that might cause interesting behaviour:
Assume a program running in two threads: t1 and t2 sharing one team X.
The events occur in this order:
Thread t1 enters a within block for X
Thread t2 enters a within block for X. Note that the team is already
active because t1 is executing a within block.
Thread t1 leaves the within block. (trick-)Question #1: should X be
deactivated? It was inactive when t1 entered the block.
Thread t2 leaves the within block. Question #2: t2 entered the within
block when X was active, should X stay active/ be re-activated?
OT indeed does answer both questions with "yes", causing a race condition.
Issue #2: §5.2(c)-3: The team has not to be deactivated from code
within the "within" block to create the described behaviour.
Deactivation from another thread (possibly unaware of the "within"
block) will(and should!) have the very same result.
I concede the point that technically the whole program is inside the
within-block, while any thread is inside it.
But the simple problem is nonetheless present: if another thread
activates/deactivates a team while another thread is inside a "within"
block, the team will revert globally to the state it held before
entering the within. This constitutes a race condition requiring manual
synchronization, which is not explicitly covered by the language
definition. Alas, this is probably more appropriate for a "programmer's
guide" than the definition.
C) The constructive part (not quite yet).
The question arises: How to handle the situation.
People might try to write something like this, as synchronizing within
blocks appears to be a good idea. This basically takes out any danger of
another thread changing the team's activation state.
--------------------------------------
synchronized(testTeam){
within(testTeam){
....
}
}
--------------------------------------
Which naturally raises a new issue entirely: the "totally unanticipated
team activation" or "oops, sorry about being late" effect (so far my
proposals to name this).
For those wondering "where is the problem": consider this
--------------------------------------
synchronized(testTeam){
within(testTeam){
... //we *know* that the team is active here
}
}
//but here all bets are off. <-!!!
--------------------------------------
Apparently this guarantees the desired thread safety at the cost of some
performance. No other thread will mess with the activation(*) _while_ a
thread is inside the synchronized block. But that doesn't take out the
global semantics of the team activation:
Assume a program running in two threads: t1 and t2 sharing one team x of
the type X. A has a bound role with a callin on a base class B's method
m. Let b be an instance of B.
The events occur in this order:
Thread t1 enters a synchronized block for x Thread t1 enters a within
block for x Thread t2 calls b.m . As t1 has the lock on x, neither the
callin nor the method will be executed yet Thread t1 leaves the within
block. x will be deactivated Thread t1 leaves the synchronized block. t2
obtains the lock on x The callin for b.m gets executed in t2, despite x
being no longer active. This causes implicit activation of x (!).
This shows somewhat of a grey area regarding §5.3. : implicit
activation can happen without externalized roles and/or team-level
methods as well.
Also, this means that a team might become become active, despite no
other thread activating it explicitely (not even implicitly).
D) The real constructive part (;-) )
A few ways to deal with threads I found useful:
The graceful way to temporarily deactivate a team without causing undue
trouble for other threads.
--------------------------------------
synchronized(testTeam){
//we don't know anything here
within(testTeam){
testTeam.deactivate();
.... //we *know* that the team is inactive here
}//gracefully return to the previous state
//the team should be in the same state as before again
}// from here on all bets are off
--------------------------------------
Guard predicates can be used to limit Teams to certain threads. Not the
most elegant solution, but it does work.
--------------------------------------
public class team OneThreadTeam
base when(Thread.currentThread() == myThread){
Thread myThread = ...
--------------------------------------
Also, threadlocal variables holding a team can be quite powerful, when
used with a guard predicate, as the example above.
--------------------------------------
ThreadLocal threadLocalTeam = new ThreadLocal(); Team t =
(Team)(threadBase.threadLocalTeam .get());
within(t){ // note that t should be chained to its thread(s) by a
guard predicate
...
--------------------------------------
So:
- mixing within and activate/deactivate, especially in multi-threaded
programs, may lead to unexpected behaviour
- using "within" in multi-threaded programs may require synchronization
- synchronizing "within" blocks may lead to unexpected behaviour (...)
- reusable Teams (*cough*) need to address thread safety/ be robust
enough to be used in a multi-threaded environment
I wonder, if others had/have good ideas to deal with threads in OT/J.
Especially beyond the mere of dealing with their management, but to
actually have a benefit of using them (one such area is/might be the
asynchronous execution of callins).
But also it would be interesting for me to know, if there are any plans
for future developments in this area.
Thanks in advance for your feedback;
Regards,
Henry
* Actually that is only half the truth: The statements only holds true
_while_ inside the synchronized block. Other, unsynchronized, within
blocks might still have side-effects on the state after leaving the block.
More information about the otj-users
mailing list