Преглед изворни кода

Add LRUConns and ActivityMonitoredConn unit tests
Also fixed bug in LRUConns.CloseOldest: panic when
called with empty list.

Rod Hynes пре 9 година
родитељ
комит
dcf605d6f1
3 измењених фајлова са 282 додато и 11 уклоњено
  1. 4 5
      psiphon/common/net.go
  2. 269 0
      psiphon/common/net_test.go
  3. 9 6
      psiphon/common/throttled_test.go

+ 4 - 5
psiphon/common/net.go

@@ -130,14 +130,13 @@ func (conns *LRUConns) Add(conn net.Conn) *LRUConnsEntry {
 func (conns *LRUConns) CloseOldest() {
 	conns.mutex.Lock()
 	oldest := conns.list.Back()
-	conn, ok := oldest.Value.(net.Conn)
 	if oldest != nil {
 		conns.list.Remove(oldest)
 	}
 	// Release mutex before closing conn
 	conns.mutex.Unlock()
-	if ok {
-		conn.Close()
+	if oldest != nil {
+		oldest.Value.(net.Conn).Close()
 	}
 }
 
@@ -178,8 +177,8 @@ func (entry *LRUConnsEntry) Touch() {
 //
 // When an inactivity timeout is specified, the network I/O will
 // timeout after the specified period of read inactivity. Optionally,
-// ActivityMonitoredConn will also consider the connection active when
-// data is written to it.
+// for the purpose of inactivity only, ActivityMonitoredConn will also
+// consider the connection active when data is written to it.
 //
 // When a LRUConnsEntry is specified, then the LRU entry is promoted on
 // either a successful read or write.

+ 269 - 0
psiphon/common/net_test.go

@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2016, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package common
+
+import (
+	"net"
+	"sync/atomic"
+	"testing"
+	"testing/iotest"
+	"time"
+)
+
+type dummyConn struct {
+	t        *testing.T
+	timeout  *time.Timer
+	isClosed int32
+}
+
+func (c *dummyConn) Read(b []byte) (n int, err error) {
+	if c.timeout != nil {
+		select {
+		case <-c.timeout.C:
+			return 0, iotest.ErrTimeout
+		default:
+		}
+	}
+	return len(b), nil
+}
+
+func (c *dummyConn) Write(b []byte) (n int, err error) {
+	if c.timeout != nil {
+		select {
+		case <-c.timeout.C:
+			return 0, iotest.ErrTimeout
+		default:
+		}
+	}
+	return len(b), nil
+}
+
+func (c *dummyConn) Close() error {
+	atomic.StoreInt32(&c.isClosed, 1)
+	return nil
+}
+
+func (c *dummyConn) IsClosed() bool {
+	return atomic.LoadInt32(&c.isClosed) == 1
+}
+
+func (c *dummyConn) LocalAddr() net.Addr {
+	c.t.Fatal("LocalAddr not implemented")
+	return nil
+}
+
+func (c *dummyConn) RemoteAddr() net.Addr {
+	c.t.Fatal("RemoteAddr not implemented")
+	return nil
+}
+
+func (c *dummyConn) SetDeadline(t time.Time) error {
+	duration := t.Sub(time.Now())
+	if c.timeout == nil {
+		c.timeout = time.NewTimer(duration)
+	} else {
+		if !c.timeout.Stop() {
+			<-c.timeout.C
+		}
+		c.timeout.Reset(duration)
+	}
+	return nil
+}
+
+func (c *dummyConn) SetReadDeadline(t time.Time) error {
+	c.t.Fatal("SetReadDeadline not implemented")
+	return nil
+}
+
+func (c *dummyConn) SetWriteDeadline(t time.Time) error {
+	c.t.Fatal("SetWriteDeadline not implemented")
+	return nil
+}
+
+func TestActivityMonitoredConn(t *testing.T) {
+	buffer := make([]byte, 1024)
+
+	conn, err := NewActivityMonitoredConn(
+		&dummyConn{},
+		200*time.Millisecond,
+		true,
+		nil)
+	if err != nil {
+		t.Fatalf("NewActivityMonitoredConn failed")
+	}
+
+	startTime := time.Now()
+
+	time.Sleep(100 * time.Millisecond)
+
+	_, err = conn.Read(buffer)
+	if err != nil {
+		t.Fatalf("read before initial timeout failed")
+	}
+
+	time.Sleep(100 * time.Millisecond)
+
+	_, err = conn.Read(buffer)
+	if err != nil {
+		t.Fatalf("previous read failed to extend timeout")
+	}
+
+	time.Sleep(100 * time.Millisecond)
+
+	_, err = conn.Write(buffer)
+	if err != nil {
+		t.Fatalf("previous read failed to extend timeout")
+	}
+
+	time.Sleep(100 * time.Millisecond)
+
+	_, err = conn.Read(buffer)
+	if err != nil {
+		t.Fatalf("previous write failed to extend timeout")
+	}
+
+	lastSuccessfulReadTime := time.Now()
+
+	time.Sleep(100 * time.Millisecond)
+
+	_, err = conn.Write(buffer)
+	if err != nil {
+		t.Fatalf("previous read failed to extend timeout")
+	}
+
+	time.Sleep(300 * time.Millisecond)
+
+	_, err = conn.Read(buffer)
+	if err != iotest.ErrTimeout {
+		t.Fatalf("failed to timeout")
+	}
+
+	if startTime.Round(time.Millisecond) != conn.GetStartTime().Round(time.Millisecond) {
+		t.Fatalf("unexpected GetStartTime")
+	}
+
+	if lastSuccessfulReadTime.Round(time.Millisecond) != conn.GetLastActivityTime().Round(time.Millisecond) {
+		t.Fatalf("unexpected GetLastActivityTimes")
+	}
+
+	diff := lastSuccessfulReadTime.Sub(startTime).Nanoseconds() - conn.GetActiveDuration().Nanoseconds()
+	if diff < 0 {
+		diff = -diff
+	}
+	if diff > (1 * time.Millisecond).Nanoseconds() {
+		t.Fatalf("unexpected GetActiveDuration")
+	}
+}
+
+func TestActivityMonitoredLRUConns(t *testing.T) {
+
+	lruConns := NewLRUConns()
+
+	dummy1 := &dummyConn{}
+	conn1, err := NewActivityMonitoredConn(dummy1, 0, true, lruConns.Add(dummy1))
+	if err != nil {
+		t.Fatalf("NewActivityMonitoredConn failed")
+	}
+
+	dummy2 := &dummyConn{}
+	conn2, err := NewActivityMonitoredConn(dummy2, 0, true, lruConns.Add(dummy2))
+	if err != nil {
+		t.Fatalf("NewActivityMonitoredConn failed")
+	}
+
+	dummy3 := &dummyConn{}
+	conn3, err := NewActivityMonitoredConn(dummy3, 0, true, lruConns.Add(dummy3))
+	if err != nil {
+		t.Fatalf("NewActivityMonitoredConn failed")
+	}
+
+	buffer := make([]byte, 1024)
+
+	conn1.Read(buffer)
+	conn2.Read(buffer)
+	conn3.Read(buffer)
+
+	conn3.Write(buffer)
+	conn2.Write(buffer)
+	conn1.Write(buffer)
+
+	if dummy1.IsClosed() || dummy2.IsClosed() || dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	lruConns.CloseOldest()
+
+	if dummy1.IsClosed() || dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	lruConns.CloseOldest()
+
+	if dummy1.IsClosed() || !dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	lruConns.CloseOldest()
+
+	if !dummy1.IsClosed() || !dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+}
+
+func TestLRUConns(t *testing.T) {
+	lruConns := NewLRUConns()
+
+	dummy1 := &dummyConn{}
+	entry1 := lruConns.Add(dummy1)
+
+	dummy2 := &dummyConn{}
+	entry2 := lruConns.Add(dummy2)
+
+	dummy3 := &dummyConn{}
+	entry3 := lruConns.Add(dummy3)
+
+	entry3.Touch()
+	entry2.Touch()
+	entry1.Touch()
+
+	if dummy1.IsClosed() || dummy2.IsClosed() || dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	lruConns.CloseOldest()
+
+	if dummy1.IsClosed() || dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	lruConns.CloseOldest()
+
+	if dummy1.IsClosed() || !dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+
+	entry1.Remove()
+
+	lruConns.CloseOldest()
+
+	if dummy1.IsClosed() || !dummy2.IsClosed() || !dummy3.IsClosed() {
+		t.Fatalf("unexpected IsClosed state")
+	}
+}

+ 9 - 6
psiphon/common/throttled_test.go

@@ -65,12 +65,15 @@ func TestThrottledConn(t *testing.T) {
 		UpstreamBytesPerSecond:   1024 * 1024,
 	})
 
-	run(t, RateLimits{
-		DownstreamUnlimitedBytes: 0,
-		DownstreamBytesPerSecond: 1024 * 1024 / 8,
-		UpstreamUnlimitedBytes:   0,
-		UpstreamBytesPerSecond:   1024 * 1024 / 8,
-	})
+	// This test takes > 1 min to run, so disabled for now
+	/*
+		run(t, RateLimits{
+			DownstreamUnlimitedBytes: 0,
+			DownstreamBytesPerSecond: 1024 * 1024 / 8,
+			UpstreamUnlimitedBytes:   0,
+			UpstreamBytesPerSecond:   1024 * 1024 / 8,
+		})
+	*/
 }
 
 func run(t *testing.T, rateLimits RateLimits) {