Thread-safety and cache-lifecycle contract

Read side by side

On this page

This document defines the supported concurrency model for Polyphase Float 0.11.0 on the POSIX-thread baseline. It distinguishes objects that may be shared for reading from mutable state that requires exclusive ownership.

Summary table

Resource Concurrent use
Distinct ppf_t destinations with distinct contexts Supported
One ppf_t used only as a read-only input Supported
One ppf_t mutated by more than one thread Not supported without external locking
Thread-default context Private to the calling thread
One explicit ppf_context_t shared by threads Not supported without external locking
Immutable divisor plan applied by several threads Supported
Global constant and transcendental caches Internally synchronized
Global cache purge during active calculations Supported; purge may block
One GMP random state shared by threads Not supported without external locking
Library unload while Polyphase threads are active Not supported

Thread-default context

ppf_default_context() returns a _Thread_local context. Its precision, rounding mode, exponent range, flags, allocator, and scratch arena are isolated from every other thread.

The first use of heap-backed thread state registers a POSIX thread-specific destructor. When registration succeeds, normal thread termination releases:

ppf_buildopt_thread_cleanup_p() reports whether the process created the thread-specific cleanup key. ppf_thread_cleanup() performs the same cleanup explicitly and may be called repeatedly. ppf_default_context_cleanup() is a compatibility alias.

Applications should call ppf_thread_cleanup() before unloading the shared library and may call it on long-lived worker threads after a large temporary workload. The process main thread should use explicit cleanup when leak-clean shutdown evidence is required, because process termination semantics are not a substitute for application-controlled resource release.

Automatic thread cleanup does not clear user-owned ppf_t objects, divisor plans, random states, or explicit contexts. Those resources remain the application's responsibility.

Explicit contexts

An explicit ppf_context_t is mutable execution state. Its flags and scratch arena can change during an operation. It therefore has one-thread-at-a-time ownership unless the application supplies external synchronization.

Separate contexts may use the same allocator callbacks concurrently only when the allocator implementation and its opaque state are themselves thread-safe.

ppf_context_clear() may be called directly on the current thread-default context. Version 0.11.0 recognizes that case and makes the next ppf_default_context() call recreate the documented defaults.

Floating-point objects

A fully initialized ppf_t may be read by several threads at the same time if no thread can mutate, clear, resize, swap, or reinitialize it. This supports shared constants and operands with thread-private outputs.

Any operation that can modify an object requires exclusive ownership of that object for the duration of the call. This includes operations where an output aliases an input. An object must not be cleared while another thread is reading it.

Divisor plans

A completed ppf_divisor_plan_t is immutable except for its atomic application counter. It may be applied concurrently by several threads when each call uses its own mutable destination and context. The plan must not be destroyed until all applications have completed.

Global caches

Constant caches and fixed-point transcendental caches retain their mutexes and initialized GMP objects for the process lifetime. A purge clears cached values and oversized storage but does not destroy synchronization objects.

The following operations are supported while other threads calculate constants or logarithms:

A purge acquires the corresponding cache mutexes and can block until an active builder releases them. A calculation that starts after a purge may rebuild the entry.

ppf_free_cache2(PPF_FREE_LOCAL_CACHE) affects only the calling thread's scratch pool and thread-local product workspaces. It does not release state owned by another live thread.

Internal OpenMP

The optional OpenMP layer is independent of the application-level ownership rules. Several application threads may invoke Polyphase operations concurrently when their destinations and contexts are disjoint and their shared inputs are read-only. The library may cap effective worker counts or select a serial path when already inside an OpenMP region.

The ThreadSanitizer validation build deliberately uses PPF_OPENMP=OFF so race reports describe the Polyphase POSIX-thread layer rather than an uninstrumented OpenMP runtime.

Unsupported patterns

The following require application-level serialization or are outside the 0.11.0 contract:

Validation

The focused concurrency suite covers:

See THREAD_SAFETY_VALIDATION_0.11.0.md for the release-specific evidence and remaining limitations.