Skip to content

Introduce SafeBoxStateManager for better state orchestration #17

@harrytmthy

Description

@harrytmthy

Background

In Issue #12, we introduced lifecycle state tracking via SafeBoxStateListener and SafeBoxGlobalStateObserver to monitor SafeBox transitions (STARTING, WRITING, IDLE, CLOSED). Initially, state emissions were performed directly inside SafeBoxBlobStore, tied to internal write completions.

We also added .closeWhenIdle() to allow SafeBox instances to defer closure until all writes were done. However, this approach had critical timing limitations.


Problem

The original definition of WRITING and IDLE was too granular. SafeBoxBlobStore emitted IDLE after each individual blob write, but this didn’t reflect the actual lifecycle of an edit() or commit() operation at the SafeBox level.

Consider:

safeBox.edit {
    putString("key1", "value1")
    putInt("key2", 2)
}
safeBox.closeWhenIdle()

The flaw:

  • Each .edit() launches a coroutine that may perform multiple blob writes.
  • SafeBoxBlobStore emits IDLE as soon as one write finishes.
  • Meanwhile, .closeWhenIdle() may observe this premature IDLE and begin closing.
  • This can result in WRITING being emitted after CLOSED, leading to race conditions and invalid state flows.

Final Solution

We replaced all write-level state emission logic with a centralized lifecycle coordinator:
SafeBoxStateManager

This internal component:

  • Tracks concurrent edits via atomic counters
  • Emits:
    • STARTING during initial read
    • WRITING once when the first edit begins
    • IDLE only when all edits complete
    • CLOSED after closeWhenIdle() or manual closure
  • Ensures deterministic transitions, even under rapid .apply() / .commit() usage
  • Fully decouples lifecycle concerns from SafeBoxBlobStore

Implementation Highlights

  • All lifecycle events now originate from SafeBoxStateManager
  • SafeBox uses launchCommitWithWritingState and launchApplyWithWritingState to delegate state-aware operations
  • writeCompleted is tracked via CompletableDeferred to block closeWhenIdle() until all writes settle
  • Test coverage added to verify transition order:
    STARTING → WRITING → IDLE → CLOSED
  • Manual loop in test uses Thread.sleep(3) for CPU-friendly polling

Benefits

  • No more premature IDLE emissions
  • Guaranteed CLOSED only after final WRITING ends
  • Supports aggressive async edits without race
  • Clear mental model: states represent full SafeBox lifecycle
  • Simplifies future debugging, instrumentation, and test hooks

Related Issues

Sub-issues

Metadata

Metadata

Assignees

Labels

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions