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

@Internal public final class JsonIdentity extends Object
The durable state shared by every JSON table of one identity (a plugin, an external data folder, or one directly constructed operator): its redo journal, the sequence numbers its records draw from, the commit gate, the checkpoint mutex, the registry of its operators and the generation counter behind their dirty marks (20.4-RESEARCH.md section 12.7).

Commit gate. A commit holds the gate's read side from allocating its record's sequence number until its changes are in the operators' committed entries; a write outside a transaction holds it while it applies. A checkpoint takes the write side only to capture the dirty entries and allocate its own number, so every commit numbered below the checkpoint is fully applied before the capture and every other commit is numbered above it.

Checkpoint. checkpoint() writes every entry changed since the last checkpoint, of every operator of the identity, to its file through one checkpoint record, then truncates the journal. Checkpoints of one identity run one at a time, from capture to clearing the dirty marks.

Recovery. recoverOnce() replays the identity's committed records into its table files before any of its tables is read. Success is remembered; a refusal (a damaged record, an unreadable file) is not, so the next request for one of the identity's operators runs recovery again, for example after the server operator has moved a damaged record aside.

Internal to the JSON backend. Public only because JsonTransactionManager, in another package, holds one; not part of the supported API.

Since:
6.3.0
  • Field Details

    • STEP_CHECKPOINT_CAPTURED

      static final String STEP_CHECKPOINT_CAPTURED
      Fired after a checkpoint's capture and number allocation, with the gate released, before its record is written.
      See Also:
    • STEP_CHECKPOINT_TRUNCATED

      static final String STEP_CHECKPOINT_TRUNCATED
      Fired after a checkpoint's truncation, before its dirty marks are cleared.
      See Also:
    • NO_STEP_HOOK

      private static final JsonIdentity.StepHook NO_STEP_HOOK
    • stepHook

      private static volatile JsonIdentity.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.
    • name

      private final String name
      The identity's name, used only in messages.
    • journal

      private final JsonJournal journal
    • commitGate

      private final ReentrantReadWriteLock commitGate
      See the class javadoc. Lock order: table locks, then this, then the visibility lock.
    • checkpointMutex

      private final ReentrantLock checkpointMutex
      Held by a checkpoint from its capture until its dirty marks are cleared: one checkpoint at a time.
    • operators

      private final List<SimpleJsonDataOperator<?>> operators
      The identity's operators, in registration order.
    • generations

      private final AtomicLong generations
      Source of dirty-mark generations; a mark's generation changes whenever its key is written again.
    • recovered

      private boolean recovered
      Whether recoverOnce() has succeeded; guarded by this.
  • Constructor Details

    • JsonIdentity

      private JsonIdentity(String name, Path root, Path journalDirectory)
  • Method Details

    • atRoot

      static JsonIdentity atRoot(String name, Path root)
      The identity of a plugin or an external data folder: its tables are the sub-directories of root, and its journal is <root>/.ultitools-journal.
      Parameters:
      name - the identity's name, for messages
      root - the directory whose sub-directories are the identity's tables
      Returns:
      the identity
    • forStandaloneTable

      static JsonIdentity forStandaloneTable(String tableDirectory)
      The identity of one directly constructed operator: its table directory's parent is the root, and its journal is <table dir>.ultitools-journal, a sibling of the table directory, so the table directory never holds a journal file.
      Parameters:
      tableDirectory - the operator's table directory
      Returns:
      the identity
    • name

      public String name()
      The identity's name, for messages.
      Returns:
      the plugin name, data-folder path or table directory this identity stands for
    • journal

      JsonJournal journal()
    • commitGate

    • checkpointMutex

      ReentrantLock checkpointMutex()
    • tableName

      String tableName(String tableDirectory)
      The name a journal record gives tableDirectory: its path relative to the identity root. A table directory that is not a direct child of the root gives a name the journal refuses, so a record can never write outside the table's own directory.
    • register

      void register(SimpleJsonDataOperator<?> operator)
      Adds operator to the operators a checkpoint captures.
    • operators

      List<SimpleJsonDataOperator<?>> operators()
    • nextGeneration

      long nextGeneration()
      A generation no dirty mark of this identity has carried before.
    • recoverOnce

      void recoverOnce()
      Brings the identity's table files to the state of its committed journal records, once. Called before any of the identity's tables is read. Success is remembered; a refusal is not, so the next call runs recovery again.
      Throws:
      DataAccessException - naming the file if a committed record is damaged (DATA_INTEGRITY_VIOLATION, question 8 option A), naming the file, table and id if a committed record holds an id this file system cannot name (DATA_PERSISTENCE_FAILED, question 12 option A), or DATA_PERSISTENCE_FAILED if a journal or table file cannot be read, written or deleted, or a table or journal directory cannot be forced to disk; either way the identity stays closed and the next request runs recovery again
    • checkpoint

      void checkpoint()
      Writes every entry changed since the last checkpoint to its file, crash atomically, and truncates the journal (20.4-RESEARCH.md section 12.7). In order:
      1. take the checkpoint mutex, so checkpoints of this identity run one at a time, waiting for one already running;
      2. under the commit gate's write side, capture every dirty key of every operator with its generation and its committed value or absence, and allocate the checkpoint's sequence number K; nothing captured: return without writing anything;
      3. write checkpoint record K, then the captured entity files (deleting those of deleted entries), force the table directories, and delete every record numbered up to K in ascending order, K last (JsonJournal.checkpoint(long, List));
      4. clear each captured dirty mark whose generation is unchanged, so a key written again after the capture stays dirty for the next checkpoint.
      Because a commit holds the gate's read side from its number to its apply, every commit numbered below K is in the captured values and every later one is numbered above K, so the truncation never drops a commit.
      Throws:
      DataAccessException - (DATA_PERSISTENCE_FAILED) if a record or entity file cannot be written, moved or deleted, or a journal or table directory cannot be forced to disk; the dirty marks stay, so the next checkpoint writes the same entries again
    • entriesOf

      private List<JsonJournal.Entry> entriesOf(List<JsonIdentity.Captured> captured)
    • installStepHook

      static void installStepHook(JsonIdentity.StepHook hook)
    • resetStepHook

      static void resetStepHook()
    • fire

      static void fire(String step)