git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] trace2: tolerate failed timestamp formatting

From
Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com>
Date
Jul 15, 2026, 16:12 UTC
Message-ID
<pull.2178.git.1784131932489.gitgitgadget@gmail.com>
From: Derrick Stolee <stolee@gmail.com>
Some users reported issues of repeated messages:
  fatal: recursion detected in die handler

This wasn't happening every time, but we eventually captured a GIT_TRACE2_PERF log file with this issue and revealed an interesting internal detail, failing with this message:

  unable to format message: %4d-%02d-%02dT%02d:%02d:%02d.%06ldZ

This specific format string tracks to tr2_tbuf_utc_datetime_extended() in trace2/tr2_tbuf.c. This logic began as tr2_tbuf_utc_time() in ee4512ed481 (trace2: create new combined trace facility, 2019-02-22) but was later split in bad229aef23 (trace2: clarify UTC datetime formatting, 2019-04-15).

This use of xsnprintf() is writing a very specific datetime format into a 32-character buffer. The format requires that the input data will not overflow the format digits or the buffer will not hold the result. Since we are using xsnprintf() here, those failures turn into die() events.

This method and its siblings, tr2_tbuf_local_time() and tr2_tbuf_utc_datetime(), are used in the tracing library. The extended form is used only for the 'event' format, which these users were using via a config setting for use in client-side telemetry. The non-extended form is used to help generate the 'SID' that defines the process in the traces.

Not only are these inappropriate times for a failure, but the extended method is called specifially during the 'atexit' event, which was triggering this problem in a loop as the 'atexit' event would be retriggered by the die().

I could not determine the exact cause of why these errors started occuring in a bunch. My best guess is that these users are dogfooding an early operating system version that is more likely to fail in the gettimeofday() function and thus leaves the structures uninitialized and potentially violating the expected values.

However, for full defense-in-depth I made several modifications:
1. Both 'tv' and 'tm' structs are initialized with zero values, allowing
   an erroring gettimeofday() or gmtime_r() method to leave them
   zero-valued. A zero-valued date is better than a die() here.
2. Replace the use of xsnprintf() with snprintf() to avoid the
   possibility of calling die() here. Instead, check the response to see
   if there was a failure. On failure, put a blank value into the buffer
   instead of possibly allowing a value that would not format correctly
   for a trace2 consumer. This value should be seen as obviously wrong
   and therefore signals a problem.

As the core issue in this code seems to require a system method returning an error, no test accompanies this change.

This change removes all uses of xsnprintf() from the trace2/ directory. There are two uses of xstrdup() that could be considered for removal, but they only die() on out-of-memory errors instead of formatting issues. I chose to leave those in place for now.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
---
    trace2: tolerate failed timestamp formatting
    
    As mentioned, this is based on real trace logs of failed commands users
    are seeing.
    
    I wish I had a better way to test this or to be 100% sure that the
    system call was failing. But users were seeing failures and these seemed
    like appropriate changes.
    
    Thanks, -Stolee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2178%2Fderrickstolee%2Ftrace2-dont-die-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2178/derrickstolee/trace2-dont-die-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2178
 trace2/tr2_tbuf.c | 49 ++++++++++++++++++++++++++++++++---------------
 1 file changed, 34 insertions(+), 15 deletions(-)
diff --git a/trace2/tr2_tbuf.c b/trace2/tr2_tbuf.c
index c3b3822ed7..ef57376f3c 100644
--- a/trace2/tr2_tbuf.c
+++ b/trace2/tr2_tbuf.c
@@ -3,45 +3,64 @@
 
 void tr2_tbuf_local_time(struct tr2_tbuf *tb)
 {
-	struct timeval tv;
-	struct tm tm;
+	struct timeval tv = { 0 };
+	struct tm tm = { 0 };
 	time_t secs;
+	int len;
 
 	gettimeofday(&tv, NULL);
 	secs = tv.tv_sec;
 	localtime_r(&secs, &tm);
 
-	xsnprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld", tm.tm_hour,
-		  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+	len = snprintf(tb->buf, sizeof(tb->buf), "%02d:%02d:%02d.%06ld",
+		       tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+
+	if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
+		const char *blank = "00:00:00.000000";
+		strlcpy(tb->buf, blank, sizeof(tb->buf));
+	}
 }
 
 void tr2_tbuf_utc_datetime_extended(struct tr2_tbuf *tb)
 {
-	struct timeval tv;
-	struct tm tm;
+	struct timeval tv = { 0 };
+	struct tm tm = { 0 };
 	time_t secs;
+	int len;
 
 	gettimeofday(&tv, NULL);
 	secs = tv.tv_sec;
 	gmtime_r(&secs, &tm);
 
-	xsnprintf(tb->buf, sizeof(tb->buf),
-		  "%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ", tm.tm_year + 1900,
-		  tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
-		  (long)tv.tv_usec);
+	len = snprintf(tb->buf, sizeof(tb->buf),
+		       "%4d-%02d-%02dT%02d:%02d:%02d.%06ldZ",
+		       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+		       tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+
+	if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
+		const char *blank = "1900-00-00T00:00:00.000000Z";
+		strlcpy(tb->buf, blank, sizeof(tb->buf));
+	}
 }
 
 void tr2_tbuf_utc_datetime(struct tr2_tbuf *tb)
 {
-	struct timeval tv;
-	struct tm tm;
+	struct timeval tv = { 0 };
+	struct tm tm = { 0 };
 	time_t secs;
+	int len;
 
 	gettimeofday(&tv, NULL);
 	secs = tv.tv_sec;
 	gmtime_r(&secs, &tm);
 
-	xsnprintf(tb->buf, sizeof(tb->buf), "%4d%02d%02dT%02d%02d%02d.%06ldZ",
-		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
-		  tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+	len = snprintf(tb->buf, sizeof(tb->buf),
+		       "%4d%02d%02dT%02d%02d%02d.%06ldZ",
+		       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+		       tm.tm_hour, tm.tm_min, tm.tm_sec, (long)tv.tv_usec);
+
+	if (len < 0 || (size_t)len >= sizeof(tb->buf)) {
+		const char *blank = "19000000T000000.000000Z";
+		strlcpy(tb->buf, blank, sizeof(tb->buf));
+	}
 }

base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
-- 
gitgitgadget
Next: Taylor Blau
Message 1 of 43 in “trace2: tolerate failed timestamp formatting”
  1. trace2: tolerate failed timestamp formattingDerrick Stolee via GitGitGadget, Jul 15, 2026
  2. Taylor BlauJul 17, 2026
  3. Derrick StoleeJul 18, 2026
  4. Junio C HamanoJul 20, 2026
  5. Taylor BlauJul 20, 2026
  6. Junio C HamanoJul 29, 2026
  7. Derrick StoleeJul 31, 2026
  8. Junio C HamanoJul 31, 2026
  9. 0/7 trace2: stop allowing die()Derrick Stolee via GitGitGadget, Aug 25, 2026
  10. 1/7 banned-die: create header for banning of functionsDerrick Stolee via GitGitGadget, Aug 25, 2026
  11. Junio C HamanoAug 25, 2026
  12. Derrick StoleeAug 31, 2026
  13. Patrick SteinhardtAug 31, 2026
  14. Elijah NewrenAug 25, 2026
  15. Derrick StoleeAug 31, 2026
  16. Jeff KingAug 27, 2026
  17. Derrick StoleeAug 31, 2026
  18. 2/7 trace2: tolerate failed timestamp formattingDerrick Stolee via GitGitGadget, Aug 25, 2026
  19. 3/7 trace2: remove use of xstrdup()Derrick Stolee via GitGitGadget, Aug 25, 2026
  20. Elijah NewrenAug 25, 2026
  21. Derrick StoleeAug 31, 2026
  22. 4/7 trace2: remove use of ALLOC_ARRAY()Derrick Stolee via GitGitGadget, Aug 25, 2026
  23. 5/7 trace2: remove use of xstrfmt()Derrick Stolee via GitGitGadget, Aug 25, 2026
  24. Elijah NewrenAug 25, 2026
  25. Junio C HamanoAug 25, 2026
  26. Derrick StoleeAug 31, 2026
  27. 6/7 trace2: remove use of ALLOC_GROW()Derrick Stolee via GitGitGadget, Aug 25, 2026
  28. Elijah NewrenAug 25, 2026
  29. 7/7 trace2: remove use of xcalloc()Derrick Stolee via GitGitGadget, Aug 25, 2026
  30. Jeff KingAug 27, 2026
  31. Derrick StoleeAug 31, 2026
  32. Jeff KingSep 1, 2026
  33. Jeff KingSep 1, 2026
  34. Derrick StoleeSep 1, 2026
  35. 0/7 trace2: stop allowing die()Derrick Stolee via GitGitGadget, Aug 31, 2026
  36. 1/7 banned-die: create header for banning of functionsDerrick Stolee via GitGitGadget, Aug 31, 2026
  37. 2/7 trace2: tolerate failed timestamp formattingDerrick Stolee via GitGitGadget, Aug 31, 2026
  38. 3/7 trace2: remove use of xstrdup()Derrick Stolee via GitGitGadget, Aug 31, 2026
  39. 4/7 trace2: remove use of ALLOC_ARRAY()Derrick Stolee via GitGitGadget, Aug 31, 2026
  40. 5/7 trace2: remove use of xstrfmt()Derrick Stolee via GitGitGadget, Aug 31, 2026
  41. 6/7 trace2: remove use of ALLOC_GROW()Derrick Stolee via GitGitGadget, Aug 31, 2026
  42. 7/7 trace2: remove use of xcalloc()Derrick Stolee via GitGitGadget, Aug 31, 2026
  43. Derrick StoleeOct 6, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.