C++ modernisation followups: FFI thread safety, RAII, jthread migration#420
Merged
C++ modernisation followups: FFI thread safety, RAII, jthread migration#420
Conversation
Collaborator
Author
TSan run — 2026-04-19Built with Four unique races were reported, all pre-existing and untouched by this PR:
These fall under existing backlog items: the Nothing reported on code this PR changed — the atomic |
Both tables were carrying "not thread safe" comments and could race when scheme instances registered foreign functions concurrently or when a reader on one thread observed a table element before the writer's store was visible. - s_ffiTable / s_ffiCount (SchemeS7.cpp): s_ffiCount becomes std::atomic<int> with fetch_add so slot assignment is unique; each table entry becomes std::atomic<foreign_func>, paired release/acquire so the trampoline's load sees a fully-constructed entry without needing a mutex on the hot path. - LLVM_SCHEME_FF_MAP (EXTLLVM.cpp): guard with std::shared_mutex. The getter now uses find() instead of operator[], fixing a latent bug that would insert an empty string for unknown ff pointers, and returns "" when the key is absent. std::map keeps stored strings stable across inserts, so the c_str() a reader copies out under its shared lock stays valid while the caller uses it (we never erase entries).
Two small ownership fixes from the #419 follow-up backlog: - AudioDevice::m_threads[MAX_RT_AUDIO_THREADS] was a C array of raw owning EXTThread* that were `new`ed in init and never `delete`d. Now a std::array<std::unique_ptr<EXTThread>, N>. The dtor detaches each running thread so unique_ptr destruction doesn't call std::terminate on the underlying std::thread (the RT threads intentionally run `while(true)` and are cleaned up at process exit). The unused getMTThreads() accessor is removed; no callers existed. - The imp_randd PRNG was a heap-allocated thread_local pointer that leaked on thread exit and required a null check per call. Now a value-typed thread_local seeded with time(nullptr) on first access per thread, keeping the existing per-thread seeding semantics.
The primary delayed-connect thread and the linenoise REPL thread were spawned via `new extemp::EXTThread(...)` + start(), leaking the EXTThread object (never joined or detached, destructor never runs). Both are fire-and-forget — they run until the process exits and need none of EXTThread's RT-scheduling or subsume-current-thread features. Replace with std::thread constructed in place and detached, so the std::thread object's destructor doesn't trip std::terminate and the lambda captures make the arg wiring explicit.
- TASK-040 (FFI thread safety): mark Done; moved to completed/. - TASK-039: tick off m_threads and PRNG; leave singletons and SchemeTask::m_ptr as remaining scope. - TASK-044: tick off the two REPL threads in Extempore.cpp; list the call sites that must stay on EXTThread (RT priority / setSubsume / xtlang ABI) and the deferred candidates (TaskScheduler, OSC).
867f11e to
0c3a4c0
Compare
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #419 covering three backlog items: TASK-040, partial TASK-039 and partial TASK-044. Based on
cpp-modernisationso it only shows the follow-up work.TASK-040 — FFI thread safety (done)
s_ffiCountis nowstd::atomic<int>withfetch_addfor slot assignment.s_ffiTableentries arestd::atomic<foreign_func>with release/acquire pairing betweenmk_foreign_funcandffi_trampoline<N>— no mutex on the hot path.LLVM_SCHEME_FF_MAPis guarded withstd::shared_mutex(shared on get, unique on set). The getter now usesfind()rather thanoperator[], which also fixes a latent bug of inserting empty strings for unknown keys.TASK-039 — RAII (partial)
AudioDevice::m_threads[MAX_RT_AUDIO_THREADS]rawEXTThread*array →std::array<std::unique_ptr<EXTThread>, N>. Destructor detaches each running thread sounique_ptrdestruction doesn't tripstd::terminate(these threads intentionally runwhile(true)for the life of the process). The unusedgetMTThreads()accessor is removed.thread_local std::minstd_rand*→ value-typedthread_local std::minstd_rand, seeded lazily withtime(nullptr)on first access. No more heap allocation, no more per-call null check.Remaining (kept on the backlog): SchemeProcess/REPL registries → Meyers singletons, and
SchemeTask::m_ptr→std::variant.TASK-044 — EXTThread → std::thread/jthread (partial)
Extempore.cppwere leakednew EXTThread(...). Nowstd::thread(...).detach(), matching the fire-and-forget intent.Kept on EXTThread for documented reasons (RT priority,
setSubsume, or xtlang FFI ABI): AudioDevice m_threads, SchemeProcess::m_threadTask/m_threadServer, and the xtlangthread_forkAPI.Test plan
cmake --build build --target extempore— cleanctest --label-regex \"libs-core|cpp-unit|compiler-unit\"— 19/19 passctest --label-regex \"libs-external\"— 1/1 passcmake --build build --target aot_external_audio— builds./extempore --batch \"(begin (println 'hello) (quit 0))\"— runs