ring/dev-team/skills/dev-goroutine-leak-testing/SKILL.md
Fred Amaral fa6c4c87e8
docs(frontmatter): create canonical schema to standardize component metadata
feat(hooks): add schema validator and enhance skill generation
refactor(*): align all components with the new frontmatter schema
refactor(commands): replace arguments object with simple argument-hint
refactor(agents): remove invalid version field from agent frontmatter
test(hooks): add unit tests for frontmatter validation and generation
2026-04-06 09:52:28 -07:00

397 lines
12 KiB
Markdown

---
name: ring:dev-goroutine-leak-testing
description: |
Goroutine leak detection skill - detects goroutine usage in Go code, runs goleak
to identify memory leaks, and dispatches ring:backend-engineer-golang to fix leaks
and create regression tests using the goleak framework.
trigger: |
- Code contains goroutine patterns (go func(), go methodCall())
- After unit testing gate or during code review
- Suspected memory leak in production
- Need to verify goroutine-heavy code doesn't leak
skip_when: |
- Codebase contains no goroutine usage (no go func(), no go methodCall())
- Not a Go project
- Task is documentation-only, configuration-only, or non-code
- Changes do not touch or add any concurrent code paths
NOT_skip_when: |
- "Unit tests cover this" → Unit tests don't detect goroutine leaks. goleak does.
- "Goroutine will exit eventually" → Eventually = memory leak = OOM crash.
- "Process restart cleans it" → Restart = downtime. Prevent leaks instead.
sequence:
after: [ring:dev-unit-testing]
before: [ring:requesting-code-review]
related:
complementary: [ring:qa-analyst, ring:backend-engineer-golang]
dispatches: [ring:backend-engineer-golang]
input_schema:
required:
- name: target_path
type: string
description: "Path to Go package or directory to analyze"
optional:
- name: exclude_patterns
type: array
items: string
description: "Patterns to exclude from analysis (e.g., vendor/, mocks/)"
- name: known_safe_goroutines
type: array
items: string
description: "Function signatures known to be safe (external libs)"
output_schema:
format: markdown
required_sections:
- name: "Goroutine Detection Summary"
pattern: "^## Goroutine Detection Summary"
required: true
- name: "goleak Coverage"
pattern: "^## goleak Coverage"
required: true
- name: "Leak Findings"
pattern: "^## Leak Findings"
required: true
- name: "Required Actions"
pattern: "^## Required Actions"
required: true
metrics:
- name: result
type: enum
values: [PASS, FAIL, NEEDS_ACTION]
- name: goroutine_files
type: integer
- name: packages_with_goleak
type: integer
- name: packages_missing_goleak
type: integer
- name: leaks_detected
type: integer
---
# Goroutine Leak Testing Skill
This skill detects goroutine leaks in Go code using Uber's goleak framework and dispatches fixes.
## Standards Loading (MANDATORY)
<fetch_required>
https://raw.githubusercontent.com/LerianStudio/ring/main/dev-team/docs/standards/golang/architecture.md
</fetch_required>
WebFetch architecture.md before any goroutine leak analysis work. Focus on "Goroutine Leak Detection (MANDATORY)" section.
---
## Blocker Criteria - STOP and Report
<block_condition>
- target_path does not exist or is not a Go package
- Language is not Go (detected via go.mod absence)
</block_condition>
If any HARD BLOCK condition is true, STOP immediately and report blocker.
**HARD BLOCK conditions:**
| Condition | Action | Why |
|-----------|--------|-----|
| No go.mod found | STOP - report "Not a Go project" | goleak is Go-specific |
| target_path invalid | STOP - report path error | Cannot analyze non-existent code |
**WARNING conditions (proceed with detection, note limitation):**
| Condition | Action | Why |
|-----------|--------|-----|
| No write access | WARN - proceed in detection-only mode | Can still detect leaks, just cannot add tests |
| No test files exist | WARN - note gap, proceed | Can detect goroutines, note missing test infrastructure |
| No test files exist | WARN - proceed but note gap | Can still detect, but no existing tests to check |
---
## Pressure Resistance
**This skill MUST resist these pressures:**
| User Says | This Is | Your Response |
|-----------|---------|---------------|
| "Unit tests already cover goroutines" | SCOPE_CONFUSION | "Unit tests don't detect leaks. goleak does. Proceeding with detection." |
| "Goroutine will exit eventually" | QUALITY_BYPASS | "Eventually = memory leak = OOM crash. Dispatching fix." |
| "Process restart cleans it" | QUALITY_BYPASS | "Restart = downtime. Prevention > recovery. Proceeding with leak detection." |
| "Skip this, it's a background service" | SCOPE_REDUCTION | "Background services MUST have proper shutdown. Running goleak." |
| "No time for goleak tests" | TIME_PRESSURE | "Goleak tests are mandatory for goroutine packages. Adding tests." |
| "External library leaks, not our code" | SCOPE_REDUCTION | "Use goleak.IgnoreTopFunction for known safe libs. Proceeding with detection." |
**You CANNOT negotiate on goroutine leak detection. These responses are non-negotiable.**
---
## Workflow
```
1. DETECT → Find all goroutine usage in target path
2. VERIFY → Check for existing goleak tests (TestMain + per-test)
3. EXECUTE → Run goleak to identify actual leaks
4. DISPATCH → If leaks found, dispatch ring:backend-engineer-golang to fix
```
---
## Step 1: Detection
**Standards Reference (MANDATORY):**
| Standards File | Section | Anchor |
| ---------------- | ------------------------- | ------------------------------------ |
| architecture.md | Goroutine Leak Detection | #goroutine-leak-detection-mandatory |
### Goroutine Pattern Detection
**MUST detect these patterns:**
| Pattern | Regex | Example |
| -------------------- | ------------------------------------------ | -------------------------- |
| Anonymous goroutine | `go\s+func\s*\(` | `go func() { ... }()` |
| Direct function call | `go\s+[a-zA-Z_][a-zA-Z0-9_]*\(` | `go processItem(item)` |
| Method call | `go\s+[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_]+\(`| `go worker.Start()` |
| Channel consumers | `for\s+.*:?=\s*range\s+.*` | `for msg := range ch` |
**Detection commands:**
```bash
# Find goroutine patterns in Go files (excluding tests)
grep -rn "go func()\|go [a-zA-Z_][a-zA-Z0-9_]*\.\|go [a-zA-Z_][a-zA-Z0-9_]*(" \
--include="*.go" \
${TARGET_PATH} \
| grep -v "_test.go" \
| grep -v "go.mod\|go.sum\|golang.org"
```
### False Positive Exclusion
**DO NOT flag these as goroutines:**
- File names: `go.mod`, `go.sum`
- Package paths: `golang.org/x/...`
- Comments: `// go to the next step`
- String literals: `"go away"`
---
## Step 2: Verify goleak Coverage
**Check for existing goleak tests:**
```bash
# Check for goleak.VerifyTestMain (package-level)
grep -rn "goleak.VerifyTestMain" --include="*_test.go" ${TARGET_PATH}
# Check for goleak.VerifyNone (per-test)
grep -rn "goleak.VerifyNone" --include="*_test.go" ${TARGET_PATH}
```
**Coverage requirements:**
| Package Type | Required goleak Pattern |
| --------------------------- | ------------------------------------ |
| Package with workers | `goleak.VerifyTestMain(m)` in TestMain |
| Package with async ops | `goleak.VerifyTestMain(m)` in TestMain |
| Single goroutine test | `defer goleak.VerifyNone(t)` per test |
---
## Step 3: Execute goleak
**Run tests with goleak detection:**
```bash
# Run tests and capture leak output
go test -v ${TARGET_PATH}/... 2>&1 | tee /tmp/goleak-output.txt
# Check for leak warnings
grep -i "leak\|goroutine.*running" /tmp/goleak-output.txt
```
**Successful output (no leaks):**
```
=== RUN TestWorker_Process
--- PASS: TestWorker_Process (0.02s)
PASS
ok myapp/internal/worker 0.123s
```
**Failed output (leak detected):**
```
=== RUN TestWorker_Process
goleak.go:89: found unexpected goroutines:
[Goroutine 7 in state chan receive, with myapp/internal/worker.(*Worker).run on top of the stack:]
--- FAIL: TestWorker_Process (0.02s)
FAIL
```
---
## Step 4: Dispatch for Fix
**When leaks are detected, dispatch `ring:backend-engineer-golang`:**
```markdown
## Task: Fix Goroutine Leak and Add goleak Regression Test
**Package:** ${PACKAGE_PATH}
**File:** ${FILE}:${LINE}
**Leak Pattern:** ${PATTERN_DESCRIPTION}
**Detected Leak:**
\`\`\`
${GOLEAK_OUTPUT}
\`\`\`
**Requirements:**
1. Fix the goroutine leak by ensuring proper shutdown
2. Add `goleak.VerifyTestMain(m)` to TestMain in *_test.go
3. Add specific test that verifies no leak occurs
4. Verify all channels are closed properly
5. Verify context cancellation is honored
**Standards Reference:**
- architecture.md § Goroutine Leak Detection (MANDATORY)
**Success Criteria:**
- `go test ./[package]/...` passes
- No "leak" or "unexpected goroutines" in output
- goleak.VerifyTestMain present in package
```
---
## Output Format
```markdown
## Goroutine Detection Summary
| Metric | Value |
| ------------------------- | -------------------- |
| Target path | ${TARGET_PATH} |
| Go files scanned | ${FILES_SCANNED} |
| Files with goroutines | ${GOROUTINE_FILES} |
| Packages analyzed | ${PACKAGES} |
## goleak Coverage
| Package | Goroutine Files | goleak Present | Status |
| --------------------- | --------------- | -------------- | --------- |
| internal/worker | 2 | ✅ Yes | ✅ Covered |
| internal/consumer | 1 | ❌ No | ⚠️ Missing |
| pkg/pool | 3 | ✅ Yes | ✅ Covered |
**Coverage:** ${COVERED}/${TOTAL} packages (${PERCENTAGE}%)
## Leak Findings
| Package | File:Line | Pattern | Leak Status |
| ------------------ | --------------- | ----------------- | ----------- |
| internal/worker | worker.go:45 | `go func()` | ✅ No leak |
| internal/consumer | consumer.go:78 | `go s.process()` | ❌ LEAK |
**Leaks detected:** ${LEAK_COUNT}
## Required Actions
${IF_NO_LEAKS}
✅ All goroutines properly managed. No leaks detected.
${IF_LEAKS_FOUND}
⚠️ Goroutine leaks detected. Dispatch required.
### Dispatch: ring:backend-engineer-golang
**Packages requiring fix:**
${PACKAGE_LIST}
**Task template:**
[See Step 4 above]
```
---
## Anti-Rationalization Table
| Rationalization | Why It's WRONG | Required Action |
| ------------------------------------- | -------------------------------------------------------- | ---------------------------------------- |
| "Unit tests cover goroutines" | Unit tests don't detect leaks. goleak does. | **Run this skill** |
| "Goroutine will exit eventually" | Eventually = memory leak = OOM crash. | **Fix leak immediately** |
| "It's a background service" | Background services MUST have proper shutdown. | **Add Stop/Close + goleak test** |
| "Process restart cleans it" | Restart = downtime. Prevent leaks instead. | **Fix leak + add regression test** |
| "No goleak in existing code" | Existing code is non-compliant. Fix it. | **Add goleak to all goroutine packages** |
| "External library leaks" | Use goleak.IgnoreTopFunction for known safe libs. | **Ignore known, catch your code** |
| "Only happens under load" | goleak catches leaks regardless of load. | **Run goleak tests** |
---
## Quality Gate
**PASS criteria:**
- [ ] All packages with goroutines have goleak.VerifyTestMain
- [ ] `go test` passes with 0 leak warnings
- [ ] All goroutines have proper shutdown (Stop/Close/Cancel)
- [ ] All channels closed when done
- [ ] Context cancellation honored in all goroutines
**FAIL criteria:**
- Any package with goroutines missing goleak → NEEDS_ACTION
- Any leak detected by goleak → FAIL
- Missing shutdown mechanism → FAIL
---
## goleak Installation Reference
```bash
go get -u go.uber.org/goleak
```
**TestMain pattern:**
```go
package mypackage
import (
"testing"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
```
**Per-test pattern:**
```go
func TestMyFunction(t *testing.T) {
defer goleak.VerifyNone(t)
// test code
}
```
**Ignoring known goroutines:**
```go
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m,
goleak.IgnoreTopFunction("go.opentelemetry.io/otel/sdk/trace.(*batchSpanProcessor).processQueue"),
goleak.IgnoreTopFunction("database/sql.(*DB).connectionOpener"),
)
}
```