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
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
modules/c-api/src/CnaCApiAudio.cpp— cna_dynamic_sound_effect_instance_submit_buffer, BorrowDynamicInstance, GetSoundEffectInstancemodules/c-api/src/CnaCApiDetail.hpp— HandleRegistry::Get creation-thread checkmodules/c-api/include/CNA/C/audio.h— producer-thread statement in the submit_buffer Doxygendocs/c-api/AUDIO.md— streaming paragraph versus the threading sectionmodules/c-api/tests/pure_c/AudioStreamingSmoke.c— streaming test, single-threaded
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.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- C API: limits
- Internals
- C API internals: audio
- Known issues
- Bug index