JDK-8225690 : Multiple AttachListener threads can be created
  • Type: Bug
  • Component: hotspot
  • Sub-Component: svc
  • Affected Version: 8,11,13
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2019-06-13
  • Updated: 2023-11-08
  • Resolved: 2019-07-15
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 11 JDK 14 JDK 8 Other
11.0.13-oracleFixed 14 b06Fixed 8u401Fixed na,openjdk8u332Resolved
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
nijiaben @ PerfMa reports:

Yesterday our colleague found a strange situation. After executing jstack, I saw multiple Attach Listener threads in the output. I was very confused at the time and felt that it was unlikely that the Attach Listener should theoretically exist only one.

So I followed the code of the hotspot, this situation may indeed exist. When we execute jstack operations concurrently on a process, it is likely that multiple Attach Listener threads are created. By default, the Attach Listener is not created when jvm is started. Instead, when we first trigger through the attach mechanism, the Attach Listener thread is created by the Signal Dispatcher thread.

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);
  while (true) {
    int sig;
    {
      // FIXME : Currently we have not decieded what should be the status
      //         for this java thread blocked here. Once we decide about
      //         that we should fix this.
      sig = os::signal_wait();
    }
    if (sig == os::sigexitnum_pd()) {
       // Terminate the signal thread
       return;
    }

    switch (sig) {
      case SIGBREAK: {
        // Check if the signal is a trigger to start the Attach Listener - in that
        // case don't print stack traces.
        if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
          continue;
        }
        ...
    }
}

bool AttachListener::is_init_trigger() {
  if (init_at_startup() || is_initialized()) {
    return false;               // initialized at startup or already initialized
  }
  char fn[PATH_MAX+1];
  sprintf(fn, ".attach_pid%d", os::current_process_id());
  int ret;
  struct stat64 st;
  RESTARTABLE(::stat64(fn, &st), ret);
  if (ret == -1) {
    snprintf(fn, sizeof(fn), "%s/.attach_pid%d",
             os::get_temp_directory(), os::current_process_id());
    RESTARTABLE(::stat64(fn, &st), ret);
  }
  if (ret == 0) {
    // simple check to avoid starting the attach mechanism when
    // a bogus user creates the file
    if (st.st_uid == geteuid()) {
      init();
      return true;
    }
  }
  return false;
}

The Attach Listener thread is created in the AttachListener::init method above, but the _initialized property is used to pre-determine whether the thread needs to be created before the init method is executed, and _initialized is set to true in the attach_listener_thread_entry

static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);

  thread->record_stack_base_and_size();

  if (AttachListener::pd_init() != 0) {
    return;
  }
  AttachListener::set_initialized();
  ...
}

However, if more than one request signal is sent before _initialized=true is set, multiple Attach Listeners may be created because the Signal Dispatcher and Attach Listener threads are executed asynchronously.

We can zoom in on this problem by modifying the code in hotspot

static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);

  thread->record_stack_base_and_size();

  sleep(15);
  
  if (AttachListener::pd_init() != 0) {
    return;
  }
  AttachListener::set_initialized();
  ...
}

When the process is started, keep executing jstack <pid>, and you will eventually see that there are a lot of Attach Listener threads.

"Attach Listener" #16 daemon prio=9 os_prio=0 tid=0x00007f21a800e000 nid=0x35b8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #15 daemon prio=9 os_prio=0 tid=0x00007f21a800c000 nid=0x35a5 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #14 daemon prio=9 os_prio=0 tid=0x00007f21a800a000 nid=0x3593 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #13 daemon prio=9 os_prio=0 tid=0x00007f21a8008000 nid=0x3582 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #12 daemon prio=9 os_prio=0 tid=0x00007f21a8006800 nid=0x3570 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #11 daemon prio=9 os_prio=0 tid=0x00007f21a8004800 nid=0x355e runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #10 daemon prio=9 os_prio=0 tid=0x00007f21a8002800 nid=0x354b runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #9 daemon prio=9 os_prio=0 tid=0x00007f21a8001000 nid=0x3539 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
...

In general, the creation of the Attach Listener thread is created by the Signal Dispatcher thread, but the flag that determines whether the Signal Dispatcher can repeatedly create the Attach Listener thread is set in an Attach Listener thread. If the flag is not set in time, There may be cases where multiple Attach Listener threads are created.

Comments
I was backporting this to jdk8u-cpu and found this issue and thought of sharing it here. [~ysuenaga] in the backport of openjdk8u, the testcases are not covered as they are missing @test flag. The test are never executed because of that, and it has multiple issues like the test refers to "@modules jdk.attach/com.sun.tools.attach", which is not the case in jdk8. It also uses the /testlib structure same as mainline, but in jdk8 that is not the case.
28-09-2023

Changeset: 9c13e217 Author: Yasumasa Suenaga <ysuenaga@openjdk.org> Date: 2021-10-21 14:10:23 +0000 URL: https://git.openjdk.org/shenandoah-jdk8u/commit/9c13e21725f12cf3091086c174b1d48875c980da
01-09-2022

[~yyang] Approved. Please remember to push to: http://hg.openjdk.java.net/jdk8u/monojdk8u-dev/
13-12-2021

[~sgehwolf] Can you please take a look at this, as well as related backports(JDK-8275694 and JDK-8275695)? Thanks!
10-12-2021

I lgtm'ed your patch in https://mail.openjdk.java.net/pipermail/jdk8u-dev/2021-November/014414.html because it fixed the long vs int size issue, so should be functionally ok. I'm not a maintainer, so can't approve the backport. I'm happy to sponsor though.
10-12-2021

[~phh] Can you take a look at this? I've labeled jdk8u-fix-request. Thanks in advanced!
10-12-2021

Fix Request (8u): Backport to jdk8u. VM can't be attached If someone cleans up the /tmp directory. This patch addresses the problem. It can not apply cleanly, some minor conflicts are listed in https://mail.openjdk.java.net/pipermail/jdk8u-dev/2021-November/014412.html. Testes passed manually.
11-11-2021

Hi Christoph, right, I've requested JDK-8227738, too. But I can only apply it after this one is integrated. I haven't seen the failure from JDK-8235211, yet. I think we should probably backport that one later, too.
21-07-2021

Hi [~mdoerr], there are a few bugs linked to this one. I see you have already requested JDK-8227815. Can you please check whether JDK-8227738 and JDK-8235211 are applicable as well? Looks like potential (test-)bugs...
21-07-2021

Fix Request (11u): Should get backported for parity with 11.0.13-oracle. Applies cleanly.
20-07-2021

URL: https://hg.openjdk.java.net/jdk/jdk/rev/70fab3a8ff02 User: ysuenaga Date: 2019-07-15 22:29:55 +0000
15-07-2019

May be related to: JDK-8225193 Jcmd fails to attach, send signal "quit" and cause a lot of thread dumps
21-06-2019

A simple fix would be to add an _is_initializing flag that is also checked in is_init_trigger(). It would cause is_init_trigger() to still return true, but internally it would otherwise be a no-op. That would prevent racing concurrent attach signals from triggering the default thread-dump response. The only downside is that during that window a ctrl-/ from the command-line would also be ignored (but that is probably the case now as it could be mistaken for a subsequent attach request).
13-06-2019