Closed jkowalski closed 4 years ago
@jkowalski Can you please provide code to reproduce this behavior? And what WebDav server do you use?
package main
import (
"log"
"net/http"
"net/http/httptest"
"sync"
"github.com/studio-b12/gowebdav"
"golang.org/x/net/webdav"
)
func basicAuth(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if user, passwd, ok := r.BasicAuth(); ok {
if user == "user" && passwd == "password" {
h.ServeHTTP(w, r)
return
}
http.Error(w, "not authorized", 403)
} else {
w.Header().Set("WWW-Authenticate", `Basic realm="x"`)
w.WriteHeader(401)
}
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", basicAuth(&webdav.Handler{
FileSystem: webdav.Dir("/"),
LockSystem: webdav.NewMemLS(),
}))
server := httptest.NewServer(mux)
defer server.Close()
cli := gowebdav.NewClient(server.URL, "user", "password")
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
defer wg.Done()
f, err := cli.ReadDir("/")
log.Printf("f: %v err: %v", f, err)
}()
}
wg.Wait()
}
Run this with go -race repro.go
and it prints:
╰─➤ go run -race repro 1 ↵
==================
WARNING: DATA RACE
Read at 0x00c00011cf50 by goroutine 9:
github.com/studio-b12/gowebdav.(*Client).req()
/Users/jarek/Projects/gowebdav/requests.go:45 +0x1276
github.com/studio-b12/gowebdav.(*Client).propfind()
/Users/jarek/Projects/gowebdav/requests.go:94 +0x15a
github.com/studio-b12/gowebdav.(*Client).ReadDir()
/Users/jarek/Projects/gowebdav/client.go:164 +0x1fb
main.main.func1()
/Users/jarek/Projects/repro/main.go:48 +0x9f
Previous write at 0x00c00011cf50 by goroutine 8:
github.com/studio-b12/gowebdav.(*Client).req()
/Users/jarek/Projects/gowebdav/requests.go:54 +0xd48
github.com/studio-b12/gowebdav.(*Client).propfind()
/Users/jarek/Projects/gowebdav/requests.go:94 +0x15a
github.com/studio-b12/gowebdav.(*Client).ReadDir()
/Users/jarek/Projects/gowebdav/client.go:164 +0x1fb
main.main.func1()
/Users/jarek/Projects/repro/main.go:48 +0x9f
Goroutine 9 (running) created at:
main.main()
/Users/jarek/Projects/repro/main.go:46 +0x4de
Goroutine 8 (running) created at:
main.main()
/Users/jarek/Projects/repro/main.go:46 +0x4de
The bug is because two different instances of req()
are both racing to update request headers stored in c.headers
I think there are 2 required fixes:
c.auth
only once - use mutex or sync.Once for thatAuthorize()
method to take http.Headers
and set authentication directly on each request instead of on Client
- move the call after copying headers from c.headers
I actually have made a fix and will send PR shortly.
@jkowalski thank you for your report and patch. i skimmed though your improvement. it's definitely a solution.
Here are my points which I have to think more about.
Any updates on this? without a fix in #37 I can reproduce this issue very frequently.
We are getting the same error and I would love to see the fix merged :)
@jozuenoon I suppose you could use @jkowalski's fork for the time being. @jkowalski What is the reason for moving to http.Request from Client? Also, in requests.go, why are the Authorize call and the Header.Add loop rearranged? It's been quite a while since I've worked on this but if I remember correctly, that order was important.
The race condition is due to unsafe changes to c.auth
and parallel modifications to the Client from two separate goroutines.
While i'm sure this could be made safer with a mutex around client, it's not really necessary to share the client (for writes) at all, since all requests are individually authorized anyway. The client in this case just provides additional headers to be set on each request, which is what my PR is doing.
@jkowalski thank you! I changed my mind and applied your patch to keep the library working. we should face #38 as next task. Welcome!
I'm sometimes getting this panic when running multiple requests in parallel using a single webdav client:
Should parallel calls using single webdav client be supported?