1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package limiters2021import (22 "context"23 "errors"24 "time"25)2627var ErrClosed = errors.New("limiters: Rate bucket is closed")2829// Rate structure implements a basic rate-limiter for requests using the token30// bucket approach.31//32// Take() is expected to be called before each request. Excessive calls will33// block. Timeouts can be implemented using the TakeContext method.34//35// Rate.Close causes all waiting Take to return false. TakeContext returns36// ErrClosed in this case.37//38// If burstSize = 0, all methods are no-op and always succeed.39type Rate struct {40 bucket chan struct{}41 stop chan struct{}42}4344func NewRate(burstSize int, interval time.Duration) Rate {45 r := Rate{46 bucket: make(chan struct{}, burstSize),47 stop: make(chan struct{}),48 }4950 if burstSize == 0 {51 return r52 }5354 for i := 0; i < burstSize; i++ {55 r.bucket <- struct{}{}56 }5758 go r.fill(burstSize, interval)59 return r60}6162func (r Rate) fill(burstSize int, interval time.Duration) {63 t := time.NewTimer(interval)64 defer t.Stop()65 for {66 t.Reset(interval)67 select {68 case <-t.C:69 case <-r.stop:70 close(r.bucket)71 return72 }7374 fill:75 for i := 0; i < burstSize; i++ {76 select {77 case r.bucket <- struct{}{}:78 default:79 // If there are no Take pending and the bucket is already80 // full - don't block.81 break fill82 }83 }84 }85}8687func (r Rate) Take() bool {88 if cap(r.bucket) == 0 {89 return true90 }9192 _, ok := <-r.bucket93 return ok94}9596func (r Rate) TakeContext(ctx context.Context) error {97 if cap(r.bucket) == 0 {98 return nil99 }100101 select {102 case _, ok := <-r.bucket:103 if !ok {104 return ErrClosed105 }106 return nil107 case <-ctx.Done():108 return ctx.Err()109 }110}111112func (r Rate) Release() {113}114115func (r Rate) Close() {116 close(r.stop)117}