Skip to content

8390670: [CRaC] Fix restart of streaming JFR recordings - #345

Open
rvansa wants to merge 3 commits into
openjdk:cracfrom
rvansa:8390670_jfr_streaming
Open

8390670: [CRaC] Fix restart of streaming JFR recordings#345
rvansa wants to merge 3 commits into
openjdk:cracfrom
rvansa:8390670_jfr_streaming

Conversation

@rvansa

@rvansa rvansa commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

When a recording does not have its destination set we hit a NPE.

There's more problems beyond the NPE, though: the EventDirectoryStream used by RecordingStream can keep a file open.

Also, currently all recordings are attempted to be restarted (recreated). We should do that only with the running ones; not started and closed recordings can be left as-is.



Progress

  • Change must not contain extraneous whitespace

Issue

  • JDK-8390670: [CRaC] Fix restart of streaming JFR recordings (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/crac.git pull/345/head:pull/345
$ git checkout pull/345

Update a local copy of the PR:
$ git checkout pull/345
$ git pull https://git.openjdk.org/crac.git pull/345/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 345

View PR using the GUI difftool:
$ git pr show -t 345

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/crac/pull/345.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper

bridgekeeper Bot commented Aug 19, 2026

Copy link
Copy Markdown

👋 Welcome back rvansa! A progress list of the required criteria for merging this PR into crac will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk

openjdk Bot commented Aug 19, 2026

Copy link
Copy Markdown

@rvansa This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8390670: [CRaC] Fix restart of streaming JFR recordings

You can use pull request commands such as /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 1 new commit pushed to the crac branch:

  • d31e8dc: 8390453: [CRaC] Custom image constraints

Please see this link for an up-to-date comparison between the source branch of this pull request and the crac branch.
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the crac branch, type /integrate in a new comment.

@openjdk openjdk Bot added ready Pull request is ready to be integrated rfr Pull request is ready for review labels Aug 19, 2026
@mlbridge

mlbridge Bot commented Aug 19, 2026

Copy link
Copy Markdown

Webrevs

@rvansa
rvansa requested a review from TimPushkin August 20, 2026 13:36
Comment on lines +62 to +63
*
* @crac The recording is automatically stopped on checkpoint and re-created on restore.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no official recommendation but looks like we've been putting this as the very first tag.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment for Recording

Comment on lines +94 to +96
PlatformRecording pr = access.getPlatformRecording(rec);
// Update the owning Recording (important if this was created programmatically)
access.setPlatformRecording(r.getRecording(), pr);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we create a new Recording (let's call it R1) with the same settings as some old one (let's call it R0), it creates a new PlatformRecording, then we make the old R0 point to the new PlatformRecording. But we never make the new PlatformRecording point to R0, it remains pointing to R1. So, from the second checkpoint onwards we don't have a link to R0, the actual Recording that the user code holds, and it stops getting updated.

Also it's hard to understand the difference between r, pr, rec. I think even something like oldRecording, oldPlatformRecording, newRecording, newPlatformRecording (maybe shortened, but keeping the old/new platform/non-platform pairs visible) would make it more readable.

private void backupOriginalDestination(WriteablePath original) {
try {
File destFile = original.getReal().toFile();
if (destFile.exists()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe early return to reduce the nesting?

/**
* @test FlightRecorderStreamingTest
* @library /test/lib
* @requires (os.family == "linux")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need Linux for this, wouldn't simengine suffice?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question for the other test

Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Unexpected chunk with 0 ns duration");
}
}
path = repositoryFiles.nextPath(currentChunkStartNanos + durationNanos, getWaitForChunks());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing opens directories and files in a loop outside the lock. AI found this, worth checking the rest of the code outside the lock.

private boolean destroyed;
private JDKResource resource = new JDKResource() {
private List<PlatformRecording> futureRecordings;
private static int MAX_BACKUPS = Integer.getInteger("jdk.jfr.max_backups", 20);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be final. Or maybe we could update it on restore?

Comment on lines +235 to 236
lock.lock();
input.setFile(path);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC the PlatformRecorder's resource may change the path and delete the old file (by closing the recording) while this thread waits on the lock, then this setFile will fail since the file on the old path does not exist anymore

Comment on lines 113 to 117
rec.setMaxAge(r.getMaxAge());
rec.setMaxSize(r.getMaxSize());
pr.setInternalDuration(r.getDuration());
rec.setDumpOnExit(r.getDumpOnExit());
pr.setFlushInterval(r.getFlushInterval());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reports should also be copied

Comment on lines +435 to 442
if (file != null) {
try {
file.close();
} catch (IOException e) {
// perhaps deleted
}
file = null;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the null check needed? The only place where file is set to null is in this method which then sets in back to non-null unconditionally. The only way it remains null is if this methods throws, but the only caller (the EventDirectoryStream loop) propagates the exception out of the scope where this object exists, so another setFile call is not possible.

The null check here is good as a defense, but since it does not seem CRaC-related for us it's a needless difference from the mainline.

@Override
public void beforeCheckpoint(Context<? extends Resource> context) throws Exception {
// Ensures that the thread in processRecursionSafe() does not have an open file
lock.lock();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should put some timeout here. AI tells me this wait can last forever at least in these cases (the reproducers are also AI-generated, I didn't test them):

  1. The stream has an onEvent handler that got blocked — probably a user's fault for which it's OK to stall.
var queue = new ArrayBlockingQueue<RecordedEvent>(10);   // nobody draining it
try (var rs = new RecordingStream()) {
    rs.enable("jdk.CPULoad").withPeriod(Duration.ofMillis(1));
    rs.onEvent("jdk.CPULoad", e -> {
        try { queue.put(e); } catch (InterruptedException ignored) {}   // blocks once full
    });
    rs.startAsync();
    Thread.sleep(1000);                                  // let the queue fill
    CRaCMXBean.getCRaCMXBean().checkpointRestore();      // never returns
}
  1. Streams with recording == null where the writer never finishes, e.g. created via EventStream.openRepository to a repository of another JVM that died — this seems like something we should not stall on.
$ java -XX:StartFlightRecording -XX:FlightRecorderOptions:repository=/tmp/repo Foo &
$ sleep 2 && kill -9 %1        # chunk left with fileState != 0, never finalized

// in the JVM being checkpointed
var es = EventStream.openRepository(Path.of("/tmp/repo/<subdir>"));
es.onEvent(e -> {});
es.startAsync();
CRaCMXBean.getCRaCMXBean().checkpointRestore();   // never returns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Pull request is ready to be integrated rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

2 participants