@@ -148,6 +148,11 @@ type ClientSession struct {
148148// from being handled, and waiting for ongoing requests to return. Close then
149149// terminates the connection.
150150func (cs * ClientSession ) Close () error {
151+ // Note: keepaliveCancel access is safe without a mutex because:
152+ // 1. keepaliveCancel is only written once during startKeepalive (happens-before user Close calls)
153+ // 2. context.CancelFunc is safe to call multiple times and from multiple goroutines
154+ // 3. The keepalive goroutine calls Close on ping failure, but this is safe since
155+ // Close is idempotent and conn.Close() handles concurrent calls correctly
151156 if cs .keepaliveCancel != nil {
152157 cs .keepaliveCancel ()
153158 }
@@ -162,29 +167,7 @@ func (cs *ClientSession) Wait() error {
162167
163168// startKeepalive starts the keepalive mechanism for this client session.
164169func (cs * ClientSession ) startKeepalive (interval time.Duration ) {
165- ctx , cancel := context .WithCancel (context .Background ())
166- cs .keepaliveCancel = cancel
167-
168- go func () {
169- ticker := time .NewTicker (interval )
170- defer ticker .Stop ()
171-
172- for {
173- select {
174- case <- ctx .Done ():
175- return
176- case <- ticker .C :
177- pingCtx , pingCancel := context .WithTimeout (context .Background (), interval / 2 )
178- err := cs .Ping (pingCtx , nil )
179- pingCancel ()
180- if err != nil {
181- // Ping failed, close the session
182- _ = cs .Close ()
183- return
184- }
185- }
186- }
187- }()
170+ startKeepalive (cs , interval , & cs .keepaliveCancel )
188171}
189172
190173// AddRoots adds the given roots to the client,
0 commit comments