Release session resources on partial-setup and error paths#88
Merged
Conversation
Two leaks in the syncer session lifecycle:
- newSession opened the source transport (and started the memory-measurement
ticker goroutine) before listing refs and setting up the target. If any of
those steps failed it returned nil, err, so the caller had no session to
finish() — leaking the source conn (an SSH transport spawns a process), a
half-open target conn, and the ticker goroutine.
- finish() never stopped the measurement ticker; it was only stopped when a
Result was built, so any early error return from runSync et al. leaked the
goroutine even on a fully-constructed session.
Add a success-guarded deferred cleanup in newSession that calls finish() unless
the session is handed back, hand the target conn to the session as soon as it
is opened so cleanup can close it, and have finish() also invoke the
sync.Once-guarded measurementDone so the ticker stops on every path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e6526831fa74
nodo
approved these changes
Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two leaks in the syncer session lifecycle:
newSessionpartial-setup failure. It opened the source transport (and started the memory-measurement ticker goroutine) before listing refs and setting up the target. If any later step failed it returnednil, err— so the caller had nothing tofinish(), leaking the source conn (an SSH transport spawns a process), a half-open target conn, and the ticker goroutine.finish()never stopped the measurement ticker. It was only stopped when aResultwas built, so any early error return fromrunSyncet al. leaked the goroutine even on a fully-constructed session.Fix
success-guardeddefer s.finish()innewSession, so a partial-setup failure releases everything;success = trueis set only just before handing the session back (callers stilldefer s.finish()on success, sofinishruns exactly once either way).s.targetas soon as it's opened, so cleanup can close it even if a target ref-listing step fails.finish()also invoke thesync.Once-guardedmeasurementDone, stopping the ticker on every path without disturbing the happy-path measurement value.Tests
TestStartMeasurementStopsGoroutineAndIsIdempotentconfirms the closure stops its goroutine and is safe to call twice (the propertyfinish()now relies on);TestStartMeasurementDisabledIsInertcovers the disabled case. Fullgo test ./...passes.🤖 Generated with Claude Code
Note
Medium Risk
Touches core session lifecycle and connection cleanup on failure paths; behavior change is limited to resource teardown but affects every sync entry point that uses
newSession.Overview
Fixes resource leaks when
newSessionfails mid-setup or when a sync exits before building aResult.newSessionnow uses asuccess-guardeddefer s.finish()so partial failures still tear down the source/target transports (including SSH subprocesses) and the memory-measurement ticker started at the beginning of setup. The target connection is attached tos.targetas soon as it is opened so later ref-listing errors still get closed.finish()also invokes thesync.Once-guardedmeasurementDoneclosure, stopping the measurement goroutine on error paths without changing the value already captured on the happy path.New tests in
measurement_test.goassert thatstartMeasurementstops its goroutine and remains safe when called twice (the behaviorfinish()now depends on).Reviewed by Cursor Bugbot for commit f81cf1c. Configure here.