8220293: Deadlock in JFR string pool
authormgronlun
Tue, 21 May 2019 20:53:27 +0200
changeset 54964 ec7d6d8effc7
parent 54963 a5f5e680ea32
child 54965 e022b9cb35a5
8220293: Deadlock in JFR string pool Reviewed-by: rehn, egahlin
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp
src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp
src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp
src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp
src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp
src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp
src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp
src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp
src/hotspot/share/jfr/recorder/stringpool/jfrStringPoolBuffer.cpp
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Tue May 21 20:53:27 2019 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -136,6 +136,8 @@
 
 void JfrCheckpointManager::register_full(BufferPtr t, Thread* thread) {
   // nothing here at the moment
+  assert(t != NULL, "invariant");
+  assert(t->acquired_by(thread), "invariant");
   assert(t->retired(), "invariant");
 }
 
--- a/src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp	Tue May 21 20:53:27 2019 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 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
@@ -136,6 +136,14 @@
   _identity = NULL;
 }
 
+bool JfrBuffer::acquired_by(const void* id) const {
+  return identity() == id;
+}
+
+bool JfrBuffer::acquired_by_self() const {
+  return acquired_by(Thread::current());
+}
+
 #ifdef ASSERT
 static bool validate_to(const JfrBuffer* const to, size_t size) {
   assert(to != NULL, "invariant");
@@ -153,10 +161,6 @@
   assert(t->top() + size <= t->pos(), "invariant");
   return true;
 }
-
-bool JfrBuffer::acquired_by_self() const {
-  return identity() == Thread::current();
-}
 #endif // ASSERT
 
 void JfrBuffer::move(JfrBuffer* const to, size_t size) {
@@ -183,7 +187,6 @@
   set_concurrent_top(start());
 }
 
-// flags
 enum FLAG {
   RETIRED = 1,
   TRANSIENT = 2,
--- a/src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp	Tue May 21 20:53:27 2019 +0200
@@ -57,7 +57,6 @@
   u4 _size;
 
   const u1* stable_top() const;
-  void clear_flags();
 
  public:
   JfrBuffer();
@@ -150,6 +149,8 @@
 
   void acquire(const void* id);
   bool try_acquire(const void* id);
+  bool acquired_by(const void* id) const;
+  bool acquired_by_self() const;
   void release();
 
   void move(JfrBuffer* const to, size_t size);
@@ -166,8 +167,6 @@
   bool retired() const;
   void set_retired();
   void clear_retired();
-
-  debug_only(bool acquired_by_self() const;)
 };
 
 class JfrAgeNode : public JfrBuffer {
--- a/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp	Tue May 21 20:53:27 2019 +0200
@@ -346,19 +346,19 @@
 template <typename Mspace>
 inline bool ReleaseOp<Mspace>::process(typename Mspace::Type* t) {
   assert(t != NULL, "invariant");
-  if (t->retired() || t->try_acquire(_thread)) {
-    if (t->transient()) {
-      if (_release_full) {
-        mspace_release_full_critical(t, _mspace);
-      } else {
-        mspace_release_free_critical(t, _mspace);
-      }
-      return true;
+  // assumes some means of exclusive access to t
+  if (t->transient()) {
+    if (_release_full) {
+      mspace_release_full_critical(t, _mspace);
+    } else {
+      mspace_release_free_critical(t, _mspace);
     }
-    t->reinitialize();
-    assert(t->empty(), "invariant");
-    t->release(); // publish
+    return true;
   }
+  t->reinitialize();
+  assert(t->empty(), "invariant");
+  assert(!t->retired(), "invariant");
+  t->release(); // publish
   return true;
 }
 
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp	Tue May 21 20:53:27 2019 +0200
@@ -339,9 +339,9 @@
 void JfrStorage::register_full(BufferPtr buffer, Thread* thread) {
   assert(buffer != NULL, "invariant");
   assert(buffer->retired(), "invariant");
+  assert(buffer->acquired_by(thread), "invariant");
   if (!full_buffer_registration(buffer, _age_mspace, control(), thread)) {
     handle_registration_failure(buffer);
-    buffer->release();
   }
   if (control().should_post_buffer_full_message()) {
     _post_box.post(MSG_FULLBUFFER);
@@ -376,8 +376,8 @@
     }
   }
   assert(buffer->empty(), "invariant");
+  assert(buffer->identity() != NULL, "invariant");
   control().increment_dead();
-  buffer->release();
   buffer->set_retired();
 }
 
@@ -738,13 +738,14 @@
   Scavenger(JfrStorageControl& control, Mspace* mspace) : _control(control), _mspace(mspace), _count(0), _amount(0) {}
   bool process(Type* t) {
     if (t->retired()) {
+      assert(t->identity() != NULL, "invariant");
+      assert(t->empty(), "invariant");
       assert(!t->transient(), "invariant");
       assert(!t->lease(), "invariant");
-      assert(t->empty(), "invariant");
-      assert(t->identity() == NULL, "invariant");
       ++_count;
       _amount += t->total_size();
       t->clear_retired();
+      t->release();
       _control.decrement_dead();
       mspace_release_full_critical(t, _mspace);
     }
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp	Tue May 21 20:53:27 2019 +0200
@@ -92,7 +92,6 @@
   size_t processed() const { return ConcurrentWriteOp<Operation>::processed(); }
 };
 
-
 template <typename Operation>
 class MutexedWriteOp {
  private:
@@ -104,6 +103,15 @@
   size_t processed() const { return _operation.processed(); }
 };
 
+template <typename Operation>
+class ExclusiveOp : private MutexedWriteOp<Operation> {
+ public:
+  typedef typename Operation::Type Type;
+  ExclusiveOp(Operation& operation) : MutexedWriteOp<Operation>(operation) {}
+  bool process(Type* t);
+  size_t processed() const { return MutexedWriteOp<Operation>::processed(); }
+};
+
 enum jfr_operation_mode {
   mutexed = 1,
   concurrent
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp	Tue May 21 20:53:27 2019 +0200
@@ -26,6 +26,7 @@
 #define SHARE_JFR_RECORDER_STORAGE_JFRSTORAGEUTILS_INLINE_HPP
 
 #include "jfr/recorder/storage/jfrStorageUtils.hpp"
+#include "runtime/thread.inline.hpp"
 
 template <typename T>
 inline bool UnBufferedWriteToChunk<T>::write(T* t, const u1* data, size_t size) {
@@ -75,6 +76,28 @@
   return result;
 }
 
+template <typename Type>
+static void retired_sensitive_acquire(Type* t) {
+  assert(t != NULL, "invariant");
+  if (t->retired()) {
+    return;
+  }
+  Thread* const thread = Thread::current();
+  while (!t->try_acquire(thread)) {
+    if (t->retired()) {
+      return;
+    }
+  }
+}
+
+template <typename Operation>
+inline bool ExclusiveOp<Operation>::process(typename Operation::Type* t) {
+  retired_sensitive_acquire(t);
+  assert(t->acquired_by_self() || t->retired(), "invariant");
+  // User is required to ensure proper release of the acquisition
+  return MutexedWriteOp<Operation>::process(t);
+}
+
 template <typename Operation>
 inline bool DiscardOp<Operation>::process(typename Operation::Type* t) {
   assert(t != NULL, "invariant");
--- a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp	Tue May 21 20:53:27 2019 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -140,93 +140,76 @@
   return current_epoch;
 }
 
-class StringPoolWriteOp  {
+template <template <typename> class Operation>
+class StringPoolOp {
  public:
   typedef JfrStringPoolBuffer Type;
  private:
-  UnBufferedWriteToChunk<Type> _writer;
+  Operation<Type> _op;
   Thread* _thread;
   size_t _strings_processed;
  public:
-  StringPoolWriteOp(JfrChunkWriter& writer, Thread* thread) : _writer(writer), _thread(thread), _strings_processed(0) {}
+  StringPoolOp() : _op(), _thread(Thread::current()), _strings_processed(0) {}
+  StringPoolOp(JfrChunkWriter& writer, Thread* thread) : _op(writer), _thread(thread), _strings_processed(0) {}
   bool write(Type* buffer, const u1* data, size_t size) {
-    buffer->acquire(_thread); // blocking
+    assert(buffer->acquired_by(_thread) || buffer->retired(), "invariant");
     const uint64_t nof_strings_used = buffer->string_count();
     assert(nof_strings_used > 0, "invariant");
     buffer->set_string_top(buffer->string_top() + nof_strings_used);
     // "size processed" for string pool buffers is the number of processed string elements
     _strings_processed += nof_strings_used;
-    const bool ret = _writer.write(buffer, data, size);
-    buffer->release();
-    return ret;
+    return _op.write(buffer, data, size);
   }
   size_t processed() { return _strings_processed; }
 };
 
-typedef StringPoolWriteOp WriteOperation;
-typedef ConcurrentWriteOp<WriteOperation> ConcurrentWriteOperation;
+template <typename Type>
+class StringPoolDiscarderStub {
+ public:
+  bool write(Type* buffer, const u1* data, size_t size) {
+    // stub only, discard happens at higher level
+    return true;
+  }
+};
+
+typedef StringPoolOp<UnBufferedWriteToChunk> WriteOperation;
+typedef StringPoolOp<StringPoolDiscarderStub> DiscardOperation;
+typedef ExclusiveOp<WriteOperation> ExclusiveWriteOperation;
+typedef ExclusiveOp<DiscardOperation> ExclusiveDiscardOperation;
+typedef ReleaseOp<JfrStringPoolMspace> StringPoolReleaseOperation;
+typedef CompositeOperation<ExclusiveWriteOperation, StringPoolReleaseOperation> StringPoolWriteOperation;
+typedef CompositeOperation<ExclusiveDiscardOperation, StringPoolReleaseOperation> StringPoolDiscardOperation;
 
 size_t JfrStringPool::write() {
   Thread* const thread = Thread::current();
   WriteOperation wo(_chunkwriter, thread);
-  ConcurrentWriteOperation cwo(wo);
-  assert(_free_list_mspace->is_full_empty(), "invariant");
-  process_free_list(cwo, _free_list_mspace);
-  return wo.processed();
-}
-
-typedef MutexedWriteOp<WriteOperation> MutexedWriteOperation;
-typedef ReleaseOp<JfrStringPoolMspace> StringPoolReleaseOperation;
-typedef CompositeOperation<MutexedWriteOperation, StringPoolReleaseOperation> StringPoolWriteOperation;
-
-size_t JfrStringPool::write_at_safepoint() {
-  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
-  Thread* const thread = Thread::current();
-  WriteOperation wo(_chunkwriter, thread);
-  MutexedWriteOperation mwo(wo);
+  ExclusiveWriteOperation ewo(wo);
   StringPoolReleaseOperation spro(_free_list_mspace, thread, false);
-  StringPoolWriteOperation spwo(&mwo, &spro);
+  StringPoolWriteOperation spwo(&ewo, &spro);
   assert(_free_list_mspace->is_full_empty(), "invariant");
   process_free_list(spwo, _free_list_mspace);
   return wo.processed();
 }
 
-class StringPoolBufferDiscarder {
- private:
-  Thread* _thread;
-  size_t _processed;
- public:
-  typedef JfrStringPoolBuffer Type;
-  StringPoolBufferDiscarder() : _thread(Thread::current()), _processed(0) {}
-  bool process(Type* buffer) {
-    buffer->acquire(_thread); // serialized access
-    const u1* const current_top = buffer->top();
-    const size_t unflushed_size = buffer->pos() - current_top;
-    if (unflushed_size == 0) {
-      assert(buffer->string_count() == 0, "invariant");
-      buffer->release();
-      return true;
-    }
-    buffer->set_top(current_top + unflushed_size);
-    const uint64_t nof_strings_used = buffer->string_count();
-    buffer->set_string_top(buffer->string_top() + nof_strings_used);
-    // "size processed" for string pool buffers is the number of string elements
-    _processed += (size_t)nof_strings_used;
-    buffer->release();
-    return true;
-  }
-  size_t processed() const { return _processed; }
-};
+size_t JfrStringPool::write_at_safepoint() {
+  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
+  return write();
+}
 
 size_t JfrStringPool::clear() {
-  StringPoolBufferDiscarder discard_operation;
+  DiscardOperation discard_operation;
+  ExclusiveDiscardOperation edo(discard_operation);
+  StringPoolReleaseOperation spro(_free_list_mspace, Thread::current(), false);
+  StringPoolDiscardOperation spdo(&edo, &spro);
   assert(_free_list_mspace->is_full_empty(), "invariant");
-  process_free_list(discard_operation, _free_list_mspace);
+  process_free_list(spdo, _free_list_mspace);
   return discard_operation.processed();
 }
 
 void JfrStringPool::register_full(BufferPtr t, Thread* thread) {
   // nothing here at the moment
+  assert(t != NULL, "invariant");
+  assert(t->acquired_by(thread), "invariant");
   assert(t->retired(), "invariant");
 }
 
--- a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPoolBuffer.cpp	Tue May 21 13:06:52 2019 -0400
+++ b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPoolBuffer.cpp	Tue May 21 20:53:27 2019 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -29,11 +29,9 @@
 
 void JfrStringPoolBuffer::reinitialize() {
   assert(acquired_by_self() || retired(), "invariant");
-  concurrent_top();
-  set_pos((start()));
   set_string_pos(0);
   set_string_top(0);
-  set_concurrent_top(start());
+  JfrBuffer::reinitialize();
 }
 
 uint64_t JfrStringPoolBuffer::string_pos() const {
@@ -57,7 +55,7 @@
 }
 
 void JfrStringPoolBuffer::increment(uint64_t value) {
-  assert(acquired_by_self() || retired(), "invariant");
+  assert(acquired_by_self(), "invariant");
   ++_string_count_pos;
 }