mirror of https://github.com/tailscale/tailscale/
types/logger: rate limited: more hysteresis, better messages.
- Switch to our own simpler token bucket, since x/time/rate is missing necessary stuff (can't provide your own time func; can't check the current bucket contents) and it's overkill anyway. - Add tests that actually include advancing time. - Don't remove the rate limit on a message until there's enough room to print at least two more of them. When we do, we'll also print how many we dropped, as a contextual reminder that some were previously lost. (This is more like how the Linux kernel does it.) - Reformat the [RATE LIMITED] messages to be shorter, and to not corrupt original message. Instead, we print the message, then print its format string. - Use %q instead of \"%s\", for more accurate parsing later, if the format string contained quotes. Fixes #1772 Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>apenwarr/statefix
parent
20e04418ff
commit
19c3e6cc9e
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenBucket is a simple token bucket style rate limiter.
|
||||||
|
|
||||||
|
// It's similar in function to golang.org/x/time/rate.Limiter, which we
|
||||||
|
// can't use because:
|
||||||
|
// - It doesn't give access to the number of accumulated tokens, which we
|
||||||
|
// need for implementing hysteresis;
|
||||||
|
// - It doesn't let us provide our own time function, which we need for
|
||||||
|
// implementing proper unit tests.
|
||||||
|
// rate.Limiter is also much more complex than necessary, but that wouldn't
|
||||||
|
// be enough to disqualify it on its own.
|
||||||
|
//
|
||||||
|
// Unlike rate.Limiter, this token bucket does not attempt to
|
||||||
|
// do any locking of its own. Don't try to access it re-entrantly.
|
||||||
|
// That's fine inside this types/logger package because we already have
|
||||||
|
// locking at a higher level.
|
||||||
|
type tokenBucket struct {
|
||||||
|
remaining int
|
||||||
|
max int
|
||||||
|
tick time.Duration
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTokenBucket(tick time.Duration, max int, now time.Time) *tokenBucket {
|
||||||
|
return &tokenBucket{max, max, tick, now}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tokenBucket) Get() bool {
|
||||||
|
if tb.remaining > 0 {
|
||||||
|
tb.remaining--
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tokenBucket) Refund(n int) {
|
||||||
|
b := tb.remaining + n
|
||||||
|
if b > tb.max {
|
||||||
|
tb.remaining = tb.max
|
||||||
|
} else {
|
||||||
|
tb.remaining = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *tokenBucket) AdvanceTo(t time.Time) {
|
||||||
|
diff := t.Sub(tb.t)
|
||||||
|
|
||||||
|
// only use up whole ticks. The remainder will be used up
|
||||||
|
// next time.
|
||||||
|
ticks := int(diff / tb.tick)
|
||||||
|
tb.t = tb.t.Add(time.Duration(ticks) * tb.tick)
|
||||||
|
|
||||||
|
tb.Refund(ticks)
|
||||||
|
}
|
Loading…
Reference in New Issue