types/logger: fix deadlock RateLimitedFn reentrancy

Fix regression from 19c3e6cc9e
which made the locking coarser.

Found while debugging #2245, which ended up looking like a tswin/Windows
issue where Crawshaw had blocked cmd.exe's output.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-06-25 08:14:27 -07:00 committed by Brad Fitzpatrick
parent 59e9b44f53
commit 8a4dffee07
2 changed files with 21 additions and 5 deletions

View File

@ -106,7 +106,6 @@ func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int,
} }
mu.Lock() mu.Lock()
defer mu.Unlock()
rl, ok := msgLim[format] rl, ok := msgLim[format]
if ok { if ok {
msgCache.MoveToFront(rl.ele) msgCache.MoveToFront(rl.ele)
@ -138,8 +137,8 @@ func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int,
rl.nBlocked = 0 rl.nBlocked = 0
} }
if rl.nBlocked == 0 && rl.bucket.Get() { if rl.nBlocked == 0 && rl.bucket.Get() {
logf(format, args...) hitLimit := rl.bucket.remaining == 0
if rl.bucket.remaining == 0 { if hitLimit {
// Enter "blocked" mode immediately after // Enter "blocked" mode immediately after
// reaching the burst limit. We want to // reaching the burst limit. We want to
// always accompany the format() message // always accompany the format() message
@ -148,12 +147,16 @@ func RateLimitedFnWithClock(logf Logf, f time.Duration, burst int, maxCache int,
// message anyway. But this way they can // message anyway. But this way they can
// be on two separate lines and we don't // be on two separate lines and we don't
// corrupt the original message. // corrupt the original message.
logf("[RATELIMIT] format(%q)", format)
rl.nBlocked = 1 rl.nBlocked = 1
} }
return mu.Unlock() // release before calling logf
logf(format, args...)
if hitLimit {
logf("[RATELIMIT] format(%q)", format)
}
} else { } else {
rl.nBlocked++ rl.nBlocked++
mu.Unlock()
} }
} }
} }

View File

@ -170,3 +170,16 @@ func TestSynchronization(t *testing.T) {
}) })
} }
} }
// test that RateLimitedFn is safe for reentrancy without deadlocking
func TestRateLimitedFnReentrancy(t *testing.T) {
rlogf := RateLimitedFn(t.Logf, time.Nanosecond, 10, 10)
rlogf("Hello.")
rlogf("Hello, %v", ArgWriter(func(bw *bufio.Writer) {
bw.WriteString("world")
}))
rlogf("Hello, %v", ArgWriter(func(bw *bufio.Writer) {
bw.WriteString("bye")
rlogf("boom") // this used to deadlock
}))
}