A tiny, fast C++20 rate limiter library with an in-process, sharded token-bucket core, clean C++ and C interfaces, and first-class CMake packaging. Ships with examples, unit tests, and an optional Google Benchmark suite.
- OS: Windows (MSYS2/MinGW, MSVC), Linux, macOS
- Toolchain: CMake (≥3.16), Ninja (recommended), C++20
- Packaging:
find_package(Ratelimit CONFIG REQUIRED)and pkg-config - Visibility: Exports controlled via
RL_API, hidden by default on shared builds
- Sharded in-process token-bucket implementation for excellent concurrency
- Fixed-point arithmetic for precise refill and capacity handling
- Header-only public API surface with compact implementation
- Clean separation of concerns (core, C API, JNI optional)
- Unit tests with GoogleTest
- Optional microbenchmarks with Google Benchmark
- CMake install rules +
RatelimitConfig.cmakefor downstreamfind_package - Optional
ratelimit.pcfor pkg-config consumers
.
├─ include/ratelimit/ # Public headers
│ ├─ clock.h
│ ├─ export.h
│ ├─ rate_limiter.h
│ ├─ ratelimit_c.h
│ ├─ types.h
│ ├─ version.h
│ └─ version.h.in # Template -> generated version.h at build time
├─ src/
│ └─ inproc_rate_limiter.cpp
├─ c_api/
│ └─ ratelimit_c.cpp
├─ bench/
│ └─ bench_core.cpp # Optional (GBM)
├─ tests/
│ └─ test_core.cpp # GoogleTest
├─ examples/
│ └─ cli.cpp # Simple CLI smoke test
├─ cmake/
│ ├─ RatelimitConfig.cmake.in
│ └─ ratelimit.pc.in # Optional pkg-config template
├─ CMakeLists.txt
├─ README.md
├─ LICENSE
└─ .gitignore
-
Configure the build (static core + shared C++ and C DLLs by default):
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -
Build:
ninja -C build -
Run tests (if enabled):
ctest --test-dir build --output-on-failure -
Install (choose your prefix):
cmake --install build --prefix "C:/Users/you/ratelimit-install"
You’ll find:
- Headers under
<prefix>/include/ratelimit - Libraries and import libs under
<prefix>/lib(Linux/macOS) or<prefix>/binand<prefix>/lib(Windows) - CMake package files under
<prefix>/lib/cmake/Ratelimit - Optional
ratelimit.pcunder<prefix>/lib/pkgconfig
cmake -S consumer -B consumer-build -G Ninja -DCMAKE_PREFIX_PATH="C:/Users/you/ratelimit-install"
ninja -C consumer-build
consumer/CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
project(consumer LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Ratelimit CONFIG REQUIRED)
add_executable(consumer main.cpp)
# Link against the static core or the shared C++ library:
target_link_libraries(consumer PRIVATE Ratelimit::ratelimit_core)
# or: target_link_libraries(consumer PRIVATE Ratelimit::ratelimit)
consumer/main.cpp:
#include <ratelimit/rate_limiter.h>
#include <iostream>
int main() {
rl::RateLimiterInProc limiter;
rl::Limit lim{10, 5}; // capacity=10, refill=5/s
auto d = limiter.allow("test-key", lim);
std::cout << (d.allowed ? "allowed" : "denied")
<< " remaining=" << d.remaining
<< " reset_ms=" << d.reset_ms << "\n";
return 0;
}
cmake -S consumer -B consumer-build -G Ninja -DRatelimit_DIR="C:/Users/you/ratelimit-install/lib/cmake/Ratelimit"
ninja -C consumer-build
c_consumer.c:
#include <ratelimit/ratelimit_c.h>
#include <stdio.h>
int main() {
printf("%s\n", rl_version());
return 0;
}
Compile and link against the installed C library (on Windows, add the install bin to PATH to run).
After building:
./build/rl_cli
Enter keys (Ctrl+D to exit). Using capacity=10, refill=5/s
foo
foo -> allowed=yes remaining=9 reset_ms=0
You can also pipe input:
yes foo | head -n 15 | ./build/rl_cli
Enable at configure time:
cmake -S . -B build -G Ninja -DRL_BUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release
ninja -C build rl_bench
./build/rl_bench
Example output (will vary by CPU):
BM_UniformKeys 260 ns items_per_second=3.8M/s
BM_HotKey 135 ns items_per_second=7.4M/s
- Token bucket using Q32.32 fixed-point counters for precise refill math
- Sharded hash map: key space split by FNV-1a hash; reduces lock contention
- Read path first tries shared lock; insert uses unique lock
- Clock abstraction for deterministic tests (mockable clock)
- Overflow-safe arithmetic and careful atomics
- On Windows, public API functions/classes are decorated with
__declspec(dllexport/dllimport)viaRL_APImacro in headers. - On Unix-like platforms, shared libraries are compiled with
-fvisibility=hidden -fvisibility-inlines-hiddenand public symbols explicitly exported withRL_API. - The static core (
ratelimit_core) definesRL_STATICprivately, so headers compiled within that target use non-dll semantics without leaking to downstream consumers.
Common CMake options:
RL_BUILD_SHARED(ON|OFF) — Build shared C++ libraryratelimitin addition to static coreRL_BUILD_TESTS(ON|OFF) — Build and run GoogleTest unit testsRL_BUILD_BENCH(ON|OFF) — Build Google Benchmark suiteRL_BUILD_JNI(ON|OFF) — Build JNI bridge (requires Java/JNI)RL_ENABLE_TSAN(ON|OFF) — Build with ThreadSanitizer (where supported)
Example:
cmake -S . -B build -G Ninja -DRL_BUILD_SHARED=ON -DRL_BUILD_TESTS=ON -DRL_BUILD_BENCH=OFF
If you enabled pkg-config template, install produces ratelimit.pc.
Example:
export PKG_CONFIG_PATH="/your/prefix/lib/pkgconfig:$PKG_CONFIG_PATH"
pkg-config --cflags --libs ratelimit --define-prefix
Q: Which target should I link?
Use Ratelimit::ratelimit_core for the static core or Ratelimit::ratelimit for the shared C++ library. The C API is in libratelimit_c (shared).
Q: Windows linking notes?
Make sure the bin folder containing DLLs is on PATH at runtime. When using MSYS2/MinGW, you’ll see libratelimit.dll and import libs in lib.
Q: Thread safety?
Per-key operations are safe; the structure uses sharded maps and shared/unique locks internally. Your own key strings should remain valid for the duration of each call.
Q: License?
See the LICENSE file.
This project is released under the terms of the license in the LICENSE file.
- GoogleTest, Google Benchmark
- The broader C++ community for patterns on symbol visibility and CMake packaging