I've been playing with server which redirects requests but then I noticed 
weird races.
What is odd that if these races are expected it would mean that using 
`*File` in `req.Body`, for requests which are redirected, is not safe - 
this is because we have races between `Read` and `Close`.

I've run following script with: `go run -race concept.go`

package main

import (
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
"time"
)

const (
addr1 = "localhost:8060"
addr2 = "localhost:8061"
)

type customReader struct {
iter int
size int
}

func (r *customReader) Read(b []byte) (int, error) {
maxRead := r.size - r.iter
if maxRead == 0 {
return 0, io.EOF
}
if maxRead > len(b) {
maxRead = len(b)
}
n, err := rand.Read(b[:maxRead])
r.iter += maxRead
return n, err
}

func (r *customReader) Close() error {
// Uncomment this for even more races :((
// r.iter = 0
return nil
}

func (r *customReader) Reset() {
r.iter = 0
}

func second(w http.ResponseWriter, r *http.Request) {
ioutil.ReadAll(r.Body)
}

func first(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "http://"+addr2+"/second";, http.StatusTemporaryRedirect)
}

func main() {
mux1 := http.NewServeMux()
mux1.HandleFunc("/first", first)
srv1 := http.Server{
Addr:    addr1,
Handler: mux1,
}
go func() {
err := srv1.ListenAndServe()
fmt.Printf("err: %v", err)
}()

mux2 := http.NewServeMux()
mux2.HandleFunc("/second", second)
srv2 := http.Server{
Addr:    addr2,
Handler: mux2,
}
go func() {
err := srv2.ListenAndServe()
fmt.Printf("err: %v", err)
}()

time.Sleep(time.Second)
client := http.DefaultClient

wg := &sync.WaitGroup{}
for i := 0; i < 1000; i++ {
reader := &customReader{size: 900000}
wg.Add(1)
go func(r *customReader) {
for {
req, err := http.NewRequest(http.MethodPut, "http://"+addr1+"/first";, r)
if err != nil {
fmt.Printf("%v", err)
}
req.GetBody = func() (io.ReadCloser, error) {
return &customReader{size: 900000}, nil
}

if resp, err := client.Do(req); err != nil {
// fmt.Printf("error: %v", err)
} else if resp.StatusCode >= http.StatusBadRequest {
// fmt.Printf("status code: %d", resp.StatusCode)
}

// Reset reader and try to reuse it in next request
r.Reset()
}

wg.Done()
}(reader)
}

wg.Wait() // infinite wait

srv1.Close()
srv2.Close()
}



Races which are occuring:

==================
WARNING: DATA RACE
Write at 0x00c0003e00a0 by goroutine 87:
  main.main.func3()
      /home/januszm/concept.go:43 +0x55

Previous write at 0x00c0003e00a0 by goroutine 3899:
  [failed to restore the stack]

Goroutine 87 (running) created at:
  main.main()
      /home/januszm/concept.go:91 +0x3b0

Goroutine 3899 (finished) created at:
  net/http.(*Transport).dialConn()
      /usr/local/go/src/net/http/transport.go:1358 +0xb89
  net/http.(*Transport).getConn.func4()
      /usr/local/go/src/net/http/transport.go:1015 +0xd0
==================
==================
WARNING: DATA RACE
Write at 0x00c0002ca750 by goroutine 488:
  main.main.func3()
      /home/januszm/concept.go:43 +0x55

Previous write at 0x00c0002ca750 by goroutine 1951:
  main.(*customReader).Read()
      /home/januszm/concept.go:33 +0x156
  net/http.transferBodyReader.Read()
      /usr/local/go/src/net/http/transfer.go:62 +0x77
  io.copyBuffer()
      /usr/local/go/src/io/io.go:402 +0x143
  net/http.(*transferWriter).writeBody()
      /usr/local/go/src/io/io.go:364 +0x76e
  net/http.(*Request).write()
      /usr/local/go/src/net/http/request.go:655 +0x7c3
  net/http.(*persistConn).writeLoop()
      /usr/local/go/src/net/http/transport.go:1961 +0x321

Goroutine 488 (running) created at:
  main.main()
      /home/januszm/concept.go:91 +0x3b0

Goroutine 1951 (running) created at:
  net/http.(*Transport).dialConn()
      /usr/local/go/src/net/http/transport.go:1358 +0xb89
  net/http.(*Transport).getConn.func4()
      /usr/local/go/src/net/http/transport.go:1015 +0xd0
==================

When L39 is uncommented, we also get:

==================
WARNING: DATA RACE
Write at 0x00c0001620e0 by goroutine 58:
  main.(*customReader).Close()
      /home/januszm/concept.go:39 +0x3a
  net/http.(*Client).do()
      /usr/local/go/src/net/http/request.go:1346 +0x6f2
  main.main.func3()
      /usr/local/go/src/net/http/client.go:509 +0x183

Previous write at 0x00c0001620e0 by goroutine 245:
  main.(*customReader).Read()
      /home/januszm/concept.go:33 +0x156
  net/http.transferBodyReader.Read()
      /usr/local/go/src/net/http/transfer.go:62 +0x77
  io.copyBuffer()
      /usr/local/go/src/io/io.go:402 +0x143
  net/http.(*transferWriter).writeBody()
      /usr/local/go/src/io/io.go:364 +0x76e
  net/http.(*Request).write()
      /usr/local/go/src/net/http/request.go:655 +0x7c3
  net/http.(*persistConn).writeLoop()
      /usr/local/go/src/net/http/transport.go:1961 +0x321

Goroutine 58 (running) created at:
  main.main()
      /home/januszm/concept.go:87 +0x3b0

Goroutine 245 (running) created at:
  net/http.(*Transport).dialConn()
      /usr/local/go/src/net/http/transport.go:1358 +0xb89
  net/http.(*Transport).getConn.func4()
      /usr/local/go/src/net/http/transport.go:1015 +0xd0
==================

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to