diff --git a/Source/Engine/Content/Content.cpp b/Source/Engine/Content/Content.cpp index 66e18a231..4a9ea500b 100644 --- a/Source/Engine/Content/Content.cpp +++ b/Source/Engine/Content/Content.cpp @@ -30,6 +30,7 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Scripting/ManagedCLR/MClass.h" +#include "Engine/Scripting/Internal/InternalCalls.h" #include "Engine/Scripting/Scripting.h" #if USE_EDITOR #include "Editor/Editor.h" @@ -346,17 +347,21 @@ int32 LoadingThread::Run() ContentLoadTask* task; ThisLoadThread = this; + MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr; while (Platform::AtomicRead(&_exitFlag) == 0) { if (LoadTasks.try_dequeue(task)) { Run(task); + MONO_THREAD_INFO_GET(monoThreadInfo); } else { + MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo); LoadTasksMutex.Lock(); LoadTasksSignal.Wait(LoadTasksMutex); LoadTasksMutex.Unlock(); + MONO_EXIT_GC_SAFE_WITH_INFO; } } diff --git a/Source/Engine/Scripting/Internal/InternalCalls.h b/Source/Engine/Scripting/Internal/InternalCalls.h index b72993234..81d86ea34 100644 --- a/Source/Engine/Scripting/Internal/InternalCalls.h +++ b/Source/Engine/Scripting/Internal/InternalCalls.h @@ -9,7 +9,7 @@ #if defined(__clang__) // Helper utility to override vtable entry with automatic restore -// See BindingsGenerator.Cpp.cs that generates virtuall method wrappers for scripting to properly call overriden base method +// See BindingsGenerator.Cpp.cs that generates virtual method wrappers for scripting to properly call overriden base method struct FLAXENGINE_API VTableFunctionInjector { void** VTableAddr; @@ -100,3 +100,49 @@ T& InternalGetReference(T* obj) DebugLog::ThrowNullReference(); return *obj; } + +#ifdef USE_MONO_AOT_COOP + +// Cooperative Suspend - where threads suspend themselves when the runtime requests it. +// https://www.mono-project.com/docs/advanced/runtime/docs/coop-suspend/ +typedef struct _MonoStackData { + void* stackpointer; + const char* function_name; +} MonoStackData; +#if BUILD_DEBUG +#define MONO_STACKDATA(x) MonoStackData x = { &x, __func__ } +#else +#define MONO_STACKDATA(x) MonoStackData x = { &x, NULL } +#endif +#define MONO_THREAD_INFO_TYPE struct MonoThreadInfo +DLLIMPORT extern "C" MONO_THREAD_INFO_TYPE* mono_thread_info_attach(void); +DLLIMPORT extern "C" void* mono_threads_enter_gc_safe_region_with_info(MONO_THREAD_INFO_TYPE* info, MonoStackData* stackdata); +DLLIMPORT extern "C" void mono_threads_exit_gc_safe_region_internal(void* cookie, MonoStackData* stackdata); +#ifndef _MONO_UTILS_FORWARD_ +typedef struct _MonoDomain MonoDomain; +DLLIMPORT extern "C" MonoDomain* mono_domain_get(void); +#endif +#define MONO_ENTER_GC_SAFE \ + do { \ + MONO_STACKDATA(__gc_safe_dummy); \ + void* __gc_safe_cookie = mono_threads_enter_gc_safe_region_internal(&__gc_safe_dummy) +#define MONO_EXIT_GC_SAFE \ + mono_threads_exit_gc_safe_region_internal(__gc_safe_cookie, &__gc_safe_dummy); \ + } while (0) +#define MONO_ENTER_GC_SAFE_WITH_INFO(info) \ + do { \ + MONO_STACKDATA(__gc_safe_dummy); \ + void* __gc_safe_cookie = mono_threads_enter_gc_safe_region_with_info((info), &__gc_safe_dummy) +#define MONO_EXIT_GC_SAFE_WITH_INFO MONO_EXIT_GC_SAFE +#define MONO_THREAD_INFO_GET(info) if (!info && mono_domain_get()) info = mono_thread_info_attach() + +#else + +#define MONO_ENTER_GC_SAFE +#define MONO_EXIT_GC_SAFE +#define MONO_ENTER_GC_SAFE_WITH_INFO(info) +#define MONO_EXIT_GC_SAFE_WITH_INFO +#define MONO_THREAD_INFO_GET(info) +#define mono_thread_info_attach() nullptr + +#endif diff --git a/Source/Engine/Scripting/Runtime/DotNet.cpp b/Source/Engine/Scripting/Runtime/DotNet.cpp index 2e98094b2..ed662801e 100644 --- a/Source/Engine/Scripting/Runtime/DotNet.cpp +++ b/Source/Engine/Scripting/Runtime/DotNet.cpp @@ -2147,7 +2147,13 @@ bool InitHostfxr() #endif // Adjust GC threads suspending mode to not block attached native threads (eg. Job System) + // https://www.mono-project.com/docs/advanced/runtime/docs/coop-suspend/ +#if USE_MONO_AOT_COOP + Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("coop")); + Platform::SetEnvironmentVariable(TEXT("MONO_SLEEP_ABORT_LIMIT"), TEXT("5000")); // in ms +#else Platform::SetEnvironmentVariable(TEXT("MONO_THREADS_SUSPEND"), TEXT("preemptive")); +#endif #if defined(USE_MONO_AOT_MODE) // Enable AOT mode (per-platform) diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index e90a2e847..692a088b7 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -15,6 +15,7 @@ #include "Engine/Profiler/ProfilerMemory.h" #if USE_CSHARP #include "Engine/Scripting/ManagedCLR/MCore.h" +#include "Engine/Scripting/Internal/InternalCalls.h" #endif #define JOB_SYSTEM_ENABLED 1 @@ -184,6 +185,7 @@ int32 JobSystemThread::Run() JobData data; Function job; bool attachCSharpThread = true; + MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr; while (Platform::AtomicRead(&ExitFlag) == 0) { // Try to get a job @@ -205,6 +207,7 @@ int32 JobSystemThread::Run() { MCore::Thread::Attach(); attachCSharpThread = false; + monoThreadInfo = mono_thread_info_attach(); } #endif @@ -244,9 +247,11 @@ int32 JobSystemThread::Run() else { // Wait for signal + MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo); JobsMutex.Lock(); JobsSignal.Wait(JobsMutex); JobsMutex.Unlock(); + MONO_EXIT_GC_SAFE_WITH_INFO; } } return 0; diff --git a/Source/Engine/Threading/ThreadPool.cpp b/Source/Engine/Threading/ThreadPool.cpp index 4943469a5..d7410d0fc 100644 --- a/Source/Engine/Threading/ThreadPool.cpp +++ b/Source/Engine/Threading/ThreadPool.cpp @@ -15,6 +15,7 @@ #include "Engine/Platform/CPUInfo.h" #include "Engine/Platform/Thread.h" #include "Engine/Profiler/ProfilerMemory.h" +#include "Engine/Scripting/Internal/InternalCalls.h" FLAXENGINE_API bool IsInMainThread() { @@ -117,6 +118,7 @@ int32 ThreadPool::ThreadProc() Platform::SetThreadAffinityMask(THREAD_POOL_AFFINITY_MASK((int32)index)); #endif ThreadPoolTask* task; + MONO_THREAD_INFO_TYPE* monoThreadInfo = nullptr; // Work until end while (Platform::AtomicRead(&ThreadPoolImpl::ExitFlag) == 0) @@ -125,12 +127,15 @@ int32 ThreadPool::ThreadProc() if (ThreadPoolImpl::Jobs.try_dequeue(task)) { task->Execute(); + MONO_THREAD_INFO_GET(monoThreadInfo); } else { + MONO_ENTER_GC_SAFE_WITH_INFO(monoThreadInfo); ThreadPoolImpl::JobsMutex.Lock(); ThreadPoolImpl::JobsSignal.Wait(ThreadPoolImpl::JobsMutex); ThreadPoolImpl::JobsMutex.Unlock(); + MONO_EXIT_GC_SAFE_WITH_INFO; } }