Skip to content

feat(api): add non-blocking Tracker close-and-drain completion receipt - #429

Open
ThoriaDevelopment wants to merge 1 commit into
toxicity188:v3from
ThoriaDevelopment:feat/tracker-close-and-drain
Open

ThoriaDevelopment wants to merge 1 commit into
toxicity188:v3from
ThoriaDevelopment:feat/tracker-close-and-drain

Conversation

@ThoriaDevelopment

Copy link
Copy Markdown

Summary

Implements #417: a non-blocking completion receipt that proves a Tracker has fully drained before the plugin removes the owning entity.

Tracker#closeAndDrain() closes the tracker and returns a CompletionStage<Void> that completes only when all tracker-originated work has ended:

  1. no new tracker-originated source or pipeline work can begin;
  2. scheduled recurrence has been cancelled;
  3. any updater invocation already running has returned;
  4. queued or deferred tracker work has either completed or been invalidated;
  5. tracker-originated callbacks and tasks that could access the source have completed or been invalidated;
  6. pipeline despawn and final close work has completed; and
  7. after completion, no future tracker-originated access to the source occurs.

It never blocks and is safe to call from the main server thread:

tracker.closeAndDrain()
    .thenRunAsync(() -> {
        // safe: the tracker can no longer access the source
        entity.remove();
    });

Impacted modules

  • api (kr.toxicity.model.api.tracker.Tracker, CloseReason.DRAIN)
  • api test source set (new: TrackerCloseAndDrainTest + JUnit test dependencies in api/build.gradle.kts)

How it works

  • A quiescence gate inside tick(): once draining is requested, the next completed updater invocation (including one already in flight) runs any still-queued tasks and completes the receipt exactly once.
  • Completion attempts are ordered by the tracker monitor and the ticking flag is set before the quiescence check, so a close racing a running updater can never complete the receipt while the updater is still inside its handler (Dekker-style ordering).
  • close(CloseReason.DRAIN) cancels the scheduler without interrupting the in-flight updater — interrupting could abort pipeline work mid-flight before the receipt completes. Legacy reasons (REMOVE, DESPAWN, PLUGIN_DISABLE) keep their previous interrupt semantics.
  • task(...) rejects new work from the moment draining begins; tasks queued before the drain are flushed before completion or invalidated.
  • start() now refuses to reschedule a closed tracker (a late spawnPacketHandler callback could previously restart the scheduler after close).
  • pipeline.despawn() runs before the receipt completes, satisfying postcondition 6; the constructor's late skin-context callback is invalidated once the tracker closes, satisfying postcondition 5/7.
  • If a queued task or close handler throws, the receipt completes exceptionally instead of hanging; repeated/reentrant calls return the same stage.

Compatibility

  • Purely additive public API (closeAndDrain(), @since 3.4.2, matching the repo's @since versioning convention). No existing signatures changed.
  • close() and shutdown() keep the same interrupt/despawn semantics for all pre-existing reasons. One deliberate behavior change: a throwing close handler is now logged and still fully tears the tracker down (previously teardown was skipped and the exception propagated).

Tests / checks run

  • TrackerCloseAndDrainTest (7 tests, all green): immediate completion when idle; queued-task invalidation after drain; queued work runs before completion; latch-blocked updater defers the receipt until it returns; reentrant/idempotent closes; exceptional completion for failing queued tasks and close handlers.
  • :bettermodel-api:check and :bettermodel-api:checkLicenses — pass.

This contribution was prepared with AI assistance (Codebuff) and reviewed by a human before submission.

Close a tracker and return a completion stage that proves all tracker-originated work has ended, so plugins can safely remove the owning entity without a race. Refs toxicity188#417.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant