Data race: SetLogger writes logger field unsynchronized; NewConnection already started goroutines
- Dominant language
- Go
- Stars
- 230
- Forks
- 37
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
**Data race in `Connection.SetLogger`**: `NewConnection` spawns the `receive()` and `processNotifications()` goroutines in its constructor, and `SetLogger` writes the `logger` field with no synchronization. The goroutines read the same field concurrently via `loggerOrDefault()`. This is a guaranteed data race any time `SetLogger` is called after construction.
## How it bites
`go test -race` aborts in any client that calls `conn.SetLogger(...)` after creating the connection (the common pattern — there is no way to set the logger at construction time). In our project this fails CI in every ACP subprocess package: agy, claudecode, copilot, cursor, gemini, and the generic client.
## Reproduce
```go
conn := NewClientSideConnection(client, stdin, stdout)
conn.SetLogger(slog.New(slog.DiscardHandler)) // races with receive goroutine
```
Run with `-race`. The read is observed in `loggerOrDefault()` from the `receive()`/`shutdownReceive()` goroutine; the write is `SetLogger`.
## Suggested fix
Make the logger field race-free. Since it is a single independently-replaced immutable pointer accessed on a hot path, an atomic is the simplest correct fix:
```go
import "sync/atomic"
logger atomic.Pointer[slog.Logger]
func (c *Connection) SetLogger(l *slog.Logger) { c.logger.Store(l) }
func (c *Connection) loggerOrDefault() *slog.Logger {
if l := c.logger.Load(); l != nil {
return l
}
return slog.Default()
}
```
A mutex/`RWMutex` also works; an atomic avoids lock overhead on the hot `loggerOrDefault()` path and any lock-reentrancy interplay with the connection's existing mutexes. Alternatively, adding a constructor option that installs the logger before the goroutines start would make the field effectively immutable, but atomic storage still keeps post-construction `SetLogger` calls safe.
I can open a PR with the atomic patch if preferred.
Contributor guide
No contributing guide indexed for this repository
Research direction
Locate Connection, NewConnection, SetLogger, and loggerOrDefault, then run go test -race against the affected ACP subprocess packages to reproduce the report. Make logger access race-free while preserving post-construction SetLogger calls, and confirm the race detector passes for agy, claudecode, copilot, cursor, gemini, and the generic client.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100