blob: 2aedf93669a2da40b39330f4ca6986e25c413416 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package conn
import (
"bytes"
"io"
"sync"
"testing"
"time"
"v.io/v23/context"
"v.io/v23/flow"
"v.io/v23/flow/message"
_ "v.io/x/ref/runtime/factories/fake"
"v.io/x/ref/runtime/protocols/debug"
"v.io/x/ref/test"
)
func block(c *Conn, p int) chan struct{} {
w := &singleMessageWriter{writeCh: make(chan struct{}), p: p}
w.next, w.prev = w, w
ready, unblock := make(chan struct{}), make(chan struct{})
c.mu.Lock()
c.activateWriterLocked(w)
c.notifyNextWriterLocked(w)
c.mu.Unlock()
go func() {
<-w.writeCh
close(ready)
<-unblock
c.mu.Lock()
c.deactivateWriterLocked(w)
c.notifyNextWriterLocked(w)
c.mu.Unlock()
}()
<-ready
return unblock
}
func waitFor(f func() bool) {
t := time.NewTicker(10 * time.Millisecond)
defer t.Stop()
for _ = range t.C {
if f() {
return
}
}
}
func waitForWriters(ctx *context.T, conn *Conn, num int) {
waitFor(func() bool {
conn.mu.Lock()
count := 0
for _, w := range conn.activeWriters {
if w != nil {
count++
for _, n := w.neighbors(); n != w; _, n = n.neighbors() {
count++
}
}
}
conn.mu.Unlock()
return count >= num
})
}
type readConn struct {
flow.Conn
ch chan message.Message
ctx *context.T
}
func (r *readConn) ReadMsg() ([]byte, error) {
b, err := r.Conn.ReadMsg()
if len(b) > 0 {
m, _ := message.Read(r.ctx, b)
switch msg := m.(type) {
case *message.OpenFlow:
if msg.ID > 1 { // Ignore the blessings flow.
r.ch <- m
}
case *message.Data:
if msg.ID > 1 { // Ignore the blessings flow.
r.ch <- m
}
}
}
return b, err
}
func TestOrdering(t *testing.T) {
// We will send nmessages*mtu bytes on each flow.
// For the test to work properly we should send < defaultBufferSize(2^20) bytes.
// If we don't then it's possible for the teardown message to be sent before some
// flows finish writing because the flows' writes will become blocked from
// counter exhaustion.
// 4*4*2^16 == 2^20 so it's the perfect number.
const nflows = 4
const nmessages = 4
ctx, shutdown := test.V23Init()
defer shutdown()
ch := make(chan message.Message, 100)
fctx := debug.WithFilter(ctx, func(c flow.Conn) flow.Conn {
return &readConn{c, ch, ctx}
})
flows, accept, dc, ac := setupFlows(t, "debug", "local/", ctx, fctx, true, nflows)
unblock := block(dc, 0)
var wg sync.WaitGroup
wg.Add(2 * nflows)
defer wg.Wait()
for _, f := range flows {
go func(fl flow.Flow) {
if _, err := fl.WriteMsg(randData[:defaultMtu*nmessages]); err != nil {
panic(err)
}
wg.Done()
}(f)
go func() {
fl := <-accept
buf := make([]byte, defaultMtu*nmessages)
if _, err := io.ReadFull(fl, buf); err != nil {
panic(err)
}
if !bytes.Equal(buf, randData[:defaultMtu*nmessages]) {
t.Fatal("unequal data")
}
wg.Done()
}()
}
waitForWriters(ctx, dc, nflows+1)
// Now close the conn which will send a teardown message, but only after
// the other flows finish their current write.
go dc.Close(ctx, nil)
defer func() { <-dc.Closed(); <-ac.Closed() }()
close(unblock)
// OK now we expect all the flows to write interleaved messages.
for i := 0; i < nmessages; i++ {
found := map[uint64]bool{}
for j := 0; j < nflows; j++ {
m := <-ch
switch msg := m.(type) {
case *message.OpenFlow:
found[msg.ID] = true
case *message.Data:
found[msg.ID] = true
}
}
if len(found) != nflows {
t.Fatalf("Did not recieve a message from each flow in round %d: %v", i, found)
}
}
}