Prerequisites. You can read C, understand readiness-based event loops, and have a C11 compiler, OpenSSL, the rl-c-client source tree, and libev development files installed.
This example submits one resource rate limit and one latency guard without blocking on Ratelimitly network I/O. After admission, successful work is measured and one latency report is submitted; denied work runs neither step.
This self-contained program uses one ev_io watcher per runtime-owned UDP socket
and a one-shot ev_timer for the active admission deadline. Read callbacks drain
response datagrams, timer callbacks advance retries, and the completion callback
decides whether protected work may run.
The application owns the loop, watchers, timer, request, and copied outcome. The runtime owns the client and sockets, so watchers must stop before runtime destruction closes those sockets.
flowchart TD
Start["Submit resource limit + latency guard"] --> Watch["Start ev_io watchers"]
Watch --> Timer["Arm one-shot ev_timer"]
Timer --> Loop["Run ev_run"]
Loop --> Event{"Which callback ran?"}
Event -->|Socket readable| Read["Drain runtime datagrams"]
Event -->|Timer expired| Timeout["Advance timeout and retry state"]
Read --> Decision{"Admission complete?"}
Timeout --> Decision
Decision -->|No| Rearm["Recompute and re-arm deadline"]
Rearm --> Loop
Decision -->|Denied| Reject["Skip work and latency report"]
Decision -->|Allowed| Work["Run work, measure it, report one sample"]
Install libev, then build the client library and this folder:
sudo apt-get install libev-dev # Debian or Ubuntu
brew install libev # macOS
make -C ../..
make
export RATELIMITLY_AUTH_KEY=rl-aes1...
./libev-example
The equivalent CMake build is:
cmake -S . -B build
cmake --build build
RATELIMITLY_AUTH_KEY=rl-aes1... ./build/libev-example
An admitted run exits 0 and prints the response plus measured latency. A policy denial exits 2 and names the resource limit, latency guard, or both; setup and transport failures exit 1.
RATELIMITLY_AUTH_KEY is required. Its encoded key ID selects production P0
discovery at:
_ratelimitly._udp.c-<key-id>.p0.ratelimitly.com
RATELIMITLY_TENANT is optional and overrides that key-derived tenant name. In
normal production use, leave the following local-test variables unset:
export RATELIMITLY_EXAMPLE_SERVER_HOST=127.0.0.1
export RATELIMITLY_EXAMPLE_SERVER_PORT=39082
Set RATELIMITLY_EXAMPLE_SERVER_HOST and RATELIMITLY_EXAMPLE_SERVER_PORT
together, or set neither. Supplying only one is a configuration error. When
both are set, the runtime uses that fixed UDP endpoint instead of DNS discovery.
Do not commit authentication keys.
The latency guard is a pre-work admission decision based on samples already
stored for libev-protected-service. The observed latency is a different,
post-work value: r_runtime_admission_run_and_report() measures the successful
prepare_response() call with a monotonic clock and reports one sample back to the
same tracker.
Resource denial or latency-guard denial skips prepare_response(). Cancellation,
transport failure, and protected-work failure also produce no latency sample.
prepare_response() is intentionally short and synchronous so event-loop plumbing
stays visible. Real protected work must not block the libev thread. After
admission, start the asynchronous operation, retain connection and request
state, record a monotonic start time, and call
r_client_admission_report_latency() only after successful completion. Marshal
that completion back to the loop thread before touching the client.
Re-arm ev_timer after every timeout transition because a retry can publish a
new deadline. Stop all ev_io watchers and the timer before destroying the
runtime.
This source targets libev Unix file-descriptor backends on Linux and macOS. It
does not narrow a WinSock SOCKET into libev’s int watcher field; use libuv,
libevent, or the native Win32 example on Windows.
Ubuntu CI builds and runs this binary against a synthetic responder for allowed, resource-denied, and latency-denied outcomes. Trusted main runs also exercise key-derived production P0 discovery and admission. macOS compatibility is a source/build claim here, not an execution claim from this repository’s CI. Production latency reporting is fire-and-forget; the per-example P0 run proves local report submission, not server acknowledgement of that datagram.
| Term | Meaning here |
|---|---|
ev_io |
A libev watcher that invokes a callback when a socket is readable. |
ev_timer |
A libev timer watcher; this example reuses it as a one-shot admission deadline. |
SOCKET |
WinSock’s native socket-handle type, which this Unix-focused example deliberately does not narrow. |
| latency guard | The pre-work policy check against previously reported service latency. |
| latency sample | The post-work duration reported only after admitted work succeeds. |
| P0 | The production Ratelimitly DNS and service tier selected from the key. |
| CMake | Cross-platform build-system generator provided as an alternative to Make. |