Protection against fork (#735)

If a thread forks, while another thread is holding an snmalloc lock, then the allocator could stop working.

This patch attempts to protect against the cases of this. There is one case that is not covered. If a fork occurs during the very first allocation. This can result in the installation of the fork handler racing with the fork, and all bets are off.
This commit is contained in:
Matthew Parkinson
2025-03-04 13:44:59 +00:00
committed by GitHub
parent c2e22ccb79
commit ccc03ce0fc
9 changed files with 312 additions and 16 deletions

View File

@@ -0,0 +1,81 @@
#include <iostream>
#include <snmalloc/snmalloc.h>
#ifndef SNMALLOC_PTHREAD_ATFORK_WORKS
int main()
{
std::cout << "Test did not run" << std::endl;
return 0;
}
#else
# include <pthread.h>
# include <thread>
void simulate_allocation()
{
snmalloc::PreventFork pf;
}
int main()
{
// Counter for the number of threads that are blocking the fork
std::atomic<size_t> block = false;
// Specifies that the forking thread has observed that all the blocking
// threads are in place.
std::atomic<bool> forking = false;
size_t N = 3;
snmalloc::message<1024>("Testing PreventFork");
snmalloc::message<1024>("Adding alternative calls to pthread_atfork");
pthread_atfork(simulate_allocation, simulate_allocation, simulate_allocation);
snmalloc::message<1024>("Initialising PreventFork singleton");
{
// Cause initialisation of the PreventFork singleton to call pthread_atfork.
snmalloc::PreventFork pf;
}
snmalloc::message<1024>("Adding alternative calls to pthread_atfork");
pthread_atfork(simulate_allocation, simulate_allocation, simulate_allocation);
snmalloc::message<1024>("Creating other threads");
for (size_t i = 0; i < N; i++)
{
std::thread t([&block, &forking, i]() {
{
snmalloc::PreventFork pf;
snmalloc::message<1024>("Thread {} blocking fork", i);
block++;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
while (!forking)
std::this_thread::yield();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
snmalloc::message<1024>("Thread {} releasing block", i);
block--;
}
});
t.detach();
}
snmalloc::message<1024>("Waiting for all threads to block fork");
while (block != N)
std::this_thread::yield();
snmalloc::message<1024>("Forking");
forking = true;
fork();
if (block)
{
snmalloc::message<1024>("PreventFork failed");
return 1;
}
snmalloc::message<1024>("PreventFork passed");
return 0;
}
#endif