openssl/openssl
Threading
Active contributors: Pauli, Neil Horman, Tomas Mraz, Bob Beck
Purpose
OpenSSL is thread-safe: an OSSL_LIB_CTX (and any object it owns) can be used concurrently from multiple threads without external synchronisation. This page explains the primitives that make that work.
The code lives in two places:
- Per-platform implementation files:
crypto/threads_pthread.c(37 KB),crypto/threads_win.c(20 KB),crypto/threads_none.c(no-op forno-threadsbuilds),crypto/threads_lib.c,crypto/threads_common.c. - Public façade:
include/openssl/crypto.h.indeclaresCRYPTO_THREAD_*andCRYPTO_RWLOCK *.
Primitives
| Primitive | Purpose |
|---|---|
CRYPTO_RWLOCK * |
Reader-writer lock. CRYPTO_THREAD_lock_new, _read_lock, _write_lock, _unlock, _lock_free. |
CRYPTO_ONCE |
One-time initialiser. CRYPTO_THREAD_run_once(&once, init_fn). |
CRYPTO_THREAD_LOCAL |
Thread-local pointer. _init, _get_local, _set_local, _cleanup_local. |
CRYPTO_REF_COUNT |
Refcount. CRYPTO_atomic_add is the cross-platform primitive. |
| RCU (read-copy-update) | A read-mostly synchronisation primitive used by the property cache and the namemap. CRYPTO_THREAD_lock_new(CRYPTO_RWLOCK_RCU) produces an RCU-style lock; ossl_rcu_* helpers in crypto/threads_*.c. |
OSSL_thread_stop_handler_fn |
A callback registered by providers to clean up per-thread state when a thread exits. |
The implementations are thin wrappers over the OS:
- pthreads:
pthread_rwlock_*,pthread_once,pthread_key_create/pthread_setspecific,__atomic_*builtins, plus a custom RCU. - Windows:
SRWLOCK,InitOnceExecuteOnce,FlsAlloc/FlsSetValue,Interlocked*, plus a custom RCU. no-threads: every primitive is a no-op (asserts in debug builds if a real lock is taken).
Thread-local cleanup
Providers and some libcrypto subsystems keep per-thread state (the per-thread DRBG, the per-thread error stack, the OBJ_NID cache, …). When a thread exits, that state has to be released. crypto/threads_common.c maintains a per-OSSL_LIB_CTX registry of cleanup callbacks; pthread_key_create's destructor (or its Windows equivalent) walks the registry and runs them.
A provider registers via OSSL_FUNC_CORE_THREAD_START from the dispatch table it received in OSSL_provider_init.
Why RCU?
The property/namemap caches are read on every EVP_*_fetch. Locking them with a normal RW-lock would be a major contention point in any multithreaded TLS server. The RCU implementation in crypto/threads_*.c lets readers proceed lock-free and only synchronises writers via ossl_rcu_synchronize (which waits until every reader has crossed a quiescent point).
crypto/property/defn_cache.c uses RCU to expose property definitions; crypto/core_namemap.c uses RCU for name lookups. Updates are rare (when a provider is loaded), reads are constant.
Reference counting
include/internal/refcount.h wraps a small set of macros over compiler intrinsics:
int CRYPTO_UP_REF(int *val, int *ret); /* atomic increment, returns ok */
int CRYPTO_DOWN_REF(int *val, int *ret); /* atomic decrement; *ret == 0 means "destroy now" */These are used by every refcounted struct in OpenSSL (X509, EVP_PKEY, SSL_SESSION, OSSL_PROVIDER, BIO, …). The implementations preferred order:
- C11
<stdatomic.h>if available. __atomic_*GCC/Clang builtins.Interlocked*on Windows.- A spinlock fallback.
Concurrency model
Most APIs are safe to call concurrently on different objects in the same OSSL_LIB_CTX. The same object usually requires serialisation by the caller, except where explicitly documented otherwise (e.g. fetched EVP_MD * handles are immutable and safely shareable).
Per-libctx caches (provider list, namemap, property cache, default property, method stores) are internally synchronised. Callers do not need a lock to fetch from them concurrently.
Lock contention diagnostics
util/analyze-contention-log.sh and util/lock-contention-graph.sh parse trace output to identify hot locks. They read the output of the BN_CTX/PROVIDER/STORE trace categories combined with timing instrumentation enabled at compile time.
Initialization
OpenSSL self-initialises the threading subsystem on first use (crypto/init.c); explicit OPENSSL_init_crypto(0, NULL) calls are unnecessary except in unusual fork/dlopen scenarios. The pthread_atfork handler in init.c re-initialises per-process state in the child after fork().
no-threads builds
./Configure no-threads removes thread support entirely. crypto/threads_none.c provides no-op implementations of every primitive; the build fails gracefully if anything tries to actually take a lock at runtime in debug mode. This is meaningful for embedded targets that don't have pthreads.
Integration points
- Every per-libctx cache uses RCU or RW locks here.
- Every refcounted object uses these atomics.
- Provider thread-stop callbacks plug into this infrastructure.
- The TLS state machine doesn't internally lock the
SSLobject — that is the application's responsibility.
Entry points for modification
- Adding a new platform: add a
crypto/threads_<plat>.cand wire it up in the build via theConfigurations/matrix. - Improving RCU:
crypto/threads_pthread.candcrypto/threads_win.ccarry the bulk of the implementation; cross-validate any change with the contention helpers underutil/. - Adding a per-thread state slot: register a new
OSSL_LIB_CTX_*_INDEXincrypto/context.cwith a per-thread cleanup callback.
Documentation
doc/designs/thread-api.md— design rationale for the thread API.doc/man3/CRYPTO_THREAD_run_once.pod,CRYPTO_THREAD_lock_new.pod, etc.INSTALL.md"Notes on multi-threading".
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.