8390670: [CRaC] Fix restart of streaming JFR recordings - #345
Conversation
|
👋 Welcome back rvansa! A progress list of the required criteria for merging this PR into |
|
@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: 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
Please see this link for an up-to-date comparison between the source branch of this pull request and the ➡️ To integrate this PR with the above commit message to the |
Webrevs
|
| * | ||
| * @crac The recording is automatically stopped on checkpoint and re-created on restore. |
There was a problem hiding this comment.
There's no official recommendation but looks like we've been putting this as the very first tag.
There was a problem hiding this comment.
Same comment for Recording
| PlatformRecording pr = access.getPlatformRecording(rec); | ||
| // Update the owning Recording (important if this was created programmatically) | ||
| access.setPlatformRecording(r.getRecording(), pr); |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
Maybe early return to reduce the nesting?
| /** | ||
| * @test FlightRecorderStreamingTest | ||
| * @library /test/lib | ||
| * @requires (os.family == "linux") |
There was a problem hiding this comment.
Why do we need Linux for this, wouldn't simengine suffice?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Should be final. Or maybe we could update it on restore?
| lock.lock(); | ||
| input.setFile(path); |
There was a problem hiding this comment.
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
| rec.setMaxAge(r.getMaxAge()); | ||
| rec.setMaxSize(r.getMaxSize()); | ||
| pr.setInternalDuration(r.getDuration()); | ||
| rec.setDumpOnExit(r.getDumpOnExit()); | ||
| pr.setFlushInterval(r.getFlushInterval()); |
There was a problem hiding this comment.
reports should also be copied
| if (file != null) { | ||
| try { | ||
| file.close(); | ||
| } catch (IOException e) { | ||
| // perhaps deleted | ||
| } | ||
| file = null; | ||
| } |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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):
- The stream has an
onEventhandler 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
}- Streams with
recording == nullwhere the writer never finishes, e.g. created viaEventStream.openRepositoryto 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
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
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/crac.git pull/345/head:pull/345$ git checkout pull/345Update a local copy of the PR:
$ git checkout pull/345$ git pull https://git.openjdk.org/crac.git pull/345/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 345View PR using the GUI difftool:
$ git pr show -t 345Using diff file
Download this PR as a diff file:
https://git.openjdk.org/crac/pull/345.diff
Using Webrev
Link to Webrev Comment