Make ServerConn safe for concurrent use

ServerConn's textproto.Conn is safe for concurrent access. This adds
calls to StartResponse() and EndResponse() to put that to use wherever
Cmd() is called.
https://golang.org/pkg/net/textproto/#Conn.Cmd

Additionally, this adds a sync.Mutex on ServerConn to provide
concurrency protection for data commands such as STOR and RETR that need
to run through synchronous steps.
This commit is contained in:
Brian Foshee
2016-02-29 15:44:03 -05:00
parent 025815df64
commit 025459f901
2 changed files with 146 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"io/ioutil"
"net/textproto"
"sync"
"testing"
"time"
)
@@ -221,3 +222,105 @@ func TestWrongLogin(t *testing.T) {
t.Fatal("expected error, got nil")
}
}
func TestConcurrentAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}
c, err := DialTimeout("localhost:21", 5*time.Second)
if err != nil {
t.Fatal(err)
}
err = c.Login("anonymous", "anonymous")
if err != nil {
t.Fatal(err)
}
err = c.ChangeDir("incoming")
if err != nil {
t.Error(err)
}
wg := sync.WaitGroup{}
files := []string{"test1", "test2"}
for _, f := range files {
wg.Add(1)
go func(fn string) {
data := bytes.NewBufferString(testData)
err := c.Stor(fn, data)
if err != nil {
t.Error(err)
}
_, err = c.List(".")
if err != nil {
t.Error(err)
}
err = c.Rename(fn, fn+"tset")
if err != nil {
t.Error(err)
}
r, err := c.Retr(fn + "tset")
if err != nil {
t.Error(err)
} else {
buf, err := ioutil.ReadAll(r)
if err != nil {
t.Error(err)
}
if string(buf) != testData {
t.Errorf("'%s'", buf)
}
r.Close()
}
r, err = c.RetrFrom(fn+"tset", 5)
if err != nil {
t.Error(err)
} else {
buf, err := ioutil.ReadAll(r)
if err != nil {
t.Error(err)
}
expected := testData[5:]
if string(buf) != expected {
t.Errorf("read %q, expected %q", buf, expected)
}
r.Close()
}
err = c.Delete(fn + "tset")
if err != nil {
t.Error(err)
}
wg.Done()
}(f)
}
wg.Wait()
err = c.Logout()
if err != nil {
if protoErr := err.(*textproto.Error); protoErr != nil {
if protoErr.Code != StatusNotImplemented {
t.Error(err)
}
} else {
t.Error(err)
}
}
c.Quit()
err = c.NoOp()
if err == nil {
t.Error("Expected error")
}
}