Skip to content

Commit

Permalink
Fix data race in fasthttputil.pipeConn (#645)
Browse files Browse the repository at this point in the history
* add tests for fasthttputil.InmemoryListener
* fix data race in pipeConn
* update use of readDeadlineChLock
  • Loading branch information
kkty authored and erikdubbelboer committed Sep 4, 2019
1 parent 4fa45fa commit 8713335
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 2 deletions.
92 changes: 92 additions & 0 deletions fasthttputil/inmemory_listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ package fasthttputil

import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"sync"
"testing"
"time"
)
Expand Down Expand Up @@ -90,3 +96,89 @@ func TestInmemoryListener(t *testing.T) {
t.Fatalf("timeout")
}
}

// echoServerHandler implements http.Handler.
type echoServerHandler struct {
t *testing.T
}

func (s *echoServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
time.Sleep(time.Millisecond * 100)
if _, err := io.Copy(w, r.Body); err != nil {
s.t.Fatalf("unexpected error: %s", err)
}
}

func testInmemoryListenerHTTP(t *testing.T, f func(t *testing.T, client *http.Client)) {
ln := NewInmemoryListener()
defer ln.Close()

client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return ln.Dial()
},
},
Timeout: time.Second,
}

server := &http.Server{
Handler: &echoServerHandler{t},
}

go func() {
if err := server.Serve(ln); err != nil && err != http.ErrServerClosed {
t.Fatalf("unexpected error: %s", err)
}
}()

f(t, client)

ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel()
server.Shutdown(ctx)
}

func testInmemoryListenerHTTPSingle(t *testing.T, client *http.Client, content string) {
res, err := client.Post("http://...", "text/plain", bytes.NewBufferString(content))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
b, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
s := string(b)
if string(b) != content {
t.Fatalf("unexpected response %s, expecting %s", s, content)
}
}

func TestInmemoryListenerHTTPSingle(t *testing.T) {
testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {
testInmemoryListenerHTTPSingle(t, client, "request")
})
}

func TestInmemoryListenerHTTPSerial(t *testing.T) {
testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {
for i := 0; i < 10; i++ {
testInmemoryListenerHTTPSingle(t, client, fmt.Sprintf("request_%d", i))
}
})
}

func TestInmemoryListenerHTTPConcurrent(t *testing.T) {
testInmemoryListenerHTTP(t, func(t *testing.T, client *http.Client) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
testInmemoryListenerHTTPSingle(t, client, fmt.Sprintf("request_%d", i))
}(i)
}
wg.Wait()
})
}
12 changes: 10 additions & 2 deletions fasthttputil/pipeconns.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type pipeConn struct {

readDeadlineCh <-chan time.Time
writeDeadlineCh <-chan time.Time

readDeadlineChLock sync.Mutex
}

func (c *pipeConn) Write(p []byte) (int, error) {
Expand Down Expand Up @@ -158,9 +160,12 @@ func (c *pipeConn) readNextByteBuffer(mayBlock bool) error {
if !mayBlock {
return errWouldBlock
}
c.readDeadlineChLock.Lock()
readDeadlineCh := c.readDeadlineCh
c.readDeadlineChLock.Unlock()
select {
case c.b = <-c.rCh:
case <-c.readDeadlineCh:
case <-readDeadlineCh:
c.readDeadlineCh = closedDeadlineCh
// rCh may contain data when deadline is reached.
// Read the data before returning ErrTimeout.
Expand Down Expand Up @@ -214,7 +219,10 @@ func (c *pipeConn) SetReadDeadline(deadline time.Time) error {
if c.readDeadlineTimer == nil {
c.readDeadlineTimer = time.NewTimer(time.Hour)
}
c.readDeadlineCh = updateTimer(c.readDeadlineTimer, deadline)
readDeadlineCh := updateTimer(c.readDeadlineTimer, deadline)
c.readDeadlineChLock.Lock()
c.readDeadlineCh = readDeadlineCh
c.readDeadlineChLock.Unlock()
return nil
}

Expand Down

0 comments on commit 8713335

Please sign in to comment.