diff --git a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp index 6ca107cc0f9ae5..03af6020c03945 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalRedhawkUnix.cpp @@ -90,6 +90,16 @@ extern "C" void RaiseFailFastException(PEXCEPTION_RECORD arg1, PCONTEXT arg2, ui abort(); } +static void UnmaskActivationSignal() +{ + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, INJECT_ACTIVATION_SIGNAL); + + int sigmaskRet = pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); + _ASSERTE(sigmaskRet == 0); +} + static void TimeSpecAdd(timespec* time, uint32_t milliseconds) { uint64_t nsec = time->tv_nsec + (uint64_t)milliseconds * tccMilliSecondsToNanoSeconds; @@ -501,6 +511,8 @@ extern "C" void PalAttachThread(void* thread) #else tls_destructionMonitor.SetThread(thread); #endif + + UnmaskActivationSignal(); } // Detach thread from OS notifications. @@ -1034,6 +1046,27 @@ REDHAWK_PALEXPORT UInt32_BOOL REDHAWK_PALAPI PalRegisterHijackCallback(_In_ PalH ASSERT(g_pHijackCallback == NULL); g_pHijackCallback = callback; +#ifdef __APPLE__ + void *libSystem = dlopen("/usr/lib/libSystem.dylib", RTLD_LAZY); + if (libSystem != NULL) + { + int (*dispatch_allow_send_signals_ptr)(int) = (int (*)(int))dlsym(libSystem, "dispatch_allow_send_signals"); + if (dispatch_allow_send_signals_ptr != NULL) + { + int status = dispatch_allow_send_signals_ptr(INJECT_ACTIVATION_SIGNAL); + _ASSERTE(status == 0); + } + } + + // TODO: Once our CI tools can get upgraded to xcode >= 15.3, replace the code above by this: + // if (__builtin_available(macOS 14.4, iOS 17.4, tvOS 17.4, *)) + // { + // // Allow sending the activation signal to dispatch queue threads + // int status = dispatch_allow_send_signals(INJECT_ACTIVATION_SIGNAL); + // _ASSERTE(status == 0); + // } +#endif // __APPLE__ + return AddSignalHandler(INJECT_ACTIVATION_SIGNAL, ActivationHandler, &g_previousActivationHandler); } @@ -1053,7 +1086,7 @@ REDHAWK_PALEXPORT void REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_opt_ void* p if ((status == EAGAIN) || (status == ESRCH) #ifdef __APPLE__ - // On Apple, pthread_kill is not allowed to be sent to dispatch queue threads + // On Apple, pthread_kill is not allowed to be sent to dispatch queue threads on macOS older than 14.4 or iOS/tvOS older than 17.4 || (status == ENOTSUP) #endif ) diff --git a/src/coreclr/pal/src/exception/signal.cpp b/src/coreclr/pal/src/exception/signal.cpp index c9112432f8d930..c9e65b1ac1dc82 100644 --- a/src/coreclr/pal/src/exception/signal.cpp +++ b/src/coreclr/pal/src/exception/signal.cpp @@ -35,6 +35,7 @@ SET_DEFAULT_DEBUG_CHANNEL(EXCEPT); // some headers have code with asserts, so do #include #include +#include #if !HAVE_MACH_EXCEPTIONS #include "pal/init.h" @@ -74,6 +75,9 @@ static void sigterm_handler(int code, siginfo_t *siginfo, void *context); #ifdef INJECT_ACTIVATION_SIGNAL static void inject_activation_handler(int code, siginfo_t *siginfo, void *context); extern void* g_InvokeActivationHandlerReturnAddress; +#ifdef __APPLE__ +bool g_canSendSignalToDispatchQueueThreads = false; +#endif // __APPLE__ #endif static void sigill_handler(int code, siginfo_t *siginfo, void *context); @@ -239,6 +243,27 @@ BOOL SEHInitializeSignals(CorUnix::CPalThread *pthrCurrent, DWORD flags) #ifdef INJECT_ACTIVATION_SIGNAL if (flags & PAL_INITIALIZE_REGISTER_ACTIVATION_SIGNAL) { +#ifdef __APPLE__ + void *libSystem = dlopen("/usr/lib/libSystem.dylib", RTLD_LAZY); + if (libSystem != NULL) + { + int (*dispatch_allow_send_signals_ptr)(int) = (int (*)(int))dlsym(libSystem, "dispatch_allow_send_signals"); + if (dispatch_allow_send_signals_ptr != NULL) + { + int st = dispatch_allow_send_signals_ptr(INJECT_ACTIVATION_SIGNAL); + g_canSendSignalToDispatchQueueThreads = (st == 0); + } + } + + // TODO: Once our CI tools can get upgraded to xcode >= 15.4, replace the code above by this: + // if (__builtin_available(macOS 14.4, *)) + // { + // // Allow sending the activation signal to dispatch queue threads + // int st = dispatch_allow_send_signals(INJECT_ACTIVATION_SIGNAL); + // g_canSendSignalToDispatchQueueThreads = (st == 0); + // } +#endif // __APPLE__ + handle_signal(INJECT_ACTIVATION_SIGNAL, inject_activation_handler, &g_previous_activation); g_registered_activation_handler = true; } @@ -471,6 +496,32 @@ static void sigfpe_handler(int code, siginfo_t *siginfo, void *context) invoke_previous_action(&g_previous_sigfpe, code, siginfo, context); } +void UnmaskActivationSignal() +{ + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, INJECT_ACTIVATION_SIGNAL); + + int sigmaskRet = pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); + if (sigmaskRet != 0) + { + ASSERT("pthread_sigmask failed; error number is %d\n", sigmaskRet); + } +} + +void MaskActivationSignal() +{ + sigset_t signal_set; + sigemptyset(&signal_set); + sigaddset(&signal_set, INJECT_ACTIVATION_SIGNAL); + + int sigmaskRet = pthread_sigmask(SIG_BLOCK, &signal_set, NULL); + if (sigmaskRet != 0) + { + ASSERT("pthread_sigmask failed; error number is %d\n", sigmaskRet); + } +} + #if !HAVE_MACH_EXCEPTIONS /*++ @@ -493,24 +544,12 @@ extern "C" void signal_handler_worker(int code, siginfo_t *siginfo, void *contex // to correctly fill in this value. // Unmask the activation signal now that we are running on the original stack of the thread - sigset_t signal_set; - sigemptyset(&signal_set); - sigaddset(&signal_set, INJECT_ACTIVATION_SIGNAL); - - int sigmaskRet = pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); - if (sigmaskRet != 0) - { - ASSERT("pthread_sigmask failed; error number is %d\n", sigmaskRet); - } + UnmaskActivationSignal(); returnPoint->returnFromHandler = common_signal_handler(code, siginfo, context, 2, (size_t)0, (size_t)siginfo->si_addr); // We are going to return to the alternate stack, so block the activation signal again - sigmaskRet = pthread_sigmask(SIG_BLOCK, &signal_set, NULL); - if (sigmaskRet != 0) - { - ASSERT("pthread_sigmask failed; error number is %d\n", sigmaskRet); - } + MaskActivationSignal(); RtlRestoreContext(&returnPoint->context, NULL); } @@ -886,10 +925,13 @@ PAL_ERROR InjectActivationInternal(CorUnix::CPalThread* pThread) // the process exits. #ifdef __APPLE__ - // On Apple, pthread_kill is not allowed to be sent to dispatch queue threads - if (status == ENOTSUP) + if (!g_canSendSignalToDispatchQueueThreads) { - return ERROR_NOT_SUPPORTED; + // On macOS older than 14.4, pthread_kill is not allowed to sent a signal to dispatch queue threads + if (status == ENOTSUP) + { + return ERROR_NOT_SUPPORTED; + } } #endif diff --git a/src/coreclr/pal/src/include/pal/signal.hpp b/src/coreclr/pal/src/include/pal/signal.hpp index 6a3e41b4de473f..9331f30b584daf 100644 --- a/src/coreclr/pal/src/include/pal/signal.hpp +++ b/src/coreclr/pal/src/include/pal/signal.hpp @@ -117,4 +117,14 @@ Function : --*/ void SEHCleanupSignals(); +/*++ +Function : + UnmaskActivationSignal + + Unmask the INJECT_ACTIVATION_SIGNAL for the current thread + + (no parameters, no return value) +--*/ +void UnmaskActivationSignal(); + #endif /* _PAL_SIGNAL_HPP_ */ diff --git a/src/coreclr/pal/src/init/sxs.cpp b/src/coreclr/pal/src/init/sxs.cpp index ddb084098efa51..4c1772896a99ca 100644 --- a/src/coreclr/pal/src/init/sxs.cpp +++ b/src/coreclr/pal/src/init/sxs.cpp @@ -91,6 +91,9 @@ AllocatePalThread(CPalThread **ppThread) PROCAddThread(pThread, pThread); + // Unmask the activation signal so that GC can suspend this thread + UnmaskActivationSignal(); + exit: *ppThread = pThread; return palError; diff --git a/src/tests/Regressions/coreclr/GitHub_102887/CMakeLists.txt b/src/tests/Regressions/coreclr/GitHub_102887/CMakeLists.txt new file mode 100644 index 00000000000000..03d44205e8f714 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_102887/CMakeLists.txt @@ -0,0 +1,9 @@ +# The test is valid only on Apple +if (CLR_CMAKE_TARGET_APPLE) + include_directories(${INC_PLATFORM_DIR}) + + add_library(nativetest102887 SHARED nativetest102887.cpp) + + # add the install targets + install (TARGETS nativetest102887 DESTINATION bin) +endif () \ No newline at end of file diff --git a/src/tests/Regressions/coreclr/GitHub_102887/nativetest102887.cpp b/src/tests/Regressions/coreclr/GitHub_102887/nativetest102887.cpp new file mode 100644 index 00000000000000..18818472ffaf56 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_102887/nativetest102887.cpp @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include + +#include +#include +#include + +extern "C" DLL_EXPORT void StartDispatchQueueThread(void (*work)(void* args)) +{ + dispatch_queue_global_t q = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); + dispatch_async_f(q, NULL, work); +} + +extern "C" DLL_EXPORT bool SupportsSendingSignalsToDispatchQueueThread() +{ + void *libSystem = dlopen("/usr/lib/libSystem.dylib", RTLD_LAZY); + bool result = false; + if (libSystem != NULL) + { + int (*dispatch_allow_send_signals_ptr)(int) = (int (*)(int))dlsym(libSystem, "dispatch_allow_send_signals"); + result = (dispatch_allow_send_signals_ptr != NULL); + } + + return result; +} \ No newline at end of file diff --git a/src/tests/Regressions/coreclr/GitHub_102887/test102887.cs b/src/tests/Regressions/coreclr/GitHub_102887/test102887.cs new file mode 100644 index 00000000000000..5fa45b1ee1598d --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_102887/test102887.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Xunit; + +public class Test102887 +{ + delegate void DispatchQueueWork(IntPtr args); + [DllImport("nativetest102887")] + private static extern void StartDispatchQueueThread(DispatchQueueWork start); + + [DllImport("nativetest102887")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SupportsSendingSignalsToDispatchQueueThread(); + + static volatile int s_cnt; + static ManualResetEvent s_workStarted = new ManualResetEvent(false); + + private static void RunOnDispatchQueueThread(IntPtr args) + { + s_workStarted.Set(); + while (true) + { + s_cnt++; + } + } + + [Fact] + public static void TestEntryPoint() + { + // Skip the test if the current OS doesn't support sending signals to the dispatch queue threads + if (SupportsSendingSignalsToDispatchQueueThread()) + { + Console.WriteLine("Sending signals to dispatch queue thread is supported, testing it now"); + StartDispatchQueueThread(RunOnDispatchQueueThread); + s_workStarted.WaitOne(); + + for (int i = 0; i < 100; i++) + { + GC.Collect(2); + } + } + } +} + diff --git a/src/tests/Regressions/coreclr/GitHub_102887/test102887.csproj b/src/tests/Regressions/coreclr/GitHub_102887/test102887.csproj new file mode 100644 index 00000000000000..bd482959498fdc --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_102887/test102887.csproj @@ -0,0 +1,17 @@ + + + + true + true + 1 + + + + + + + + + + +