Add a per-directory lock file to the capture daemon.

Only one daemon can write to a given output directory at a time. The
lock holds the daemon PID and is removed on a clean exit; a lock whose
owner is verifiably gone is taken over on the next start, and a lock
whose owner cannot be read or that reads as alive must be removed by
hand.
This commit is contained in:
Bartosz Taudul
2026-09-03 01:51:20 +02:00
parent 6735c64306
commit d288c38c98
2 changed files with 64 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
#ifdef _WIN32
# include <process.h>
# include <windows.h>
#else
# include <unistd.h>
@@ -7,7 +8,10 @@
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <map>
#include <mutex>
@@ -37,12 +41,29 @@ static uint16_t g_listenPort = 8086;
static std::string g_filterName;
static int g_filterPort = 0;
static int64_t g_memoryLimit = -1;
static std::string g_lockFile;
void SigInt( int )
{
g_shutdown.store( true, std::memory_order_relaxed );
}
static void RemoveLockFile()
{
std::error_code ec;
std::filesystem::remove( g_lockFile, ec );
}
static long ReadLockPid( const std::string& path )
{
FILE* in = fopen( path.c_str(), "r" );
if( !in ) return -1;
long pid = -1;
const int result = fscanf( in, "%li", &pid );
fclose( in );
return ( result == 1 && pid > 0 ) ? pid : -1;
}
struct ClientStats
{
std::atomic<float> mbps{0};
@@ -337,7 +358,47 @@ int main( int argc, char** argv )
std::filesystem::create_directories( outputDir );
InitTerminalDetection();
g_lockFile = outputDir + "/.tracy-capture-daemon.lock";
{
int lockFd = -1;
for( int attempt = 0; attempt < 5; attempt++ )
{
lockFd = open( g_lockFile.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0644 );
if( lockFd >= 0 ) break;
if( errno != EEXIST )
{
fprintf( stderr, "Error: Cannot create the lock file %s: %s\n", g_lockFile.c_str(), strerror( errno ) );
return 1;
}
const long owner = ReadLockPid( g_lockFile );
if( owner <= 0 )
{
fprintf( stderr, "Error: The lock file %s has no readable owner PID; remove it by hand if no daemon is running\n", g_lockFile.c_str() );
return 1;
}
// EPERM: the process exists under another user; treat it as alive.
if( kill( owner, 0 ) == 0 || errno == EPERM )
{
fprintf( stderr, "Error: Another capture daemon (pid %ld) is already using the output directory %s\n", owner, outputDir.c_str() );
return 1;
}
std::error_code ec;
std::filesystem::remove( g_lockFile, ec );
}
if( lockFd < 0 )
{
fprintf( stderr, "Error: Another capture daemon is already using the output directory %s\n", outputDir.c_str() );
return 1;
}
#ifdef _WIN32
const std::string pid = std::to_string( _getpid() );
#else
const std::string pid = std::to_string( getpid() );
#endif
write( lockFd, pid.c_str(), pid.size() );
close( lockFd );
}
atexit( RemoveLockFile );
#ifdef _WIN32
signal( SIGINT, SigInt );
#else