CNA-BUG-213: audio.h and AUDIO.md call cna_dynamic_sound_effect_instance_submit_buffer safe from a producer thread, but every call off the creation thread returns CNA_RESULT_THREAD

CNA snapshot c1c316b9  ·  Known Issues › Current bugs  ·  source links pinned to c1c316b9

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

The submission routes resolve the instance through HandleRegistry::Get, which refuses any thread other than the handle's creation thread, so the documented canonical producer-thread usage fails; AUDIO.md's own threading section states the stricter rule.

Identifier
CNA-BUG-213
Category
Bug
Subsystem
C API & bindings
Status
Open
Verified against
CNA c1c316b9 (c1c316b9c7a846ce8002809c151fcd1af14942c9)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
cna_dynamic_sound_effect_instance_submit_buffer, cna_dynamic_sound_effect_instance_submit_float_buffer_ext

Expected behaviour

The Doxygen of cna_dynamic_sound_effect_instance_submit_buffer in audio.h says copying the bytes “is what makes this route safe to call from a producer thread while playback runs, which is the canonical usage”; AUDIO.md repeats it for streaming instances.

Actual behaviour at TARGET

Both routes in CnaCApiAudio.cpp call BorrowDynamicInstance → GetSoundEffectInstance → HandleRegistry::Get, and Get in CnaCApiDetail.hpp returns CNA_RESULT_THREAD whenever the calling thread is not the slot's creation thread. AUDIO.md's “Threads, hardware and shutdown” section says every audio call runs on the game creation thread, so the documents contradict each other and the code follows the stricter statement.

Since C ABI 0.39.0 (CBIND-141) a game can opt in with cna_game_set_foreign_thread_calls_ext(game, CNA_TRUE): a call another thread's handle refused is then queued for the game thread, and the producer blocks until Update/Draw or cna_game_run_foreign_thread_calls_ext serves it. The switch is off by default, so the documented producer-thread call still returns CNA_RESULT_THREAD; with it on, submission is serialized through the game thread rather than made concurrently with playback. CBIND-154 (ABI 0.43.0) lifted the creating-thread rule only for cna_sound_effect_play and cna_sound_effect_play_with_settings; the submission routes keep it.

Source locations

Evidence

Checked by reading at c1c316b9; not executed.

Focused reproduction

/* Illustrative C; not compiled. 'instance' was created on the game thread. */
static int producer(void* arg) {
    CNA_Handle instance = *(CNA_Handle*)arg;
    uint8_t pcm[4096] = {0};
    return (int)cna_dynamic_sound_effect_instance_submit_buffer(instance, pcm, sizeof pcm, 0, 4096);
}
/* Expected by audio.h: CNA_RESULT_SUCCESS. Actual by reading: CNA_RESULT_THREAD. */

Current tests

AudioStreamingSmoke.c submits from the creation thread only.

Regression test

A pure-C test that submits from a second thread and asserts whichever contract is chosen; then align audio.h and AUDIO.md (or relax the thread check for these two routes, if the native instance is safe for it).

Blast radius

C and binding code that follows the header and feeds a streaming instance from an audio producer thread. Single-threaded submission is unaffected.

Workaround

Marshal submissions to the game creation thread, or enable cna_game_set_foreign_thread_calls_ext and accept that each submission waits for the game thread.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

User guide
C API: limits
Known issues
Bug index