From ef2e5379f5290fd1d8a57c9e11544b03c86d1b3b Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 6 Apr 2026 21:00:36 +0000 Subject: [PATCH] 8380467: JFR: RepositoryFiles.updatePaths() searches for chunkfiles incorrectly Reviewed-by: egahlin --- .../internal/consumer/RepositoryFiles.java | 8 +- .../streaming/TestRepositoryFilesRescan.java | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryFilesRescan.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java index 09b64efbdd0..1e875691537 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -181,9 +181,9 @@ public final class RepositoryFiles { List added = new ArrayList<>(); Set current = new HashSet<>(); for (Path p : dirStream) { - if (!pathLookup.containsKey(p)) { - String s = p.toString(); - if (s.endsWith(".jfr")) { + String s = p.toString(); + if (s.endsWith(".jfr")) { + if (!pathLookup.containsKey(p)) { added.add(p); Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "New file found: " + p.toAbsolutePath()); } diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryFilesRescan.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryFilesRescan.java new file mode 100644 index 00000000000..0cb80d18698 --- /dev/null +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryFilesRescan.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jfr.api.consumer.streaming; + +import java.nio.file.Files; +import java.nio.file.Path; + +import jdk.jfr.Recording; +import jdk.jfr.internal.consumer.RepositoryFiles; +import jdk.test.lib.Asserts; + +/** + * @test + * @summary Verifies that RepositoryFiles does not oscillate between + * forgetting and rediscovering chunk files between scans + * @requires vm.hasJFR + * @library /test/lib + * @modules jdk.jfr/jdk.jfr.internal.consumer + * @run main/othervm jdk.jfr.api.consumer.streaming.TestRepositoryFilesRescan + */ +public class TestRepositoryFilesRescan { + + public static void main(String... args) throws Exception { + Path dir = Files.createTempDirectory("jfr-rescan-test"); + try { + createChunkFile(dir, "chunk1.jfr"); + createChunkFile(dir, "chunk2.jfr"); + testNoOscillation(dir); + } finally { + deleteDirectory(dir); + } + } + + /** + * firstPath(0, false) will return non-null only when updatePaths discovers + * genuinely new chunk files; with no files added or removed between + * calls the result must be null after the first successful call. + */ + private static void testNoOscillation(Path dir) { + RepositoryFiles rf = new RepositoryFiles(dir, false); + Asserts.assertNotNull(rf.firstPath(0, false), "Call 1: expected non-null (initial discovery of .jfr files)"); + + Path p2 = rf.firstPath(0, false); + Asserts.assertNull(p2, "Call 2: expected null (no new chunks), got " + p2); + + // Call 3: still no new files. This confirms call 2 did not wipe pathLookup, making all files look "new" again. + Path p3 = rf.firstPath(0, false); + Asserts.assertNull(p3, "Call 3: expected null (no new chunks), got " + p3 + + " — pathLookup is oscillating between populated and empty"); + } + + private static void createChunkFile(Path dir, String name) throws Exception { + try (Recording r = new Recording()) { + r.start(); + Thread.sleep(20); + r.stop(); + r.dump(dir.resolve(name)); + } + } + + private static void deleteDirectory(Path dir) { + try { + try (var stream = Files.list(dir)) { + stream.forEach(p -> { + try { + Files.deleteIfExists(p); + } catch (Exception e) { + // Do nothing + } + }); + } + Files.deleteIfExists(dir); + } catch (Exception e) { + // best-effort cleanup + } + } +}