Open-Source Wikis

/

OpenSSL

/

Subsystems

/

Threading

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 for no-threads builds), crypto/threads_lib.c, crypto/threads_common.c.
  • Public façade: include/openssl/crypto.h.in declares CRYPTO_THREAD_* and CRYPTO_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:

  1. C11 <stdatomic.h> if available.
  2. __atomic_* GCC/Clang builtins.
  3. Interlocked* on Windows.
  4. 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 SSL object — that is the application's responsibility.

Entry points for modification

  • Adding a new platform: add a crypto/threads_<plat>.c and wire it up in the build via the Configurations/ matrix.
  • Improving RCU: crypto/threads_pthread.c and crypto/threads_win.c carry the bulk of the implementation; cross-validate any change with the contention helpers under util/.
  • Adding a per-thread state slot: register a new OSSL_LIB_CTX_*_INDEX in crypto/context.c with 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.

Threading – OpenSSL wiki | Factory