88import static org .junit .jupiter .api .Assertions .assertTrue ;
99
1010import datadog .trace .api .config .ProfilingConfig ;
11+ import datadog .trace .api .time .ControllableTimeSource ;
12+ import datadog .trace .api .time .SystemTimeSource ;
13+ import datadog .trace .api .time .TimeSource ;
1114import datadog .trace .bootstrap .config .provider .ConfigProvider ;
1215import java .io .IOException ;
1316import java .nio .file .FileVisitResult ;
1619import java .nio .file .Paths ;
1720import java .nio .file .attribute .BasicFileAttributes ;
1821import java .nio .file .attribute .PosixFilePermissions ;
19- import java .time .Duration ;
2022import java .util .ArrayList ;
2123import java .util .List ;
2224import java .util .Properties ;
2527import java .util .concurrent .TimeUnit ;
2628import java .util .concurrent .atomic .AtomicBoolean ;
2729import java .util .concurrent .atomic .AtomicReference ;
28- import java .util .concurrent .locks .LockSupport ;
2930import java .util .stream .Stream ;
3031import org .junit .jupiter .api .Test ;
3132import org .junit .jupiter .params .ParameterizedTest ;
@@ -232,23 +233,33 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc, boolean tim
232233 void testCleanupWithTimeout (boolean shouldSucceed , String section ) throws Exception {
233234 /*
234235 * Test that cleanup correctly handles timeout conditions.
235- * Uses a hook that introduces delays at specific points in the file tree walk.
236+ * Uses a ControllableTimeSource for fully deterministic behavior:
237+ * - Success case: time is never advanced, so timeout never occurs
238+ * - Failure case: time is advanced past timeout on first matching hook call
236239 *
237- * Timing strategy (more generous for JDK 8 compatibility):
238- * - Base delay: 100ms (was 10ms - too tight for JDK 8 timer resolution)
239- * - Success case: 500ms timeout with 100ms delays → plenty of margin
240- * - Failure case: 20ms timeout with 100ms delays → guaranteed timeout
240+ * This approach is independent of how many times hooks are invoked.
241241 */
242- long delayMs = 100 ;
242+ long timeoutMs = 100 ;
243+ ControllableTimeSource timeSource = new ControllableTimeSource ();
244+
243245 AtomicBoolean withTimeout = new AtomicBoolean (false );
246+ AtomicBoolean timeAdvanced = new AtomicBoolean (false );
244247 TempLocationManager .CleanupHook delayer =
245248 new TempLocationManager .CleanupHook () {
249+ private void maybeAdvanceTime () {
250+ // Only advance time once, and only for failure case
251+ if (!shouldSucceed && timeAdvanced .compareAndSet (false , true )) {
252+ // Advance well past the timeout
253+ timeSource .advance (TimeUnit .MILLISECONDS .toNanos (timeoutMs * 10 ));
254+ }
255+ }
256+
246257 @ Override
247258 public FileVisitResult preVisitDirectory (
248259 Path dir , BasicFileAttributes attrs , boolean timeout ) throws IOException {
249260 withTimeout .compareAndSet (false , timeout );
250261 if (section .equals ("preVisitDirectory" )) {
251- waitFor ( Duration . ofMillis ( delayMs ) );
262+ maybeAdvanceTime ( );
252263 }
253264 return TempLocationManager .CleanupHook .super .preVisitDirectory (dir , attrs , timeout );
254265 }
@@ -258,7 +269,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc, boolean timeo
258269 throws IOException {
259270 withTimeout .compareAndSet (false , timeout );
260271 if (section .equals ("visitFileFailed" )) {
261- waitFor ( Duration . ofMillis ( delayMs ) );
272+ maybeAdvanceTime ( );
262273 }
263274 return TempLocationManager .CleanupHook .super .visitFileFailed (file , exc , timeout );
264275 }
@@ -268,7 +279,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc, boolean tim
268279 throws IOException {
269280 withTimeout .compareAndSet (false , timeout );
270281 if (section .equals ("postVisitDirectory" )) {
271- waitFor ( Duration . ofMillis ( delayMs ) );
282+ maybeAdvanceTime ( );
272283 }
273284 return TempLocationManager .CleanupHook .super .postVisitDirectory (dir , exc , timeout );
274285 }
@@ -278,7 +289,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs, boolean t
278289 throws IOException {
279290 withTimeout .compareAndSet (false , timeout );
280291 if (section .equals ("visitFile" )) {
281- waitFor ( Duration . ofMillis ( delayMs ) );
292+ maybeAdvanceTime ( );
282293 }
283294 return TempLocationManager .CleanupHook .super .visitFile (file , attrs , timeout );
284295 }
@@ -287,16 +298,15 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs, boolean t
287298 Files .createTempDirectory (
288299 "ddprof-test-" ,
289300 PosixFilePermissions .asFileAttribute (PosixFilePermissions .fromString ("rwx------" )));
290- TempLocationManager instance = instance (baseDir , false , delayer );
301+ TempLocationManager instance = instance (baseDir , false , delayer , timeSource );
291302 Path mytempdir = instance .getTempDir ();
292303 Path otherTempdir = mytempdir .getParent ().resolve ("pid_0000" );
293304 Files .createDirectories (otherTempdir );
294305 Files .createFile (mytempdir .resolve ("dummy" ));
295306 Files .createFile (otherTempdir .resolve ("dummy" ));
296- // Success: 500ms timeout (5 * 100ms) with 100ms delays → should complete
297- // Failure: 20ms timeout (0.2 * 100ms) with 100ms delays → should timeout
298- boolean rslt =
299- instance .cleanup ((long ) (delayMs * (shouldSucceed ? 5 : 0.2d )), TimeUnit .MILLISECONDS );
307+ // Set controllable time AFTER files are created so their modification times are in the past
308+ timeSource .set (TimeUnit .MILLISECONDS .toNanos (System .currentTimeMillis ()));
309+ boolean rslt = instance .cleanup (timeoutMs , TimeUnit .MILLISECONDS );
300310 assertEquals (shouldSucceed , rslt );
301311 assertNotEquals (shouldSucceed , withTimeout .get ()); // timeout = !shouldSucceed
302312 }
@@ -313,23 +323,21 @@ private static Stream<Arguments> timeoutTestArguments() {
313323
314324 private TempLocationManager instance (
315325 Path baseDir , boolean withStartupCleanup , TempLocationManager .CleanupHook cleanupHook ) {
326+ return instance (baseDir , withStartupCleanup , cleanupHook , SystemTimeSource .INSTANCE );
327+ }
328+
329+ private TempLocationManager instance (
330+ Path baseDir ,
331+ boolean withStartupCleanup ,
332+ TempLocationManager .CleanupHook cleanupHook ,
333+ TimeSource timeSource ) {
316334 Properties props = new Properties ();
317335 props .put (ProfilingConfig .PROFILING_TEMP_DIR , baseDir .toString ());
318336 props .put (
319337 ProfilingConfig .PROFILING_UPLOAD_PERIOD ,
320338 "0" ); // to force immediate cleanup; must be a string value!
321339
322340 return new TempLocationManager (
323- ConfigProvider .withPropertiesOverride (props ), withStartupCleanup , cleanupHook );
324- }
325-
326- private void waitFor (Duration timeout ) {
327- long target = System .nanoTime () + timeout .toNanos ();
328- while (System .nanoTime () < target ) {
329- long toSleep = target - System .nanoTime ();
330- if (toSleep > 0 ) {
331- LockSupport .parkNanos (toSleep );
332- }
333- }
341+ ConfigProvider .withPropertiesOverride (props ), withStartupCleanup , cleanupHook , timeSource );
334342 }
335343}
0 commit comments