upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/util.h
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-29 12:46:33 -0300
committerGitHub <noreply@github.com>2026-07-29 12:46:33 -0300
commit6e836ed5cb5f41391e335b4f508dc6c0e8915ba5 (patch)
tree77d9175f60192242bbca4ad43b3ae44c7eb827a5 /src/util.h
parent1227068a504418521f3238acf9c4dca297141d27 (diff)
util: stop debug.log dropping lines under concurrency (#29)
OutputDebugStringF resolved its path lazily with no lock: static string strDebugFile; if (strDebugFile.empty()) { ... strDebugFile = dir + "\\" + "debug.log"; } FILE* fileout = fopen(strDebugFile.c_str(), "a"); if (fileout) { ... } One thread could read that string while another was still assigning to it, and at startup three threads log within milliseconds of each other. When it raced, fopen received a torn path, returned NULL, and the `if (fileout)` dropped the line with no error anywhere. Several threads calling fopen on the same file at once had no protection either. Measured over six runs, counting first-statement printfs that are unconditional and must appear every time: ThreadBtfAccept started 4/6 ThreadNostrSeed started 5/6 btfpeers: (cached peers) 2/6 Over eight runs after the change, all three are 8/8. This matters beyond tidiness. A missing line was read as a thread that never started, and a real investigation was spent on it. Any reasoning of the form "it is not in the log, so it did not happen" was unsound against this build. Path resolution and the write now share one critical section. The rest of the function already had its own for the OutputDebugString accumulation path; only the file half was unguarded.
Diffstat (limited to 'src/util.h')
-rw-r--r--src/util.h56
1 files changed, 36 insertions, 20 deletions
diff --git a/src/util.h b/src/util.h
index 641b838..b583248 100644
--- a/src/util.h
+++ b/src/util.h
@@ -269,29 +269,45 @@ string GetAppDir();
269inline int OutputDebugStringF(const char* pszFormat, ...) 269inline int OutputDebugStringF(const char* pszFormat, ...)
270{ 270{
271#if 1 // debug.log always enabled 271#if 1 // debug.log always enabled
272 // Write to data directory so it's always findable 272 // Write to data directory so it's always findable.
273 static string strDebugFile; 273 //
274 if (strDebugFile.empty()) { 274 // Path resolution and the write are both under the lock, because neither
275 string dir = GetAppDir(); 275 // used to be under anything. strDebugFile was filled in lazily inside an
276 if (!dir.empty()) 276 // `if (empty())`, so one thread could read the path while another was
277 strDebugFile = dir + 277 // still assigning to it -- and at startup three threads log within
278 // milliseconds of each other. When that raced, fopen got a torn path,
279 // returned NULL, and the `if (fileout)` below dropped the line silently.
280 // Measured before this change: unconditional first-statement printfs in
281 // ThreadBtfAccept, ThreadNostrSeed and the cached-peer thread reached the
282 // file in only 4/6, 5/6 and 2/6 of runs respectively. Concluding a thread
283 // never ran because its line was absent was not sound, and the lock also
284 // stops several threads calling fopen on the same file at once.
285 static CCriticalSection cs_debugFile;
286 CRITICAL_BLOCK(cs_debugFile)
287 {
288 static string strDebugFile;
289 if (strDebugFile.empty()) {
290 string dir = GetAppDir();
291 if (!dir.empty())
292 strDebugFile = dir +
278#ifdef _WIN32 293#ifdef _WIN32
279 "\\" 294 "\\"
280#else 295#else
281 "/" 296 "/"
282#endif 297#endif
283 + "debug.log"; 298 + "debug.log";
284 else 299 else
285 strDebugFile = "debug.log"; 300 strDebugFile = "debug.log";
286 } 301 }
287 FILE* fileout = fopen(strDebugFile.c_str(), "a"); 302 FILE* fileout = fopen(strDebugFile.c_str(), "a");
288 if (fileout) 303 if (fileout)
289 { 304 {
290 va_list arg_ptr; 305 va_list arg_ptr;
291 va_start(arg_ptr, pszFormat); 306 va_start(arg_ptr, pszFormat);
292 vfprintf(fileout, pszFormat, arg_ptr); 307 vfprintf(fileout, pszFormat, arg_ptr);
293 va_end(arg_ptr); 308 va_end(arg_ptr);
294 fclose(fileout); 309 fclose(fileout);
310 }
295 } 311 }
296 312
297#ifndef _WIN32 313#ifndef _WIN32