event: fix Resubscribe deadlock when unsubscribing after inner sub ends (#28359)

A goroutine is used to manage the lifetime of subscriptions managed by
resubscriptions. When the subscription ends with no error, the resub
goroutine ends as well. However, the resub goroutine needs to live
long enough to read from the unsub channel. Otheriwse, an Unsubscribe
call deadlocks when writing to the unsub channel.

This is fixed by adding a buffer to the unsub channel.
pull/28399/head
Inphi 1 year ago committed by GitHub
parent a6a0ae45b6
commit ffc6a0f36e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      event/subscription.go
  2. 24
      event/subscription_test.go

@ -120,7 +120,7 @@ func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscriptio
backoffMax: backoffMax, backoffMax: backoffMax,
fn: fn, fn: fn,
err: make(chan error), err: make(chan error),
unsub: make(chan struct{}), unsub: make(chan struct{}, 1),
} }
go s.loop() go s.loop()
return s return s

@ -154,3 +154,27 @@ func TestResubscribeWithErrorHandler(t *testing.T) {
t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs) t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs)
} }
} }
func TestResubscribeWithCompletedSubscription(t *testing.T) {
t.Parallel()
quitProducerAck := make(chan struct{})
quitProducer := make(chan struct{})
sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) {
return NewSubscription(func(unsubscribed <-chan struct{}) error {
select {
case <-quitProducer:
quitProducerAck <- struct{}{}
return nil
case <-unsubscribed:
return nil
}
}), nil
})
// Ensure producer has started and exited before Unsubscribe
close(quitProducer)
<-quitProducerAck
sub.Unsubscribe()
}

Loading…
Cancel
Save