From d288c38c988639d70f0c0664d9f5baea9d45cef8 Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Thu, 3 Sep 2026 01:51:20 +0200 Subject: [PATCH] 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. --- capture/src/capturedaemon.cpp | 63 ++++++++++++++++++++++++++++++++++- manual/tracy.tex | 2 ++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/capture/src/capturedaemon.cpp b/capture/src/capturedaemon.cpp index 86ee1089..21c215ab 100644 --- a/capture/src/capturedaemon.cpp +++ b/capture/src/capturedaemon.cpp @@ -1,4 +1,5 @@ #ifdef _WIN32 +# include # include #else # include @@ -7,7 +8,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -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 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 diff --git a/manual/tracy.tex b/manual/tracy.tex index 6ce4c0e0..786808af 100644 --- a/manual/tracy.tex +++ b/manual/tracy.tex @@ -3331,6 +3331,8 @@ Each client is captured to a separate file in the output directory, named accord Press \keys{\ctrl + C} to stop discovery and gracefully shut down all active captures. Each capture thread will finish writing its trace file before the daemon exits. +The daemon locks the output directory and refuses to start if a running daemon already owns it, so only one daemon can write to a given directory at a time. + \subsection{External monitoring} \label{externalmonitoring}