test(provision): swap to concurrent-safe broadcaster in 7-burst harness
CI Platform (Go) ran with -race and the concurrent test tripped the detector: captureBroadcaster (sequential-test stub) writes lastData unguarded; 7 fan-out goroutines call markProvisionFailed → that stub concurrently. Local non-race run had hidden it. Introduce concurrentSafeBroadcaster (mutex-counted) for this single fan-out test. Sequential tests keep using captureBroadcaster — the fix is local to the test that creates the goroutines. Verified ./internal/handlers passes with -race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a19724194
commit
4f64c4366f
@ -126,8 +126,13 @@ func TestProvisionWorkspaceCP_ConcurrentBurst_NoSilentDrop(t *testing.T) {
|
||||
// goroutine entered AND reached the recorded Start() call.
|
||||
rec := &recordingCPProv{startErr: fmt.Errorf("simulated CP rejection")}
|
||||
|
||||
cap := &captureBroadcaster{}
|
||||
handler := NewWorkspaceHandler(cap, nil, "http://localhost:8080", t.TempDir())
|
||||
// Concurrent-safe broadcaster — captureBroadcaster (used by sequential
|
||||
// tests in workspace_provision_test.go) writes lastData unguarded.
|
||||
// Under -race + 7 fan-out goroutines that's a real data race; this
|
||||
// stub serializes via mutex and only counts (we don't need the
|
||||
// payload for any assertion below).
|
||||
bcast := &concurrentSafeBroadcaster{}
|
||||
handler := NewWorkspaceHandler(bcast, nil, "http://localhost:8080", t.TempDir())
|
||||
handler.SetCPProvisioner(rec)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@ -203,6 +208,26 @@ type safeWriter struct {
|
||||
mu *sync.Mutex
|
||||
}
|
||||
|
||||
// concurrentSafeBroadcaster is a thread-safe events.EventEmitter stub
|
||||
// for the 7-goroutine fan-out test. captureBroadcaster (the canonical
|
||||
// sequential-test stub in workspace_provision_test.go) writes its
|
||||
// lastData field without synchronization — under -race that's a true
|
||||
// data race when 7 markProvisionFailed calls run concurrently. This
|
||||
// stub only counts (no payload retention) and serializes via mutex.
|
||||
type concurrentSafeBroadcaster struct {
|
||||
mu sync.Mutex
|
||||
count int
|
||||
}
|
||||
|
||||
func (b *concurrentSafeBroadcaster) BroadcastOnly(_ string, _ string, _ interface{}) {}
|
||||
|
||||
func (b *concurrentSafeBroadcaster) RecordAndBroadcast(_ context.Context, _, _ string, _ interface{}) error {
|
||||
b.mu.Lock()
|
||||
b.count++
|
||||
b.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *safeWriter) Write(p []byte) (int, error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user