@marcel91200 / caddy Mirror · read-only / commits / 99687582
Fork 0

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 +}