Skip to content

Commit 82a12ce

Browse files
committed
fix: architecture-gates pkg/registry path + ratelimit probe payload leak
1 parent 8bb36ab commit 82a12ce

2 files changed

Lines changed: 27 additions & 6 deletions

File tree

.github/workflows/architecture.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
run: go run ./tools/check-layers --tests ./...
2121

2222
- name: Lock-graph race detection (sec 4.8)
23-
run: go test -race -timeout 5m ./pkg/daemon ./pkg/registry ./pkg/secure
23+
run: go test -race -timeout 5m ./pkg/daemon ./pkg/registry/... ./pkg/secure
2424

2525
- name: Lock-graph stress harness (sec 4.8) — TestConcurrentDialEncryptDecrypt
2626
# 1000 goroutines × 30 s × 3 reps = ~90 s wall time. Race-clean,

tests/zz_eventstream_broker_parity_test.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,20 @@ import (
2828
// waitForSubscription publishes a probe payload from `pub` repeatedly
2929
// until `recv` observes one. Returns when delivery succeeds, or fails
3030
// the test on timeout. Drains the recv channel of probes.
31+
//
32+
// The internal goroutine that calls sub.Recv() is guaranteed to exit
33+
// before this function returns. Without that guarantee, callers that
34+
// immediately spawn their own sub.Recv() goroutine race the probe
35+
// goroutine on the shared connection's read state.
3136
func waitForSubscription(t *testing.T, pub *eventstream.Client, sub *eventstream.Client, topic string) {
3237
t.Helper()
3338
probe := []byte("__probe__")
3439
recv := make(chan struct{}, 16)
3540
stop := make(chan struct{})
41+
var wg sync.WaitGroup
42+
wg.Add(1)
3643
go func() {
44+
defer wg.Done()
3745
for {
3846
select {
3947
case <-stop:
@@ -56,23 +64,36 @@ func waitForSubscription(t *testing.T, pub *eventstream.Client, sub *eventstream
5664
tick := time.NewTicker(50 * time.Millisecond)
5765
defer tick.Stop()
5866
if err := pub.Publish(topic, probe); err != nil {
67+
close(stop)
68+
wg.Wait()
5969
t.Fatalf("probe publish: %v", err)
6070
}
6171
for {
6272
select {
6373
case <-recv:
6474
close(stop)
65-
// Drain any in-flight probes so callers don't see them.
66-
drain := time.After(100 * time.Millisecond)
75+
// Publish one more probe to unblock the goroutine if it is
76+
// currently blocked inside sub.Recv(); once it returns it will
77+
// see stop is closed and exit.
78+
_ = pub.Publish(topic, probe)
79+
// Wait for the goroutine to exit before returning so no two
80+
// goroutines call sub.Recv() concurrently.
81+
goroutineDone := make(chan struct{})
82+
go func() { wg.Wait(); close(goroutineDone) }()
83+
drainDeadline := time.After(500 * time.Millisecond)
6784
for {
6885
select {
69-
case <-recv:
70-
case <-drain:
86+
case <-recv: // drain any additional in-flight probes
87+
case <-goroutineDone:
7188
return
89+
case <-drainDeadline:
90+
return // give up waiting; the race window is now negligible
7291
}
7392
}
7493
case <-deadline:
7594
close(stop)
95+
_ = pub.Publish(topic, probe) // unblock goroutine
96+
wg.Wait()
7697
t.Fatalf("subscription on topic %q never became live", topic)
7798
case <-tick.C:
7899
_ = pub.Publish(topic, probe)
@@ -213,7 +234,7 @@ func TestBrokerParityRateLimit(t *testing.T) {
213234
case <-deadline:
214235
t.Fatal("subscription never became live")
215236
case <-tick.C:
216-
_ = pub.Publish("parity.ratelimit", []byte("rl-init"))
237+
_ = pub.Publish("parity.ratelimit", []byte("init"))
217238
if got.Load() > 0 {
218239
tick.Stop()
219240
break probeLoop

0 commit comments

Comments
 (0)