Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 06:00 UTC
usagepool: avoid lock inversion after constructor failure (#7968)
James Ko committed
Sep 2, 2026 at 17:33 UTC
996875820142dea9c4065cac941cf22118d8c946
2 files changed
+70
-7
usagepool.go
+7
-7
@@ -94,18 +94,18 @@ func (up *UsagePool) LoadOrNew(key any, construct Constructor) (value any, loade
94
value, err = construct()
95
if err == nil {
96
upv.value = value
97
+ upv.Unlock()
98
} else {
99
upv.err = err
100
+ upv.Unlock()
101
up.Lock()
100
- // this *should* be safe, I think, because we have a
101
- // write lock on upv, but we might also need to ensure
102
- // that upv.err is nil before doing this, since we
103
- // released the write lock on up during construct...
104
- // but then again it's also after midnight...
105
- delete(up.pool, key)
102
+ // Another constructor may have replaced a failed entry while
103
+ // this goroutine waited for the pool lock.
104
+ if up.pool[key] == upv {
105
+ delete(up.pool, key)
106
+ }
107
up.Unlock()
108
}
108
- upv.Unlock()
109
}
110
return value, loaded, err
111
}
usagepool_test.go
new
+63
@@ -0,0 +1,63 @@
1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+package caddy
16
+
17
+import (
18
+ "errors"
19
+ "testing"
20
+ "time"
21
+)
22
+
23
+func TestUsagePoolFailedConstructorDoesNotInvertLocks(t *testing.T) {
24
+ pool := NewUsagePool()
25
+ constructorStarted := make(chan struct{})
26
+ finishConstructor := make(chan struct{})
27
+ loadResult := make(chan error, 1)
28
+ constructErr := errors.New("construction failed")
29
+
30
+ go func() {
31
+ _, _, err := pool.LoadOrNew("key", func() (Destructor, error) {
32
+ close(constructorStarted)
33
+ <-finishConstructor
34
+ return nil, constructErr
35
+ })
36
+ loadResult <- err
37
+ }()
38
+
39
+ <-constructorStarted
40
+ pool.RLock()
41
+ entry := pool.pool["key"]
42
+ close(finishConstructor)
43
+
44
+ entryReadable := make(chan struct{})
45
+ go func() {
46
+ entry.RLock()
47
+ entry.RUnlock()
48
+ close(entryReadable)
49
+ }()
50
+
51
+ select {
52
+ case <-entryReadable:
53
+ pool.RUnlock()
54
+ case <-time.After(time.Second):
55
+ pool.RUnlock()
56
+ <-loadResult
57
+ t.Fatal("failed constructor held the entry lock while waiting for the pool lock")
58
+ }
59
+
60
+ if err := <-loadResult; !errors.Is(err, constructErr) {
61
+ t.Fatalf("LoadOrNew() error = %v, want %v", err, constructErr)
62
+ }
63
+}