Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Thread Safety

Prism provides the following thread safety guarantees:

  • The functions prism_init and prism_shutdown are thread-safe. Multiple threads MAY call prism_init concurrently; each call returns an independent context. However, prism_shutdown MUST only be called once per context, and the context MUST NOT be used from any thread after prism_shutdown has been called on that context. If one thread calls prism_shutdown while 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, for prism_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_initialize on 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, and prism_registry_get consist 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_acquire and prism_registry_acquire_best consist 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 on prism_registry_acquire or prism_registry_acquire_best being 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 PrismBackend instance from multiple threads concurrently without external synchronization. This restriction applies even to logically independent operations; for example, calling prism_backend_get_rate from one thread while another thread calls prism_backend_set_volume on 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, and prism_registry_acquire_best functions 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_memory MAY 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_acquire or prism_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 PrismRegistryBuilder is 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_retain and prism_registry_release are thread-safe. Note that the final prism_registry_release (or the final prism_shutdown on a context bound to the registry, whichever comes last) finalizes the registry on the calling thread; any userdata_free functions 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 userdata when no create function is supplied) MUST be synchronized by the implementation.
  • The functions prism_availability_poll_pause and prism_availability_poll_resume are 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 through PrismConfig are 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 call prism_shutdown on the owning context. The single-threaded backend constraint continues to apply to any backend instance the callback creates or acquires. prism_availability_auto_power_supported is thread-safe and MAY be called at any time.
  • The logging functions prism_set_log_handler, prism_set_log_level, prism_log, and prism_log_flush are thread-safe and MAY be called from any thread concurrently, including before prism_init and after prism_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_shutdown is 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.