[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Concurrency Debugging
|
||||
|
||||
## Goroutine Leaks
|
||||
|
||||
**Symptoms:** Memory slowly increasing, goroutine count growing, no obvious CPU spike.
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Use pprof goroutine profile (see [pprof.md](./pprof.md)) with `?debug=2` for human-readable output, then look for goroutines stuck in `chan receive`.
|
||||
|
||||
For Go 1.26 diagnostics, there is also an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by `GOEXPERIMENT=goroutineleakprofile`; do not rely on it as default stable behavior.
|
||||
|
||||
```bash
|
||||
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
|
||||
go tool pprof http://localhost:6060/debug/pprof/goroutineleak
|
||||
```
|
||||
|
||||
Keep existing tools: `go.uber.org/goleak` in tests, `runtime.NumGoroutine()` for coarse monitoring, `/debug/pprof/goroutine?debug=2` for stack dumps, and `go test -race ./...` for race checks.
|
||||
|
||||
```go
|
||||
// Programmatic monitoring — log goroutine count to detect leaks
|
||||
go func() {
|
||||
for {
|
||||
log.Printf("goroutines: %d", runtime.NumGoroutine())
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
// In tests, use goleak to detect goroutine leaks
|
||||
// import "go.uber.org/goleak"
|
||||
// func TestMain(m *testing.M) { goleak.VerifyTestMain(m) }
|
||||
```
|
||||
|
||||
**Common causes:**
|
||||
|
||||
```go
|
||||
// 1. Unclosed channel — goroutine blocks forever
|
||||
// BAD
|
||||
for {
|
||||
job := <-jobs
|
||||
process(job)
|
||||
}
|
||||
// GOOD
|
||||
for {
|
||||
select {
|
||||
case job, ok := <-jobs:
|
||||
if !ok { return }
|
||||
process(job)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Forgotten response body close — leaks HTTP connection
|
||||
// Always defer resp.Body.Close() after HTTP calls.
|
||||
// See production-debug.md for the correct pattern.
|
||||
|
||||
// 3. time.After in hot loop — allocates a new timer each iteration
|
||||
// BAD
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
do()
|
||||
}
|
||||
}
|
||||
// GOOD — reuse a ticker for repeated intervals
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
do()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Race Conditions
|
||||
|
||||
**Symptoms:** Intermittent failures, "sometimes works sometimes doesn't", different results on different machines.
|
||||
|
||||
**Diagnosis:** Race conditions MUST be tested with the `-race` flag:
|
||||
|
||||
```bash
|
||||
go test -race ./...
|
||||
go run -race main.go
|
||||
# Race detector slows code ~10x but finds data races reliably
|
||||
```
|
||||
|
||||
**Common patterns:**
|
||||
|
||||
- Shared map without mutex
|
||||
- Shared variable without atomic
|
||||
- Publishing reference before initialization
|
||||
- go func() accessing outer variables without synchronization
|
||||
|
||||
## Deadlocks
|
||||
|
||||
**Symptoms:** Program hangs, goroutines stuck in "chan receive" or "mutex lock".
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
```bash
|
||||
curl http://localhost:6060/debug/pprof/goroutine?debug=2
|
||||
|
||||
# Or programmatic
|
||||
runtime.Stack(buf, true)
|
||||
```
|
||||
|
||||
**Common patterns:**
|
||||
|
||||
1. **Circular wait** — A waits for B, B waits for A
|
||||
2. **Forgotten channel send** — sender goroutine exited
|
||||
3. **Wrong lock order** — always acquire locks in the same order
|
||||
Reference in New Issue
Block a user