Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@
## 2026-07-28 - [Validating Context Cancellation for Blocking OS Executions]
**Learning:** Functions that wrap long-running system operations (such as `exec.CommandContext` or `net.DialContext`) generally handle context cancellation automatically, but early aborts for network calls need explicit testing. Merely relying on lower-level Go standard library propagation does not automatically guarantee zero side-effects if the cancellation happens before execution logic completes in consumer boundaries.
**Action:** Always include fine-grained tests that explicitly pass a pre-cancelled context to network discovery and connection routines (e.g. Ping, LookupAddr) to definitively prove zero leakage of OS processes or goroutines on early aborts.
## 2026-06-25 - Active context cancellation testing patterns
**Learning:** To statistically prove zero socket/goroutine leaks upon premature context cancellation in Go, active testing techniques using delayed cancellation combined with runtime metric evaluation (`runtime.NumGoroutine()`) before and after a settling period are required.
**Action:** Use a short sleep prior to `cancel()` in a background goroutine, followed by an execution step of the target code, ending with a loop that breaks if the final goroutine count drops back to within the allowed delta (e.g., `<= before+3`). This definitively proves the absence of leaks across async tasks.
52 changes: 52 additions & 0 deletions pkg/discovery/net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,55 @@ func TestPingCancellationActive(t *testing.T) {
t.Errorf("Goroutine leak detected in active Ping cancellation: before=%d, after=%d", before, after)
}
}

func TestReverseDNSCancellationActive(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
before := runtime.NumGoroutine()

go func() {
time.Sleep(5 * time.Millisecond)
cancel()
}()

res := ReverseDNS(ctx, "192.0.2.1")
if res != "" {
t.Errorf("ReverseDNS() with actively cancelled context should return empty string, got: %q", res)
}

for i := 0; i < 10; i++ {
if runtime.NumGoroutine() <= before+3 {
return
}
time.Sleep(10 * time.Millisecond)
}
after := runtime.NumGoroutine()
if after > before+3 {
t.Errorf("Goroutine leak detected in active ReverseDNS cancellation: before=%d, after=%d", before, after)
}
}

func TestGetMACCancellationActive(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
before := runtime.NumGoroutine()

go func() {
time.Sleep(5 * time.Millisecond)
cancel()
}()

res := GetMAC(ctx, "192.0.2.1")
if res != "" {
t.Errorf("GetMAC() with actively cancelled context should return empty string, got: %q", res)
}

for i := 0; i < 10; i++ {
if runtime.NumGoroutine() <= before+3 {
return
}
time.Sleep(10 * time.Millisecond)
}
after := runtime.NumGoroutine()
if after > before+3 {
t.Errorf("Goroutine leak detected in active GetMAC cancellation: before=%d, after=%d", before, after)
}
}
36 changes: 36 additions & 0 deletions pkg/ports/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"net"
"net/http/httptest"
"runtime"
"strconv"
"testing"
"time"
)

func TestScanPorts(t *testing.T) {
Expand Down Expand Up @@ -65,3 +67,37 @@ func TestScanPortsCancellation(t *testing.T) {
t.Errorf("expected 0 ports returned after cancellation, got %d", count)
}
}

func TestScanPortsCancellationActive(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
before := runtime.NumGoroutine()

go func() {
time.Sleep(5 * time.Millisecond)
cancel()
}()

ports := []int{80, 81, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087}
openChan := ScanPorts(ctx, "192.0.2.1", ports, 1000)

count := 0
for range openChan {
count++
}

if count != 0 {
t.Errorf("expected 0 ports returned after active cancellation, got %d", count)
}

// let background routines settle
for i := 0; i < 10; i++ {
if runtime.NumGoroutine() <= before+3 {
return
}
time.Sleep(10 * time.Millisecond)
}
after := runtime.NumGoroutine()
if after > before+3 {
t.Errorf("Goroutine leak detected in active ScanPorts cancellation: before=%d, after=%d", before, after)
}
}