Skip to content

Commit 59dc033

Browse files
parttimenerdPaul Hohensee
authored andcommitted
8364258: ThreadGroup constant pool serialization is not normalized
Reviewed-by: phh Backport-of: 1e2bf070f0cb9e852839347d1f5711c583091d85
1 parent 2f89740 commit 59dc033

File tree

11 files changed

+420
-467
lines changed

11 files changed

+420
-467
lines changed

src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp

Lines changed: 0 additions & 418 deletions
This file was deleted.
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
/*
2+
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
#include "precompiled.hpp"
26+
#include "jfr/jni/jfrJavaSupport.hpp"
27+
#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
28+
#include "jfr/recorder/checkpoint/types/jfrThreadGroupManager.hpp"
29+
#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp"
30+
#include "jfr/utilities/jfrAllocation.hpp"
31+
#include "jfr/utilities/jfrLinkedList.inline.hpp"
32+
#include "memory/resourceArea.hpp"
33+
#include "runtime/handles.inline.hpp"
34+
#include "runtime/jniHandles.inline.hpp"
35+
#include "runtime/safepoint.hpp"
36+
#include "runtime/semaphore.hpp"
37+
#include "runtime/thread.inline.hpp"
38+
#include "utilities/growableArray.hpp"
39+
40+
class ThreadGroupExclusiveAccess : public StackObj {
41+
private:
42+
static Semaphore _mutex_semaphore;
43+
public:
44+
ThreadGroupExclusiveAccess() { _mutex_semaphore.wait(); }
45+
~ThreadGroupExclusiveAccess() { _mutex_semaphore.signal(); }
46+
};
47+
48+
Semaphore ThreadGroupExclusiveAccess::_mutex_semaphore(1);
49+
50+
static traceid next_id() {
51+
static traceid _tgid = 1; // 1 is reserved for thread group "VirtualThreads"
52+
return ++_tgid;
53+
}
54+
55+
class JfrThreadGroup : public JfrCHeapObj {
56+
template <typename, typename>
57+
friend class JfrLinkedList;
58+
private:
59+
mutable const JfrThreadGroup* _next;
60+
const JfrThreadGroup* _parent;
61+
traceid _tgid;
62+
char* _tg_name; // utf8 format
63+
jweak _tg_handle;
64+
mutable u2 _generation;
65+
66+
public:
67+
JfrThreadGroup(Handle tg, const JfrThreadGroup* parent) :
68+
_next(nullptr), _parent(parent), _tgid(next_id()), _tg_name(nullptr),
69+
_tg_handle(JNIHandles::make_weak_global(tg)), _generation(0) {
70+
const char* name = java_lang_ThreadGroup::name(tg());
71+
if (name != nullptr) {
72+
const size_t len = strlen(name);
73+
_tg_name = JfrCHeapObj::new_array<char>(len + 1);
74+
strncpy(_tg_name, name, len + 1);
75+
}
76+
}
77+
78+
~JfrThreadGroup() {
79+
JNIHandles::destroy_weak_global(_tg_handle);
80+
if (_tg_name != nullptr) {
81+
JfrCHeapObj::free(_tg_name, strlen(_tg_name) + 1);
82+
}
83+
}
84+
85+
const JfrThreadGroup* next() const { return _next; }
86+
87+
traceid id() const { return _tgid; }
88+
89+
const char* name() const {
90+
return _tg_name;
91+
}
92+
93+
const JfrThreadGroup* parent() const { return _parent; }
94+
95+
traceid parent_id() const {
96+
return _parent != nullptr ? _parent->id() : 0;
97+
}
98+
99+
bool is_dead() const {
100+
return JNIHandles::resolve(_tg_handle) == nullptr;
101+
}
102+
103+
bool operator==(oop tg) const {
104+
assert(tg != nullptr, "invariant");
105+
return tg == JNIHandles::resolve(_tg_handle);
106+
}
107+
108+
bool should_write() const {
109+
return !JfrTraceIdEpoch::is_current_epoch_generation(_generation);
110+
}
111+
112+
void set_written() const {
113+
assert(should_write(), "invariant");
114+
_generation = JfrTraceIdEpoch::epoch_generation();
115+
}
116+
};
117+
118+
typedef JfrLinkedList<const JfrThreadGroup> JfrThreadGroupList;
119+
120+
static JfrThreadGroupList* _list = nullptr;
121+
122+
static JfrThreadGroupList& list() {
123+
assert(_list != nullptr, "invariant");
124+
return *_list;
125+
}
126+
127+
bool JfrThreadGroupManager::create() {
128+
assert(_list == nullptr, "invariant");
129+
_list = new JfrThreadGroupList();
130+
return _list != nullptr;
131+
}
132+
133+
void JfrThreadGroupManager::destroy() {
134+
delete _list;
135+
_list = nullptr;
136+
}
137+
138+
static int populate(GrowableArray<Handle>* hierarchy, const JavaThread* jt, Thread* current) {
139+
assert(hierarchy != nullptr, "invariant");
140+
assert(jt != nullptr, "invariant");
141+
assert(current == Thread::current(), "invariant");
142+
143+
oop thread_oop = jt->threadObj();
144+
if (thread_oop == nullptr) {
145+
return 0;
146+
}
147+
// Immediate thread group.
148+
const Handle tg_handle(current, java_lang_Thread::threadGroup(thread_oop));
149+
if (tg_handle.is_null()) {
150+
return 0;
151+
}
152+
hierarchy->append(tg_handle);
153+
154+
// Thread group parent and then its parents...
155+
Handle parent_tg_handle(current, java_lang_ThreadGroup::parent(tg_handle()));
156+
157+
while (parent_tg_handle != nullptr) {
158+
hierarchy->append(parent_tg_handle);
159+
parent_tg_handle = Handle(current, java_lang_ThreadGroup::parent(parent_tg_handle()));
160+
}
161+
162+
return hierarchy->length();
163+
}
164+
165+
class JfrThreadGroupLookup : public ResourceObj {
166+
static const int invalid_iterator = -1;
167+
private:
168+
GrowableArray<Handle>* _hierarchy;
169+
mutable int _iterator;
170+
171+
public:
172+
JfrThreadGroupLookup(const JavaThread* jt, Thread* current) :
173+
_hierarchy(new GrowableArray<Handle>(16)),
174+
_iterator(populate(_hierarchy, jt, current) - 1) {}
175+
176+
bool has_next() const {
177+
return _iterator > invalid_iterator;
178+
}
179+
180+
const Handle& next() const {
181+
assert(has_next(), "invariant");
182+
return _hierarchy->at(_iterator--);
183+
}
184+
};
185+
186+
static const JfrThreadGroup* find_or_add(const Handle& tg_oop, const JfrThreadGroup* parent) {
187+
assert(parent == nullptr || list().in_list(parent), "invariant");
188+
const JfrThreadGroup* tg = list().head();
189+
const JfrThreadGroup* result = nullptr;
190+
while (tg != nullptr) {
191+
if (*tg == tg_oop()) {
192+
assert(tg->parent() == parent, "invariant");
193+
result = tg;
194+
tg = nullptr;
195+
continue;
196+
}
197+
tg = tg->next();
198+
}
199+
if (result == nullptr) {
200+
result = new JfrThreadGroup(tg_oop, parent);
201+
list().add(result);
202+
}
203+
return result;
204+
}
205+
206+
static traceid find_tgid(const JfrThreadGroupLookup& lookup) {
207+
const JfrThreadGroup* tg = nullptr;
208+
const JfrThreadGroup* ptg = nullptr;
209+
while (lookup.has_next()) {
210+
tg = find_or_add(lookup.next(), ptg);
211+
ptg = tg;
212+
}
213+
return tg != nullptr ? tg->id() : 0;
214+
}
215+
216+
static traceid find(const JfrThreadGroupLookup& lookup) {
217+
ThreadGroupExclusiveAccess lock;
218+
return find_tgid(lookup);
219+
}
220+
221+
traceid JfrThreadGroupManager::thread_group_id(JavaThread* jt) {
222+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);)
223+
ResourceMark rm(jt);
224+
HandleMark hm(jt);
225+
const JfrThreadGroupLookup lookup(jt, jt);
226+
return find(lookup);
227+
}
228+
229+
traceid JfrThreadGroupManager::thread_group_id(const JavaThread* jt, Thread* current) {
230+
assert(jt != nullptr, "invariant");
231+
assert(current != nullptr, "invariant");
232+
assert(!current->is_Java_thread() || JavaThread::cast(current)->thread_state() == _thread_in_vm, "invariant");
233+
ResourceMark rm(current);
234+
HandleMark hm(current);
235+
const JfrThreadGroupLookup lookup(jt, current);
236+
return find(lookup);
237+
}
238+
239+
static void write_virtual_thread_group(JfrCheckpointWriter& writer) {
240+
writer.write_key(1); // 1 is reserved for VirtualThread group
241+
writer.write<traceid>(0); // parent
242+
const oop vgroup = java_lang_Thread_Constants::get_VTHREAD_GROUP();
243+
assert(vgroup != (oop)nullptr, "invariant");
244+
const char* const vgroup_name = java_lang_ThreadGroup::name(vgroup);
245+
assert(vgroup_name != nullptr, "invariant");
246+
writer.write(vgroup_name);
247+
}
248+
249+
static int write_thread_group(JfrCheckpointWriter& writer, const JfrThreadGroup* tg, bool to_blob = false) {
250+
assert(tg != nullptr, "invariant");
251+
if (tg->should_write() || to_blob) {
252+
writer.write_key(tg->id());
253+
writer.write(tg->parent_id());
254+
writer.write(tg->name());
255+
if (!to_blob) {
256+
tg->set_written();
257+
}
258+
return 1;
259+
}
260+
return 0;
261+
}
262+
263+
// For writing all live thread groups while removing and deleting dead thread groups.
264+
void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer) {
265+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());)
266+
267+
const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet
268+
269+
// First write the pre-defined ThreadGroup for virtual threads.
270+
write_virtual_thread_group(writer);
271+
int number_of_groups_written = 1;
272+
273+
const JfrThreadGroup* next = nullptr;
274+
const JfrThreadGroup* prev = nullptr;
275+
276+
{
277+
ThreadGroupExclusiveAccess lock;
278+
const JfrThreadGroup* tg = list().head();
279+
while (tg != nullptr) {
280+
next = tg->next();
281+
if (tg->is_dead()) {
282+
prev = list().excise(prev, tg);
283+
assert(!list().in_list(tg), "invariant");
284+
delete tg;
285+
tg = next;
286+
continue;
287+
}
288+
number_of_groups_written += write_thread_group(writer, tg);
289+
prev = tg;
290+
tg = next;
291+
}
292+
}
293+
294+
assert(number_of_groups_written > 0, "invariant");
295+
writer.write_count(number_of_groups_written, count_offset);
296+
}
297+
298+
// For writing a specific thread group and its ancestry.
299+
void JfrThreadGroupManager::serialize(JfrCheckpointWriter& writer, traceid tgid, bool to_blob) {
300+
DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(JavaThread::current());)
301+
// save context
302+
const JfrCheckpointContext ctx = writer.context();
303+
304+
writer.write_type(TYPE_THREADGROUP);
305+
const uint64_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet
306+
307+
int number_of_groups_written = 0;
308+
309+
{
310+
ThreadGroupExclusiveAccess lock;
311+
const JfrThreadGroup* tg = list().head();
312+
while (tg != nullptr) {
313+
if (tgid == tg->id()) {
314+
while (tg != nullptr) {
315+
number_of_groups_written += write_thread_group(writer, tg, to_blob);
316+
tg = tg->parent();
317+
}
318+
break;
319+
}
320+
tg = tg->next();
321+
}
322+
}
323+
324+
if (number_of_groups_written == 0) {
325+
// nothing to write, restore context
326+
writer.set_context(ctx);
327+
return;
328+
}
329+
330+
assert(number_of_groups_written > 0, "invariant");
331+
writer.write_count(number_of_groups_written, count_offset);
332+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -22,44 +22,27 @@
2222
*
2323
*/
2424

25-
#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
26-
#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
25+
#ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP
26+
#define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP
2727

28-
#include "jfr/utilities/jfrAllocation.hpp"
2928
#include "jfr/utilities/jfrTypes.hpp"
30-
#include "jni.h"
29+
#include "memory/allStatic.hpp"
3130

3231
class JfrCheckpointWriter;
33-
template <typename>
34-
class GrowableArray;
35-
class JfrThreadGroupsHelper;
36-
class JfrThreadGroupPointers;
3732

38-
class JfrThreadGroup : public JfrCHeapObj {
39-
friend class JfrCheckpointThreadClosure;
40-
private:
41-
static JfrThreadGroup* _instance;
42-
class JfrThreadGroupEntry;
43-
GrowableArray<JfrThreadGroupEntry*>* _list;
44-
45-
JfrThreadGroup();
46-
JfrThreadGroupEntry* find_entry(const JfrThreadGroupPointers& ptrs) const;
47-
JfrThreadGroupEntry* new_entry(JfrThreadGroupPointers& ptrs);
48-
int add_entry(JfrThreadGroupEntry* const tge);
49-
50-
void write_thread_group_entries(JfrCheckpointWriter& writer) const;
51-
void write_selective_thread_group(JfrCheckpointWriter* writer, traceid thread_group_id) const;
33+
class JfrThreadGroupManager : public AllStatic {
34+
friend class JfrRecorder;
5235

53-
static traceid thread_group_id_internal(JfrThreadGroupsHelper& helper);
54-
static JfrThreadGroup* instance();
55-
static void set_instance(JfrThreadGroup* new_instance);
36+
private:
37+
static bool create();
38+
static void destroy();
5639

5740
public:
58-
~JfrThreadGroup();
5941
static void serialize(JfrCheckpointWriter& w);
60-
static void serialize(JfrCheckpointWriter* w, traceid thread_group_id);
42+
static void serialize(JfrCheckpointWriter& w, traceid tgid, bool is_blob);
43+
6144
static traceid thread_group_id(JavaThread* thread);
6245
static traceid thread_group_id(const JavaThread* thread, Thread* current);
6346
};
6447

65-
#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUP_HPP
48+
#endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADGROUPMANAGER_HPP

0 commit comments

Comments
 (0)