Skip to content

Add rate limiting to xapi#7175

Open
cplaursen wants to merge 24 commits into
masterfrom
feature/throttling2
Open

Add rate limiting to xapi#7175
cplaursen wants to merge 24 commits into
masterfrom
feature/throttling2

Conversation

@cplaursen

Copy link
Copy Markdown
Contributor

No description provided.

cplaursen and others added 16 commits April 2, 2026 11:01
Token buckets form the core of the XAPI rate limiter. Token buckets hold
tokens, which are refilled over time according to their refill rate up
to a maximum.

Each client making calls to XAPI will have one token bucket associated;
each call takes tokens out of the bucket, and delays will be enforced
such that any requests made to an empty bucket have to wait until the
bucket has been refilled.

The token bucket library implements functions for token bucket creation
and thread-safe token consumption. Refills are calculated only when the
bucket is observed by tracking a last_refill timestamp.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Token buckets form the basis of the rate limiting, but on their own they
lack a way of enabling fairness when waiting.

For this, we add a separate queuing mechanism which registers callbacks
for requests that are waiting on a token bucket to refill. A worker
thread is tasked with awakening requests as enough tokens are placed
back in the bucket.

We expose an interface for submitting requests to a queue, both in
synchronous and asynchronous mode.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
We need an in-memory key-value store to catalogue callers for the purposes of
rate limiting and usage tracking. We want to support the specification of
callers via prefixes, which means that we cannot use a regular hashtable.

Instead, we can use an association list with an LRU cache, which should amortise
usage as we expect most calls to come from the same small group of callers.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
This commit adds a thread-safe counter that tracks API call usage, with the
objective of exposing a counter to an RRD.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
The throttling feature branch is out of date, and we need to keep it up
to date in order for it to build with the latest dependencies.
The rate limit library is currently called xapi_rate_limit, which will clash
with the xapi file associated to the rate limit datamodel. Renaming to
rate_limit_lib.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
As per the rate_limit design, this commit adds two new datamodels to xapi:

- Caller: tracks usage statistics on clients calling into xapi. Clients are
  currently identified by their ip address and HTTP user agent
- Rate_limit: lets xapi admins apply rate limits to groups of callers. Rate
  limiters allow a fixed number of "tokens" (number correlated with call cost)
  to be used per second; if the token count is exceeded, the calls get throttled.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@cloud.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
@cplaursen

Copy link
Copy Markdown
Contributor Author

Will be ready to merge once #7171 has been merged. I've thoroughly tested the feature with unit tests, quicktests, and XenRT, and it is gated behind the rate_limit flag in xapi.conf.

There is an issue where tokens with a low maximum capacity hang forever
when requests that cost more than the capacity come in, as they must
wait for the bucket to refill past its capacity. We fix this by letting
buckets go negative only when these conditions are met, in order to avoid
deadlocks.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Currently, the caller table is being initialised in xapi.ml, but this means
that additions to the caller datamodel are not tracked until the in-memory
structure is initialised, which leads to tests failing.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
An IPv4 connection can surface at the socket as an IPv4-mapped IPv6
address (e.g. ::ffff:1.2.3.4) depending on how the listening socket is
bound. Ipaddr.to_string preserves the IPv6 form, which then fails to
match string comparisons against the plain IPv4 form of the same
address, breaking the per-caller rate-limit table lookups.

Add Http_svr.canonical_ip_string and route the client IP through it in
string_of_client, Context.get_client_ip, and the caller-id assembled in
Xapi_http.add_handler.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Caller_table uses Atomic for lock-free reads, but its writers are
non-CAS Atomic.get/set pairs and refresh_caller_rate_limit's
delete-then-insert is not atomic. Concurrent refreshes for the same
caller could both read the pre-mutation state, and the later insert
would then be silently refused as a duplicate, leaving the table with
stale state.

Replace the create-path-only mutex with caller_table_mutex, held around
create, destroy, refresh_caller_rate_limit, and the auto-create
re-check. Split create into create/create_locked so the auto-create
path can reuse the body under the same lock. register keeps bypassing
the mutex - it runs single-threaded at start of day.

Also preserve the caller's stats across a rate_limit refresh, so
attaching or detaching a rate_limit no longer resets the calls/tokens
counters accumulated since xapi startup.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
add_caller was clearing the caller's rate_limit to Ref.null before
setting the new value, which briefly left the caller unlimited and
fired two full caller_table refreshes (each an extra DB read via the
callback). The datamodel's reverse-relation machinery already removes
the caller from any previous Rate_limit's callers set on assignment,
so set the target directly and notify once.

set_burst_size and set_fill_rate no longer notify attached callers:
the caller_table entry still points at the same rate_limit_ref and
install_bucket has already swapped the in-memory bucket, so the next
find_bucket on the dispatch path picks up the new parameters. The
old notifications were pure overhead.

Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
@cplaursen
cplaursen marked this pull request as ready for review July 16, 2026 09:48
@cplaursen
cplaursen requested review from GabrielBuica and mg12 July 16, 2026 09:48
Signed-off-by: Christian Pardillo Laursen <christian.pardillolaursen@citrix.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants