xmlrpc_extended
📖 Full documentation · Issues · Discussions
xmlrpc_extended is a small, dependency-light package that upgrades Python's
stdlib XML-RPC server with bounded thread-pool concurrency and explicit overload
handling.
Current package scope
ThreadPoolXMLRPCServer— drop-inSimpleXMLRPCServerreplacement with a boundedThreadPoolExecutor- bounded worker and pending-request limits with configurable overload policies (block, close, fault, HTTP 503)
- graceful executor shutdown
- request body size limiting through
LimitedXMLRPCRequestHandler max_pendingcounts queued requests beyond the active worker pool; total outstanding capacity ismax_workers + max_pendingXMLRPCClientcontext manager with configurable timeoutSO_REUSEPORTscale-out helpers for Linux (xmlrpc_extended.multiprocess)XMLRPCASGIApp— ASGI 3 adapter; deploy on uvicorn/hypercorn/granian withasync defand sync handlers
Quickstart
from xmlrpc_extended import ServerOverloadPolicy, ThreadPoolXMLRPCServer
def add(left, right):
return left + right
server = ThreadPoolXMLRPCServer(
("127.0.0.1", 8000),
max_workers=8,
max_pending=16,
overload_policy=ServerOverloadPolicy.FAULT,
logRequests=False,
)
server.register_introspection_functions()
server.register_function(add, "add")
server.serve_forever()
With max_workers=8, max_pending=16, up to 8 requests run concurrently and up
to 16 additional requests may wait for a worker. After that, the configured
overload policy is applied.
Overload semantics and queueing model
Capacity model
total outstanding capacity = max_workers + max_pending
| Parameter | Role |
|---|---|
max_workers |
Maximum requests executing concurrently in the thread pool |
max_pending |
Maximum requests waiting for a free worker slot |
request_queue_size |
OS-level TCP accept backlog (before the application sees the request) |
Default: max_pending=None resolves to max_workers, so by default the total capacity is 2 × max_workers. For latency-sensitive deployments set max_pending=0 to fail fast instead of queuing.
Overload policy behavior
| Policy | What happens when capacity is exhausted |
|---|---|
BLOCK (default) |
The accept thread blocks until a worker slot opens. Requests queue at the OS level. Suitable for trusted, low-variance load. |
CLOSE |
The connection is closed immediately without sending a response. The client receives a connection-reset error. Suitable for shedding load fast. |
FAULT |
An XML-RPC fault response is returned with the configured overload_fault_code and overload_fault_string. Clients that speak XML-RPC can distinguish overload from method errors. |
HTTP_503 |
An HTTP 503 Service Unavailable response is returned. Non-XML-RPC clients (e.g. HTTP health-checkers) can detect overload at the transport layer. |
Recommended defaults
| Deployment | Suggested settings |
|---|---|
| Embedded / internal tool | max_workers=4, max_pending=None (= 4), BLOCK |
| Service with SLA | max_workers=N, max_pending=0, FAULT or HTTP_503 |
| Behind a load balancer | max_workers=N, max_pending=small, CLOSE or HTTP_503 |
Restricting accepted URL paths
By default the server accepts XML-RPC requests at / and /RPC2. Restrict this with rpc_paths:
server = ThreadPoolXMLRPCServer(
("127.0.0.1", 8000),
rpc_paths=("/rpc",), # only /rpc is accepted; all others return 404
)
Observability — server stats
ThreadPoolXMLRPCServer.stats() returns a ServerStats snapshot with the following counters:
| Field | Meaning |
|---|---|
active |
Requests currently executing in worker threads |
queued |
Requests submitted to the thread pool but not yet running |
rejected_close |
Requests rejected with connection close (CLOSE policy) |
rejected_fault |
Requests rejected with fault response (FAULT policy) |
rejected_503 |
Requests rejected with HTTP 503 (HTTP_503 policy) |
completed |
Requests that finished successfully |
errored |
Requests that raised an exception in the handler |
snap = server.stats()
print(f"active={snap.active} queued={snap.queued} completed={snap.completed}")
Client helpers
xmlrpc_extended.client provides XMLRPCClient, a context-manager wrapper with an explicit timeout:
from xmlrpc_extended.client import XMLRPCClient
with XMLRPCClient("http://127.0.0.1:8000/", timeout=5.0) as proxy:
result = proxy.add(1, 2)
The default timeout is 30 seconds. Retrying failed requests is the caller's responsibility — XML-RPC methods are not necessarily idempotent.
Scale-out with SO_REUSEPORT (Linux only)
On Linux, multiple server processes can share the same port using SO_REUSEPORT:
from xmlrpc_extended.multiprocess import create_reuseport_socket, spawn_workers
from xmlrpc_extended import ThreadPoolXMLRPCServer
def run_worker():
sock = create_reuseport_socket("0.0.0.0", 8000)
server = ThreadPoolXMLRPCServer(("0.0.0.0", 0), bind_and_activate=False)
server.socket = sock
server.server_activate()
server.register_function(lambda x: x + 1, "inc")
server.serve_forever()
if __name__ == "__main__":
processes = spawn_workers(run_worker, num_workers=4)
for p in processes:
p.join()
See xmlrpc_extended.multiprocess module docs for full details.
Development approach
This repository was bootstrapped with TDD:
- define server behavior in integration-style tests
- implement the public API to satisfy those tests
- keep the package small and compatible with stdlib XML-RPC patterns
Local validation
python -m pip install . --no-deps
python -m unittest discover -s tests -v
Benchmarks
pip install .
# All implementations compared side by side (Simple, Threaded, ThreadPool, ASGI)
python benchmarks/benchmark_all.py
# Focused ASGI in-process benchmark (higher-concurrency ASGI tuning)
python benchmarks/benchmark_asgi.py
# Thread-pool server vs stdlib SimpleXMLRPCServer (legacy script)
python benchmarks/benchmark_server.py --requests 200 --clients 8 --sleep 0.02
Security
⚠️
xmlrpc_extendedis not suitable for untrusted public networks. See SECURITY.md for the full security posture, deployment checklist, and vulnerability reporting process.
- Use
max_request_sizeto reject oversized payloads early. - Restrict URL paths with
rpc_paths. - Never set
allow_dotted_names=Trueon handlers exposed to untrusted clients.
Request-size and request-shape expectations
LimitedXMLRPCRequestHandler enforces the following rules on every incoming
POST request before any XML-RPC processing occurs:
| Condition | HTTP response |
|---|---|
Content-Length header missing |
411 Length Required |
Content-Length non-integer / negative |
400 Bad Request |
Content-Length exceeds max_request_size |
413 Payload Too Large |
Transfer-Encoding: chunked |
501 Not Implemented |
Only requests with a valid, non-negative Content-Length within the configured
limit are forwarded to the XML-RPC dispatcher. The default max_request_size is
1 MiB (1 048 576 bytes) and can be set via ThreadPoolXMLRPCServer's
constructor.
Roadmap
| Milestone | Target | Description |
|---|---|---|
| M2 | 0.3.0 | Metrics hooks, observability examples, benchmarks ✅ |
| M3 | 0.4.0 | Multi-process / SO_REUSEPORT, HTTP 503 rejection, client helpers ✅ |
| M4 | 0.5.0 | ASGI adapter (XMLRPCASGIApp) ✅ |
ASGI / async integration
XMLRPCASGIApp is a drop-in ASGI 3 adapter bundled with the core package (no extra install required). Deploy on any ASGI server:
from xmlrpc_extended.asgi import XMLRPCASGIApp
app = XMLRPCASGIApp()
async def greet(name: str) -> str:
return f"Hello, {name}!"
app.register_function(greet, "greet")
# uvicorn your_module:app --host 127.0.0.1 --port 8000
Sync handlers run in a thread pool via asyncio.to_thread; async def handlers execute directly in the event loop. See the ASGI Integration guide for deployment recipes, testing patterns, and migration notes from ThreadPoolXMLRPCServer.
Metadata
Release files for xmlrpc-extended 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| xmlrpc_extended-1.0.0.tar.gz | 34.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| xmlrpc_extended-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.4 kB
Release files / xmlrpc_extended-1.0.0.tar.gz
| Download URL | xmlrpc_extended-1.0.0.tar.gz |
|---|---|
| Size | 34.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f1a0bc96bc3a7b53026b84917f367b4d821ca2b3a195babcee6a8caa8b76413e
|
|
BLAKE2b-256 checksum How to use checksums |
0b1798e7cab286678881bac72b4f52aecd86aa0ab0f05594e854dd2e15ae76ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Release files / xmlrpc_extended-1.0.0-py3-none-any.whl
| Download URL | xmlrpc_extended-1.0.0-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bce7e9139b91bb33f9e03141b0f2f983ee1883c2762c5d8c7d4adf2f7a7dfde5
|
|
BLAKE2b-256 checksum How to use checksums |
01c93d618b01c5372b51a2569e9cda8f415378d9e93bf356448fa0bcc147b6d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|