diff --git a/shared/source/page_fault_manager/linux/cpu_page_fault_manager_linux.h b/shared/source/page_fault_manager/linux/cpu_page_fault_manager_linux.h index 74d5c9076fdf9..53fdc729c1cd6 100644 --- a/shared/source/page_fault_manager/linux/cpu_page_fault_manager_linux.h +++ b/shared/source/page_fault_manager/linux/cpu_page_fault_manager_linux.h @@ -38,7 +38,10 @@ class PageFaultManagerLinux : public virtual CpuPageFaultManager { std::vector previousPageFaultHandlers; - int handlerIndex = 0; + // Depth of the handler chain on the calling thread. SIGSEGV can be delivered to several + // threads at the same time, and a chained handler may block until the others arrive + // (e.g. a managed runtime's GC safepoint handler). + inline static thread_local int handlerIndex = 0; }; class CpuPageFaultManagerLinux final : public PageFaultManagerLinux {}; diff --git a/shared/test/unit_test/page_fault_manager/linux/cpu_page_fault_manager_linux_tests.cpp b/shared/test/unit_test/page_fault_manager/linux/cpu_page_fault_manager_linux_tests.cpp index 5bd2156e979f9..8ccbb58a6951e 100644 --- a/shared/test/unit_test/page_fault_manager/linux/cpu_page_fault_manager_linux_tests.cpp +++ b/shared/test/unit_test/page_fault_manager/linux/cpu_page_fault_manager_linux_tests.cpp @@ -15,8 +15,11 @@ #include "gtest/gtest.h" +#include +#include #include #include +#include using namespace NEO; @@ -75,6 +78,13 @@ class MockFailPageFaultManager : public PageFaultManagerLinux { pageFaultHandlerWrapper(signal, info, context); } + static void mockBlockingPageFaultHandler(int signal, siginfo_t *info, void *context) { + blockingMockCalls++; + for (int i = 0; i < 1000 && blockingMockCalls < 2; i++) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + static void mockPageFaultSimpleHandler(int signal) { simpleMockCalled = true; } @@ -89,6 +99,7 @@ class MockFailPageFaultManager : public PageFaultManagerLinux { simpleMockCalled = false; simpleMockCalled2 = false; staticVerifyCalled = false; + blockingMockCalls = 0; } static bool mockCalled; @@ -96,6 +107,7 @@ class MockFailPageFaultManager : public PageFaultManagerLinux { static bool simpleMockCalled; static bool simpleMockCalled2; static bool staticVerifyCalled; + static std::atomic blockingMockCalls; bool verifyCalled = false; int numPageFaultHandled = 0; }; @@ -105,6 +117,7 @@ bool MockFailPageFaultManager::mockCalled2 = false; bool MockFailPageFaultManager::simpleMockCalled = false; bool MockFailPageFaultManager::simpleMockCalled2 = false; bool MockFailPageFaultManager::staticVerifyCalled = false; +std::atomic MockFailPageFaultManager::blockingMockCalls = 0; TEST_F(PageFaultManagerLinuxTest, givenPageFaultThatNEOShouldNotHandleAndSigInfoFlagSetThenSaSigactionIsCalled) { struct sigaction previousHandler = {}; @@ -127,6 +140,27 @@ TEST_F(PageFaultManagerLinuxTest, givenPageFaultThatNEOShouldNotHandleAndSigInfo sigaction(SIGSEGV, &previousHandler, nullptr); } +TEST_F(PageFaultManagerLinuxTest, givenPageFaultsThatNEOShouldNotHandleOnTwoThreadsAtOnceThenSaSigactionIsCalledOnBoth) { + struct sigaction previousHandler = {}; + struct sigaction mockHandler = {}; + mockHandler.sa_flags = SA_SIGINFO; + mockHandler.sa_sigaction = MockFailPageFaultManager::mockBlockingPageFaultHandler; + auto retVal = sigaction(SIGSEGV, &mockHandler, &previousHandler); + EXPECT_EQ(retVal, 0); + + auto mockPageFaultManager = std::make_unique(); + + // the first thread waits inside the previous handler until the second one gets there + std::thread thread1([] { std::raise(SIGSEGV); }); + std::thread thread2([] { std::raise(SIGSEGV); }); + thread1.join(); + thread2.join(); + EXPECT_EQ(2, MockFailPageFaultManager::blockingMockCalls); + + mockPageFaultManager.reset(); + sigaction(SIGSEGV, &previousHandler, nullptr); +} + TEST_F(PageFaultManagerLinuxTest, givenPageFaultThatNEOShouldNotHandleThenSaHandlerIsCalled) { struct sigaction previousHandler = {}; struct sigaction mockHandler = {};