Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ class PageFaultManagerLinux : public virtual CpuPageFaultManager {

std::vector<struct sigaction> 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 {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@

#include "gtest/gtest.h"

#include <atomic>
#include <chrono>
#include <csignal>
#include <sys/mman.h>
#include <thread>

using namespace NEO;

Expand Down Expand Up @@ -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;
}
Expand All @@ -89,13 +99,15 @@ class MockFailPageFaultManager : public PageFaultManagerLinux {
simpleMockCalled = false;
simpleMockCalled2 = false;
staticVerifyCalled = false;
blockingMockCalls = 0;
}

static bool mockCalled;
static bool mockCalled2;
static bool simpleMockCalled;
static bool simpleMockCalled2;
static bool staticVerifyCalled;
static std::atomic<int> blockingMockCalls;
bool verifyCalled = false;
int numPageFaultHandled = 0;
};
Expand All @@ -105,6 +117,7 @@ bool MockFailPageFaultManager::mockCalled2 = false;
bool MockFailPageFaultManager::simpleMockCalled = false;
bool MockFailPageFaultManager::simpleMockCalled2 = false;
bool MockFailPageFaultManager::staticVerifyCalled = false;
std::atomic<int> MockFailPageFaultManager::blockingMockCalls = 0;

TEST_F(PageFaultManagerLinuxTest, givenPageFaultThatNEOShouldNotHandleAndSigInfoFlagSetThenSaSigactionIsCalled) {
struct sigaction previousHandler = {};
Expand All @@ -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<MockFailPageFaultManager>();

// 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 = {};
Expand Down