Skip to content

Threads and free-threaded Python

SecretKey, PrivateKey, PublicKey and JWKSClient objects are thread-safe: create one per key or JWKS URL, and share it. Key objects are immutable, apart from caches (of token headers, and of how to decode into each class) that threads may fill concurrently. A JWKSClient guards its keys with a lock.

import secrets
from concurrent.futures import ThreadPoolExecutor

import ryjwt

key = ryjwt.SecretKey(secrets.token_bytes(32), algorithms=["HS256"])
tokens = [key.encode({"sub": f"user-{i}"}) for i in range(100)]

with ThreadPoolExecutor(max_workers=4) as pool:
    claims = list(pool.map(key.decode, tokens))

assert claims[42] == {"sub": "user-42"}

Free-threaded Python

On free-threaded Python (3.14t and 3.15t), ryjwt doesn't turn the GIL back on: threads encode and decode in parallel. Install as usual; uv picks the cp314t or cp315t wheel.

On regular Python, ryjwt holds the GIL while it works, so threads take turns. A call takes about a microsecond for a typical HS256 token, so this rarely matters. To use several cores, use processes or the free-threaded build.

JWKS clients

A JWKSClient runs one fetch at a time, on its own daemon thread, shared by every caller: sync or async, on any thread or event loop. Background refreshes need no event loop, so they work with a loop per request (asyncio.run(...) each time) or none.