66#include " cinderx/Jit/codegen/code_section.h"
77#include " cinderx/Jit/config.h"
88#include " cinderx/Jit/jit_rt.h"
9+ #include " cinderx/module_state.h"
910
1011#ifdef WIN32
1112#include < Windows.h>
2223#endif
2324
2425#include < cstring>
26+ #include < new>
2527
2628namespace cinderx ::jit {
2729
@@ -211,6 +213,8 @@ ICodeAllocator* CodeAllocator::make() {
211213}
212214
213215AllocateResult CodeAllocator::addCode (asmjit::CodeHolder* code) {
216+ std::lock_guard lock{runtime_mutex_};
217+
214218 void * addr = nullptr ;
215219 asmjit::Error error = runtime_.add (&addr, code);
216220
@@ -222,6 +226,8 @@ AllocateResult CodeAllocator::addCode(asmjit::CodeHolder* code) {
222226}
223227
224228asmjit::Error CodeAllocator::releaseCode (void * code) {
229+ std::lock_guard lock{runtime_mutex_};
230+
225231 // Find the size of the allocated region.
226232 asmjit::JitAllocator* inner = runtime_.allocator ();
227233 asmjit::JitAllocator::Span span;
@@ -247,9 +253,12 @@ asmjit::Error CodeAllocator::releaseCode(void* code) {
247253}
248254
249255bool CodeAllocator::contains (const void * ptr) const {
256+ // query() is internally thread-safe, but it takes asmjit's own lock, which
257+ // can't be recovered in a forked child. Going through runtime_mutex_ keeps
258+ // that lock free whenever a fork can happen.
259+ std::lock_guard lock{runtime_mutex_};
260+
250261 asmjit::JitAllocator::Span unused;
251- // asmjit docs don't say that query() is thread-safe, but peeking at the
252- // implementation shows that it is.
253262 return runtime_.allocator ()->query (unused, const_cast <void *>(ptr)) ==
254263 asmjit::kErrorOk ;
255264}
@@ -262,6 +271,21 @@ const asmjit::Environment& CodeAllocator::asmJitEnvironment() const {
262271 return runtime_.environment ();
263272}
264273
274+ void CodeAllocator::atForkPrepare () {
275+ runtime_mutex_.lock ();
276+ }
277+
278+ void CodeAllocator::atForkParent () {
279+ runtime_mutex_.unlock ();
280+ }
281+
282+ void CodeAllocator::atForkChild () {
283+ // Reuse the storage to get a fresh, unlocked mutex. The inherited one is
284+ // still locked by atForkPrepare() and destroying a locked mutex is
285+ // undefined, so its lifetime is ended without running its destructor.
286+ new (&runtime_mutex_) std::mutex{};
287+ }
288+
265289CodeAllocatorCinder::~CodeAllocatorCinder () {
266290 for (std::span<uint8_t > alloc : allocations_) {
267291#ifndef WIN32
@@ -484,4 +508,50 @@ bool CodeAllocatorCinder::contains(const void* ptr) const {
484508 return false ;
485509}
486510
511+ void CodeAllocatorCinder::atForkPrepare () {
512+ CodeAllocator::atForkPrepare ();
513+ allocator_mutex_.lock ();
514+ }
515+
516+ void CodeAllocatorCinder::atForkParent () {
517+ allocator_mutex_.unlock ();
518+ CodeAllocator::atForkParent ();
519+ }
520+
521+ void CodeAllocatorCinder::atForkChild () {
522+ new (&allocator_mutex_) std::mutex{};
523+ CodeAllocator::atForkChild ();
524+ }
525+
526+ void codeAllocatorAtForkPrepare () {
527+ ModuleState* state = cinderx::getModuleState ();
528+ if (state != nullptr && state->code_allocator != nullptr ) {
529+ state->code_allocator ->atForkPrepare ();
530+ }
531+ #if defined(__linux__)
532+ // Innermost: ensureSpace() reaches this while holding allocator_mutex_.
533+ cinder_jit_region_mutex_.lock ();
534+ #endif
535+ }
536+
537+ void codeAllocatorAtForkParent () {
538+ #if defined(__linux__)
539+ cinder_jit_region_mutex_.unlock ();
540+ #endif
541+ ModuleState* state = cinderx::getModuleState ();
542+ if (state != nullptr && state->code_allocator != nullptr ) {
543+ state->code_allocator ->atForkParent ();
544+ }
545+ }
546+
547+ void codeAllocatorAtForkChild () {
548+ #if defined(__linux__)
549+ new (&cinder_jit_region_mutex_) std::mutex{};
550+ #endif
551+ ModuleState* state = cinderx::getModuleState ();
552+ if (state != nullptr && state->code_allocator != nullptr ) {
553+ state->code_allocator ->atForkChild ();
554+ }
555+ }
556+
487557} // namespace cinderx::jit
0 commit comments