├── demo ├── test.cpp ├── wait_example.cpp ├── reset_burst.cpp ├── reserve_example.cpp └── reset_rate.cpp ├── .gitignore ├── LICENSE ├── README.md └── include └── Limiter.h /demo/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | std::cout< 18 | #include 19 | 20 | #include "../include/Limiter.h" 21 | 22 | token_bucket::Limiter* lim_p; 23 | 24 | int main() { 25 | lim_p = new token_bucket::Limiter(3.0, 5.0); 26 | for(int loop_time = 0; loop_time < 5; ) { 27 | //wait(cosume tokens num, max wait time/s ) 28 | bool ok = lim_p->wait(1.0, std::chrono::seconds(20)); 29 | if (ok) { 30 | /* 31 | * write your codes here 32 | */ 33 | ++loop_time; 34 | } else { 35 | std::this_thread::sleep_for(std::chrono::microseconds(10)); 36 | // if exceed max wait time 37 | } 38 | } 39 | delete lim_p; 40 | return 0; 41 | } 42 | ``` 43 | 44 | - for more examples, please read the codes in dir `demo/` 45 | -------------------------------------------------------------------------------- /demo/wait_example.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * These codes below explain how to generate 3.0 tokens per second 3 | * and cosume 2.0 tokens every time execute your codes. 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../include/Limiter.h" 12 | 13 | using std::chrono::duration_cast; 14 | using std::chrono::microseconds; 15 | using std::chrono::seconds; 16 | using std::chrono::system_clock; 17 | 18 | token_bucket::Limiter* lim_p; 19 | 20 | void foo (int th_num) { 21 | for(int loop_time = 0; loop_time < 5; ) { 22 | //wait(cosume tokens num, max wait time/s ) 23 | bool ok = lim_p->wait(2.0, seconds(20)); 24 | if (ok) { 25 | /* 26 | * write your codes here 27 | */ 28 | std::cout << "thread_num = " << th_num 29 | << ", loop_time = " << loop_time 30 | << " time : " 31 | << duration_cast(system_clock::now().time_since_epoch()).count() 32 | << std::endl; 33 | ++loop_time; 34 | } else { 35 | //if exceed max wait time 36 | std::this_thread::sleep_for(microseconds(1000)); 37 | } 38 | } 39 | } 40 | 41 | int main() { 42 | //first parameter->generate n tokens per s, second parameter->burst tokens 43 | //means generate 3.0 tokens per s 44 | //when rate <= 0, it will not generator tokens anymore 45 | lim_p = new token_bucket::Limiter(3.0, 5.0); 46 | std::vector vec(5, nullptr); 47 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 48 | vec[th_num] = new std::thread(foo, th_num); 49 | } 50 | 51 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 52 | vec[th_num]->join(); 53 | delete vec[th_num]; 54 | } 55 | delete lim_p; 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /demo/reset_burst.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * These codes below explain how to reset "burst" when the instance of Limiter 3 | * you already constructed success 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../include/Limiter.h" 12 | 13 | using std::chrono::system_clock; 14 | using std::chrono::seconds; 15 | using std::chrono::milliseconds; 16 | using std::chrono::duration_cast; 17 | 18 | token_bucket::Limiter* lim_p; 19 | 20 | void printer () { 21 | for(int loop_time = 0; loop_time < 5; ) { 22 | //reserve(cosume tokens num, max wait time/s ) 23 | std::shared_ptr reservation_p = 24 | lim_p->reserve(loop_time + 1, seconds(20)); 25 | if (reservation_p->get_ok()) { 26 | std::this_thread::sleep_for(reservation_p->duration_to_act()); 27 | 28 | std::cout << "loop_time = " << loop_time <<" time_ms : " 29 | << duration_cast(system_clock::now().time_since_epoch()).count() 30 | << " cosume tokens:" << reservation_p->get_tokens() << std::endl; 31 | ++loop_time; 32 | } else { 33 | //if exceed max wait time 34 | std::cout << "fail to take tokens, lim_p->burst() = " << lim_p->burst() << std::endl; 35 | std::this_thread::sleep_for(milliseconds(500)); 36 | } 37 | } 38 | } 39 | 40 | void burst_setter() { 41 | std::this_thread::sleep_for(seconds(5)); 42 | std::cout << "reset burst to 5.0\n"; 43 | lim_p->set_burst(5.0); 44 | } 45 | 46 | int main() { 47 | //first parameter->generate n tokens per s, second parameter->burst tokens 48 | //means generate 5.0 tokens per s 49 | //when rate <= 0, it will not generator tokens anymore 50 | lim_p = new token_bucket::Limiter(5.0, 4.0); 51 | std::thread th_printer(printer); 52 | std::thread th_setter(burst_setter); 53 | 54 | th_printer.join(); 55 | th_setter.join(); 56 | delete lim_p; 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /demo/reserve_example.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * These codes below explain how to generate 3.0 tokens per second 3 | * and cosume 2.0 tokens every time execute your codes with member 4 | * function reserve. 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../include/Limiter.h" 13 | 14 | using std::chrono::system_clock; 15 | using std::chrono::seconds; 16 | using std::chrono::microseconds; 17 | using std::chrono::duration_cast; 18 | 19 | token_bucket::Limiter* lim_p; 20 | 21 | 22 | void foo (int th_num) { 23 | for(int loop_time = 0; loop_time < 5; ) { 24 | //reserve(cosume tokens num, max wait time/s ) 25 | std::shared_ptr reservation_p = 26 | lim_p->reserve(2.0, seconds(20)); 27 | if (reservation_p->get_ok()) { 28 | std::this_thread::sleep_for(reservation_p->duration_to_act()); 29 | /* 30 | * write your codes here 31 | */ 32 | std::cout << "thread_num = "<< th_num 33 | << ", loop_time = " << loop_time <<" time : " 34 | << duration_cast(system_clock::now().time_since_epoch()).count() 35 | << " cosume tokens:" << reservation_p->get_tokens() << std::endl; 36 | ++loop_time; 37 | } else { 38 | //if exceed max wait time 39 | std::this_thread::sleep_for(microseconds(1000)); 40 | } 41 | } 42 | } 43 | 44 | int main() { 45 | //first parameter->generate n tokens per s, second parameter->burst tokens 46 | //means generate 3.0 tokens per s 47 | //when rate <= 0, it will not generator tokens anymore 48 | lim_p = new token_bucket::Limiter(3.0, 5.0); 49 | std::vector vec(5, nullptr); 50 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 51 | vec[th_num] = new std::thread(foo, th_num); 52 | } 53 | 54 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 55 | vec[th_num]->join(); 56 | delete vec[th_num]; 57 | } 58 | delete lim_p; 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /demo/reset_rate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * These codes below explain how to reset "rate" when the instance of Limiter 3 | * you already constructed success 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../include/Limiter.h" 12 | 13 | using std::chrono::system_clock; 14 | using std::chrono::seconds; 15 | using std::chrono::milliseconds; 16 | using std::chrono::duration_cast; 17 | 18 | token_bucket::Limiter* lim_p; 19 | 20 | void printer () { 21 | for(int loop_time = 0; loop_time < 5; ) { 22 | //reserve(cosume tokens num, max wait time/s ) 23 | std::shared_ptr reservation_p = 24 | lim_p->reserve(1.0, seconds(10)); 25 | if (reservation_p->get_ok()) { 26 | std::this_thread::sleep_for(reservation_p->duration_to_act()); 27 | 28 | std::cout << "loop_time = " << loop_time <<" time_ms : " 29 | << duration_cast(system_clock::now().time_since_epoch()).count() 30 | << " cosume tokens:" << reservation_p->get_tokens() << std::endl; 31 | ++loop_time; 32 | } else { 33 | //if exceed max wait time 34 | std::this_thread::sleep_for(milliseconds(500)); 35 | std::cout<<"fail sleep 500ms\n"; 36 | } 37 | } 38 | } 39 | 40 | void burst_setter() { 41 | std::this_thread::sleep_for(milliseconds(3500)); 42 | std::cout << "reset rate to 5.0\n"; 43 | lim_p->set_rate(5.0); 44 | } 45 | 46 | int main() { 47 | //first parameter->generate n tokens per s, second parameter->burst tokens 48 | //means generate 1.0 tokens per s 49 | //when rate <= 0, it will not generator tokens anymore 50 | lim_p = new token_bucket::Limiter(1.0, 5.0); 51 | 52 | std::thread th_setter(burst_setter); 53 | 54 | std::vector vec(2, nullptr); 55 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 56 | vec[th_num] = new std::thread(printer); 57 | } 58 | 59 | th_setter.join(); 60 | for (int th_num = 0; th_num < static_cast(vec.size()); th_num++) { 61 | vec[th_num]->join(); 62 | delete vec[th_num]; 63 | } 64 | delete lim_p; 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /include/Limiter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace token_bucket { 11 | 12 | using std::chrono::duration; 13 | using std::chrono::steady_clock; 14 | using std::chrono::time_point; 15 | using std::chrono::nanoseconds; 16 | 17 | static bool is_equal_d(double d1, double d2) { 18 | static const double delta = 1e-20; 19 | return (d1 < d2 ? d2 - d1 : d1 - d2) < delta; 20 | } 21 | 22 | template 23 | class AutoRelease { 24 | public: 25 | AutoRelease(T *mu) : _m_mu(mu){ 26 | _m_mu->lock(); 27 | } 28 | ~AutoRelease() { 29 | _m_mu->unlock(); 30 | } 31 | private: 32 | T *_m_mu; 33 | }; 34 | 35 | // A Reservation holds information about events that are permitted by a Limiter to happen after a delay. 36 | class Limiter; 37 | class Reservation { 38 | public: 39 | friend Limiter; 40 | typedef time_point time_point_ns; 41 | Reservation(bool ok, double tokens, time_point_ns time_to_act) : 42 | _m_ok(ok), _m_tokens(tokens), _m_time_to_act(time_to_act) {} 43 | 44 | bool get_ok() { 45 | return _m_ok; 46 | } 47 | 48 | double get_tokens() { 49 | return _m_tokens; 50 | } 51 | 52 | nanoseconds duration_to_act() { 53 | return _m_time_to_act -steady_clock::now(); 54 | } 55 | private: 56 | //is reserve success 57 | bool _m_ok; 58 | //the tokens reserved 59 | double _m_tokens; 60 | //permitted time to act 61 | time_point_ns _m_time_to_act; 62 | }; 63 | 64 | class Limiter { 65 | public: 66 | typedef time_point time_point_ns; 67 | Limiter(double rate, double burst); 68 | std::shared_ptr reserve(double n, const nanoseconds& max_time_to_wait); 69 | 70 | bool wait(double n, const nanoseconds& max_time_to_wait); 71 | 72 | bool set_rate(double rate); 73 | bool set_burst(double burst); 74 | 75 | double tokens() { 76 | return _m_tokens; 77 | } 78 | 79 | double rate() { 80 | return _m_rate; 81 | } 82 | 83 | double burst() { 84 | return _m_burst; 85 | } 86 | private: 87 | static const uint64_t POW_S_TO_NS = 1000000000; 88 | 89 | void update(time_point_ns now); 90 | double duration_to_tokens(nanoseconds duration) { 91 | return duration.count() * _m_rate / POW_S_TO_NS; 92 | } 93 | 94 | nanoseconds tokens_to_duration(double tokens) { 95 | if (_m_rate < 0.0 || is_equal_d(_m_rate, 0.0)) { 96 | return nanoseconds(0); 97 | } 98 | return nanoseconds(static_cast(tokens / _m_rate * POW_S_TO_NS)); 99 | } 100 | 101 | // rate per s 102 | double _m_rate; 103 | 104 | // every deta_T in [start, end], there is always 105 | // deta_T * _m_rate + burst >= n 106 | // n is the number of tokens taken in deta_T 107 | double _m_burst; 108 | 109 | // remaining tokens at _m_last_update_time 110 | double _m_tokens; 111 | 112 | // last_update_time is the last time the limiter's tokens field was updated 113 | time_point_ns _m_last_update_time; 114 | // last_event_time is the latest time of a rate-limited event (past or future) 115 | std::mutex mu; 116 | }; 117 | 118 | Limiter::Limiter(double rate, double burst) : 119 | _m_burst(burst), _m_tokens(0), _m_last_update_time(steady_clock::now()), mu() { 120 | _m_rate = rate < 0.0 ? 0.0 : rate; 121 | } 122 | 123 | 124 | //reset rate 125 | bool Limiter::set_rate(double rate) { 126 | AutoRelease autoRelease(&mu); 127 | update(steady_clock::now()); 128 | if (rate >= 0) { 129 | nanoseconds dura = tokens_to_duration(_m_tokens); 130 | _m_rate = rate; 131 | _m_tokens = duration_to_tokens(dura); 132 | _m_rate = _m_rate > _m_burst ? _m_burst : _m_rate; 133 | return true; 134 | } 135 | return false; 136 | } 137 | 138 | //reset burst 139 | bool Limiter::set_burst(double burst) { 140 | AutoRelease autoRelease(&mu); 141 | update(steady_clock::now()); 142 | _m_burst = burst; 143 | return true; 144 | } 145 | 146 | std::shared_ptr Limiter::reserve(double n, const nanoseconds& max_time_to_wait){ 147 | AutoRelease autoRelease(&mu); 148 | time_point_ns now = steady_clock::now(); 149 | std::shared_ptr reservation_p = std::make_shared(false, n, now); 150 | if (now < _m_last_update_time) { 151 | _m_last_update_time = now; 152 | } 153 | update(now); 154 | 155 | if (n >= 0 && n <= _m_burst && duration_to_tokens(max_time_to_wait) - n >= -_m_tokens) { 156 | //ok 157 | _m_tokens -= n; 158 | reservation_p->_m_ok = true; 159 | reservation_p->_m_tokens = n; 160 | reservation_p->_m_time_to_act = _m_tokens >= 0 ? now : tokens_to_duration(-_m_tokens) + now; 161 | } 162 | return reservation_p; 163 | } 164 | 165 | bool Limiter::wait(double n, const nanoseconds& max_time_to_wait) { 166 | std::shared_ptr reservation_p = reserve(n, max_time_to_wait); 167 | time_point_ns curr = steady_clock::now(); 168 | if (reservation_p->_m_ok && reservation_p->_m_time_to_act >= curr) { 169 | std::this_thread::sleep_until(reservation_p->_m_time_to_act); 170 | } 171 | return reservation_p->_m_ok; 172 | } 173 | 174 | void Limiter::update(time_point_ns now) { 175 | if (now < _m_last_update_time) { 176 | _m_last_update_time = now; 177 | return; 178 | } 179 | double curr_tokens = duration_to_tokens(now - _m_last_update_time) + _m_tokens; 180 | if (curr_tokens > _m_burst) { 181 | curr_tokens = _m_burst; 182 | } 183 | _m_tokens = curr_tokens; 184 | _m_last_update_time = now; 185 | } 186 | 187 | } //token_bucet 188 | --------------------------------------------------------------------------------