Class ReentryGuard<T>
- Type Parameters:
T
- the type used to record information about a violation. It cannot beVoid
.
One example where this has been applied deals with updating actions upon changes in context. If,
in the course of determining which actions are enabled, one of the isEnabled
methods
displays an error dialog, the Swing thread reenters its main loop while that dialog is showing,
but before isEnabled
has returned. This can cause all sorts of unexpected behaviors.
Namely, a timer could fire, context could change again, etc., and the list of actions being
updated may also change. At worst, this could result in many exceptions being thrown, because a
data structure has been modified concurrently. At best, if the loop is allowed to finish, there's
a lot of wasted time updating actions that will never be displayed.
In that example, the loop that updates the actions would be the "guarded block." Any point at which the list of actions is modified might result in "reentrant access" and should be checked.
This class provides a primitive for instrumenting, detecting, and properly reacting to such conditions. For example, if the modification should not be allowed at all, the guard can throw an exception at the moment of reentrant access. Alternatively, if the modification should be allowed, the guard would simply set a flag, then the guarded block can check that flag and terminate early.
This implementation is not thread safe. It is designed to check for reentrant access,
not concurrent access. The client must ensure that only one thread enters the guarded block or
calls checkAccess()
at a time. Otherwise, the behavior is undefined.
public class ActionManager { private final ReentryGuard<Throwable> reentryGuard = new ReentryGuard<>() { @Override public Throwable violated(boolean nested, Throwable previous) { if (previous != null) { return previous; } return new Throwable(); // record the stack of the violation } }; private final List<Action> actions; public void addAction(Action action) { // Notify the guard we've committed some reentrant behavior. // Would need to add this to anything that modifies the action list. reentryGuard.checkAccess(); actions.add(action); } public void updateActions(Context ctx) { try (Guarded guarded = reentryGuard.enter()) { // There is no need to create a copy, since we'll bail before the next iteration for (Action action : actions) { boolean enabled = action.isEnabledForContext(ctx); if (reentryGuard.getViolation() != null) { break; // Actions has been modified. Bail. // NOTE: This leaves the update incomplete. // Something has to call updateActions again. } actions.setEnabled(enabled); } } } }
-
Nested Class Summary
-
Constructor Summary
-
Method Summary
-
Constructor Details
-
ReentryGuard
public ReentryGuard()
-
-
Method Details
-
enter
Notify the guard of entry into the guarded blockThis should always be used in a
try-with-resources
block. This will ensure that the guard is notified of exit from the guarded block, even in exceptional circumstances.NOTE: Re-entering the guarded portion is itself a violation.
- Returns:
- a closeable for notifying the guard of exit from the guarded block, or null if reentering the guarded block
-
checkAccess
public void checkAccess()Notify the guard of access to some resource used by the guarded blockIf the access turns out to be reentrant, i.e., the thread's current stack includes a frame in the guarded block, this will call
violated(boolean, Object)
and record the result. It can be inspected later viagetViolation()
. -
violated
Record a violationThis method is called if
checkAccess()
orenter()
is called while already inside a guarded block. Its return value is stored for later inspection bygetViolation()
. It's possible multiple violations occur within one execution of the guarded block. The previous return value of this method is provided, if that is the case. To record only the first violation, this method should justreturn previous
when it is non-null. To record only the last violation, this method should disregardprevious
. To record all violations,T
will need to be a collection, and this method will need to create and/or append to the collection.- Parameters:
nested
- true if the violation is a nested call toenter()
; false if the violation is a call tocheckAccess()
previous
- the previous return value of this method, on the occasion of multiple violations- Returns:
- the record of the violation
-
getViolation
Retrieve a violation, if applicableCalling this method outside of a guarded block has undefined behavior.
- Returns:
- the violation; or null to indicate no violation
-
isViolated
public boolean isViolated()Check if there is a violationThis is equivalent to checking if
getViolation()
returns non-null.- Returns:
- true if there is a violation.
-