changeset 54385:9559ba212c18

8221102: Allow GC threads to participate in threads claiming protocol Summary: Expand claim counter from 1bit to uintx, with rare overflow handling. Reviewed-by: tschatzl, rkennke
author kbarrett
date Tue, 02 Apr 2019 13:08:38 -0400
parents cd3b7ad53265
children 00fc7ba000b4
files src/hotspot/share/gc/g1/g1ConcurrentMark.cpp src/hotspot/share/gc/shared/genCollectedHeap.cpp src/hotspot/share/gc/shared/strongRootsScope.cpp src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp src/hotspot/share/gc/z/zRootsIterator.cpp src/hotspot/share/runtime/sweeper.cpp src/hotspot/share/runtime/thread.cpp src/hotspot/share/runtime/thread.hpp test/hotspot/gtest/runtime/test_threads.cpp
diffstat 9 files changed, 260 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -1766,17 +1766,17 @@
   G1CMSATBBufferClosure _cm_satb_cl;
   G1CMOopClosure _cm_cl;
   MarkingCodeBlobClosure _code_cl;
-  int _thread_parity;
+  uintx _claim_token;
 
  public:
   G1RemarkThreadsClosure(G1CollectedHeap* g1h, G1CMTask* task) :
     _cm_satb_cl(task, g1h),
     _cm_cl(g1h, task),
     _code_cl(&_cm_cl, !CodeBlobToOopClosure::FixRelocations),
-    _thread_parity(Threads::thread_claim_parity()) {}
+    _claim_token(Threads::thread_claim_token()) {}
 
   void do_thread(Thread* thread) {
-    if (thread->claim_oops_do(true, _thread_parity)) {
+    if (thread->claim_threads_do(true, _claim_token)) {
       SATBMarkQueue& queue = G1ThreadLocalData::satb_mark_queue(thread);
       queue.apply_closure_and_empty(&_cm_satb_cl);
       if (thread->is_Java_thread()) {
--- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -824,7 +824,6 @@
                                      CLDClosure* weak_cld_closure,
                                      CodeBlobToOopClosure* code_roots) {
   // General roots.
-  assert(Threads::thread_claim_parity() != 0, "must have called prologue code");
   assert(code_roots != NULL, "code root closure should always be set");
   // _n_termination for _process_strong_tasks should be set up stream
   // in a method not running in a GC worker.  Otherwise the GC worker
--- a/src/hotspot/share/gc/shared/strongRootsScope.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/gc/shared/strongRootsScope.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2019, 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
@@ -37,7 +37,7 @@
 }
 
 StrongRootsScope::StrongRootsScope(uint n_threads) : _n_threads(n_threads) {
-  Threads::change_thread_claim_parity();
+  Threads::change_thread_claim_token();
 }
 
 StrongRootsScope::~StrongRootsScope() {
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -213,15 +213,15 @@
 class ShenandoahSATBThreadsClosure : public ThreadClosure {
 private:
   ShenandoahSATBBufferClosure* _satb_cl;
-  int _thread_parity;
+  uintx _claim_token;
 
 public:
   ShenandoahSATBThreadsClosure(ShenandoahSATBBufferClosure* satb_cl) :
     _satb_cl(satb_cl),
-    _thread_parity(Threads::thread_claim_parity()) {}
+    _claim_token(Threads::thread_claim_token()) {}
 
   void do_thread(Thread* thread) {
-    if (thread->claim_oops_do(true, _thread_parity)) {
+    if (thread->claim_threads_do(true, _claim_token)) {
       ShenandoahThreadLocalData::satb_mark_queue(thread).apply_closure_and_empty(_satb_cl);
     }
   }
--- a/src/hotspot/share/gc/z/zRootsIterator.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/gc/z/zRootsIterator.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2019, 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
@@ -179,7 +179,7 @@
     _code_cache(this) {
   assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");
   ZStatTimer timer(ZSubPhasePauseRootsSetup);
-  Threads::change_thread_claim_parity();
+  Threads::change_thread_claim_token();
   COMPILER2_PRESENT(DerivedPointerTable::clear());
   if (ClassUnloading) {
     nmethod::oops_do_marking_prologue();
@@ -404,7 +404,7 @@
     _threads(this) {
   assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint");
   ZStatTimer timer(ZSubPhasePauseRootsSetup);
-  Threads::change_thread_claim_parity();
+  Threads::change_thread_claim_token();
 }
 
 ZThreadRootsIterator::~ZThreadRootsIterator() {
--- a/src/hotspot/share/runtime/sweeper.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/runtime/sweeper.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2019, 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
@@ -219,7 +219,7 @@
   NMethodMarkingTask(NMethodMarkingThreadClosure* cl) :
     AbstractGangTask("Parallel NMethod Marking"),
     _cl(cl) {
-    Threads::change_thread_claim_parity();
+    Threads::change_thread_claim_token();
   }
 
   ~NMethodMarkingTask() {
--- a/src/hotspot/share/runtime/thread.cpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/runtime/thread.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -238,8 +238,8 @@
   set_last_handle_mark(NULL);
   DEBUG_ONLY(_missed_ic_stub_refill_verifier = NULL);
 
-  // This initial value ==> never claimed.
-  _oops_do_parity = 0;
+  // Initial value of zero ==> never claimed.
+  _threads_do_token = 0;
   _threads_hazard_ptr = NULL;
   _threads_list_ptr = NULL;
   _nested_threads_hazard_ptr_cnt = 0;
@@ -885,16 +885,14 @@
 
 
 // GC Support
-bool Thread::claim_oops_do_par_case(int strong_roots_parity) {
-  int thread_parity = _oops_do_parity;
-  if (thread_parity != strong_roots_parity) {
-    jint res = Atomic::cmpxchg(strong_roots_parity, &_oops_do_parity, thread_parity);
-    if (res == thread_parity) {
+bool Thread::claim_par_threads_do(uintx claim_token) {
+  uintx token = _threads_do_token;
+  if (token != claim_token) {
+    uintx res = Atomic::cmpxchg(claim_token, &_threads_do_token, token);
+    if (res == token) {
       return true;
-    } else {
-      guarantee(res == strong_roots_parity, "Or else what?");
-      return false;
     }
+    guarantee(res == claim_token, "invariant");
   }
   return false;
 }
@@ -3472,7 +3470,7 @@
 int         Threads::_number_of_threads = 0;
 int         Threads::_number_of_non_daemon_threads = 0;
 int         Threads::_return_code = 0;
-int         Threads::_thread_claim_parity = 0;
+uintx       Threads::_thread_claim_token = 1; // Never zero.
 size_t      JavaThread::_stack_size_at_create = 0;
 
 #ifdef ASSERT
@@ -3532,14 +3530,14 @@
 }
 
 void Threads::possibly_parallel_threads_do(bool is_par, ThreadClosure* tc) {
-  int cp = Threads::thread_claim_parity();
+  uintx claim_token = Threads::thread_claim_token();
   ALL_JAVA_THREADS(p) {
-    if (p->claim_oops_do(is_par, cp)) {
+    if (p->claim_threads_do(is_par, claim_token)) {
       tc->do_thread(p);
     }
   }
   VMThread* vmt = VMThread::vm_thread();
-  if (vmt->claim_oops_do(is_par, cp)) {
+  if (vmt->claim_threads_do(is_par, claim_token)) {
     tc->do_thread(vmt);
   }
 }
@@ -4525,27 +4523,39 @@
   VMThread::vm_thread()->oops_do(f, cf);
 }
 
-void Threads::change_thread_claim_parity() {
-  // Set the new claim parity.
-  assert(_thread_claim_parity >= 0 && _thread_claim_parity <= 2,
-         "Not in range.");
-  _thread_claim_parity++;
-  if (_thread_claim_parity == 3) _thread_claim_parity = 1;
-  assert(_thread_claim_parity >= 1 && _thread_claim_parity <= 2,
-         "Not in range.");
+void Threads::change_thread_claim_token() {
+  if (++_thread_claim_token == 0) {
+    // On overflow of the token counter, there is a risk of future
+    // collisions between a new global token value and a stale token
+    // for a thread, because not all iterations visit all threads.
+    // (Though it's pretty much a theoretical concern for non-trivial
+    // token counter sizes.)  To deal with the possibility, reset all
+    // the thread tokens to zero on global token overflow.
+    struct ResetClaims : public ThreadClosure {
+      virtual void do_thread(Thread* t) {
+        t->claim_threads_do(false, 0);
+      }
+    } reset_claims;
+    Threads::threads_do(&reset_claims);
+    // On overflow, update the global token to non-zero, to
+    // avoid the special "never claimed" initial thread value.
+    _thread_claim_token = 1;
+  }
 }
 
 #ifdef ASSERT
+void assert_thread_claimed(const char* kind, Thread* t, uintx expected) {
+  const uintx token = t->threads_do_token();
+  assert(token == expected,
+         "%s " PTR_FORMAT " has incorrect value " UINTX_FORMAT " != "
+         UINTX_FORMAT, kind, p2i(t), token, expected);
+}
+
 void Threads::assert_all_threads_claimed() {
   ALL_JAVA_THREADS(p) {
-    const int thread_parity = p->oops_do_parity();
-    assert((thread_parity == _thread_claim_parity),
-           "Thread " PTR_FORMAT " has incorrect parity %d != %d", p2i(p), thread_parity, _thread_claim_parity);
-  }
-  VMThread* vmt = VMThread::vm_thread();
-  const int thread_parity = vmt->oops_do_parity();
-  assert((thread_parity == _thread_claim_parity),
-         "VMThread " PTR_FORMAT " has incorrect parity %d != %d", p2i(vmt), thread_parity, _thread_claim_parity);
+    assert_thread_claimed("Thread", p, _thread_claim_token);
+  }
+  assert_thread_claimed("VMThread", VMThread::vm_thread(), _thread_claim_token);
 }
 #endif // ASSERT
 
--- a/src/hotspot/share/runtime/thread.hpp	Tue Apr 02 09:45:52 2019 -0700
+++ b/src/hotspot/share/runtime/thread.hpp	Tue Apr 02 13:08:38 2019 -0400
@@ -338,9 +338,8 @@
   // Point to the last handle mark
   HandleMark* _last_handle_mark;
 
-  // The parity of the last strong_roots iteration in which this thread was
-  // claimed as a task.
-  int _oops_do_parity;
+  // Claim value for parallel iteration over threads.
+  uintx _threads_do_token;
 
   // Support for GlobalCounter
  private:
@@ -647,27 +646,28 @@
   // Apply "cf->do_code_blob" (if !NULL) to all code blobs active in frames
   virtual void oops_do(OopClosure* f, CodeBlobClosure* cf);
 
-  // Handles the parallel case for the method below.
+  // Handles the parallel case for claim_threads_do.
  private:
-  bool claim_oops_do_par_case(int collection_parity);
+  bool claim_par_threads_do(uintx claim_token);
  public:
-  // Requires that "collection_parity" is that of the current roots
-  // iteration.  If "is_par" is false, sets the parity of "this" to
-  // "collection_parity", and returns "true".  If "is_par" is true,
-  // uses an atomic instruction to set the current threads parity to
-  // "collection_parity", if it is not already.  Returns "true" iff the
+  // Requires that "claim_token" is that of the current iteration.
+  // If "is_par" is false, sets the token of "this" to
+  // "claim_token", and returns "true".  If "is_par" is true,
+  // uses an atomic instruction to set the current thread's token to
+  // "claim_token", if it is not already.  Returns "true" iff the
   // calling thread does the update, this indicates that the calling thread
-  // has claimed the thread's stack as a root group in the current
-  // collection.
-  bool claim_oops_do(bool is_par, int collection_parity) {
+  // has claimed the thread in the current iteration.
+  bool claim_threads_do(bool is_par, uintx claim_token) {
     if (!is_par) {
-      _oops_do_parity = collection_parity;
+      _threads_do_token = claim_token;
       return true;
     } else {
-      return claim_oops_do_par_case(collection_parity);
+      return claim_par_threads_do(claim_token);
     }
   }
 
+  uintx threads_do_token() const { return _threads_do_token; }
+
   // jvmtiRedefineClasses support
   void metadata_handles_do(void f(Metadata*));
 
@@ -750,7 +750,6 @@
   Monitor* owned_locks() const                   { return _owned_locks;          }
   bool owns_locks() const                        { return owned_locks() != NULL; }
   bool owns_locks_but_compiled_lock() const;
-  int oops_do_parity() const                     { return _oops_do_parity; }
 
   // Deadlock detection
   bool allow_allocation()                        { return _allow_allocation_count == 0; }
@@ -2223,7 +2222,7 @@
   static int         _number_of_threads;
   static int         _number_of_non_daemon_threads;
   static int         _return_code;
-  static int         _thread_claim_parity;
+  static uintx       _thread_claim_token;
 #ifdef ASSERT
   static bool        _vm_complete;
 #endif
@@ -2256,21 +2255,23 @@
   // Does not include JNI_VERSION_1_1
   static jboolean is_supported_jni_version(jint version);
 
-  // The "thread claim parity" provides a way for threads to be claimed
+  // The "thread claim token" provides a way for threads to be claimed
   // by parallel worker tasks.
   //
-  // Each thread contains a "parity" field. A task will claim the
-  // thread only if its parity field is the same as the global parity,
-  // which is updated by calling change_thread_claim_parity().
+  // Each thread contains a "token" field. A task will claim the
+  // thread only if its token is different from the global token,
+  // which is updated by calling change_thread_claim_token().  When
+  // a thread is claimed, it's token is set to the global token value
+  // so other threads in the same iteration pass won't claim it.
   //
-  // For this to work change_thread_claim_parity() needs to be called
+  // For this to work change_thread_claim_token() needs to be called
   // exactly once in sequential code before starting parallel tasks
   // that should claim threads.
   //
-  // New threads get their parity set to 0 and change_thread_claim_parity()
-  // never sets the global parity to 0.
-  static int thread_claim_parity() { return _thread_claim_parity; }
-  static void change_thread_claim_parity();
+  // New threads get their token set to 0 and change_thread_claim_token()
+  // never sets the global token to 0.
+  static uintx thread_claim_token() { return _thread_claim_token; }
+  static void change_thread_claim_token();
   static void assert_all_threads_claimed() NOT_DEBUG_RETURN;
 
   // Apply "f->do_oop" to all root oops in all threads.
@@ -2324,6 +2325,8 @@
 
   // Deoptimizes all frames tied to marked nmethods
   static void deoptimized_wrt_marked_nmethods();
+
+  struct Test;                  // For private gtest access.
 };
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/gtest/runtime/test_threads.cpp	Tue Apr 02 13:08:38 2019 -0400
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2019, 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.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "memory/allocation.hpp"
+#include "runtime/interfaceSupport.inline.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "runtime/thread.hpp"
+#include "runtime/vmOperations.hpp"
+#include "runtime/vmThread.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/ostream.hpp"
+#include "unittest.hpp"
+
+struct Threads::Test : public AllStatic {
+  class VM_TestClaimOverflow;
+  class CountThreads;
+  class CheckClaims;
+};
+
+class Threads::Test::CountThreads : public ThreadClosure {
+  uintx _claim_token;
+  uint _java_threads_count;
+  uint _non_java_threads_count;
+  bool _need_claim;
+
+public:
+  CountThreads(uintx claim_token, bool need_claim) :
+    _claim_token(claim_token),
+    _java_threads_count(0),
+    _non_java_threads_count(0),
+    _need_claim(need_claim)
+  {}
+
+  virtual void do_thread(Thread* t) {
+    if (!_need_claim || t->claim_threads_do(true, _claim_token)) {
+      if (t->is_Java_thread()) {
+        ++_java_threads_count;
+      } else {
+        ++_non_java_threads_count;
+      }
+    }
+  }
+
+  uint java_threads_count() const { return _java_threads_count; }
+  uint non_java_threads_count() const { return _non_java_threads_count; }
+  uint count() const { return _java_threads_count + _non_java_threads_count; }
+};
+
+class Threads::Test::CheckClaims : public ThreadClosure {
+  uintx _claim_token;
+  uint _java_threads_claimed;
+  uint _java_threads_unclaimed;
+  uint _non_java_threads_claimed;
+  uint _non_java_threads_unclaimed;
+
+public:
+  CheckClaims(uintx claim_token) :
+    _claim_token(claim_token),
+    _java_threads_claimed(0),
+    _java_threads_unclaimed(0),
+    _non_java_threads_claimed(0),
+    _non_java_threads_unclaimed(0)
+  {}
+
+  virtual void do_thread(Thread* t) {
+    uintx thread_token = t->threads_do_token();
+    if (thread_token == _claim_token) {
+      if (t->is_Java_thread()) {
+        ++_java_threads_claimed;
+      } else {
+        ++_non_java_threads_claimed;
+      }
+    } else {
+      if (t->is_Java_thread()) {
+        ++_java_threads_unclaimed;
+      } else {
+        ++_non_java_threads_unclaimed;
+      }
+    }
+  }
+
+  uint java_threads_claimed() const { return _java_threads_claimed; }
+  uint java_threads_unclaimed() const { return _java_threads_unclaimed; }
+
+  uint non_java_threads_claimed() const { return _non_java_threads_claimed; }
+  uint non_java_threads_unclaimed() const { return _non_java_threads_unclaimed; }
+
+  uint claimed() const {
+    return _java_threads_claimed + _non_java_threads_claimed;
+  }
+
+  uint unclaimed() const {
+    return _java_threads_unclaimed + _non_java_threads_unclaimed;
+  }
+};
+
+class Threads::Test::VM_TestClaimOverflow : public VM_GTestExecuteAtSafepoint {
+public:
+  void doit() {
+    // Prevent changes to the NJT list while we're conducting our test.
+    MutexLockerEx ml(NonJavaThreadsList_lock, Mutex::_no_safepoint_check_flag);
+
+    _thread_claim_token = max_uintx - 1;
+
+    ASSERT_EQ(max_uintx - 1, thread_claim_token());
+    CountThreads count1(thread_claim_token(), true);
+    threads_do(&count1);
+    tty->print_cr("Testing claim overflow with %u threads", count1.count());
+    // At least the main thread and the VM thread.
+    ASSERT_LE(2u, count1.count());
+    ASSERT_LE(1u, count1.java_threads_count());
+    ASSERT_LE(1u, count1.non_java_threads_count());
+
+    ASSERT_EQ(max_uintx - 1, thread_claim_token());
+    CheckClaims check1(thread_claim_token());
+    threads_do(&check1);
+    ASSERT_EQ(count1.count(), check1.claimed());
+    ASSERT_EQ(count1.java_threads_count(), check1.java_threads_claimed());
+    ASSERT_EQ(0u, check1.java_threads_unclaimed());
+    ASSERT_EQ(count1.non_java_threads_count(), check1.non_java_threads_claimed());
+    ASSERT_EQ(0u, check1.non_java_threads_unclaimed());
+
+    change_thread_claim_token(); // No overflow yet.
+    ASSERT_EQ(max_uintx, thread_claim_token());
+
+    CountThreads count2(thread_claim_token(), false); // Claimed by PPTD below
+    possibly_parallel_threads_do(true, &count2);
+    ASSERT_EQ(count1.java_threads_count(), count2.java_threads_count());
+    ASSERT_EQ(1u, count2.non_java_threads_count()); // Only VM thread
+
+    CheckClaims check2(thread_claim_token());
+    threads_do(&check2);
+    ASSERT_EQ(count2.java_threads_count(), check2.java_threads_claimed());
+    ASSERT_EQ(0u, check2.java_threads_unclaimed());
+    ASSERT_EQ(1u, check2.non_java_threads_claimed()); // Only VM thread
+    ASSERT_EQ(count1.non_java_threads_count(),
+              check2.non_java_threads_claimed() +
+              check2.non_java_threads_unclaimed());
+
+    change_thread_claim_token(); // Expect overflow.
+    ASSERT_EQ(uintx(1), thread_claim_token());
+
+    // Verify all threads have claim value of 0 after change overflow.
+    CheckClaims check3(0);
+    threads_do(&check3);
+    ASSERT_EQ(count1.count(), check3.claimed());
+    ASSERT_EQ(0u, check3.unclaimed());
+  }
+};
+
+// Test overflow handling in Threads::change_thread_claim_token().
+TEST_VM(ThreadsTest, claim_overflow) {
+  Threads::Test::VM_TestClaimOverflow op;
+  ThreadInVMfromNative invm(JavaThread::current());
+  VMThread::execute(&op);
+}