in_tail: files falsely detected as rotated on macOS with threaded inputs, causing duplicate records
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 75/100
Línea de trabajo
Empieza en plugins/in_tail/tail_file.h, en flb_tail_target_file_name_cmp(), y compara el manejo de los nombres base en macOS con la corrección para Windows de PR #11553. Reproduce el problema usando las dos entradas tail con y sin hilos y los archivos estáticos descritos en el issue. Se considera terminado cuando la compilación de macOS ya no informa de rotaciones falsas ni vuelve a leer registros en el escenario de estrés; el issue no indica ningún archivo de prueba.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Describe the bug
On macOS, when at least one tail input has threaded on, in_tail intermittently decides that a file was rotated when it was not. The file is then picked up again by the next refresh scan and re-read from offset 0, so its records are sent again. Both the threaded input and other (main-thread) tail inputs are affected.
The cause is flb_tail_target_file_name_cmp() in plugins/in_tail/tail_file.h, which calls libc basename(3) twice and compares the results. On macOS, Apple's libc basename() returns a pointer to a single static buffer shared by the whole process. When a threaded tail input and a main-thread tail input run this compare at the same time, one thread's result overwrites the other's, strcmp() sees a mismatch, and the file is treated as rotated.
This is the same class of problem that PR #11553 fixed for Windows (thread-local compat buffer). macOS uses Apple libc and is still affected. As of 2026-11-10, master has the same code as v4.2.3.
To Reproduce
Two tail inputs, each tailing 50 static one-line files that never change, null output. Any rotation counted is therefore false.
Setup:
R=/private/tmp/fbrepro
mkdir -p $R/a $R/b
for i in $(seq -w 1 50); do
echo "static line $i" > $R/a/f$i.log
echo "static line $i" > $R/b/f$i.log
done
fluent-bit.conf:
[SERVICE]
flush 1
log_level info
http_server on
http_listen 127.0.0.1
http_port 2099
[INPUT]
name tail
alias tail_threaded
path /private/tmp/test/a/*.log
read_from_head on
threaded on
[INPUT]
name tail
alias tail_main
path /private/tmp/test/b/*.log
read_from_head on
[OUTPUT]
name null
match *
Run it, wait a few minutes, then read the counters:
fluent-bit -c fluent-bit.conf
curl -s http://127.0.0.1:2099/api/v1/metrics/prometheus \
| grep -E '^fluentbit_input_(files_rotated_total|records_total)'
Results on Fluent Bit v4.2.3, macOS 27.2 arm64, 10 minutes, plus a control run of the same config with threaded off on both inputs, run at the same time:
| Run | files_rotated_total (tail_threaded / tail_main) | records_total (expected 50 / 50) |
|---|---|---|
threaded on on the first input |
16 / 12 | 65 / 62 |
Control: threaded off |
0 / 0 | 50 / 50 |
The log shows rotations where the old and new paths are identical:
[info] [input:tail:tail_threaded] inode=210871275 handle rotation(): /private/tmp/test/A/a/f22.log => /private/tmp/test/A/a/f22.log
[info] [input:tail:tail_main] inode=210871272 handle rotation(): /private/tmp/test/A/b/f20.log => /private/tmp/test/A/b/f20.log
Expected behavior
Files that are not renamed, moved or replaced are never reported as rotated, and their records are read once.
Actual behavior
Files are reported as rotated at random. Each false rotation is followed by a full re-read of the file at the next refresh scan (60 s by default), sending duplicate records.
Root cause
flb_tail_target_file_name_cmp() (tail_file.h L80,
L104-L105), non-Windows branch:
base_a = flb_strdup(basename(name_a));
...
base_b = basename(name_b);
ret = strcmp(base_a, base_b);
...
/* FYI: 'base_b' never points to a new allocation, no flb_free is needed */
The comment, and the lack of locking, assume glibc behavior, where basename() returns a pointer into its argument. POSIX says basename() need not be thread-safe, and Apple's implementation returns a pointer to one process-wide static buffer. Calling Apple libc basename() on two different paths returns the same pointer, and the first result is overwritten:
basename(a) -> 0x1015153c0 b'first.log'
basename(b) -> 0x1015153c0 b'second.log'
same pointer: True | first result now reads: b'second.log'
(Output from calling libc basename() through Python ctypes on macOS 27.2 arm64.)
A tail input with threaded on runs its fs-check timer on its own thread; other tail inputs run theirs on the main thread. If the two compares overlap, either thread can see the other's basename. flb_tail_file_is_rotated() (tail_file.c L1913) then returns true and flb_tail_file_rotated() runs: the file's DB entry is renamed, the refresh scan finds the original path again as a new file, and it is read from offset 0.
Why particular inputs are hit
flb_input_collectors_start() (flb_input.c L1839-L1865) arms collectors in config order, and signals each threaded input to start its own collectors from inside that loop (flb_input_thread.c L744). The threaded input's timer and the next main-thread
tail input's timer therefore start at almost the same moment with the same period, and keep firing together. In our production config, only the threaded input and the input directly after it in config order showed false rotations; all other tail inputs showed none.
Additional evidence (production, macOS, v4.2.3)
- Over 18.8 hours on one host: input A (threaded, hourly rotation, about 19 real rotations) reported 54 rotations; input B (next in config order, 1 real rotation at midnight) reported 10; the other 17 tail
inputs reported 0. - Input A re-read about 62 GB in about 7.5 hours.
- Across our macOS hosts, input B's records reach the backend about 7.8x on average. Linux and Windows hosts with the same inputs show 1x.
fs_usagecaught a "rotation" while the path fromfcntl(F_GETPATH), polled every 20 ms, never changed, and there was no rename, link or write on the file.- A Python model of the compare logic produced about 0.3% false mismatches with 2 threads and 0 with 1 thread.
- Removing
threaded onfrom input A: 0 false rotations over 5h45m. Re-enabling it for 30 minutes: 5 false rotations. - 20-minute side-by-side stress test (20 tail inputs on 20 static files, 10 threaded, null output): stock 4.2.3 had 366 false rotations on threaded inputs and 43 on non-threaded inputs, with 402 records
re-read. A 4.2.3 build with an equivalent patch had 0 false rotations and 0 records re-read.
Proposed fix
On __APPLE__, use the reentrant basename_r(3) with per-call stack buffers for both names. Linux and Windows are unchanged. This applies cleanly to v4.2.3 and current master:
diff --git a/plugins/in_tail/tail_file.h b/plugins/in_tail/tail_file.h
--- a/plugins/in_tail/tail_file.h
+++ b/plugins/in_tail/tail_file.h
@@ -35,6 +35,11 @@
#include "win32.h"
#endif
+#ifdef __APPLE__
+#include <libgen.h>
+#include <sys/param.h>
+#endif
+
#ifdef FLB_HAVE_REGEX
#define FLB_HASH_TABLE_SIZE 50
#endif
@@ -69,6 +74,16 @@
char *name_b = NULL;
char *base_a = NULL;
char *base_b = NULL;
+#if defined(__APPLE__)
+ /*
+ * Apple's basename(3) returns a pointer to one static buffer shared by
+ * the whole process, so a threaded tail input and the main thread can
+ * overwrite each other's result mid-compare and report a false rotation.
+ * Use the reentrant basename_r(3) with per-call buffers instead.
+ */
+ char base_buf_a[MAXPATHLEN];
+ char base_buf_b[MAXPATHLEN];
+#endif
name_a = flb_strdup(name);
if (!name_a) {
@@ -77,7 +92,14 @@
goto out;
}
+#if defined(__APPLE__)
+ base_a = basename_r(name_a, base_buf_a);
+ if (base_a) {
+ base_a = flb_strdup(base_a);
+ }
+#else
base_a = flb_strdup(basename(name_a));
+#endif
if (!base_a) {
flb_errno();
ret = -1;
@@ -101,7 +123,16 @@
ret = -1;
goto out;
}
+#if defined(__APPLE__)
+ base_b = basename_r(name_b, base_buf_b);
+ if (!base_b) {
+ flb_errno();
+ ret = -1;
+ goto out;
+ }
+ }
+#else
base_b = basename(name_b);
+#endif
#endif
A portable alternative would be to drop libc basename() here and use a small helper that returns a pointer into the already-duplicated name, for example (untested sketch; Windows would also need to handle
\):
static inline char *tail_basename(char *path)
{
char *p = strrchr(path, '/');
return p ? p + 1 : path;
}
That avoids any shared static state on every platform, and would also cover other libcs with the same behavior.
Workaround
Do not set threaded on on tail inputs on macOS.
Your Environment
- Version used: Fluent Bit v4.2.3 (git commit 40c2f276057ed518d1b75c184a133afd1a13d61c)
- Operating System and version: macOS on Apple Silicon (repro above on macOS 27.2, arm64)
- Configuration: see "To Reproduce"
- Filters and plugins: in_tail (stat-based file watching on macOS), out_null in the repro
Related
- #11553 compat: fix basename buffer to be thread-local on Windows (same bug class, Windows only)
- #11357 Threaded mode always re-reads files from beginning regardless of DB offset or Read_from_Head setting (different mechanism)
- #11460 in_tail: Serialize DB operation for preventing resource collisions on threaded mode (different mechanism)
- Lenguaje dominante
- C
- Estrellas
- 8.2k
- Forks
- 2k
- Merge medio
- 3 d 20 h
- PR fusionados (30 d)
- 99
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Incluye un Dockerfile o un archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de fluent/fluent-bit
-
status: waiting-for-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
fluent/fluent-bit#12683 · 2 comentarios · 6 reacciones ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
fluent/fluent-bit#12657 ·
Los mantenedores suelen responder en 1 día
-
http: Transfer-Encoding chunked plus Content-Length hangs forever (CL branch shadows the chunked path)Posiblemente ocupada @sarankumar1325 la tomó hace 14 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
fluent/fluent-bit#12656 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
fluent/fluent-bit#12649 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 79/100
fluent/fluent-bit#12636 ·
Los mantenedores suelen responder en 1 día
Todos los issues de fluent/fluent-bit
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
micropython/micropython#19942 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 3 días
-
快速入门那里会不会对新手不太友好,提出优化方案Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 84/100
-
-disable-symbolserver is ignored on macOS/Linux: PDBs are still downloaded from msdl.microsoft.comAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100