JDK-8333542 : Breakpoint in parallel code does not work
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 20,21,22,23,24
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2024-06-04
  • Updated: 2024-08-15
  • Resolved: 2024-06-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 21 JDK 23 JDK 24
21.0.5-oracleFixed 23Fixed 24 b04Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Steps to reproduce:
- use the code:
class A {
    static void foo(int k) {
        if (k == 2) {
            System.out.println("HIT = " + k); // set breakpoint here
        }
    }
}


public class HelloWorld {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Start");
        for (int i = 1; i <= 100; i++) {
            int k = i;
            Thread t = new Thread(() -> {
                System.out.println("i = " + k);
                A.foo(k);
            });
            t.start();
        }

        Thread.sleep(2000);

        System.out.println("Finish");
    }
}
- compile
- use jdb to set a breakpoint and run:
>jdb HelloWorld
Initializing jdb ...
> stop at A:4
Deferring breakpoint A:4.
It will be set after the class is loaded.
> run
run HelloWorld
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Start
i = 21
i = 45
i = 11
i = 16
i = 23
i = 7
i = 51
i = 39
i = 20
i = 40
i = 31
i = 5
i = 6
i = 4
i = 41
i = 53
i = 9
i = 2
i = 13
i = 1
i = 3
i = 15
i = 22
HIT = 2
i = 10
i = 8
i = 14
i = 12
i = 17
i = 52
i = 19
i = 24
i = 44
i = 18
i = 55
i = 25
i = 56
i = 26
i = 34
i = 43
i = 37
i = 33
i = 28
i = 30
i = 32
i = 35
i = 38
i = 27
i = 47
i = 49
i = 59
i = 60
i = 48
i = 62
i = 54
i = 58
i = 64
i = 67
i = 68
i = 70
i = 73
i = 69
i = 29
i = 75
i = 77
i = 76
i = 74
i = 84
i = 81
i = 80
i = 85
i = 36
i = 86
i = 87
i = 91
i = 93
i = 92
i = 95
i = 99
i = 90
i = 96
Set deferred breakpoint A:4
i = 46
i = 61
i = 42
i = 50
i = 57
i = 63
i = 65
i = 66
i = 71
i = 72
i = 79
i = 78
i = 83
i = 82
i = 88
i = 89
i = 94
i = 98
i = 97
i = 100
Finish

The application exited

The breakpoint is fully skipped.
This was working in version before 20.
Comments
jdk21u-dev backport request I would like to have the patch in jdk21u-dev as well, for parity with OracleJDK. The backport is mostly clean (with some small differrences e.g. in cpCache.cpp) and low - to - medium risk .
16-07-2024

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk21u-dev/pull/823 Date: 2024-07-04 14:18:40 +0000
09-07-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/19938 Date: 2024-06-28 12:14:55 +0000
28-06-2024

Changeset: b3bf31a0 Author: Coleen Phillimore <coleenp@openjdk.org> Date: 2024-06-25 19:50:58 +0000 URL: https://git.openjdk.org/jdk/commit/b3bf31a0a08da679ec2fd21613243fb17b1135a9
25-06-2024

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/19755 Date: 2024-06-17 17:58:14 +0000
21-06-2024

The key change was this in link_class_impl: - ObjectLocker ol(h_init_lock, jt); + LockLinkState init_lock(this, jt); Despite the name, LockLinkState does not acquire the lock the way ObjectLocker did: LockLinkState(InstanceKlass* ik, JavaThread* current) : _ik(ik), _current(current) { ik->check_link_state_and_wait(current); } we need to hold a lock across the same scope so that the JVMTI event notification is protected by it.
13-06-2024

AFAICS before JDK-8288064 there was no notification in the linking code path. But we would have still have been holding the monitor (now the mutex) when post_class_prepare is called. So perhaps the issue is a change to scope of the mutex for other threads waiting for the loading to complete?
11-06-2024

Updating priority based on new understanding of the issue: ILW=MMM=P3
10-06-2024

This was introduced by JDK-8288064, which moved notification of the class prepare to just before call the jvmti hook: set_initialization_state_and_notify(linked, THREAD); if (JvmtiExport::should_post_class_prepare()) { JvmtiExport::post_class_prepare(THREAD, this); } Changing this so the set_initialization_state_and_notify is done after the post_class_prepare() does work because then during the handling of the ClassPrepareEvent the debugger ends up trying to use the class before it is marked as prepared, so an exception is thrown (because of a JDWP error). The "set initialization state" part needs to be separated from the notify part so the class can be marked as initialized before calling post_class_prepare() and then the notify can happen after (which is how it used to be done).
04-06-2024

I undid JDK-8295375 and it still reproduces. It looks like the ClassPrepareEvent that triggers setting up the deferred breakpoint always comes in on the first thread created, and has SUSPEND_ALL. Yet the 2nd thread gets to the breakpoint line before the breakpoint is actually setup. I suspect a hotspot or jvmti change that allows other threads that need A to run once A is prepared, but before the calls to ClassPrepareEvent event handlers have returned.
04-06-2024

Yes, it is related to simultaneous class loading: in pre 20 it was not important how ClassPrepareEvent was suspending other threads - they all were blocked on a lock inside the class loading and suspending one thread was enough to hold them all. Maybe something changed in class loading process? Now it seems like it really tries to stop all threads as requested in the ClassPrepareEvent, but for some threads it is too late.
04-06-2024

Here's my guess. One of the threads will end up being the first to trigger the loading of A. This thread will generate a ClassPrepareEvent, which when received by the debugger will trigger setting the breakpoint. From what I can tell this ClassPrepareEvent should result in suspending all threads until the debugger is done handling it, but possibly it is not. If it is not, other threads are free to run during this time, allowing them to get passed the breakpoint before it is setup. There was one change in 20 related to ClassPrepareEvents. I'm not sure if it is related or not. JDK-8295375 debug agent class tracking should not piggy back on the cbClassPrepare() callback
04-06-2024

Happening in intellij as well
04-06-2024

Is this also happening with IntelliJ?
04-06-2024

ILW=LML=P5
04-06-2024

Yes, sorry, removed the package and forgot to move line numbers
04-06-2024

> stop at A:6 Shouldn't this be A:4?
04-06-2024