Class JsonTransaction

java.lang.Object
com.ultikits.ultitools.interfaces.impl.data.json.JsonTransaction

@Internal public final class JsonTransaction extends Object
One JSON transaction's staged writes: for every SimpleJsonDataOperator the transaction has written, an overlay of staged puts and deletes, kept in first-touch order.

A write made inside the transaction is staged here instead of reaching the operator's committed entries. The operator merges the overlay into every read the same transaction makes, so the transaction sees its own writes; every other thread reads the committed entries only. commit() first makes the transaction durable: it writes every staged change to one record of the identity's journal (JsonJournal), forced to disk and renamed into place; the rename is the commit point. It then applies every overlay to its operator's committed entries while holding the identity's visibility write lock once, from before the first operator to after the last, so another thread's read sees all of a commit's changes on every table or none of them, and marks every changed key dirty for the next checkpoint. The identity's commit gate is held, read side, from the record's sequence number to the end of the apply. A transaction that staged nothing writes no record, forces nothing and takes no gate. rollback() drops the overlays; it never touches committed entries, so it cannot undo a write another caller made.

Before its first write to a table the transaction takes that table's write lock (JsonLockTable) and holds it until it commits (after the apply) or rolls back, so no other writer -- another transaction or a plain write -- can change the table in between. It records every hold it takes and releases exactly those, so a transaction that runs while another one on the same thread is suspended never releases the suspended one's holds.

Settings, applied by JsonTransactionManager right after the transaction begins:

  • Lock at first read (REPEATABLE_READ, SERIALIZABLE, and every transaction begun by DataOperator#transaction(...), question 11 A of the Phase 20.4 decisions): the first read of a table also takes its write lock, so the table stays still for the rest of the transaction and a read-modify-write cannot lose an update. The lock is requested before the read takes the visibility read side, never while holding it.
  • Read-only: every write fails with a DataAccessException before it locks or stages anything.
  • Deadline (@Transactional(timeout)): every operation checks it first and fails once it has passed, marking the transaction rollback-only first (as Spring's ResourceHolderSupport#checkTransactionTimeout does), and it bounds every lock wait (the sooner of it and the 5-second default bound).

A failed lock wait marks the transaction rollback-only (question 7 A of the Phase 20.4 decisions). A write, or a REPEATABLE_READ/SERIALIZABLE read, whose table lock cannot be obtained -- the wait reached its bound or the transaction's deadline, it would close a cycle, or the thread was interrupted -- marks this transaction rollback-only before the DataAccessException reaches the caller. Catching the exception does not save the transaction: its outermost commit discards every staged write and throws UnexpectedRollbackException. The mark is the manager's own rollback-only flag, the one a failed nested scope sets. The transaction keeps the table locks it already holds until it ends, like any other open transaction; the failed request itself holds nothing. A refusal of the statement itself -- a write in a read-only transaction, an id that cannot name its file, and the operator's other argument checks -- does not mark the transaction, as a failed statement does not end a MySQL or SQLite transaction.

Staged values are detached copies: no object the caller holds is reachable from an overlay. An instance is confined to the thread whose JsonTransactionManager context holds it (suspended contexts included), so it needs no synchronisation of its own.

Internal to the JSON backend. It lives in the operator's package so it can reach the operator's package-private apply method; JsonTransactionManager creates and drives it.

Since:
6.3.0
  • Field Details

    • STEP_APPLY_BETWEEN_OPERATORS

      static final String STEP_APPLY_BETWEEN_OPERATORS
      Fired inside the visibility write lock, after one operator's overlay is applied and before the next.
      See Also:
    • STEP_APPLIED

      static final String STEP_APPLIED
      Fired after the whole apply, once the visibility write lock is released, before the commit gate and the table locks are.
      See Also:
    • NO_STEP_HOOK

      private static final JsonTransaction.StepHook NO_STEP_HOOK
    • stepHook

      private static volatile JsonTransaction.StepHook stepHook
      Test seam: called with a step name at the points named above. A no-op in production; a test installs a callback that pauses or fails at one step and resets it afterwards.
    • manager

      private final JsonTransactionManager manager
    • overlays

      private final Map<SimpleJsonDataOperator<?>,JsonTransaction.Overlay<?>> overlays
      One overlay per operator this transaction has written, in first-touch order.
    • heldLocks

      private final Map<SimpleJsonDataOperator<?>,JsonLockTable> heldLocks
      The table locks this transaction holds, in the order it took them, with the lock table of each.
    • lockAtFirstRead

      private boolean lockAtFirstRead
      Whether a first read of a table also takes its lock (REPEATABLE_READ, SERIALIZABLE).
    • readOnly

      private boolean readOnly
    • deadlineNanos

      private Long deadlineNanos
      The System.nanoTime() value at which the transaction's timeout passes, or null.
  • Constructor Details

    • JsonTransaction

      public JsonTransaction(JsonTransactionManager manager)
      Created by JsonTransactionManager.begin() for a new outermost transaction.
      Parameters:
      manager - the manager whose context holds this transaction and whose visibility lock the commit publishes under
  • Method Details

    • overlay

      The overlay this transaction keeps for operator, or null if the transaction has not written it.
    • overlayForWrite

      <T extends BaseDataEntity<String>> JsonTransaction.Overlay<T> overlayForWrite(SimpleJsonDataOperator<T> operator)
      The overlay for operator, created (in first-touch order) if this is its first write.
    • setLockAtFirstRead

      public void setLockAtFirstRead(boolean lockAtFirstRead)
      Sets whether a table's first read also takes its write lock. Called by JsonTransactionManager.setIsolationLevel(int); not part of the supported API.
      Parameters:
      lockAtFirstRead - true for REPEATABLE_READ and SERIALIZABLE
    • setReadOnly

      public void setReadOnly(boolean readOnly)
      Sets whether writes are rejected. Called by JsonTransactionManager.setReadOnly(boolean); not part of the supported API.
      Parameters:
      readOnly - true to reject every write of this transaction
    • setDeadlineNanos

      public void setDeadlineNanos(Long deadlineNanos)
      Sets the deadline. Called by JsonTransactionManager.setTimeout(int); not part of the supported API.
      Parameters:
      deadlineNanos - a System.nanoTime() value, or null for none
    • getDeadlineNanos

      public Long getDeadlineNanos()
      The deadline, as a System.nanoTime() value, or null if none is set. Not part of the supported API.
      Returns:
      the deadline or null
    • beforeRead

      void beforeRead(SimpleJsonDataOperator<?> operator)
      Called before a read of operator takes the visibility read side: checks the deadline and, at REPEATABLE_READ or SERIALIZABLE, takes the table's write lock.
      Throws:
      DataAccessException - if the deadline has passed, or the lock wait fails; either first marks this transaction rollback-only
    • beforeWrite

      void beforeWrite(SimpleJsonDataOperator<?> operator)
      Called before a write evaluates or stages anything, never while holding either side of the visibility lock: rejects it in a read-only transaction, checks the deadline, and takes operator's table write lock for the rest of this transaction unless it already holds it.
      Throws:
      DataAccessException - if the transaction is read-only, the deadline has passed, or the lock wait fails (deadlock or lock-wait timeout); an expired deadline and a failed lock wait first mark this transaction rollback-only, so catching the exception does not save it, and its locks are released when it ends; the read-only refusal does not
    • checkDeadline

      private void checkDeadline(SimpleJsonDataOperator<?> operator)
      Refuses the operation once the deadline has passed, marking this transaction rollback-only first, as Spring's ResourceHolderSupport#checkTransactionTimeout calls setRollbackOnly() before it throws TransactionTimedOutException.
    • lock

      private void lock(SimpleJsonDataOperator<?> operator)
      Takes operator's table lock unless this transaction holds it, waiting at most until the sooner bound. A wait that fails marks this transaction rollback-only before the exception propagates.
    • markRollbackOnly

      private void markRollbackOnly()
      Marks this transaction rollback-only, for a failed lock wait or an expired deadline, through the manager's own mechanism, the way TransactionInterceptor records a failed participant: the failed request joins the transaction as one more scope and rolls that scope back, and a rollback at a nested depth sets the manager's rollback-only flag (D-08) without ending the transaction or releasing its locks. Called only from beforeRead(com.ultikits.ultitools.interfaces.impl.data.json.SimpleJsonDataOperator<?>) and beforeWrite(com.ultikits.ultitools.interfaces.impl.data.json.SimpleJsonDataOperator<?>), which an operator calls on its manager's current transaction, so the manager's active context is this transaction's.
    • hasStagedWrites

      boolean hasStagedWrites(SimpleJsonDataOperator<?> operator)
      Whether this transaction has staged any write for operator.
    • commit

      public void commit()
      Commits: with the identity's commit gate held (read side), allocates a sequence number, writes the journal record of every staged change, forced and renamed into place (the commit point), then applies every overlay to its operator's committed entries under the visibility write lock held once across all operators and marks each changed key dirty. Releases the gate, then the table locks. The overlays are discarded and the table locks released whether or not the commit succeeded. A transaction that staged nothing writes no record and takes neither the gate nor the visibility lock.
      Throws:
      DataAccessException - (TRANSACTION_FAILED) if the journal record could not be written; nothing was committed, and the transaction is rolled back
    • hasAnyStagedWrite

      private boolean hasAnyStagedWrite()
    • buildRecord

      private List<JsonJournal.Entry> buildRecord()
      The journal entries of every staged write, operators in first-touch order and each operator's writes in staging order.
      Throws:
      DataAccessException - (TRANSACTION_FAILED) if a staged key cannot name an entity file (it is not one path segment); nothing was committed
    • addEntries

      private static <T extends BaseDataEntity<String>> void addEntries(SimpleJsonDataOperator<?> operator, JsonTransaction.Overlay<?> overlay, List<JsonJournal.Entry> record)
    • writeRecord

      private static void writeRecord(JsonIdentity identity, List<JsonJournal.Entry> record)
      Allocates the record's number and writes the record; its rename is the commit point.
    • rollback

      public void rollback()
      Discards every staged write and releases this transaction's table locks. Committed entries are not touched.
    • applyOverlays

      private void applyOverlays()
    • releaseLocks

      private void releaseLocks()
      Releases every table lock this transaction took, most recent first. A release that fails does not stop the others; the first failure is rethrown afterwards with the rest suppressed.
    • apply

      private static <T extends BaseDataEntity<String>> void apply(SimpleJsonDataOperator<?> operator, JsonTransaction.Overlay<?> overlay)
    • installStepHook

      static void installStepHook(JsonTransaction.StepHook hook)
    • resetStepHook

      static void resetStepHook()