Skip to content

Commit e12d479

Browse files
committed
Fix #77: double lock in NewContext + Purge.
1 parent 4a5ab06 commit e12d479

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

digest.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ func (c digestCache) Swap(i, j int) {
7272
// Purge removes count oldest entries from DigestAuth.clients
7373
func (da *DigestAuth) Purge(count int) {
7474
da.mutex.Lock()
75-
defer da.mutex.Unlock()
75+
da.purgeLocked(count)
76+
da.mutex.Unlock()
77+
}
78+
79+
func (da *DigestAuth) purgeLocked(count int) {
7680
entries := make([]digestCacheEntry, 0, len(da.clients))
7781
for nonce, client := range da.clients {
7882
entries = append(entries, digestCacheEntry{nonce, client.lastSeen})
@@ -259,7 +263,7 @@ func (da *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.C
259263
} else {
260264
// return back digest WWW-Authenticate header
261265
if len(da.clients) > da.ClientCacheSize+da.ClientCacheTolerance {
262-
da.Purge(da.ClientCacheTolerance * 2)
266+
da.purgeLocked(da.ClientCacheTolerance * 2)
263267
}
264268
nonce := RandomKey()
265269
da.clients[nonce] = &digestClient{nc: 0, lastSeen: time.Now().UnixNano()}

digest_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package auth
22

33
import (
4+
"context"
45
"net/http"
56
"net/url"
7+
"sync"
68
"testing"
79
"time"
810
)
@@ -76,3 +78,44 @@ func TestDigestAuthParams(t *testing.T) {
7678
t.Fatalf("failed to parse uri with embedded commas, got %q want %q", params["uri"], want)
7779
}
7880
}
81+
82+
func TestNewContextNoDeadlock(t *testing.T) {
83+
t.Parallel()
84+
const (
85+
realm = "example.com"
86+
user = "user"
87+
)
88+
secrets := func(u, r string) string {
89+
if u == user && r == realm {
90+
return "aa78524fceb0e50fd8ca96dd818b8cf9"
91+
}
92+
return ""
93+
}
94+
da := NewDigestAuthenticator(realm, secrets)
95+
da.ClientCacheSize = 10
96+
da.ClientCacheTolerance = 1
97+
var wg sync.WaitGroup
98+
for i := 0; i < 100; i++ {
99+
ctx := context.Background()
100+
req, err := http.NewRequest("GET", "/", nil)
101+
if err != nil {
102+
t.Fatalf("Failed to create http.Request: %v", err)
103+
}
104+
wg.Add(1)
105+
go func() {
106+
defer wg.Done()
107+
done := make(chan struct{})
108+
go func() {
109+
da.NewContext(ctx, req)
110+
close(done)
111+
}()
112+
select {
113+
case <-done:
114+
return
115+
case <-time.After(time.Second):
116+
t.Error("deadlock detected")
117+
}
118+
}()
119+
}
120+
wg.Wait()
121+
}

0 commit comments

Comments
 (0)