diff --git a/go.mod b/go.mod index 7cbe979b..e6ebac8f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/prometheus/client_golang v1.20.0 github.com/vishvananda/netns v0.0.4 go.uber.org/atomic v1.11.0 - go.uber.org/ratelimit v0.2.0 + go.uber.org/ratelimit v0.3.1 go.uber.org/zap v1.27.0 golang.org/x/sync v0.8.0 google.golang.org/grpc v1.65.0 @@ -30,7 +30,7 @@ require ( github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.4 // indirect - github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect diff --git a/go.sum b/go.sum index 0dc6133c..dc746a1a 100644 --- a/go.sum +++ b/go.sum @@ -14,9 +14,9 @@ github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b h1:5/++qT1/z812ZqBvqQt6ToRswSuPZ/B33m6xVHRzADU= github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b/go.mod h1:4+EPqMRApwwE/6yo6CxiHoSnBzjRr3jsqer7frxP8y4= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= -github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= @@ -240,7 +240,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -273,15 +272,14 @@ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVf go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= -go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/vendor/github.com/andres-erbsen/clock/.travis.yml b/vendor/github.com/andres-erbsen/clock/.travis.yml deleted file mode 100644 index ca785e51..00000000 --- a/vendor/github.com/andres-erbsen/clock/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -go: - - 1.3 - - 1.4 - - release - - tip -sudo: false diff --git a/vendor/github.com/andres-erbsen/clock/LICENSE b/vendor/github.com/benbjohnson/clock/LICENSE similarity index 94% rename from vendor/github.com/andres-erbsen/clock/LICENSE rename to vendor/github.com/benbjohnson/clock/LICENSE index ddf4e001..ce212cb1 100644 --- a/vendor/github.com/andres-erbsen/clock/LICENSE +++ b/vendor/github.com/benbjohnson/clock/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Ben Johnson, Copyright (c) 2015 Yahoo Inc. +Copyright (c) 2014 Ben Johnson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/andres-erbsen/clock/README.md b/vendor/github.com/benbjohnson/clock/README.md similarity index 69% rename from vendor/github.com/andres-erbsen/clock/README.md rename to vendor/github.com/benbjohnson/clock/README.md index f744e766..4f1f82fc 100644 --- a/vendor/github.com/andres-erbsen/clock/README.md +++ b/vendor/github.com/benbjohnson/clock/README.md @@ -1,22 +1,25 @@ -clock [![Build Status](https://travis-ci.org/andres-erbsen/clock.svg)](https://travis-ci.org/andres-erbsen/clock) [![Coverage Status](https://coveralls.io/repos/andres-erbsen/clock/badge.png?branch=master)](https://coveralls.io/r/andres-erbsen/clock?branch=master) [![GoDoc](https://godoc.org/github.com/andres-erbsen/clock?status.png)](https://godoc.org/github.com/andres-erbsen/clock) ![Project status](http://img.shields.io/status/experimental.png?color=red) +clock ===== +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/benbjohnson/clock) + Clock is a small library for mocking time in Go. It provides an interface around the standard library's [`time`][time] package so that the application can use the realtime clock while tests can use the mock clock. -[time]: http://golang.org/pkg/time/ +The module is currently maintained by @djmitche. +[time]: https://pkg.go.dev/github.com/benbjohnson/clock ## Usage ### Realtime Clock Your application can maintain a `Clock` variable that will allow realtime and -mock clocks to be interchangable. For example, if you had an `Application` type: +mock clocks to be interchangeable. For example, if you had an `Application` type: ```go -import "github.com/andres-erbsen/clock" +import "github.com/benbjohnson/clock" type Application struct { Clock clock.Clock @@ -43,7 +46,7 @@ In your tests, you will want to use a `Mock` clock: import ( "testing" - "github.com/andres-erbsen/clock" + "github.com/benbjohnson/clock" ) func TestApplication_DoSomething(t *testing.T) { @@ -55,7 +58,7 @@ func TestApplication_DoSomething(t *testing.T) { Now that you've initialized your application to use the mock clock, you can adjust the time programmatically. The mock clock always starts from the Unix -epoch (midnight, Jan 1, 1970 UTC). +epoch (midnight UTC on Jan 1, 1970). ### Controlling time @@ -80,13 +83,13 @@ mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC Timers and Tickers are also controlled by this same mock clock. They will only execute when the clock is moved forward: -``` +```go mock := clock.NewMock() count := 0 // Kick off a timer to increment every 1 mock second. go func() { - ticker := clock.Ticker(1 * time.Second) + ticker := mock.Ticker(1 * time.Second) for { <-ticker.C count++ @@ -94,11 +97,9 @@ go func() { }() runtime.Gosched() -// Move the clock forward 10 second. +// Move the clock forward 10 seconds. mock.Add(10 * time.Second) // This prints 10. fmt.Println(count) ``` - - diff --git a/vendor/github.com/andres-erbsen/clock/clock.go b/vendor/github.com/benbjohnson/clock/clock.go similarity index 68% rename from vendor/github.com/andres-erbsen/clock/clock.go rename to vendor/github.com/benbjohnson/clock/clock.go index b58b7032..40555b30 100644 --- a/vendor/github.com/andres-erbsen/clock/clock.go +++ b/vendor/github.com/benbjohnson/clock/clock.go @@ -1,24 +1,32 @@ package clock import ( + "context" "sort" "sync" "time" ) +// Re-export of time.Duration +type Duration = time.Duration + // Clock represents an interface to the functions in the standard library time // package. Two implementations are available in the clock package. The first // is a real-time clock which simply wraps the time package's functions. The -// second is a mock clock which will only make forward progress when +// second is a mock clock which will only change when // programmatically adjusted. type Clock interface { After(d time.Duration) <-chan time.Time AfterFunc(d time.Duration, f func()) *Timer Now() time.Time + Since(t time.Time) time.Duration + Until(t time.Time) time.Duration Sleep(d time.Duration) Tick(d time.Duration) <-chan time.Time Ticker(d time.Duration) *Ticker Timer(d time.Duration) *Timer + WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc) + WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc) } // New returns an instance of a real-time clock. @@ -37,6 +45,10 @@ func (c *clock) AfterFunc(d time.Duration, f func()) *Timer { func (c *clock) Now() time.Time { return time.Now() } +func (c *clock) Since(t time.Time) time.Duration { return time.Since(t) } + +func (c *clock) Until(t time.Time) time.Duration { return time.Until(t) } + func (c *clock) Sleep(d time.Duration) { time.Sleep(d) } func (c *clock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) } @@ -51,6 +63,14 @@ func (c *clock) Timer(d time.Duration) *Timer { return &Timer{C: t.C, timer: t} } +func (c *clock) WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc) { + return context.WithDeadline(parent, d) +} + +func (c *clock) WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc) { + return context.WithTimeout(parent, t) +} + // Mock represents a mock clock that only moves forward programmically. // It can be preferable to a real-time clock when testing time-based functionality. type Mock struct { @@ -65,7 +85,7 @@ func NewMock() *Mock { return &Mock{now: time.Unix(0, 0)} } -// Add moves the current time of the mock clock forward by the duration. +// Add moves the current time of the mock clock forward by the specified duration. // This should only be called from a single goroutine at a time. func (m *Mock) Add(d time.Duration) { // Calculate the final current time. @@ -83,11 +103,11 @@ func (m *Mock) Add(d time.Duration) { m.now = t m.mu.Unlock() - // Give a small buffer to make sure the other goroutines get handled. + // Give a small buffer to make sure that other goroutines get handled. gosched() } -// Sets the current time of the mock clock to a specific one. +// Set sets the current time of the mock clock to a specific one. // This should only be called from a single goroutine at a time. func (m *Mock) Set(t time.Time) { // Continue to execute timers until there are no more before the new time. @@ -102,13 +122,13 @@ func (m *Mock) Set(t time.Time) { m.now = t m.mu.Unlock() - // Give a small buffer to make sure the other goroutines get handled. + // Give a small buffer to make sure that other goroutines get handled. gosched() } // runNextTimer executes the next timer in chronological order and moves the // current time to the timer's next tick time. The next time is not executed if -// it's next time if after the max time. Returns true if a timer is executed. +// its next time is after the max time. Returns true if a timer was executed. func (m *Mock) runNextTimer(max time.Time) bool { m.mu.Lock() @@ -158,6 +178,16 @@ func (m *Mock) Now() time.Time { return m.now } +// Since returns time since `t` using the mock clock's wall time. +func (m *Mock) Since(t time.Time) time.Duration { + return m.Now().Sub(t) +} + +// Until returns time until `t` using the mock clock's wall time. +func (m *Mock) Until(t time.Time) time.Duration { + return t.Sub(m.Now()) +} + // Sleep pauses the goroutine for the given duration on the mock clock. // The clock must be moved forward in a separate goroutine. func (m *Mock) Sleep(d time.Duration) { @@ -188,30 +218,23 @@ func (m *Mock) Ticker(d time.Duration) *Ticker { // Timer creates a new instance of Timer. func (m *Mock) Timer(d time.Duration) *Timer { + m.mu.Lock() + defer m.mu.Unlock() ch := make(chan time.Time, 1) t := &Timer{ - C: ch, - c: ch, - mock: m, - next: m.Now().Add(d), + C: ch, + c: ch, + mock: m, + next: m.now.Add(d), + stopped: false, } - m.addTimer((*internalTimer)(t)) + m.timers = append(m.timers, (*internalTimer)(t)) return t } -func (m *Mock) addTimer(t *internalTimer) { - m.mu.Lock() - defer m.mu.Unlock() - m.timers = append(m.timers, t) -} - -func (m *Mock) removeClockTimer(t clockTimer) bool { - m.mu.Lock() - defer m.mu.Unlock() - ret := false +func (m *Mock) removeClockTimer(t clockTimer) { for i, timer := range m.timers { if timer == t { - ret = true copy(m.timers[i:], m.timers[i+1:]) m.timers[len(m.timers)-1] = nil m.timers = m.timers[:len(m.timers)-1] @@ -219,7 +242,6 @@ func (m *Mock) removeClockTimer(t clockTimer) bool { } } sort.Sort(m.timers) - return ret } // clockTimer represents an object with an associated start time. @@ -238,48 +260,66 @@ func (a clockTimers) Less(i, j int) bool { return a[i].Next().Before(a[j].Next() // Timer represents a single event. // The current time will be sent on C, unless the timer was created by AfterFunc. type Timer struct { - C <-chan time.Time - c chan time.Time - timer *time.Timer // realtime impl, if set - next time.Time // next tick time - mock *Mock // mock clock, if set - fn func() // AfterFunc function, if set + C <-chan time.Time + c chan time.Time + timer *time.Timer // realtime impl, if set + next time.Time // next tick time + mock *Mock // mock clock, if set + fn func() // AfterFunc function, if set + stopped bool // True if stopped, false if running } -// Stop turns off the timer. +// Stop turns off the ticker. func (t *Timer) Stop() bool { if t.timer != nil { return t.timer.Stop() } - return t.mock.removeClockTimer((*internalTimer)(t)) + + t.mock.mu.Lock() + registered := !t.stopped + t.mock.removeClockTimer((*internalTimer)(t)) + t.stopped = true + t.mock.mu.Unlock() + return registered } -// Reset changes the timer to expire after duration d. It returns true if the -// timer had been active, false if the timer had expired or been stopped. +// Reset changes the expiry time of the timer func (t *Timer) Reset(d time.Duration) bool { if t.timer != nil { return t.timer.Reset(d) } - ret := t.mock.removeClockTimer((*internalTimer)(t)) - t.next = t.mock.Now().Add(d) - t.mock.addTimer((*internalTimer)(t)) - return ret + + t.mock.mu.Lock() + t.next = t.mock.now.Add(d) + defer t.mock.mu.Unlock() + + registered := !t.stopped + if t.stopped { + t.mock.timers = append(t.mock.timers, (*internalTimer)(t)) + } + + t.stopped = false + return registered } type internalTimer Timer func (t *internalTimer) Next() time.Time { return t.next } func (t *internalTimer) Tick(now time.Time) { + // a gosched() after ticking, to allow any consequences of the + // tick to complete + defer gosched() + + t.mock.mu.Lock() if t.fn != nil { - t.fn() + // defer function execution until the lock is released, and + defer t.fn() } else { - select { - case t.c <- now: - default: - } + t.c <- now } t.mock.removeClockTimer((*internalTimer)(t)) - gosched() + t.stopped = true + t.mock.mu.Unlock() } // Ticker holds a channel that receives "ticks" at regular intervals. @@ -297,8 +337,24 @@ func (t *Ticker) Stop() { if t.ticker != nil { t.ticker.Stop() } else { + t.mock.mu.Lock() t.mock.removeClockTimer((*internalTicker)(t)) + t.mock.mu.Unlock() + } +} + +// Reset resets the ticker to a new duration. +func (t *Ticker) Reset(dur time.Duration) { + if t.ticker != nil { + t.ticker.Reset(dur) + return } + + t.mock.mu.Lock() + defer t.mock.mu.Unlock() + + t.d = dur + t.next = t.mock.now.Add(dur) } type internalTicker Ticker @@ -315,3 +371,8 @@ func (t *internalTicker) Tick(now time.Time) { // Sleep momentarily so that other goroutines can process. func gosched() { time.Sleep(1 * time.Millisecond) } + +var ( + // type checking + _ Clock = &Mock{} +) diff --git a/vendor/github.com/benbjohnson/clock/context.go b/vendor/github.com/benbjohnson/clock/context.go new file mode 100644 index 00000000..eb67594f --- /dev/null +++ b/vendor/github.com/benbjohnson/clock/context.go @@ -0,0 +1,86 @@ +package clock + +import ( + "context" + "fmt" + "sync" + "time" +) + +func (m *Mock) WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + return m.WithDeadline(parent, m.Now().Add(timeout)) +} + +func (m *Mock) WithDeadline(parent context.Context, deadline time.Time) (context.Context, context.CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return context.WithCancel(parent) + } + ctx := &timerCtx{clock: m, parent: parent, deadline: deadline, done: make(chan struct{})} + propagateCancel(parent, ctx) + dur := m.Until(deadline) + if dur <= 0 { + ctx.cancel(context.DeadlineExceeded) // deadline has already passed + return ctx, func() {} + } + ctx.Lock() + defer ctx.Unlock() + if ctx.err == nil { + ctx.timer = m.AfterFunc(dur, func() { + ctx.cancel(context.DeadlineExceeded) + }) + } + return ctx, func() { ctx.cancel(context.Canceled) } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent context.Context, child *timerCtx) { + if parent.Done() == nil { + return // parent is never canceled + } + go func() { + select { + case <-parent.Done(): + child.cancel(parent.Err()) + case <-child.Done(): + } + }() +} + +type timerCtx struct { + sync.Mutex + + clock Clock + parent context.Context + deadline time.Time + done chan struct{} + + err error + timer *Timer +} + +func (c *timerCtx) cancel(err error) { + c.Lock() + defer c.Unlock() + if c.err != nil { + return // already canceled + } + c.err = err + close(c.done) + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { return c.deadline, true } + +func (c *timerCtx) Done() <-chan struct{} { return c.done } + +func (c *timerCtx) Err() error { return c.err } + +func (c *timerCtx) Value(key interface{}) interface{} { return c.parent.Value(key) } + +func (c *timerCtx) String() string { + return fmt.Sprintf("clock.WithDeadline(%s [%s])", c.deadline, c.deadline.Sub(c.clock.Now())) +} diff --git a/vendor/go.uber.org/ratelimit/.gitignore b/vendor/go.uber.org/ratelimit/.gitignore index aa346ac4..23bd4890 100644 --- a/vendor/go.uber.org/ratelimit/.gitignore +++ b/vendor/go.uber.org/ratelimit/.gitignore @@ -2,5 +2,10 @@ /vendor cover.html cover.out +profile.out +stat.csv +stat.txt +stat.html *.swp +.idea diff --git a/vendor/go.uber.org/ratelimit/CHANGELOG.md b/vendor/go.uber.org/ratelimit/CHANGELOG.md index b2b89cf7..6d265cbd 100644 --- a/vendor/go.uber.org/ratelimit/CHANGELOG.md +++ b/vendor/go.uber.org/ratelimit/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## Unreleased +- No changes yet. + +## v0.3.1 - 2024-03-04 +### Fixed +- Fixed a bug related to maxSlack boundary detection. #124 + Thanks to @smallnest for reporting and @storozhukBM for fixing. + +## v0.3.0 - 2023-07-08 +### Changed +- Switched to a more efficient internal implementation. No API or behavior changes. + [#100](https://github.com/uber-go/ratelimit/pull/100) + ## v0.2.0 - 2021-03-02 ### Added - Allow configuring the limiter with custom slack. diff --git a/vendor/go.uber.org/ratelimit/Makefile b/vendor/go.uber.org/ratelimit/Makefile index 5bab5d77..7eb6d246 100644 --- a/vendor/go.uber.org/ratelimit/Makefile +++ b/vendor/go.uber.org/ratelimit/Makefile @@ -6,8 +6,18 @@ GO_FILES := $(shell \ -o -name '*.go' -print | cut -b3-) .PHONY: bench -bench: - go test -bench=. ./... +bench: bin/benchstat bin/benchart + go test -timeout 3h -count=5 -run=xxx -bench=BenchmarkRateLimiter ./... | tee stat.txt + @$(GOBIN)/benchstat stat.txt + @$(GOBIN)/benchstat -csv stat.txt > stat.csv + @$(GOBIN)/benchart 'RateLimiter;xAxisType=log' stat.csv stat.html + @open stat.html + +bin/benchstat: tools/go.mod + @cd tools && go install golang.org/x/perf/cmd/benchstat + +bin/benchart: tools/go.mod + @cd tools && go install github.com/storozhukBM/benchart bin/golint: tools/go.mod @cd tools && go install golang.org/x/lint/golint diff --git a/vendor/go.uber.org/ratelimit/README.md b/vendor/go.uber.org/ratelimit/README.md index a05a2a88..84a273f9 100644 --- a/vendor/go.uber.org/ratelimit/README.md +++ b/vendor/go.uber.org/ratelimit/README.md @@ -39,8 +39,20 @@ func main() { } ``` +## FAQ: +- What's the major diff v.s. https://pkg.go.dev/golang.org/x/time/rate? (based on #77) + + This ratelimiter was meant to have a (1) simple API and (2) minimal overhead. For more complex use-cases [x/time/rate] is a great choice. See [here][redit] for historical context, and [here][bench] for benchmarks (from 2016). + +- Why does example_test.go fail when I run it locally on Windows? (based on #80) + + Windows has some known issues with timers precision. See golang/go#44343. We don't expect to work around it. + [cov-img]: https://codecov.io/gh/uber-go/ratelimit/branch/master/graph/badge.svg?token=zhLeUjjrm2 [cov]: https://codecov.io/gh/uber-go/ratelimit [doc-img]: https://pkg.go.dev/badge/go.uber.org/ratelimit [doc]: https://pkg.go.dev/go.uber.org/ratelimit [test-img]: https://github.com/uber-go/ratelimit/workflows/test/badge.svg +[redit]: https://www.reddit.com/r/golang/comments/59k2bi/ubergoratelimit_a_golang_blocking_leakybucket/d99ob9q +[x/time/rate]: https://pkg.go.dev/golang.org/x/time/rate +[bench]: https://gist.github.com/prashantv/26016a7dbc6fc1ec52d8c2b6591f3582 diff --git a/vendor/go.uber.org/ratelimit/limiter_atomic.go b/vendor/go.uber.org/ratelimit/limiter_atomic.go index 745aa4cb..ac6465ca 100644 --- a/vendor/go.uber.org/ratelimit/limiter_atomic.go +++ b/vendor/go.uber.org/ratelimit/limiter_atomic.go @@ -64,7 +64,7 @@ func newAtomicBased(rate int, opts ...Option) *atomicLimiter { } // Take blocks to ensure that the time spent between multiple -// Take calls is on average time.Second/rate. +// Take calls is on average per/rate. func (t *atomicLimiter) Take() time.Time { var ( newState state diff --git a/vendor/go.uber.org/ratelimit/limiter_atomic_int64.go b/vendor/go.uber.org/ratelimit/limiter_atomic_int64.go new file mode 100644 index 00000000..8d370b73 --- /dev/null +++ b/vendor/go.uber.org/ratelimit/limiter_atomic_int64.go @@ -0,0 +1,92 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ratelimit // import "go.uber.org/ratelimit" + +import ( + "sync/atomic" + "time" +) + +type atomicInt64Limiter struct { + //lint:ignore U1000 Padding is unused but it is crucial to maintain performance + // of this rate limiter in case of collocation with other frequently accessed memory. + prepadding [64]byte // cache line size = 64; created to avoid false sharing. + state int64 // unix nanoseconds of the next permissions issue. + //lint:ignore U1000 like prepadding. + postpadding [56]byte // cache line size - state size = 64 - 8; created to avoid false sharing. + + perRequest time.Duration + maxSlack time.Duration + clock Clock +} + +// newAtomicBased returns a new atomic based limiter. +func newAtomicInt64Based(rate int, opts ...Option) *atomicInt64Limiter { + // TODO consider moving config building to the implementation + // independent code. + config := buildConfig(opts) + perRequest := config.per / time.Duration(rate) + l := &atomicInt64Limiter{ + perRequest: perRequest, + maxSlack: time.Duration(config.slack) * perRequest, + clock: config.clock, + } + atomic.StoreInt64(&l.state, 0) + return l +} + +// Take blocks to ensure that the time spent between multiple +// Take calls is on average time.Second/rate. +func (t *atomicInt64Limiter) Take() time.Time { + var ( + newTimeOfNextPermissionIssue int64 + now int64 + ) + for { + now = t.clock.Now().UnixNano() + timeOfNextPermissionIssue := atomic.LoadInt64(&t.state) + + switch { + case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)): + // if this is our first call or t.maxSlack == 0 we need to shrink issue time to now + newTimeOfNextPermissionIssue = now + case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest): + // a lot of nanoseconds passed since the last Take call + // we will limit max accumulated time to maxSlack + newTimeOfNextPermissionIssue = now - int64(t.maxSlack) + default: + // calculate the time at which our permission was issued + newTimeOfNextPermissionIssue = timeOfNextPermissionIssue + int64(t.perRequest) + } + + if atomic.CompareAndSwapInt64(&t.state, timeOfNextPermissionIssue, newTimeOfNextPermissionIssue) { + break + } + } + + sleepDuration := time.Duration(newTimeOfNextPermissionIssue - now) + if sleepDuration > 0 { + t.clock.Sleep(sleepDuration) + return time.Unix(0, newTimeOfNextPermissionIssue) + } + // return now if we don't sleep as atomicLimiter does + return time.Unix(0, now) +} diff --git a/vendor/go.uber.org/ratelimit/limiter_mutexbased.go b/vendor/go.uber.org/ratelimit/limiter_mutexbased.go index 1408f1c5..7bd18f79 100644 --- a/vendor/go.uber.org/ratelimit/limiter_mutexbased.go +++ b/vendor/go.uber.org/ratelimit/limiter_mutexbased.go @@ -34,7 +34,7 @@ type mutexLimiter struct { clock Clock } -// newMutexBased returns a new atomic based limiter. +// newMutexBased returns a new mutex based limiter. func newMutexBased(rate int, opts ...Option) *mutexLimiter { // TODO consider moving config building to the implementation // independent code. @@ -49,7 +49,7 @@ func newMutexBased(rate int, opts ...Option) *mutexLimiter { } // Take blocks to ensure that the time spent between multiple -// Take calls is on average time.Second/rate. +// Take calls is on average per/rate. func (t *mutexLimiter) Take() time.Time { t.Lock() defer t.Unlock() diff --git a/vendor/go.uber.org/ratelimit/ratelimit.go b/vendor/go.uber.org/ratelimit/ratelimit.go index b5b16e57..22b88ecd 100644 --- a/vendor/go.uber.org/ratelimit/ratelimit.go +++ b/vendor/go.uber.org/ratelimit/ratelimit.go @@ -23,7 +23,7 @@ package ratelimit // import "go.uber.org/ratelimit" import ( "time" - "github.com/andres-erbsen/clock" + "github.com/benbjohnson/clock" ) // Note: This file is inspired by: @@ -54,7 +54,7 @@ type config struct { // New returns a Limiter that will limit to the given RPS. func New(rate int, opts ...Option) Limiter { - return newAtomicBased(rate, opts...) + return newAtomicInt64Based(rate, opts...) } // buildConfig combines defaults with options. diff --git a/vendor/modules.txt b/vendor/modules.txt index 82497a86..8d54698c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -45,9 +45,9 @@ github.com/Microsoft/hcsshim/pkg/ociwclayer # github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b ## explicit; go 1.13 github.com/RussellLuo/slidingwindow -# github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 -## explicit -github.com/andres-erbsen/clock +# github.com/benbjohnson/clock v1.3.0 +## explicit; go 1.15 +github.com/benbjohnson/clock # github.com/beorn7/perks v1.0.1 ## explicit; go 1.11 github.com/beorn7/perks/quantile @@ -429,8 +429,8 @@ go.uber.org/atomic # go.uber.org/multierr v1.11.0 ## explicit; go 1.19 go.uber.org/multierr -# go.uber.org/ratelimit v0.2.0 -## explicit; go 1.14 +# go.uber.org/ratelimit v0.3.1 +## explicit; go 1.20 go.uber.org/ratelimit # go.uber.org/zap v1.27.0 ## explicit; go 1.19