Which Swift Actor Serializes CoreML Operations in FluidVoice?

FluidVoice uses a dedicated private Swift actor named TranscriptionExecutor to guarantee that only one Core ML transcription runs at a time, preventing simultaneous access that leads to race conditions and memory corruption.

FluidVoice, an open-source transcription app, relies on strict concurrency control when interacting with Core ML models. The TranscriptionExecutor actor in Sources/Fluid/Services/ASRService.swift serves as the gatekeeper for all transcription operations, ensuring thread-safe execution across the application.

How TranscriptionExecutor Serializes CoreML Work

The TranscriptionExecutor actor lives inside ASRService.swift (lines 13-30) and provides a run(_:) method that queues async transcription tasks. This design ensures that each Core ML operation awaits the previous task before starting the next, eliminating race conditions when multiple providers attempt to invoke Core ML simultaneously.

Key Responsibilities

  • Queueing: The actor stores the last submitted Task in a property named lastTask. Each new Task first awaits the previous one's result (_ = await previous?.result), creating a serial execution chain.
  • Cancellation: The cancelAndAwaitPending() method cancels any in-flight currentOperationTask and then awaits the final task in the chain, ensuring clean shutdowns.
  • Isolation: As a Swift actor, the compiler guarantees that mutable state (lastTask, currentOperationTask) is accessed serially, even from concurrent callers.

Implementation Details

In Sources/Fluid/Services/ASRService.swift, the actor definition enforces exclusive access to the underlying Core ML model:

// Conceptual implementation based on lines 13-30
actor TranscriptionExecutor {
    private var lastTask: Task<Void, Never>?
    private var currentOperationTask: Task<Void, Error>?
    
    func run<T>(_ operation: @escaping () async throws -> T) async throws -> T {
        // Queues work sequentially
        let newTask = Task { [weak self] in
            _ = await self?.lastTask?.result
            return try await operation()
        }
        lastTask = newTask
        return try await newTask.value
    }
    
    func cancelAndAwaitPending() async {
        // Cancels and awaits cleanup
        currentOperationTask?.cancel()
        _ = await lastTask?.result
    }
}

Real-World Usage in FluidVoice

All transcription calls in ASRService route through this actor. Here are the practical patterns used throughout the codebase.

Basic Transcription Pattern

When transcribing the final audio buffer, the service invokes the actor at lines 68-78:

// Inside ASRService.stop()
let finalResult: ASRTranscriptionResult = try await transcriptionExecutor.run { [provider] in
    try await provider.transcribeFinal(pcm)   // Core ML work happens here
}

Dictionary Training Operations

The same actor serializes dictionary-training buffer transcription, ensuring that training data processing never collides with real-time transcription:

let result: ASRTranscriptionResult = try await transcriptionExecutor.run {
    try await provider.transcribeFinal(pcmSamples)
}

Cancellation Handling

When a user aborts a recording, the service calls:

await transcriptionExecutor.cancelAndAwaitPending()

This immediately cancels any pending Core ML work and waits for the actor to reach a clean state before proceeding.

Why Actor Isolation Prevents CoreML Crashes

Core ML models are not inherently thread-safe for concurrent inference. When multiple providers—such as ExternalCoreMLTranscriptionProvider or ParakeetRealtimeProvider—attempt to run transcription simultaneously without coordination, the resulting memory corruption can crash the application.

The TranscriptionExecutor actor solves this by:

  1. Enforcing Serial Access: Swift's actor model guarantees that only one task executes within the actor's isolated context at any moment.
  2. Preventing Resource Contention: By awaiting the previous task before starting a new one, the actor ensures the Core ML model is never accessed concurrently.
  3. Safe Cancellation: The cancelAndAwaitPending() method provides a race-free way to abort long-running transcription without leaving the model in an undefined state.

Summary

  • FluidVoice uses the TranscriptionExecutor actor to serialize all Core ML transcription operations.
  • Located in Sources/Fluid/Services/ASRService.swift (lines 13-30), this actor guarantees that only one transcription runs at a time.
  • The run(_:) method queues tasks sequentially, while cancelAndAwaitPending() handles cancellation safely.
  • This pattern prevents race conditions and memory corruption when multiple providers like ExternalCoreMLTranscriptionProvider or ParakeetRealtimeProvider access Core ML models.

Frequently Asked Questions

What is the name of the Swift actor that serializes CoreML operations in FluidVoice?

The actor is named TranscriptionExecutor. It is defined as a private actor within ASRService.swift and ensures that only one Core ML transcription operation executes at a time by serializing access through its run(_:) method.

Where is the TranscriptionExecutor implemented in the FluidVoice codebase?

The TranscriptionExecutor actor is implemented in Sources/Fluid/Services/ASRService.swift at lines 13-30. All transcription calls throughout the application route through this actor, including final buffer transcription (lines 68-78) and dictionary training operations.

How does TranscriptionExecutor prevent concurrent CoreML access?

The actor prevents concurrent access by storing the last submitted task in a lastTask property and requiring each new task to await the previous one's completion before starting. Because TranscriptionExecutor is a Swift actor, the compiler enforces that its mutable state (lastTask, currentOperationTask) can only be accessed serially, even when called from concurrent contexts.

Can pending transcription operations be cancelled in FluidVoice?

Yes. The TranscriptionExecutor provides a cancelAndAwaitPending() method that cancels any in-flight currentOperationTask and awaits the final task in the chain. This allows the application to safely abort transcription when a user stops recording without risking memory corruption or leaving the Core ML model in an inconsistent state.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →