rpc: fix subscription corner case and speed up tests

Notifier tracks whether subscription are 'active'. A subscription
becomes active when the subscription ID has been sent to the client. If
the client sends notifications in the request handler before the
subscription becomes active they are dropped. The tests tried to work
around this problem by always waiting 5s before sending the first
notification.

Fix it by buffering notifications until the subscription becomes active.
This speeds up all subscription tests.

Also fix TestSubscriptionMultipleNamespaces to wait for three messages
instead of six. The test now finishes just after all notifications have
been received and doesn't hit the 30s timeout anymore.
This commit is contained in:
Felix Lange 2018-10-08 17:05:28 +02:00
parent 72a076840b
commit 46b4286c08
2 changed files with 35 additions and 28 deletions

View file

@ -52,9 +52,10 @@ type notifierKey struct{}
// Server callbacks use the notifier to send notifications. // Server callbacks use the notifier to send notifications.
type Notifier struct { type Notifier struct {
codec ServerCodec codec ServerCodec
subMu sync.RWMutex // guards active and inactive maps subMu sync.Mutex
active map[ID]*Subscription active map[ID]*Subscription
inactive map[ID]*Subscription inactive map[ID]*Subscription
buffer map[ID][]interface{} // unsent notifications of inactive subscriptions
} }
// newNotifier creates a new notifier that can be used to send subscription // newNotifier creates a new notifier that can be used to send subscription
@ -64,6 +65,7 @@ func newNotifier(codec ServerCodec) *Notifier {
codec: codec, codec: codec,
active: make(map[ID]*Subscription), active: make(map[ID]*Subscription),
inactive: make(map[ID]*Subscription), inactive: make(map[ID]*Subscription),
buffer: make(map[ID][]interface{}),
} }
} }
@ -88,20 +90,26 @@ func (n *Notifier) CreateSubscription() *Subscription {
// Notify sends a notification to the client with the given data as payload. // Notify sends a notification to the client with the given data as payload.
// If an error occurs the RPC connection is closed and the error is returned. // If an error occurs the RPC connection is closed and the error is returned.
func (n *Notifier) Notify(id ID, data interface{}) error { func (n *Notifier) Notify(id ID, data interface{}) error {
n.subMu.RLock() n.subMu.Lock()
defer n.subMu.RUnlock() defer n.subMu.Unlock()
sub, active := n.active[id] if sub, active := n.active[id]; active {
if active { n.send(sub, data)
notification := n.codec.CreateNotification(string(id), sub.namespace, data) } else {
if err := n.codec.Write(notification); err != nil { n.buffer[id] = append(n.buffer[id], data)
n.codec.Close()
return err
}
} }
return nil return nil
} }
func (n *Notifier) send(sub *Subscription, data interface{}) error {
notification := n.codec.CreateNotification(string(sub.ID), sub.namespace, data)
err := n.codec.Write(notification)
if err != nil {
n.codec.Close()
}
return err
}
// Closed returns a channel that is closed when the RPC connection is closed. // Closed returns a channel that is closed when the RPC connection is closed.
func (n *Notifier) Closed() <-chan interface{} { func (n *Notifier) Closed() <-chan interface{} {
return n.codec.Closed() return n.codec.Closed()
@ -127,9 +135,15 @@ func (n *Notifier) unsubscribe(id ID) error {
func (n *Notifier) activate(id ID, namespace string) { func (n *Notifier) activate(id ID, namespace string) {
n.subMu.Lock() n.subMu.Lock()
defer n.subMu.Unlock() defer n.subMu.Unlock()
if sub, found := n.inactive[id]; found { if sub, found := n.inactive[id]; found {
sub.namespace = namespace sub.namespace = namespace
n.active[id] = sub n.active[id] = sub
delete(n.inactive, id) delete(n.inactive, id)
// Send buffered notifications.
for _, data := range n.buffer[id] {
n.send(sub, data)
}
delete(n.buffer, id)
} }
} }

View file

@ -65,7 +65,6 @@ func (s *NotificationTestService) SomeSubscription(ctx context.Context, n, val i
// test expects n events, if we begin sending event immediately some events // test expects n events, if we begin sending event immediately some events
// will probably be dropped since the subscription ID might not be send to // will probably be dropped since the subscription ID might not be send to
// the client. // the client.
time.Sleep(5 * time.Second)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if err := notifier.Notify(subscription.ID, val+i); err != nil { if err := notifier.Notify(subscription.ID, val+i); err != nil {
return return
@ -287,32 +286,26 @@ func TestSubscriptionMultipleNamespaces(t *testing.T) {
timeout := time.After(30 * time.Second) timeout := time.After(30 * time.Second)
subids := make(map[string]string, 2*len(namespaces)) subids := make(map[string]string, 2*len(namespaces))
count := make(map[string]int, 2*len(namespaces)) count := make(map[string]int, 2*len(namespaces))
allReceived := func() bool {
for { done := len(count) == len(namespaces)
done := true for _, c := range count {
for id := range count { if c < n {
if count, found := count[id]; !found || count < (2*n) {
done = false done = false
} }
} }
return done
if done && len(count) == len(namespaces) {
break
} }
for !allReceived() {
select { select {
case err := <-errors:
t.Fatal(err)
case suc := <-successes: // subscription created case suc := <-successes: // subscription created
subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string) subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string)
case notification := <-notifications:
count[notification.Params.Subscription]++
case err := <-errors:
t.Fatal(err)
case failure := <-failures: case failure := <-failures:
t.Errorf("received error: %v", failure.Error) t.Errorf("received error: %v", failure.Error)
case notification := <-notifications:
if cnt, found := count[notification.Params.Subscription]; found {
count[notification.Params.Subscription] = cnt + 1
} else {
count[notification.Params.Subscription] = 1
}
case <-timeout: case <-timeout:
for _, namespace := range namespaces { for _, namespace := range namespaces {
subid, found := subids[namespace] subid, found := subids[namespace]