├── .circleci
└── config.yml
├── .gitignore
├── .idea
├── .gitignore
├── dictionaries
│ └── asf.xml
├── misc.xml
├── modules.xml
├── ratelimit_meter.iml
├── runConfigurations
│ ├── bench__stable__std_.xml
│ ├── test__nightly__no_std_.xml
│ ├── test__stable__no_std_.xml
│ └── test__stable__std_.xml
└── vcs.xml
├── .travis.yml
├── CONTRIBUTING.md
├── Cargo.toml
├── CoC.md
├── LICENSE.txt
├── README.md
├── SECURITY.md
├── benches
├── algorithms.rs
├── criterion.rs
├── multi_threaded.rs
├── no_op.rs
└── single_threaded.rs
├── bors.toml
├── src
├── algorithms.rs
├── algorithms
│ ├── gcra.rs
│ └── leaky_bucket.rs
├── clock.rs
├── clock
│ ├── no_std.rs
│ └── with_std.rs
├── errors.rs
├── example_algorithms.rs
├── lib.rs
├── state.rs
├── state
│ ├── direct.rs
│ └── keyed.rs
├── test_utilities.rs
├── test_utilities
│ ├── algorithms.rs
│ └── variants.rs
└── thread_safety.rs
└── tests
├── gcra.rs
├── keyed.rs
├── leaky_bucket.rs
└── memory.rs
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # TemplateCIConfig { bench: BenchEntry(MatrixEntry { run: true, run_cron: false, version: "stable", install_commandline: None, commandline: "cargo bench" }), clippy: ClippyEntry(MatrixEntry { run: true, run_cron: false, version: "stable", install_commandline: Some("rustup component add clippy"), commandline: "cargo clippy -- -D warnings" }), rustfmt: RustfmtEntry(MatrixEntry { run: true, run_cron: false, version: "stable", install_commandline: Some("rustup component add rustfmt"), commandline: "cargo fmt -v -- --check" }), additional_matrix_entries: {"no_std_stable": CustomEntry(MatrixEntry { run: true, run_cron: false, version: "stable", install_commandline: None, commandline: "cargo test --no-default-features --features no_std" }), "no_std_nightly": CustomEntry(MatrixEntry { run: true, run_cron: false, version: "nightly", install_commandline: None, commandline: "cargo +nightly test --no-default-features --features no_std" })}, cache: "cargo", os: "linux", dist: "xenial", versions: ["stable", "nightly"], test_commandline: "cargo test --verbose --all", scheduled_test_branches: ["master"], test_schedule: "0 0 * * 0" }
2 | version: "2.1"
3 |
4 | executors:
5 | stable:
6 | docker:
7 | - image: liuchong/rustup:stable
8 | nightly:
9 | docker:
10 | - image: liuchong/rustup:nightly
11 | beta:
12 | docker:
13 | - image: liuchong/rustup:beta
14 |
15 | commands:
16 | cargo_test:
17 | description: "Run `cargo test`"
18 | steps:
19 | - run:
20 | name: "Clean out rust-toolchain"
21 | command: "rm -f rust-toolchain"
22 | - run:
23 | name: "Toolchain debug info"
24 | command: "rustc --version"
25 | - run:
26 | name: Test
27 | command: cargo test --verbose --all
28 |
29 | jobs:
30 | test:
31 | parameters:
32 | version:
33 | type: executor
34 | version_name:
35 | type: string
36 | executor: << parameters.version >>
37 | environment:
38 | CI_RUST_VERSION: << parameters.version_name >>
39 | steps:
40 | - checkout
41 | - cargo_test
42 |
43 | rustfmt:
44 | parameters:
45 | version:
46 | type: executor
47 | executor: << parameters.version >>
48 | steps:
49 | - checkout
50 | - run:
51 | name: Install
52 | command: rustup component add rustfmt
53 | - run:
54 | name: Rustfmt
55 | command: cargo fmt -v -- --check
56 |
57 | clippy:
58 | parameters:
59 | version:
60 | type: executor
61 | executor: << parameters.version >>
62 | steps:
63 | - checkout
64 | - run:
65 | name: Install
66 | command: rustup component add clippy
67 | - run:
68 | name: Clippy
69 | command: cargo clippy -- -D warnings
70 |
71 | bench:
72 | parameters:
73 | version:
74 | type: executor
75 | executor: << parameters.version >>
76 | steps:
77 | - checkout
78 | - run:
79 | name: Bench
80 | command: cargo bench
81 | no_std_stable:
82 | parameters:
83 | version:
84 | type: executor
85 | version_name:
86 | type: string
87 | executor: << parameters.version >>
88 | environment:
89 | CI_RUST_VERSION: << parameters.version_name >>
90 | steps:
91 | - checkout
92 | - run:
93 | name: cargo test --no-default-features --features no_std
94 | command: cargo test --no-default-features --features no_std
95 | no_std_nightly:
96 | parameters:
97 | version:
98 | type: executor
99 | version_name:
100 | type: string
101 | executor: << parameters.version >>
102 | environment:
103 | CI_RUST_VERSION: << parameters.version_name >>
104 | steps:
105 | - checkout
106 | - run:
107 | name: cargo +nightly test --no-default-features --features no_std
108 | command: cargo +nightly test --no-default-features --features no_std
109 |
110 | ci_success:
111 | docker:
112 | - image: alpine:latest
113 | steps:
114 | - run:
115 | name: Success
116 | command: "echo yay"
117 |
118 | workflows:
119 | continuous_integration:
120 | jobs:
121 | - test:
122 | name: test-stable
123 | version: stable
124 | version_name: stable
125 | filters: {
126 | "branches": {
127 | "ignore": [
128 | "/.*\\.tmp/"
129 | ]
130 | },
131 | "tags": {
132 | "only": [
133 | "/^v\\d+\\.\\d+\\.\\d+.*$/"
134 | ]
135 | }
136 | }
137 | - test:
138 | name: test-nightly
139 | version: nightly
140 | version_name: nightly
141 | filters: {
142 | "branches": {
143 | "ignore": [
144 | "/.*\\.tmp/"
145 | ]
146 | },
147 | "tags": {
148 | "only": [
149 | "/^v\\d+\\.\\d+\\.\\d+.*$/"
150 | ]
151 | }
152 | }
153 | - rustfmt:
154 | version: stable
155 | filters: {
156 | "branches": {
157 | "ignore": [
158 | "/.*\\.tmp/"
159 | ]
160 | },
161 | "tags": {
162 | "only": [
163 | "/^v\\d+\\.\\d+\\.\\d+.*$/"
164 | ]
165 | }
166 | }
167 | - clippy:
168 | version: stable
169 | filters: {
170 | "branches": {
171 | "ignore": [
172 | "/.*\\.tmp/"
173 | ]
174 | },
175 | "tags": {
176 | "only": [
177 | "/^v\\d+\\.\\d+\\.\\d+.*$/"
178 | ]
179 | }
180 | }
181 | - bench:
182 | version: stable
183 | filters: {
184 | "branches": {
185 | "ignore": [
186 | "/.*\\.tmp/"
187 | ]
188 | },
189 | "tags": {
190 | "only": [
191 | "/^v\\d+\\.\\d+\\.\\d+.*$/"
192 | ]
193 | }
194 | }
195 | - no_std_stable:
196 | name: "no_std_stable"
197 | version: stable
198 | version_name: stable
199 | - no_std_nightly:
200 | name: "no_std_nightly"
201 | version: nightly
202 | version_name: nightly
203 | - ci_success:
204 | requires:
205 | - test-stable
206 | - test-nightly
207 | - rustfmt
208 | - clippy
209 | - bench
210 | - no_std_stable
211 | - no_std_nightly
212 | scheduled_tests:
213 | jobs:
214 | - test:
215 | name: test-stable
216 | version: stable
217 | version_name: stable
218 | - test:
219 | name: test-nightly
220 | version: nightly
221 | version_name: nightly
222 | triggers:
223 | - schedule:
224 | cron: 0 0 * * 0
225 | filters:
226 | branches:
227 | only: [
228 | "master"
229 | ]
230 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target/
2 | **/*.rs.bk
3 | Cargo.lock
4 | /test_utilities/target
5 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Default ignored files
3 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/dictionaries/asf.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | gcra
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/ratelimit_meter.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/bench__stable__std_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/test__nightly__no_std_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/test__stable__no_std_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/test__stable__std_.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # TemplateCIConfig { bench: BenchEntry { run: true, version: "stable", allow_failure: false }, clippy: ClippyEntry { run: true, version: "nightly", allow_failure: false }, rustfmt: RustfmtEntry { run: true, version: "stable", allow_failure: false }, os: "linux", dist: "xenial", versions: ["stable", "beta", "nightly"] }
2 | os:
3 | - "linux"
4 | dist: "xenial"
5 |
6 | language: rust
7 | sudo: required
8 | cache: cargo
9 |
10 | rust:
11 | - stable
12 | - beta
13 | - nightly
14 |
15 | env:
16 | global:
17 | - RUN_TEST=true
18 | - RUN_CLIPPY=false
19 | - RUN_BENCH=false
20 |
21 | matrix:
22 | fast_finish: true
23 | include:
24 | - &rustfmt_build
25 | rust: "stable"
26 | env:
27 | - RUN_RUSTFMT=true
28 | - RUN_TEST=false
29 | - &bench_build
30 | rust: "stable"
31 | env:
32 | - RUN_BENCH=true
33 | - RUN_TEST=false
34 | - &clippy_build
35 | rust: "nightly"
36 | env:
37 | - RUN_CLIPPY=true
38 | - RUN_TEST=false
39 | allow_failures: []
40 |
41 | before_script:
42 | - bash -c 'if [[ "$RUN_RUSTFMT" == "true" ]]; then
43 | rustup component add rustfmt-preview
44 | ;
45 | fi'
46 | - bash -c 'if [[ "$RUN_CLIPPY" == "true" ]]; then
47 | rm -f ~/.cargo/bin/clippy;
48 | rustup component add clippy-preview
49 | ;
50 | fi'
51 |
52 | script:
53 | - bash -c 'if [[ "$RUN_TEST" == "true" ]]; then
54 | cargo test
55 | ;
56 | fi'
57 | - bash -c 'if [[ "$RUN_RUSTFMT" == "true" ]]; then
58 | cargo fmt -v -- --check
59 | ;
60 | fi'
61 | - bash -c 'if [[ "$RUN_BENCH" == "true" ]]; then
62 | cargo bench
63 | ;
64 | fi'
65 | - bash -c 'if [[ "$RUN_CLIPPY" == "true" ]]; then
66 | cargo clippy -- -D warnings
67 | ;
68 | fi'
69 |
70 | branches:
71 | only:
72 | # release tags
73 | - /^v\d+\.\d+\.\d+.*$/
74 | - master
75 |
76 | notifications:
77 | email:
78 | on_success: never
79 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Thanks for contributing to this project!
2 |
3 | I'm completely thrilled that you find this project useful enough to
4 | spend your time on!
5 |
6 | ## Code of Conduct
7 |
8 | Contributors are expected to adhere to the
9 | [Contributor Covenant Code of Conduct](http://contributor-covenant.org/version/1/4/),
10 | version 1.4. See [CoC.md](CoC.md) for the full text.
11 |
12 | ## Things you might do
13 |
14 | Feel free to:
15 |
16 | * [Report issues](../../issues)
17 | * [Send me a pull request](../../pulls) or
18 | * Just get in touch with me: asf@boinkor.net!
19 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2018"
3 | name = "ratelimit_meter"
4 | version = "5.0.1-dev"
5 | authors = ["Andreas Fuchs "]
6 | license = "MIT"
7 | homepage = "https://github.com/antifuchs/ratelimit_meter"
8 | repository = "https://github.com/antifuchs/ratelimit_meter.git"
9 | readme = "README.md"
10 | description = "A leaky-bucket-as-a-meter rate-limiting implementation in Rust"
11 | documentation = "https://docs.rs/ratelimit_meter"
12 | categories = ["algorithms", "network-programming", "concurrency"]
13 |
14 | # We use criterion, don't infer benchmark files.
15 | autobenches = false
16 |
17 | [package.metadata.release]
18 | sign-commit = false
19 | upload-doc = false
20 | pre-release-commit-message = "Release {{version}} 🎉🎉"
21 | pro-release-commit-message = "Start next development iteration {{version}}"
22 | tag-message = "Release {{prefix}}{{version}}"
23 | dev-version-ext = "dev"
24 | tag-prefix = "v"
25 |
26 | [package.metadata.template_ci.bench]
27 | run = true
28 | version = "stable"
29 |
30 | [package.metadata.template_ci.additional_matrix_entries]
31 |
32 | [package.metadata.template_ci.additional_matrix_entries.no_std_nightly]
33 | run = true
34 | version = "nightly"
35 | commandline = "cargo +nightly test --no-default-features --features no_std"
36 |
37 | [package.metadata.template_ci.additional_matrix_entries.no_std_stable]
38 | run = true
39 | version = "stable"
40 | commandline = "cargo test --no-default-features --features no_std"
41 |
42 | [badges]
43 | circle-ci = { repository = "antifuchs/ratelimit_meter", branch = "master" }
44 | maintenance = { status = "actively-developed" }
45 |
46 | [features]
47 | default = ["std"]
48 | std = ["parking_lot", "evmap", "nonzero_ext/std"]
49 | no_std = ["spin"]
50 |
51 | [[bench]]
52 | name = "criterion"
53 | harness = false
54 |
55 | [dependencies]
56 | nonzero_ext = {version = "0.1.5", default-features = false}
57 | spin = {version = "0.5.0", optional = true}
58 | parking_lot = {version = "0.9.0", optional = true}
59 | evmap = {version = "6.0.0", optional = true}
60 |
61 | [dev_dependencies]
62 | libc = "0.2.41"
63 | criterion = "0.2.11"
64 |
--------------------------------------------------------------------------------
/CoC.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at asf@boinkor.net. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Andreas Fuchs
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/antifuchs/ratelimit_meter) [](https://docs.rs/ratelimit_meter/) [](https://crates.io/crates/ratelimit_meter)
2 |
3 | # `ratelimit_meter` is deprecated & in maintenance-only mode
4 |
5 | In late 2019, I realized that a bunch of design decisions I made in
6 | `ratelimit_meter` (one of my first "real" rust projects) meant that
7 | some bugs were ~forever baked in. To fix that, I re-wrote the core of
8 | this library as
9 | [`governor`](https://github.com/antifuchs/governor). It is more
10 | fully-featured, more modern, [less
11 | buggy](https://github.com/antifuchs/ratelimit_meter/issues?q=is%3Aissue+label%3A%22fixed+in+governor%22)
12 | and it has way less potential for buggy usage.
13 |
14 | There is a [migration guide](https://docs.rs/governor/~0.3/governor/_guide) available.
15 |
16 | With the above in mind, please take the rest of this README with
17 | several grains of salt (and use
18 | [`governor`](https://crates.io/crates/governor) if you can)!
19 |
20 | # Rate-Limiting with leaky buckets in Rust
21 |
22 | This crate implements two rate-limiting algorithms in Rust:
23 | * a [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket#As_a_meter) and
24 | * a variation on the leaky bucket, the
25 | [generic cell rate algorithm](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm) (GCRA)
26 | for rate-limiting and scheduling.
27 |
28 | `ratelimit_meter` is usable in `no_std` mode, with a few trade-offs on
29 | features.
30 |
31 | ## Installation
32 |
33 | Add the crate `ratelimit_meter` to your `Cargo.toml`
34 | file; [the crates.io page](https://crates.io/crates/ratelimit_meter)
35 | can give you the exact thing to paste.
36 |
37 | ## API Docs
38 |
39 | Find them [on docs.rs](https://docs.rs/ratelimit_meter/) for the latest version!
40 |
41 | ## Design and implementation
42 |
43 | Unlike some other token bucket algorithms, the GCRA one assumes that
44 | all units of work are of the same "weight", and so allows some
45 | optimizations which result in much more concise and fast code (it does
46 | not even use multiplication or division in the "hot" path for a
47 | single-cell decision).
48 |
49 | All rate-limiting algorithm implementations in this crate are
50 | thread-safe. Here are some benchmarks for repeated decisions (run on
51 | my macbook pro, this will differ on your hardware, etc etc):
52 |
53 | ```
54 | $ cargo bench
55 | Finished release [optimized] target(s) in 0.16s
56 | Running target/release/deps/ratelimit_meter-9874176533f7e1a0
57 |
58 | running 1 test
59 | test test_wait_time_from ... ignored
60 |
61 | test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
62 |
63 | Running target/release/deps/criterion-67011381a5f6ed00
64 | multi_threaded/20_threads/GCRA
65 | time: [1.9664 us 2.0747 us 2.1503 us]
66 | thrpt: [465.04 Kelem/s 482.00 Kelem/s 508.55 Kelem/s]
67 | Found 10 outliers among 100 measurements (10.00%)
68 | 4 (4.00%) low severe
69 | 4 (4.00%) low mild
70 | 2 (2.00%) high mild
71 | multi_threaded/20_threads/LeakyBucket
72 | time: [2.4536 us 2.4878 us 2.5189 us]
73 | thrpt: [396.99 Kelem/s 401.96 Kelem/s 407.56 Kelem/s]
74 | Found 8 outliers among 100 measurements (8.00%)
75 | 5 (5.00%) low severe
76 | 3 (3.00%) low mild
77 |
78 | single_threaded/1_element/GCRA
79 | time: [68.613 ns 68.779 ns 68.959 ns]
80 | thrpt: [14.501 Melem/s 14.539 Melem/s 14.575 Melem/s]
81 | Found 13 outliers among 100 measurements (13.00%)
82 | 9 (9.00%) high mild
83 | 4 (4.00%) high severe
84 | single_threaded/1_element/LeakyBucket
85 | time: [64.513 ns 64.855 ns 65.272 ns]
86 | thrpt: [15.321 Melem/s 15.419 Melem/s 15.501 Melem/s]
87 | Found 16 outliers among 100 measurements (16.00%)
88 | 4 (4.00%) high mild
89 | 12 (12.00%) high severe
90 |
91 | single_threaded/multi_element/GCRA
92 | time: [96.461 ns 96.976 ns 97.578 ns]
93 | thrpt: [102.48 Melem/s 103.12 Melem/s 103.67 Melem/s]
94 | Found 11 outliers among 100 measurements (11.00%)
95 | 4 (4.00%) high mild
96 | 7 (7.00%) high severe
97 | single_threaded/multi_element/LeakyBucket
98 | time: [69.500 ns 70.359 ns 71.349 ns]
99 | thrpt: [140.16 Melem/s 142.13 Melem/s 143.88 Melem/s]
100 | Found 9 outliers among 100 measurements (9.00%)
101 | 6 (6.00%) high mild
102 | 3 (3.00%) high severe
103 |
104 | no-op single-element decision
105 | time: [23.755 ns 23.817 ns 23.883 ns]
106 | Found 11 outliers among 100 measurements (11.00%)
107 | 5 (5.00%) high mild
108 | 6 (6.00%) high severe
109 |
110 | no-op multi-element decision
111 | time: [22.772 ns 22.940 ns 23.125 ns]
112 | Found 5 outliers among 100 measurements (5.00%)
113 | 5 (5.00%) high mild
114 | ```
115 |
116 | ## Contributions welcome!
117 |
118 | I am actively hoping that this project gives people joy in using
119 | rate-limiting techniques. You can use these techniques for so many
120 | things (from throttling API requests to ensuring you don't spam people
121 | with emails about the same thing)!
122 |
123 | So if you have any thoughts about the API design, the internals, or
124 | you want to implement other rate-limiting algotrithms, I would be
125 | thrilled to have your input. See [CONTRIBUTING.md](CONTRIBUTING.md)
126 | for details!
127 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | While this library still runs on latest rust, is no longer supported with the exception of critical fixes. These
6 | are restricted to the latest release, and newer releases are unlikely to get cut.
7 |
8 | A newer, more modern, supported library exists in the form of [`governor`](https://github.com/antifuchs/governor).
9 |
10 | | Version | Supported |
11 | | ------- | ------------------ |
12 | | 4.0.x | :white_check_mark: (:information_source: ) |
13 | | < 4.x | :x: |
14 |
15 |
16 | ## Reporting a Vulnerability
17 |
18 | You can get in touch with the author via encrypted channels - see https://keybase.io/asf for a list of
19 | addresses and key IDs.
20 |
--------------------------------------------------------------------------------
/benches/algorithms.rs:
--------------------------------------------------------------------------------
1 | use criterion::{black_box, Criterion, ParameterizedBenchmark, Throughput};
2 | use ratelimit_meter::test_utilities::variants::Variant;
3 | use std::time::{Duration, Instant};
4 |
5 | pub fn bench_all(c: &mut Criterion) {
6 | bench_plain_algorithm_1elem(c);
7 | bench_plain_algorithm_multi(c);
8 | }
9 |
10 | fn bench_plain_algorithm_1elem(c: &mut Criterion) {
11 | let id = "algorithm/1";
12 | let bm = ParameterizedBenchmark::new(
13 | id,
14 | move |b, ref v| {
15 | bench_with_algorithm_variants!(v, algo, {
16 | let now = Instant::now();
17 | let ms = Duration::from_millis(20);
18 | let state = algo.state();
19 |
20 | let mut i = 0;
21 | b.iter(|| {
22 | i += 1;
23 | black_box(algo.check(&state, now + (ms * i)).is_ok());
24 | });
25 | });
26 | },
27 | Variant::ALL,
28 | )
29 | .throughput(|_s| Throughput::Elements(1));
30 | c.bench(id, bm);
31 | }
32 |
33 | fn bench_plain_algorithm_multi(c: &mut Criterion) {
34 | let id = "algorithm/multi";
35 | let elements: u32 = 10;
36 | let bm = ParameterizedBenchmark::new(
37 | id,
38 | move |b, ref v| {
39 | bench_with_algorithm_variants!(v, algo, {
40 | let now = Instant::now();
41 | let ms = Duration::from_millis(20);
42 | let state = algo.state();
43 |
44 | let mut i = 0;
45 | b.iter(|| {
46 | i += 1;
47 | black_box(algo.check_n(&state, elements, now + (ms * i)).is_ok());
48 | });
49 | });
50 | },
51 | Variant::ALL,
52 | )
53 | .throughput(|_s| Throughput::Elements(1));
54 | c.bench(id, bm);
55 | }
56 |
--------------------------------------------------------------------------------
/benches/criterion.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate criterion;
3 | #[macro_use]
4 | extern crate ratelimit_meter;
5 |
6 | mod algorithms;
7 | mod multi_threaded;
8 | mod no_op;
9 | mod single_threaded;
10 |
11 | criterion_group!(
12 | benches,
13 | algorithms::bench_all,
14 | multi_threaded::bench_all,
15 | single_threaded::bench_all,
16 | no_op::bench_all,
17 | );
18 | criterion_main!(benches);
19 |
--------------------------------------------------------------------------------
/benches/multi_threaded.rs:
--------------------------------------------------------------------------------
1 | use std::thread;
2 | use std::time::{Duration, Instant};
3 |
4 | use criterion::{black_box, Criterion, ParameterizedBenchmark, Throughput};
5 | use ratelimit_meter::clock;
6 | use ratelimit_meter::test_utilities::variants::{DirectBucket, KeyedBucket, Variant};
7 |
8 | pub fn bench_all(c: &mut Criterion) {
9 | bench_direct(c);
10 | bench_keyed(c);
11 | }
12 |
13 | fn bench_direct(c: &mut Criterion) {
14 | let id = "multi_threaded/direct";
15 |
16 | let bm = ParameterizedBenchmark::new(
17 | id,
18 | |b, ref v| {
19 | bench_with_variants!(v, lim: DirectBucket, {
20 | let now = Instant::now();
21 | let ms = Duration::from_millis(20);
22 | let mut children = vec![];
23 |
24 | for _i in 0..19 {
25 | let mut lim = lim.clone();
26 | let mut b = *b;
27 | children.push(thread::spawn(move || {
28 | let mut i = 0;
29 | b.iter(|| {
30 | i += 1;
31 | black_box(lim.check_at(now + (ms * i)).is_ok());
32 | });
33 | }));
34 | }
35 | let mut i = 0;
36 | b.iter(|| {
37 | i += 1;
38 | black_box(lim.check_at(now + (ms * i)).is_ok());
39 | });
40 | for child in children {
41 | child.join().unwrap();
42 | }
43 | });
44 | },
45 | Variant::ALL,
46 | )
47 | .throughput(|_s| Throughput::Elements(1));
48 | c.bench(id, bm);
49 | }
50 |
51 | fn bench_keyed(c: &mut Criterion) {
52 | let id = "multi_threaded/keyed";
53 |
54 | let bm = ParameterizedBenchmark::new(
55 | id,
56 | |b, ref v| {
57 | bench_with_variants!(v, lim: KeyedBucket, {
58 | let now = Instant::now();
59 | let ms = Duration::from_millis(20);
60 | let mut children = vec![];
61 |
62 | for _i in 0..19 {
63 | let mut lim = lim.clone();
64 | let mut b = *b;
65 | children.push(thread::spawn(move || {
66 | let mut i = 0;
67 | b.iter(|| {
68 | i += 1;
69 | black_box(lim.check_at(i % 100, now + (ms * i)).is_ok());
70 | });
71 | }));
72 | }
73 | let mut i = 0;
74 | b.iter(|| {
75 | i += 1;
76 | black_box(lim.check_at(i % 100, now + (ms * i)).is_ok());
77 | });
78 | for child in children {
79 | child.join().unwrap();
80 | }
81 | });
82 | },
83 | Variant::ALL,
84 | )
85 | .throughput(|_s| Throughput::Elements(1));
86 | c.bench(id, bm);
87 | }
88 |
--------------------------------------------------------------------------------
/benches/no_op.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | use ratelimit_meter::example_algorithms::{Allower, ForeverClock};
4 |
5 | use ratelimit_meter::test_utilities::algorithms::AlgorithmForTest;
6 |
7 | use criterion::{black_box, Benchmark, Criterion, Throughput};
8 |
9 | pub fn bench_all(c: &mut Criterion) {
10 | let id = "algorithm/no_op";
11 |
12 | let bm = Benchmark::new(id, move |b| {
13 | let algo = AlgorithmForTest::::default();
14 | let now = ForeverClock::now();
15 | let ms = Duration::from_millis(20);
16 |
17 | #[allow(clippy::let_unit_value)]
18 | // clippy complains that this is the unit value, but this is as much a demonstration of
19 | // the code as it is a benchmark.
20 | let state = algo.state();
21 |
22 | let mut i = 0;
23 | b.iter(|| {
24 | i += 1;
25 | black_box(algo.check(&state, now + (ms * i)).is_ok());
26 | });
27 | })
28 | .throughput(Throughput::Elements(1));
29 | c.bench(id, bm);
30 | }
31 |
--------------------------------------------------------------------------------
/benches/single_threaded.rs:
--------------------------------------------------------------------------------
1 | use std::time::{Duration, Instant};
2 |
3 | use ratelimit_meter::clock;
4 | use ratelimit_meter::test_utilities::variants::{DirectBucket, KeyedBucket, Variant};
5 |
6 | use criterion::{black_box, Criterion, ParameterizedBenchmark, Throughput};
7 |
8 | pub fn bench_all(c: &mut Criterion) {
9 | bench_direct(c);
10 | bench_keyed(c);
11 | }
12 |
13 | fn bench_direct(c: &mut Criterion) {
14 | let id = "single_threaded/direct";
15 | let bm = ParameterizedBenchmark::new(
16 | id,
17 | move |b, ref v| {
18 | bench_with_variants!(v, rl: DirectBucket, {
19 | let now = Instant::now();
20 | let ms = Duration::from_millis(20);
21 | let mut i = 0;
22 | b.iter(|| {
23 | i += 1;
24 | black_box(rl.check_at(now + (ms * i)).is_ok());
25 | });
26 | });
27 | },
28 | Variant::ALL,
29 | )
30 | .throughput(|_s| Throughput::Elements(1));
31 | c.bench(id, bm);
32 | }
33 |
34 | fn bench_keyed(c: &mut Criterion) {
35 | let id = "single_threaded/keyed";
36 | let bm = ParameterizedBenchmark::new(
37 | id,
38 | move |b, ref v| {
39 | bench_with_variants!(v, rl: KeyedBucket, {
40 | let now = Instant::now();
41 | let ms = Duration::from_millis(20);
42 | let mut i = 0;
43 | b.iter(|| {
44 | i += 1;
45 | black_box(rl.check_at(i % 100, now + (ms * i)).is_ok());
46 | });
47 | });
48 | },
49 | Variant::ALL,
50 | )
51 | .throughput(|_s| Throughput::Elements(1));
52 | c.bench(id, bm);
53 | }
54 |
--------------------------------------------------------------------------------
/bors.toml:
--------------------------------------------------------------------------------
1 | # Don't forget to update permissions and add this repo in
2 | # https://app.bors.tech/repositories so bors can see the new repo!
3 |
4 | status = [
5 | "ci/circleci: ci_success",
6 | ]
7 | timeout_sec = 300
8 | delete_merged_branches = true
--------------------------------------------------------------------------------
/src/algorithms.rs:
--------------------------------------------------------------------------------
1 | //! Rate-limiting algorithms.
2 |
3 | pub mod gcra;
4 | pub mod leaky_bucket;
5 |
6 | pub use self::gcra::*;
7 | pub use self::leaky_bucket::*;
8 |
9 | use crate::{clock, InconsistentCapacity, NegativeMultiDecision};
10 |
11 | use crate::lib::*;
12 |
13 | /// The default rate limiting algorithm in this crate: The ["leaky
14 | /// bucket"](leaky_bucket/struct.LeakyBucket.html).
15 | ///
16 | /// The leaky bucket algorithm is fairly easy to understand and has
17 | /// decent performance in most cases. If better threaded performance
18 | /// is needed, this crate also offers the
19 | /// [`GCRA`](gcra/struct.GCRA.html) algorithm.
20 | pub type DefaultAlgorithm = LeakyBucket;
21 |
22 | /// Provides additional information about non-conforming cells, most
23 | /// importantly the earliest time until the next cell could be
24 | /// considered conforming.
25 | ///
26 | /// Since this does not account for effects like thundering herds,
27 | /// users should always add random jitter to the times given.
28 | pub trait NonConformance::Instant> {
29 | /// Returns the earliest time at which a decision could be
30 | /// conforming (excluding conforming decisions made by the Decider
31 | /// that are made in the meantime).
32 | fn earliest_possible(&self) -> P;
33 |
34 | /// Returns the minimum amount of time from the time that the
35 | /// decision was made (relative to the `at` argument in a
36 | /// `Decider`'s `check_at` method) that must pass before a
37 | /// decision can be conforming. Since Durations can not be
38 | /// negative, a zero duration is returned if `from` is already
39 | /// after that duration.
40 | fn wait_time_from(&self, from: P) -> Duration {
41 | let earliest = self.earliest_possible();
42 | earliest.duration_since(earliest.min(from))
43 | }
44 | }
45 |
46 | /// The trait that implementations of metered rate-limiter algorithms
47 | /// have to implement.
48 | ///
49 | /// Implementing structures are expected to represent the "parameters"
50 | /// (e.g., the allowed requests/s), and keep the information necessary
51 | /// to make a decision, e.g. concrete usage statistics for an
52 | /// in-memory rate limiter, in the associated structure
53 | /// [`BucketState`](#associatedtype.BucketState).
54 | pub trait Algorithm::Instant>:
55 | Send + Sync + Sized + fmt::Debug
56 | {
57 | /// The state of a single rate limiting bucket.
58 | ///
59 | /// Every new rate limiting state is initialized as `Default`. The
60 | /// states must be safe to share across threads (this crate uses a
61 | /// `parking_lot` Mutex to allow that).
62 | type BucketState: RateLimitState;
63 |
64 | /// The type returned when a rate limiting decision for a single
65 | /// cell is negative. Each rate limiting algorithm can decide to
66 | /// return the type that suits it best, but most algorithms'
67 | /// decisions also implement
68 | /// [`NonConformance`](trait.NonConformance.html), to ease
69 | /// handling of how long to wait.
70 | type NegativeDecision: PartialEq + fmt::Display + fmt::Debug + Send + Sync;
71 |
72 | /// Constructs a rate limiter with the given parameters:
73 | /// `capacity` is the number of cells to allow, weighing
74 | /// `cell_weight`, every `per_time_unit`.
75 | fn construct(
76 | capacity: NonZeroU32,
77 | cell_weight: NonZeroU32,
78 | per_time_unit: Duration,
79 | ) -> Result;
80 |
81 | /// Tests if `n` cells can be accommodated in the rate limiter at
82 | /// the instant `at` and updates the rate-limiter state to account
83 | /// for the weight of the cells and updates the ratelimiter state.
84 | ///
85 | /// The update is all or nothing: Unless all n cells can be
86 | /// accommodated, the state of the rate limiter will not be
87 | /// updated.
88 | fn test_n_and_update(
89 | &self,
90 | state: &Self::BucketState,
91 | n: u32,
92 | at: P,
93 | ) -> Result<(), NegativeMultiDecision>;
94 |
95 | /// Tests if a single cell can be accommodated in the rate limiter
96 | /// at the instant `at` and updates the rate-limiter state to
97 | /// account for the weight of the cell.
98 | ///
99 | /// This method is provided by default, using the `n` test&update
100 | /// method.
101 | fn test_and_update(
102 | &self,
103 | state: &Self::BucketState,
104 | at: P,
105 | ) -> Result<(), Self::NegativeDecision> {
106 | match self.test_n_and_update(state, 1, at) {
107 | Ok(()) => Ok(()),
108 | Err(NegativeMultiDecision::BatchNonConforming(1, nc)) => Err(nc),
109 | Err(other) => unreachable!(
110 | "BUG: measuring a batch of size 1 reported insufficient capacity: {:?}",
111 | other
112 | ),
113 | }
114 | }
115 | }
116 |
117 | /// Trait that all rate limit states have to implement around
118 | /// housekeeping in keyed rate limiters.
119 | pub trait RateLimitState
: Default + Send + Sync + Eq + fmt::Debug {
120 | /// Returns the last time instant that the state had any relevance
121 | /// (i.e. the rate limiter would behave exactly as if it was a new
122 | /// rate limiter after this time).
123 | ///
124 | /// If the state has not been touched for a given amount of time,
125 | /// the keyed rate limiter will expire it.
126 | ///
127 | /// # Thread safety
128 | /// This uses a bucket state snapshot to determine eligibility;
129 | /// race conditions can occur.
130 | fn last_touched(&self, params: &P) -> Option;
131 | }
132 |
133 | #[cfg(feature = "std")]
134 | mod std {
135 | use crate::clock;
136 | use evmap::ShallowCopy;
137 |
138 | /// Trait implemented by all rate limit states that are compatible
139 | /// with the KeyedRateLimiters.
140 | pub trait KeyableRateLimitState
{
45 | let data = self.0.snapshot();
46 | Some(data.0? + params.tau)
47 | }
48 | }
49 |
50 | /// Returned in case of a negative rate-limiting decision. Indicates
51 | /// the earliest instant that a cell might get accepted again.
52 | ///
53 | /// To avoid thundering herd effects, client code should always add a
54 | /// random amount of jitter to wait time estimates.
55 | #[derive(Debug, PartialEq)]
56 | pub struct NotUntil(P);
57 |
58 | impl fmt::Display for NotUntil
{
65 | #[inline]
66 | fn earliest_possible(&self) -> P {
67 | self.0
68 | }
69 | }
70 |
71 | /// Implements the virtual scheduling description of the Generic Cell
72 | /// Rate Algorithm, attributed to ITU-T in recommendation I.371
73 | /// Traffic control and congestion control in B-ISDN; from
74 | /// [Wikipedia](https://en.wikipedia.org/wiki/Generic_cell_rate_algorithm).
75 | ///
76 | ///
77 | /// While algorithms like leaky-bucket rate limiters allow cells to be
78 | /// distributed across time in any way, GCRA is a rate-limiting *and*
79 | /// traffic-shaping algorithm. It mandates that a minimum amount of
80 | /// time passes between cells being measured. For example, if your API
81 | /// mandates that only 20 requests can be made per second, GCRA will
82 | /// ensure that each request is at least 50ms apart from the previous
83 | /// request. This makes GCRA suitable for shaping traffic in
84 | /// networking and telecom equipment (it was initially made for
85 | /// asynchronous transfer mode networks), or for outgoing workloads on
86 | /// *consumers* of attention, e.g. distributing outgoing emails across
87 | /// a day.
88 | ///
89 | /// # A note about batch decisions
90 | /// In a blatant side-stepping of the above traffic-shaping criteria,
91 | /// this implementation of GCRA comes with an extension that allows
92 | /// measuring multiple cells at once, assuming that if a pause of
93 | /// `n*(the minimum time between cells)` has passed, we can allow a
94 | /// single big batch of `n` cells through. This assumption may not be
95 | /// correct for your application, but if you depend on GCRA's
96 | /// traffic-shaping properties, it's better to not use the `_n`
97 | /// suffixed check functions.
98 | ///
99 | /// # Example
100 | /// In this example, we construct a rate-limiter with the GCR
101 | /// algorithm that can accommodate 20 cells per second. This translates
102 | /// to the GCRA parameters τ=1s, T=50ms (that's 1s / 20 cells).
103 | ///
104 | /// ```
105 | /// # use ratelimit_meter::{DirectRateLimiter, GCRA};
106 | /// # use std::num::NonZeroU32;
107 | /// # use std::time::{Instant, Duration};
108 | /// # #[macro_use] extern crate nonzero_ext;
109 | /// # extern crate ratelimit_meter;
110 | /// # #[cfg(feature = "std")]
111 | /// # fn main () {
112 | /// let mut limiter = DirectRateLimiter::::per_second(nonzero!(20u32));
113 | /// let now = Instant::now();
114 | /// let ms = Duration::from_millis(1);
115 | /// assert_eq!(Ok(()), limiter.check_at(now)); // the first cell is free
116 | /// for i in 0..20 {
117 | /// // Spam a lot:
118 | /// assert!(limiter.check_at(now).is_ok(), "at {}", i);
119 | /// }
120 | /// // We have exceeded the bucket capacity:
121 | /// assert!(limiter.check_at(now).is_err());
122 | ///
123 | /// // After a sufficient time period, cells are allowed again:
124 | /// assert_eq!(Ok(()), limiter.check_at(now + ms*50));
125 | /// # }
126 | /// # #[cfg(not(feature = "std"))] fn main() {}
127 | /// ```
128 | #[derive(Debug, Clone)]
129 | pub struct GCRA::Instant> {
130 | // The "weight" of a single packet in units of time.
131 | t: Duration,
132 |
133 | // The "capacity" of the bucket.
134 | tau: Duration,
135 |
136 | point: PhantomData
,
137 | }
138 |
139 | impl Algorithm
for GCRA
{
140 | type BucketState = State
;
141 |
142 | type NegativeDecision = NotUntil
;
143 |
144 | fn construct(
145 | capacity: NonZeroU32,
146 | cell_weight: NonZeroU32,
147 | per_time_unit: Duration,
148 | ) -> Result {
149 | if capacity < cell_weight {
150 | return Err(InconsistentCapacity::new(capacity, cell_weight));
151 | }
152 | Ok(GCRA {
153 | t: (per_time_unit / capacity.get()) * cell_weight.get(),
154 | tau: per_time_unit,
155 | point: PhantomData,
156 | })
157 | }
158 |
159 | /// Tests if a single cell can be accommodated by the
160 | /// rate-limiter and updates the state, if so.
161 | fn test_and_update(
162 | &self,
163 | state: &Self::BucketState,
164 | t0: P,
165 | ) -> Result<(), Self::NegativeDecision> {
166 | let tau = self.tau;
167 | let t = self.t;
168 | state.0.measure_and_replace(|tat| {
169 | // the "theoretical arrival time" of the next cell:
170 | let tat = tat.0.unwrap_or(t0);
171 | if t0 < tat.saturating_sub(tau) {
172 | (Err(NotUntil(tat)), None)
173 | } else {
174 | (Ok(()), Some(Tat(Some(cmp::max(tat, t0) + t))))
175 | }
176 | })
177 | }
178 |
179 | /// Tests if `n` cells can be accommodated by the rate-limiter
180 | /// and updates rate limiter state iff they can be.
181 | ///
182 | /// As this method is an extension of GCRA (using multiplication),
183 | /// it is likely not as fast (and not as obviously "right") as the
184 | /// single-cell variant.
185 | fn test_n_and_update(
186 | &self,
187 | state: &Self::BucketState,
188 | n: u32,
189 | t0: P,
190 | ) -> Result<(), NegativeMultiDecision> {
191 | let tau = self.tau;
192 | let t = self.t;
193 | state.0.measure_and_replace(|tat| {
194 | let tat = tat.0.unwrap_or(t0);
195 | let tat = match n {
196 | 0 => t0,
197 | 1 => tat,
198 | _ => {
199 | let weight = t * (n - 1);
200 | if (weight + t) > tau {
201 | // The bucket capacity can never accommodate this request
202 | return (Err(NegativeMultiDecision::InsufficientCapacity(n)), None);
203 | }
204 | tat + weight
205 | }
206 | };
207 |
208 | let additional_weight = match n {
209 | 0 => Duration::new(0, 0),
210 | 1 => t,
211 | _ => t * n,
212 | };
213 | if t0 < tat.saturating_sub(tau) {
214 | (
215 | Err(NegativeMultiDecision::BatchNonConforming(n, NotUntil(tat))),
216 | None,
217 | )
218 | } else {
219 | (
220 | Ok(()),
221 | Some(Tat(Some(cmp::max(tat, t0) + additional_weight))),
222 | )
223 | }
224 | })
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/algorithms/leaky_bucket.rs:
--------------------------------------------------------------------------------
1 | //! A classic leaky bucket algorithm
2 |
3 | use crate::lib::*;
4 | use crate::thread_safety::ThreadsafeWrapper;
5 | use crate::{
6 | algorithms::{Algorithm, RateLimitState},
7 | clock, InconsistentCapacity, NegativeMultiDecision, NonConformance,
8 | };
9 |
10 | /// Implements the industry-standard leaky bucket rate-limiting
11 | /// as-a-meter. The bucket keeps a "fill height", pretending to drip
12 | /// steadily (which reduces the fill height), and increases the fill
13 | /// height with every cell that is found conforming. If cells would
14 | /// make the bucket overflow, they count as non-conforming.
15 | ///
16 | /// # Drip implementation
17 | ///
18 | /// Instead of having a background task update the bucket's fill
19 | /// level, this implementation re-computes the fill level of the
20 | /// bucket on every call to [`check`](#method.check) and related
21 | /// methods.
22 | ///
23 | /// # Wait time calculation
24 | ///
25 | /// If the cell does not fit, this implementation computes the minimum
26 | /// wait time until the cell can be accommodated. This minimum wait
27 | /// time does not account for thundering herd effects or other
28 | /// problems in concurrent resource acquisition, so users of this
29 | /// library must take care to apply positive jitter to these wait
30 | /// times.
31 | ///
32 | /// # Example
33 | /// ``` rust
34 | /// # use ratelimit_meter::{DirectRateLimiter, LeakyBucket};
35 | /// # #[macro_use] extern crate nonzero_ext;
36 | /// # extern crate ratelimit_meter;
37 | /// # #[cfg(feature = "std")]
38 | /// # fn main () {
39 | /// let mut lb = DirectRateLimiter::::per_second(nonzero!(2u32));
40 | /// assert_eq!(Ok(()), lb.check());
41 | /// # }
42 | /// # #[cfg(not(feature = "std"))] fn main() {}
43 | /// ```
44 | #[derive(Debug, Clone, Eq, PartialEq)]
45 | pub struct LeakyBucket::Instant> {
46 | full: Duration,
47 | token_interval: Duration,
48 | point: PhantomData
,
49 | }
50 |
51 | /// Represents the state of a single history of decisions.
52 | #[derive(Debug, Eq, PartialEq, Clone)]
53 | pub struct State(ThreadsafeWrapper>);
54 |
55 | impl Default for State