Thread Safety
Prism provides the following thread safety guarantees:
- The functions
prism_initandprism_shutdownare thread-safe. Multiple threads MAY callprism_initconcurrently; each call returns an independent context. However,prism_shutdownMUST only be called once per context, and the context MUST NOT be used from any thread afterprism_shutdownhas been called on that context. If one thread callsprism_shutdownwhile another thread is using the same context, the behavior is undefined. - All
prism_registry_*functions are thread-safe when called on the same context from multiple threads. The registry maintains an internal backend cache: a partial mapping from backend identifiers to live backend instances, shared across all contexts bound to that registry. Contexts bound to different registries share no cache state, even for the same compiled-in backend. Each registry function is defined as an ordered sequence of one or more cache operations (lookup or install), zero or more unsynchronized factory invocations, and, forprism_registry_acquire_best, zero or more initialization attempts. A factory invocation, for the purposes of this section, is an invocation which creates a backend without initializing it. - Cache operations are atomic with respect to each other. Cache operations affecting the same backend identifier are totally ordered; the order of cache operations affecting distinct backend identifiers is unspecified. Factory invocations are not ordered with respect to cache operations or other factory invocations and MAY proceed concurrently with arbitrary other registry activity. Initialization of a particular cached backend object is serialized internally with
prism_backend_initializeon that same underlying object. This serialization does not make any other operation on the backend thread-safe. - The functions
prism_registry_count,prism_registry_id_at,prism_registry_id,prism_registry_name,prism_registry_priority,prism_registry_exists, andprism_registry_getconsist of cache operations only. They MAY execute concurrently with each other and with the unsynchronized portions of prism_registry_acquire and prism_registry_acquire_best. - The functions
prism_registry_acquireandprism_registry_acquire_bestconsist of a sequence of cache operations interleaved with factory and initialization work outside the cache lock. Consequently, two threads requesting the same uncached backend MAY each perform an independent construction; in such cases, the cache retains exactly one of the constructed instances, and all callers receive that instance. If a live cached instance exists but is not yet initialized, that instance is retained and initialized in a serialized manner. Discarded speculative instances are destroyed when their reference counts reach zero. Applications MUST NOT rely onprism_registry_acquireorprism_registry_acquire_bestbeing atomic in their entirety with respect to other registry operations. - Individual backend instances are NOT thread-safe. Applications MUST NOT call functions on the same
PrismBackendinstance from multiple threads concurrently without external synchronization. This restriction applies even to logically independent operations; for example, callingprism_backend_get_ratefrom one thread while another thread callsprism_backend_set_volumeon the same backend instance produces undefined behavior. - Different backend instances MAY be used from different threads concurrently without restriction. For example, if an application creates two backends using
prism_registry_create, those two backends may be used from separate threads without synchronization. - The
prism_registry_create,prism_registry_create_best,prism_registry_acquire, andprism_registry_acquire_bestfunctions are thread-safe with respect to the registry. However, the returned backend instances are subject to the single-threaded constraint described above. - Audio callbacks passed to
prism_backend_speak_to_memoryMAY be invoked from a thread other than the calling thread, depending on the backend. Callback implementations MUST be prepared for this possibility and MUST provide their own synchronization if they access shared state. The callback MUST NOT call any Prism function on the backend instance that initiated the synthesis, as this would violate the single-threaded backend constraint and may also cause deadlocks. - When using
prism_registry_acquireorprism_registry_acquire_best, multiple calls may return handles to the same underlying backend instance if a cached instance exists. In this case, all handles share the same backend state, and the single-threaded constraint applies across all handles. Applications that acquire backends from the cache and use them from multiple threads MUST synchronize access externally. - A registry’s backend set is fixed when the registry is created. No registration ever occurs on a live registry, so no registration operation is ordered against any registry operation described above.
- A
PrismRegistryBuilderis NOT thread-safe. A builder is a transient configuration object, so all operations on a given builder MUST be externally synchronized if it is somehow reachable from more than one thread. prism_registry_retainandprism_registry_releaseare thread-safe. Note that the finalprism_registry_release(or the finalprism_shutdownon a context bound to the registry, whichever comes last) finalizes the registry on the calling thread; anyuserdata_freefunctions belonging to custom backends may therefore be invoked on that thread.- Custom backend implementations are subject to the same single-instance constraint as callers: because applications MUST NOT call functions on one backend instance from multiple threads concurrently, a custom backend’s vtable functions are never invoked concurrently for the same instance. Distinct instances of the same custom backend MAY be invoked concurrently, and any state they share (for example, state reachable through
userdatawhen nocreatefunction is supplied) MUST be synchronized by the implementation. - The functions
prism_availability_poll_pauseandprism_availability_poll_resumeare thread-safe and MAY be called from any thread, including concurrently with each other and with the poll thread’s own activity. The availability callback and the baseline callback configured throughPrismConfigare invoked from Prism’s internal poll thread, not from a thread owned by the application; callback implementations MUST synchronize any shared state they access, and MUST NOT callprism_shutdownon the owning context. The single-threaded backend constraint continues to apply to any backend instance the callback creates or acquires.prism_availability_auto_power_supportedis thread-safe and MAY be called at any time. - The logging functions
prism_set_log_handler,prism_set_log_level,prism_log, andprism_log_flushare thread-safe and MAY be called from any thread concurrently, including beforeprism_initand afterprism_shutdown. A log handler is invoked only from Prism’s internal logging thread and is never invoked concurrently with itself; handler implementations MUST synchronize any shared state and MUST NOT call any logging function.prism_log_shutdownis thread-safe with respect to other logging functions, but the application MUST ensure it is not called from within a log handler.
Applications requiring concurrent speech synthesis from multiple threads SHOULD create separate backend instances per thread using prism_registry_create or prism_registry_create_best.