Skip to content

Chunk ticket lifecycle

Scope of this diagram. This chart covers the lifecycle of a single chunk ticket (ChunkReservation) — from allocation on behalf of a pending task, through MemoryTracker registration, through release on every exit path (normal, exception, disconnect, plugin disable). This is the invariant core that S-002 (no permanently force-loaded chunks) and S-005 (no sync chunk loads) both depend on. Related-but-separate behavior paths are intentionally out of scope here: - Who allocates the ticket and why — see diagram 01 (teleport pipeline ReqTicket stage), diagram 02 (cache generator GenerateLocation), diagram 05 (scan crawler), and diagram 08 (per-attempt chunk resolution). All of them funnel through the state machine shown here. - Platform-specific async chunk-load primitive — Spigot PaperLib.getChunkAtAsync, Folia RegionizedWorld.getChunkAtAsync, Fabric ServerWorld.getChunkFuture — see rtp-bukkit / rtp-paper / rtp-folia / rtp-fabric world adapters. - Anvil pre-filter (which avoids needing a ticket at all) — see CODE_TOUR.md §7 nuance on S-005 and ADR-016.

Companion walkthrough: CODE_TOUR.md §4 — Chunk ticket lifecycle.

stateDiagram-v2
    [*] --> ReqTicket : Task Needs Chunk

    %% 1. Chunk Loading Context
    state Chunk_Reservation_Phase {
        ReqTicket : addPluginChunkTicket()
        TrackRes : MemoryTracker.track()
    }

    %% 2. Execution Context
    state Task_Execution_Phase {
        EvalBlocks : Block Evaluation (isSafe)
    }

    %% 3. Safety Net Context (Background GC)
    state Active_Garbage_Collection {
        SweepTask : Periodic Async Sweep
        CheckStale : Is Ticket Older Than Timeout?
        ForceClose : Force reservation.close()
    }

    %% 4. Teardown Context
    state Guaranteed_Teardown_Phase {
        CloseRes : reservation.close() [try-finally]
        DropTicket : removePluginChunkTicket()
        UntrackRes : MemoryTracker.untrack()
    }

    %% Transitions
    ReqTicket --> TrackRes
    TrackRes --> EvalBlocks : Future Completes

    %% Success / Expected Failure Path
    EvalBlocks --> CloseRes : Teleport Done / Invalid Block Found

    %% Memory Leak / Stall Path
    EvalBlocks --> SweepTask : Pipeline Stalls / Exception Thrown

    %% GC Loop
    SweepTask --> CheckStale
    CheckStale --> SweepTask : Ticket Valid (Keep Alive)
    CheckStale --> ForceClose : Ticket Stale (Abandoned)

    %% Teardown Funnel
    CloseRes --> DropTicket
    ForceClose --> DropTicket
    DropTicket --> UntrackRes

    UntrackRes --> [*] : RAM Freed

%% Color legend: green = RAM released (both normal + GC paths converge), red = leak-catch / forced close, blue = async work, yellow = MemoryTracker bookkeeping
    classDef success fill:#b7e4b7,stroke:#1f6b1f,stroke-width:2px,color:#0b2a0b;
    classDef fail    fill:#f4b7b7,stroke:#8a1f1f,color:#3a0b0b;
    classDef async   fill:#cfe2ff,stroke:#1f4e8a,color:#0b1f3a;
    classDef data    fill:#fff2b3,stroke:#8a6d1f,color:#3a2f0b;
    class UntrackRes success
    class ForceClose fail
    class ReqTicket,EvalBlocks,SweepTask async
    class TrackRes,CheckStale,CloseRes,DropTicket data