# Thread Safety Guarantees in FluidAudio Managers: Swift Actor Implementation Explained

> Discover FluidAudio manager thread safety guarantees. Swift actors eliminate data races by serializing access to mutable state. Learn more about this compiler-enforced protection.

- Repository: [Fluid Inference/fluidaudio](https://github.com/fluidinference/fluidaudio)
- Tags: internals
- Published: 2026-03-02

---

**FluidAudio managers use Swift actors to provide compiler-enforced thread safety, automatically serializing access to mutable state and eliminating data races without requiring manual locks or queues.**

The fluidinference/fluidaudio repository implements all public manager types—including `AsrManager`, `VadManager`, `StreamingAsrManager`, `DiarizerManager`, and `PocketTtsManager`—as Swift actors. This design delivers strong thread safety guarantees for concurrent audio processing, ensuring that mutable internal state remains consistent even when accessed from multiple concurrent tasks.

## Core Thread Safety Mechanisms

### Actor-Based State Isolation

Every public manager in FluidAudio is declared as a Swift `actor`, which isolates mutable stored properties to a private serial executor. In [`Sources/FluidAudio/VAD/VadManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/VAD/VadManager.swift), the declaration `public actor VadManager` at lines 14-15 establishes this isolation boundary. This guarantees that all mutations of the manager's internal state happen sequentially, preventing data races by design.

The serial execution model ensures that every `async` method on the manager runs one-at-a-time, even when invoked from many concurrent tasks. Callers do not need to manage dispatch queues or locks manually; the actor automatically thread-hops to its private executor when receiving method calls.

### Sendable Conformance Verification

FluidAudio constrains all public API types—including model structs like `AsrModels`, `DiarizerModels`, and error enums—to conform to `Sendable`. The test suite explicitly validates this conformance in [`Tests/FluidAudioTests/SendableTests.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Tests/FluidAudioTests/SendableTests.swift) at lines 7-26, ensuring that manager instances can be safely passed across concurrency domains without introducing shared mutable state.

This combination of actor isolation and Sendable conformance allows FluidAudio managers to participate in structured concurrency patterns while maintaining compile-time safety guarantees.

## Practical Implementation Patterns

### Concurrent ASR and VAD Processing

Because managers are actors, you can safely invoke them from concurrent tasks without synchronization primitives. The following pattern demonstrates safe concurrent usage of `AsrManager` and `VadManager`:

```swift
import FluidAudio

let asr = AsrManager()
let vad = VadManager()
let audioURL = Bundle.main.url(forResource: "sample", withExtension: "wav")!

Task {
    // Runs serially on AsrManager's private executor
    let models = try await AsrModels.loadFromBundle()
    try await asr.initialize(models: models)
    let results = try await asr.transcribe(audioURL: audioURL)
    print("ASR:", results.text)
}

Task {
    // Runs serially on VadManager's private executor
    let vadResults = try await vad.process(audioURL)
    print("VAD chunks:", vadResults.count)
}

```

In [`Sources/FluidAudio/ASR/AsrManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/ASR/AsrManager.swift), the manager confines mutable state—such as `asrModels` and decoder states—to its serial executor. Similarly, `VadManager` processes audio chunks sequentially despite concurrent task invocation.

### Streaming ASR with Multiple Sessions

For real-time applications, `StreamingAsrManager` and `StreamingAsrSession` are also declared as actors. In [`Sources/FluidAudio/ASR/Streaming/StreamingAsrManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/ASR/Streaming/StreamingAsrManager.swift) (lines 7-8) and [`StreamingAsrSession.swift`](https://github.com/fluidinference/fluidaudio/blob/main/StreamingAsrSession.swift) (lines 6-7), the actor pattern ensures that each streaming session maintains consistent internal state even when processing interleaved audio chunks from concurrent sources:

```swift
let streaming = StreamingAsrManager()

Task {
    let session = await streaming.createSession()
    for chunk in audioChunks {
        try await session.process(chunk)
    }
    let transcript = await session.finish()
    print(transcript)
}

```

Each session operates as an independent actor, providing isolation between concurrent streams while the manager itself serializes high-level operations.

### Safely Handling Non-Thread-Safe Utilities

While managers provide strong safety guarantees, certain low-level utilities remain non-thread-safe by design. Classes like `NeMoMelSpectrogram` and `WhisperMelSpectrogram` contain cache-free buffers that are documented as not thread-safe in [`Sources/FluidAudio/ASR/Streaming/NeMoMelSpectrogram.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/ASR/Streaming/NeMoMelSpectrogram.swift) at lines 19-22.

To use these utilities safely, create fresh instances per task rather than sharing them across concurrency domains:

```swift
Task {
    // Create a new instance for each concurrent operation
    let mel = NeMoMelSpectrogram()
    let features = try mel.compute(inputBuffer)
    
    // Pass features to an actor for processing
    let result = try await asr.transcribe(features: features)
    print(result.text)
}

```

This pattern isolates mutable buffer state within a single task, then transfers immutable `Sendable` data (the features) to the actor-based manager for inference.

## Summary

- **FluidAudio managers are Swift actors**: Declarations like `public actor VadManager` in [`Sources/FluidAudio/VAD/VadManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/VAD/VadManager.swift) enforce automatic serialization of mutable state.
- **Compiler-enforced safety**: The `Sendable` conformance verified in [`Tests/FluidAudioTests/SendableTests.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Tests/FluidAudioTests/SendableTests.swift) ensures safe cross-domain passing of managers and their associated types.
- **Zero manual synchronization required**: Actor isolation eliminates the need for locks, queues, or atomic operations when calling manager methods from concurrent tasks.
- **Explicit non-thread-safe boundaries**: Utilities such as `NeMoMelSpectrogram` are documented as requiring per-task instantiation, with all other manager APIs providing full thread safety.

## Frequently Asked Questions

### Are FluidAudio managers thread-safe by default?

Yes. All public manager types—including `AsrManager`, `VadManager`, and `StreamingAsrManager`—are implemented as Swift actors. According to the source code in [`Sources/FluidAudio/VAD/VadManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/VAD/VadManager.swift) and related files, this actor declaration guarantees that mutable internal state is accessed serially, preventing data races without requiring manual synchronization from callers.

### Can I call a FluidAudio manager from multiple concurrent tasks simultaneously?

Yes. Because the managers are actors, they automatically serialize incoming method calls. Even when you invoke `asr.transcribe()` or `vad.process()` from multiple concurrent `Task` blocks, the actor's private executor ensures these calls execute one-at-a-time, maintaining state consistency while allowing natural concurrency patterns in your calling code.

### What parts of FluidAudio are not thread-safe?

Only specific low-level utilities such as `NeMoMelSpectrogram` and `WhisperMelSpectrogram` are documented as non-thread-safe, as noted in [`Sources/FluidAudio/ASR/Streaming/NeMoMelSpectrogram.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/ASR/Streaming/NeMoMelSpectrogram.swift). These classes use cache-free buffers that must not be shared across threads. The solution is to instantiate these objects fresh for each concurrent task, then pass their output to the thread-safe actor-based managers for further processing.

### Do I need to use @MainActor or DispatchQueue with FluidAudio managers?

No. FluidAudio managers manage their own executors automatically. Since they are declared as actors (e.g., `public actor StreamingAsrManager` in [`Sources/FluidAudio/ASR/Streaming/StreamingAsrManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/ASR/Streaming/StreamingAsrManager.swift)), method calls automatically dispatch to the actor's private serial executor. You can invoke these managers from any concurrency context—main thread, background tasks, or structured concurrency groups—without manually specifying dispatch queues or `@MainActor` annotations.