Class JsonLockTable
JsonStore, shared by every operator
the store hands out (an operator constructed directly keeps its own). A table is one
SimpleJsonDataOperator, so one entity type of one plugin.
Ownership. A lock is owned by a thread, with a hold count. A thread that already owns a
table takes it again at once (the count goes up), so a REQUIRES_NEW or
NOT_SUPPORTED body re-enters the locks of the transaction it suspended instead of
waiting for itself. Each caller releases exactly the holds it took; the table is free when the
count is back at zero.
Waiting. A request for a table another thread owns registers the caller as waiting for
that table and waits until the table is free or the caller's deadline has passed; past the
deadline it fails with a DataAccessException naming the lock-wait timeout, also when it
wakes after the deadline to find the table free: the deadline is checked before every attempt
that follows the registration. The caller
chooses the deadline: the default bound (5 seconds, question 7 option A of the Phase 20.4
decisions) from the time of the request, or the transaction's own timeout deadline if that comes
sooner.
Deadlocks. Before it registers, a requester follows the wait-for chain from the table's
owner (the owner, the table that owner waits for, that table's owner, and so on). A chain that
leads back to the requester means its wait would close a cycle: the request fails at once with a
DataAccessException naming the deadlock, without registering. Its transaction is marked
rollback-only (JsonTransaction) and releases its locks when it ends, so the other side
can go on. No global lock order is needed, which matters because module code touches tables in
any order.
One monitor. Every owner, hold count and waiter registration is guarded by one lock and changed only while holding it, and every wait is on that lock's one condition. The cycle check and the registration are one critical section, so of two requests that close a cycle at the same moment the second always sees the first one's registration and fails, while the first waits and then acquires.
Lock order. A table lock is always requested before, never while holding, either side of the identity's visibility lock; the visibility write side is always the last lock a thread takes. The two mechanisms therefore cannot wait for each other in a cycle.
- Since:
- 6.3.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classThe owning thread of one table and how many holds it has on it. -
Field Summary
FieldsModifier and TypeFieldDescription(package private) static final longThe default upper bound of one lock wait, in milliseconds (question 7, option A).private longprivate final Map<SimpleJsonDataOperator<?>, JsonLockTable.Hold> The owner and hold count of every table currently held.private final ReentrantLockprivate static final JsonTransaction.StepHookprivate final ConditionSignalled whenever a table becomes free.(package private) static final StringFired under the monitor right after a waiter has registered, before it starts waiting.private static JsonTransaction.StepHookTest seam: called withSTEP_WAITER_REGISTERED.private final Map<Thread, SimpleJsonDataOperator<?>> The table each waiting thread is waiting for. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescription(package private) voidacquire(SimpleJsonDataOperator<?> table, long deadlineNanos) Takestablefor the calling thread: at once if it is free or the caller already owns it, otherwise after waiting until it is free.(package private) longThe deadline of a wait that starts now and is bounded only by the default.private voidfailIfWaitingClosesACycle(SimpleJsonDataOperator<?> table, Thread caller) Follows the wait-for chain fromtable's owner; reachingcallermeans its wait would close a cycle.(package private) static voidprivate DataAccessExceptionlockWaitTimeout(SimpleJsonDataOperator<?> table, long start) Called holding the monitor.(package private) voidrelease(SimpleJsonDataOperator<?> table) Gives back one hold the calling thread took ontable; the table becomes free, and every waiter is woken, when the thread's last hold is given back.(package private) static void(package private) voidsetDefaultWaitMillis(long millis) Test seam: sets the default upper bound of one lock wait.private booleantryTake(SimpleJsonDataOperator<?> table, Thread caller) Takestableif it is free or owned bycaller.
-
Field Details
-
DEFAULT_LOCK_WAIT_MILLIS
static final long DEFAULT_LOCK_WAIT_MILLISThe default upper bound of one lock wait, in milliseconds (question 7, option A).- See Also:
-
STEP_WAITER_REGISTERED
Fired under the monitor right after a waiter has registered, before it starts waiting.- See Also:
-
NO_STEP_HOOK
-
stepHook
Test seam: called withSTEP_WAITER_REGISTERED. A no-op in production; a test installs a callback that only counts down a latch (it must never block, since it runs holding the monitor) and resets it afterwards. -
monitor
-
released
Signalled whenever a table becomes free. -
holds
The owner and hold count of every table currently held. Guarded bymonitor. -
waitsFor
The table each waiting thread is waiting for. Guarded bymonitor. -
defaultWaitNanos
private volatile long defaultWaitNanos
-
-
Constructor Details
-
JsonLockTable
JsonLockTable()
-
-
Method Details
-
setDefaultWaitMillis
void setDefaultWaitMillis(long millis) Test seam: sets the default upper bound of one lock wait. -
defaultDeadlineNanos
long defaultDeadlineNanos()The deadline of a wait that starts now and is bounded only by the default. -
acquire
Takestablefor the calling thread: at once if it is free or the caller already owns it, otherwise after waiting until it is free.- Parameters:
table- the table to lockdeadlineNanos- theSystem.nanoTime()value past which the wait fails- Throws:
DataAccessException- if waiting would close a wait-for cycle (deadlock), if the deadline passes first (lock-wait timeout), or if the thread is interrupted while waiting
-
release
Gives back one hold the calling thread took ontable; the table becomes free, and every waiter is woken, when the thread's last hold is given back.- Throws:
IllegalStateException- if the calling thread does not owntable
-
installStepHook
-
resetStepHook
static void resetStepHook() -
tryTake
Takestableif it is free or owned bycaller. Called holding the monitor. -
failIfWaitingClosesACycle
Follows the wait-for chain fromtable's owner; reachingcallermeans its wait would close a cycle. Called holding the monitor, right beforecallerregisters. -
lockWaitTimeout
Called holding the monitor.
-