Skip to content

Conversation

@lenzo-ka
Copy link
Contributor

@lenzo-ka lenzo-ka commented Jul 23, 2025

Make random number generator thread-safe using thread-local storage

Summary

This PR makes the Mersenne Twister random number generator (RNG) in PocketSphinx thread-safe by using thread-local storage (TLS) for its state variables. This eliminates race conditions and data corruption when multiple threads use the RNG concurrently.

Problem

The RNG implementation used global static variables (mt[] array and mti index) that were shared across all threads, causing:

  • Race conditions in multi-threaded applications
  • Non-deterministic behavior
  • Potential data corruption
  • Thread safety issues in speech recognition pipelines

Solution

  • Add platform-agnostic thread-local storage abstraction (PS_THREAD_LOCAL macro)
  • Make RNG state variables thread-local, giving each thread its own independent RNG state
  • Maintain complete backward compatibility - no API changes required

Key Features

Fully backward compatible - No code changes required for users
Thread-safe by default - Each thread gets independent RNG state
Cross-platform support - Works with C11, C++11, GCC, Clang, MSVC
Opt-out available - Can be disabled with -DPS_THREAD_LOCAL_RNG=OFF
Comprehensive testing - Includes thread safety verification tests

Implementation Details

The implementation adds PS_THREAD_LOCAL before the static RNG state:

PS_THREAD_LOCAL static unsigned long mt[N];  /* the array for the state vector */
PS_THREAD_LOCAL static int mti = N + 1;      /* mti==N+1 means mt[N] is not initialized */

When PS_USE_THREAD_LOCAL_RNG is defined (default), the macro expands to the appropriate thread-local keyword for the compiler. When disabled, it expands to nothing, reverting to the original behavior.

Testing

New tests verify:

  • Thread-local storage compilation and functionality
  • RNG thread safety (no collisions between threads)
  • Preservation of deterministic behavior within each thread
  • Cross-platform thread utilities work correctly

Run thread safety tests:

./build/test_genrand_thread_tls

Build Configuration

The feature is enabled by default but can be disabled:

cmake -DPS_THREAD_LOCAL_RNG=OFF ..

Commits

  1. feat: Add thread-local storage abstraction header - Platform-agnostic TLS support
  2. feat: Make random number generator thread-safe using TLS - Core RNG changes
  3. test: Add cross-platform thread utilities for testing - Testing infrastructure
  4. test: Add comprehensive tests for thread-local RNG - Thread safety verification
  5. build: Add CMake support for thread-local RNG feature - Build system integration
  6. docs: Add thread safety documentation - User documentation

Impact

  • Single-threaded applications: No change in behavior
  • Multi-threaded applications: Now thread-safe (fixes existing race conditions)
  • Performance: Minimal overhead from TLS access
  • Memory: Each thread allocates ~2.5KB for RNG state (624 * 4 bytes)

This is a bug fix that makes the code thread-safe without breaking any properly-functioning existing code.

Files Changed

  • src/util/thread_local.h - New TLS abstraction header
  • src/util/genrand.c - Added TLS to RNG state variables
  • src/util/genrand.h - Added thread safety documentation
  • CMakeLists.txt - Added PS_THREAD_LOCAL_RNG option
  • config.h.in - Added PS_USE_THREAD_LOCAL_RNG configuration
  • test/unit/ - Added comprehensive thread safety tests
  • docs/thread_safety.md - New documentation for thread safety

lenzo-ka and others added 7 commits July 23, 2025 08:48
Introduces PS_THREAD_LOCAL macro that provides platform-agnostic
thread-local storage support using compiler-specific keywords:
- _Thread_local for C11
- thread_local for C++11
- __thread for GCC/Clang
- __declspec(thread) for MSVC

This abstraction allows for consistent thread-local storage usage
across different compilers and platforms.
Updates the Mersenne Twister RNG implementation to use thread-local
storage for its state variables (mt[] array and mti index). This
eliminates race conditions when multiple threads use the RNG
concurrently.

Changes:
- Make mt[N] array thread-local using PS_THREAD_LOCAL
- Make mti index thread-local using PS_THREAD_LOCAL
- Add thread safety documentation to header
- Maintain backward compatibility - API unchanged

When PS_USE_THREAD_LOCAL_RNG is defined (set via CMake), each thread
gets its own RNG state, preventing data races and ensuring
deterministic behavior in multi-threaded applications.
Introduces portable thread utilities to enable multi-threaded testing
across different platforms:

- Thread creation and joining
- Mutexes for synchronization
- Barriers for thread coordination
- Platform detection for Windows (threads.h) vs POSIX (pthread)
- macOS-specific pthread barrier emulation

These utilities enable comprehensive testing of thread safety in
the RNG implementation and other concurrent code paths.
Adds test suite to verify thread safety of the RNG implementation:

Thread-local storage tests:
- test_thread_local_compile.c: Verifies TLS compilation support
- test_thread_local_basic.c: Tests thread isolation of TLS variables

RNG thread safety tests:
- test_genrand_baseline.c: Baseline single-threaded RNG behavior
- test_genrand_thread.c: Demonstrates race conditions without TLS
- test_genrand_thread_tls.c: Verifies thread safety with TLS enabled

The tests use collision detection to identify when threads share
state inappropriately, and verify that the TLS implementation
eliminates these race conditions.
Updates build system to support thread-local RNG compilation:

- Add PS_THREAD_LOCAL_RNG option (enabled by default)
- Detect compiler support for thread-local storage
- Configure PS_USE_THREAD_LOCAL_RNG macro when enabled
- Add thread utilities library for tests
- Register new test executables with CTest
- Link tests with thread utilities and pthread where needed

The feature can be disabled with -DPS_THREAD_LOCAL_RNG=OFF if
thread-local storage is not available or desired.
Documents the thread-local RNG implementation and provides guidance
for multi-threaded usage of PocketSphinx:

- Explains the thread safety issue with global RNG state
- Describes the thread-local storage solution
- Provides migration guide for existing code
- Lists performance considerations
- Documents the PS_THREAD_LOCAL_RNG build option
- Includes examples of thread-safe usage

This helps users understand and properly utilize the thread-safe
RNG functionality in their applications.
@lenzo-ka lenzo-ka requested a review from dhdaines July 23, 2025 13:06
Copy link
Contributor

@dhdaines dhdaines left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. Of course, Arthur's comment implies that we are already 11 years overdue for replacing the RNG with another algorithm!

Do you know if this works on musl libc based systems? I think we can use docker to test this in the CI...

@lenzo-ka lenzo-ka merged commit 54e61dd into main Jul 29, 2025
21 checks passed
@dhdaines dhdaines added this to the 5.1.0 milestone Aug 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants