fleetd #512 (part 1): log a positive completion line when drainAll finishes #522
Reference in New Issue
Block a user
Delete Branch "worker/512-drain-complete-line-7edd71-3"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #512, part 1 only (the Java/SessionManager half). Part 2 (the script-side detection in
scripts/redeploy-fleetd.sh) is deliberately left for a separate PR/ticket per the issue's own
scope note, and is being worked by another session in parallel.
Defect
SessionManager.drainAlldrained cleanly with no log output at all — both existing log calls(
drainSnapshot's per-session-failurelog.warn, anddrainAll's straggler-sweeplog.warn) siton abnormal paths. So a clean drain and a drain that died on the first session were indistinguishable
in the log: neither produced a line. A script trying to detect a dead shutdown drain (the failure
mode is an uncaught exception on a shutdown thread — never goes through the logger, no ERROR token)
had no positive signal to assert on.
Fix
One
log.infoat the end ofdrainAll:print the line, so it isn't gated behind
if (released > 0)or similar.drainSnapshotpasses (the main snapshot and the post-loop straggler sweep) are folded intothe ONE line, so a caller can't mistake a two-pass drain for two separate drains.
abandonedcounts sessions that were stillBUSYat the moment they were released — i.e. thewhole-drain deadline passed before they left
BUSYon their own.Design choices
drainSnapshotnow returns a small private recordDrainTally(released, abandoned)instead ofvoid, and the two tallies are combined withtally.plus(...).drainAllitself keeps itsvoidsignature — no test or caller needed a wider change.release(String paneId, ReleaseCause cause)overload now returns the removedMemberSession(wasvoid) sodrainSnapshotcan read itsstate()at the moment of removal —reusing the exact same "was it BUSY when it went?" check that
logPreservedForShutdownalreadymakes for its own WARN line. This overload has exactly one call site (inside
drainSnapshot); thepublic
release(String)overload discards the return value as before, so no other caller neededa change. Picked this over threading an
AtomicInteger/holder through both methods because itchanges fewer signatures and reuses an existing state check instead of duplicating it.
"drain complete: "for a script to grep on, numbers inreleased=/abandoned=placeholders rather than in the prefix.Suggested grep for the redeploy script (part 2)
or, to also read the numbers:
Tests
drainAllLogsACompletionLineWithTheRealCountsOnACleanDrain— two READY (non-BUSY) sessions,asserts
released=2 abandoned=0on the clean path.drainAllLogsANonZeroAbandonedCountForASessionStillBusyAtTheDeadline— reuses the existingBUSY/READY fixture from
drainAllReleasesBusyAndReadySessionsAndWaitsForBusy(the BUSY sessionnever leaves BUSY, so the drain spins the full real-time timeout and releases it anyway), asserts
released=2 abandoned=1.SessionManagerlogger toLevel.INFObefore acting and restore it tonullinfinally— an earlier test in the same class (onTurnFailedIsLoggedAtWarnWithThePriorState)sets that same logger to
Level.WARNand never restores it, which silently swallowed the newlog.infoon first attempt (caught by running the whole class, not just the new tests inisolation).
Mutation proof
Mutation A — commented out
released++;indrainSnapshot(so the line always reportsreleased=0):grep -n 'MUTATION-CB512-A'→ present (rc=0);grep -n 'released++;'→ absent (rc=1) — the 0/1pair proving the mutation actually applied, not a shell-expansion false positive.
Tests run: 2, Failures: 2, Errors: 0, Skipped: 0, exit 1. Failing assertions:expected: <true> but was: <false>ondrain complete: released=0 abandoned=0 …anddrain complete: released=0 abandoned=1 ….shasum -a 256ofSessionManager.javabefore/after: byte-identical(
d21ecd3adb3f66933e2f248a8ec81a2c324bfee323a3cc30da525842c33ea80a).Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, exit 0.Mutation B —
if (false && removed != null && removed.state() == MemberSession.State.BUSY)(the abandoned branch made unreachable):
grep -n 'MUTATION-CB512-B'→ present (rc=0);grep -nfor the un-mutatedif (removed != null && removed.state() == MemberSession.State.BUSY) {line → absent (rc=1).Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, exit 1. Failingassertion:
expected: <true> but was: <false>ondrain complete: released=2 abandoned=0 ….Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, exit 0.Build
mvn -f fleetd/pom.xml clean install— unpiped, redirected to a file,echo $?on its own line:exit=0. Final aggregate line:
Tests run: 1698, Failures: 0, Errors: 0, Skipped: 0.BUILD SUCCESS.Other silent-on-success teardown paths found in this class (not fixed — separate ticket, per scope)
reapIdle(SessionManager.java): a successfully reaped session is only logged atdebug(
"reaping idle session …"); the method's own aggregate reap count (itsintreturn value) isnever logged anywhere — its only caller,
SessionReaper.loop(), discards it. So a normal reapsweep — 0 or N sessions reaped — produces no visible (INFO+) log line, only a
warnif onesession's release throws. Same shape as the
drainAlldefect this PR fixes.releaseRemoved's ordinary-cause path: every plain release (reap, explicit stop, normalcompletion) is logged only at
debug("releasing session pane=… cause=…"), while every nearbyabnormal branch (dirty worktree preserved, worktree-remove failure) is
warn. At default INFOlevel a normal single-session teardown is invisible while any snag in it is loud — narrower than
the
drainAllcase (this fires per-session, not as an aggregate), but the same asymmetry.Not touched (out of scope, per the brief)
scripts/redeploy-fleetd.sh— left alone; another worker is editing it for a different ticket,and #512 part 2 (asserting the grep against this new line) is intentionally a separate PR.