@@ -2042,6 +2042,194 @@ void flb_test_in_tail_ignore_active_older_files()
20422042 test_tail_ctx_destroy (ctx );
20432043}
20442044
2045+ /*
2046+ * Verify that a file excluded by ignore_active_older_files is re-picked up
2047+ * once its mtime is refreshed by a new write.
2048+ *
2049+ * Sequence:
2050+ * 1. Write msg1 → engine reads it (count = 1)
2051+ * 2. Wait 4 s → purge fires (rotate_wait=1s), file is >2s old; inode
2052+ * registered as aged-out and file removed from monitoring
2053+ * 3. Write msg2 → mtime is now fresh
2054+ * 4. Wait 3 s → scan fires (refresh_interval=1s), sees fresh mtime;
2055+ * unregisters aged-out entry and re-adds file at the
2056+ * stored offset (file->offset saved at age-out time);
2057+ * engine reads only msg2 (count += 1)
2058+ * 5. Assert count == 2
2059+ */
2060+ void flb_test_in_tail_ignore_active_older_files_reread_on_update ()
2061+ {
2062+ struct flb_lib_out_cb cb_data ;
2063+ struct test_tail_ctx * ctx ;
2064+ char * file [] = {"source_file_reread.log" };
2065+ char * path = "source_file_reread.log" ;
2066+ char * msg = "TEST LINE" ;
2067+ const int expected = 2 ;
2068+ const int expected_before_rotate = 1 ;
2069+ int ret ;
2070+ int num ;
2071+ int unused ;
2072+
2073+ clear_output_num ();
2074+
2075+ cb_data .cb = cb_count_msgpack ;
2076+ cb_data .data = & unused ;
2077+
2078+ ctx = test_tail_ctx_create (& cb_data , & file [0 ], sizeof (file )/sizeof (char * ), FLB_TRUE );
2079+ if (!TEST_CHECK (ctx != NULL )) {
2080+ TEST_MSG ("test_ctx_create failed" );
2081+ return ;
2082+ }
2083+
2084+ ret = flb_input_set (ctx -> flb , ctx -> o_ffd ,
2085+ "path" , path ,
2086+ "ignore_older" , "2s" ,
2087+ "rotate_wait" , "1s" ,
2088+ "refresh_interval" , "1s" ,
2089+ "read_newly_discovered_files_from_head" , "false" ,
2090+ "ignore_active_older_files" , "on" ,
2091+ NULL );
2092+ TEST_CHECK (ret == 0 );
2093+
2094+ ret = flb_start (ctx -> flb );
2095+ if (!TEST_CHECK (ret == 0 )) {
2096+ test_tail_ctx_destroy (ctx );
2097+ return ;
2098+ }
2099+
2100+ /* Write first message and allow it to be flushed */
2101+ ret = write_msg (ctx , msg , strlen (msg ));
2102+ if (!TEST_CHECK (ret > 0 )) {
2103+ test_tail_ctx_destroy (ctx );
2104+ return ;
2105+ }
2106+
2107+ /* Wait until msg1 is consumed before starting the aging clock. */
2108+ wait_expected_num_with_timeout (5000 , expected_before_rotate , & num );
2109+ if (!TEST_CHECK (num == expected_before_rotate )) {
2110+ TEST_MSG ("msg1 not consumed in time. got=%d" , num );
2111+ test_tail_ctx_destroy (ctx );
2112+ return ;
2113+ }
2114+
2115+ /*
2116+ * Wait long enough for the purge callback (rotate_wait=1s) to fire and
2117+ * detect that the file's mtime is older than ignore_older=2s, which
2118+ * removes the file from monitoring and registers its inode as aged-out.
2119+ */
2120+ flb_time_msleep (4000 );
2121+
2122+ /* Append new content: this updates mtime so the file is no longer old */
2123+ ret = write_msg (ctx , msg , strlen (msg ));
2124+ if (!TEST_CHECK (ret > 0 )) {
2125+ test_tail_ctx_destroy (ctx );
2126+ return ;
2127+ }
2128+
2129+ /*
2130+ * Wait for the scan callback (refresh_interval=1s) to re-evaluate the
2131+ * aged-out entry, find the fresh mtime, unregister the entry, and
2132+ * re-add the file. The file is re-added at the stored offset (the read
2133+ * position saved when the file was aged out), so only msg2 — the content
2134+ * that refreshed the mtime — is flushed (count += 1).
2135+ */
2136+ wait_expected_num_with_timeout (5000 , expected , & num );
2137+
2138+ if (!TEST_CHECK (num == expected )) {
2139+ TEST_MSG ("output num error. expect=%d got=%d" , expected , num );
2140+ }
2141+
2142+ test_tail_ctx_destroy (ctx );
2143+ }
2144+
2145+ void flb_test_in_tail_ignore_active_older_files_reread_on_update_default_read_from_head ()
2146+ {
2147+ struct flb_lib_out_cb cb_data ;
2148+ struct test_tail_ctx * ctx ;
2149+ char * file [] = {"source_file_reread_default.log" };
2150+ char * path = "source_file_reread_default.log" ;
2151+ char * msg = "TEST LINE" ;
2152+ const int expected = 2 ;
2153+ const int expected_before_rotate = 1 ;
2154+ int ret ;
2155+ int num ;
2156+ int unused ;
2157+
2158+ clear_output_num ();
2159+
2160+ cb_data .cb = cb_count_msgpack ;
2161+ cb_data .data = & unused ;
2162+
2163+ ctx = test_tail_ctx_create (& cb_data , & file [0 ], sizeof (file )/sizeof (char * ), FLB_TRUE );
2164+ if (!TEST_CHECK (ctx != NULL )) {
2165+ TEST_MSG ("test_ctx_create failed" );
2166+ return ;
2167+ }
2168+
2169+ /*
2170+ * Do not set read_newly_discovered_files_from_head — leave it at its
2171+ * default (true). The fix in set_file_position must honour the saved
2172+ * offset even when ctx->read_from_head is true, so only msg2 is flushed
2173+ * on re-pickup rather than replaying msg1 from the start.
2174+ */
2175+ ret = flb_input_set (ctx -> flb , ctx -> o_ffd ,
2176+ "path" , path ,
2177+ "ignore_older" , "2s" ,
2178+ "rotate_wait" , "1s" ,
2179+ "refresh_interval" , "1s" ,
2180+ "ignore_active_older_files" , "on" ,
2181+ NULL );
2182+ TEST_CHECK (ret == 0 );
2183+
2184+ ret = flb_start (ctx -> flb );
2185+ if (!TEST_CHECK (ret == 0 )) {
2186+ test_tail_ctx_destroy (ctx );
2187+ return ;
2188+ }
2189+
2190+ /* Write first message and allow it to be flushed */
2191+ ret = write_msg (ctx , msg , strlen (msg ));
2192+ if (!TEST_CHECK (ret > 0 )) {
2193+ test_tail_ctx_destroy (ctx );
2194+ return ;
2195+ }
2196+
2197+ /* Wait until msg1 is consumed before starting the aging clock. */
2198+ wait_expected_num_with_timeout (5000 , expected_before_rotate , & num );
2199+ if (!TEST_CHECK (num == expected_before_rotate )) {
2200+ TEST_MSG ("msg1 not consumed in time. got=%d" , num );
2201+ test_tail_ctx_destroy (ctx );
2202+ return ;
2203+ }
2204+
2205+ /*
2206+ * Wait long enough for the purge callback (rotate_wait=1s) to fire and
2207+ * age out the file.
2208+ */
2209+ flb_time_msleep (4000 );
2210+
2211+ /* Append new content: updates mtime so the file is no longer old */
2212+ ret = write_msg (ctx , msg , strlen (msg ));
2213+ if (!TEST_CHECK (ret > 0 )) {
2214+ test_tail_ctx_destroy (ctx );
2215+ return ;
2216+ }
2217+
2218+ /*
2219+ * The scan callback re-adds the file from the stored offset. With the
2220+ * default read_newly_discovered_files_from_head=true, set_file_position
2221+ * must still seek to the saved offset so msg1 is not replayed.
2222+ * Total expected: 1 (msg1) + 1 (msg2) = 2.
2223+ */
2224+ wait_expected_num_with_timeout (5000 , expected , & num );
2225+
2226+ if (!TEST_CHECK (num == expected )) {
2227+ TEST_MSG ("output num error. expect=%d got=%d" , expected , num );
2228+ }
2229+
2230+ test_tail_ctx_destroy (ctx );
2231+ }
2232+
20452233void flb_test_inotify_watcher_false ()
20462234{
20472235 struct flb_lib_out_cb cb_data ;
@@ -2749,6 +2937,8 @@ TEST_LIST = {
27492937 {"skip_empty_lines_crlf" , flb_test_skip_empty_lines_crlf },
27502938 {"ignore_older" , flb_test_ignore_older },
27512939 {"ignore_active_older_files" , flb_test_in_tail_ignore_active_older_files },
2940+ {"ignore_active_older_files_reread_on_update" , flb_test_in_tail_ignore_active_older_files_reread_on_update },
2941+ {"ignore_active_older_files_reread_on_update_default_read_from_head" , flb_test_in_tail_ignore_active_older_files_reread_on_update_default_read_from_head },
27522942#ifdef FLB_HAVE_INOTIFY
27532943 {"inotify_watcher_false" , flb_test_inotify_watcher_false },
27542944 {"inotify_pause_collectors" , flb_test_inotify_pause_collectors },
0 commit comments