Skip to content

Commit be8b83b

Browse files
author
duke
committed
Automatic merge of jdk:master into master
2 parents 8bc1e6b + ac9af69 commit be8b83b

File tree

9 files changed

+112
-60
lines changed

9 files changed

+112
-60
lines changed

src/java.base/share/classes/java/lang/InheritableThreadLocal.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 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
@@ -80,7 +80,7 @@ protected T childValue(T parentValue) {
8080
*/
8181
@Override
8282
ThreadLocalMap getMap(Thread t) {
83-
return t.inheritableThreadLocals;
83+
return t.inheritableThreadLocals();
8484
}
8585

8686
/**
@@ -91,6 +91,6 @@ ThreadLocalMap getMap(Thread t) {
9191
*/
9292
@Override
9393
void createMap(Thread t, T firstValue) {
94-
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
94+
t.setInheritableThreadLocals(new ThreadLocalMap(this, firstValue));
9595
}
9696
}

src/java.base/share/classes/java/lang/System.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,10 +2254,6 @@ public void removeCarrierThreadLocal(CarrierThreadLocal<?> local) {
22542254
((ThreadLocal<?>)local).removeCarrierThreadLocal();
22552255
}
22562256

2257-
public boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local) {
2258-
return ((ThreadLocal<?>)local).isCarrierThreadLocalPresent();
2259-
}
2260-
22612257
public Object[] scopedValueCache() {
22622258
return Thread.scopedValueCache();
22632259
}

src/java.base/share/classes/java/lang/Thread.java

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ public class Thread implements Runnable {
235235
private volatile ClassLoader contextClassLoader;
236236

237237
// Additional fields for platform threads.
238-
// All fields, except task, are accessed directly by the VM.
238+
// All fields, except task and terminatingThreadLocals, are accessed directly by the VM.
239239
private static class FieldHolder {
240240
final ThreadGroup group;
241241
final Runnable task;
@@ -244,6 +244,9 @@ private static class FieldHolder {
244244
volatile boolean daemon;
245245
volatile int threadStatus;
246246

247+
// This map is maintained by the ThreadLocal class
248+
ThreadLocal.ThreadLocalMap terminatingThreadLocals;
249+
247250
FieldHolder(ThreadGroup group,
248251
Runnable task,
249252
long stackSize,
@@ -259,17 +262,41 @@ private static class FieldHolder {
259262
}
260263
private final FieldHolder holder;
261264

265+
ThreadLocal.ThreadLocalMap terminatingThreadLocals() {
266+
return holder.terminatingThreadLocals;
267+
}
268+
269+
void setTerminatingThreadLocals(ThreadLocal.ThreadLocalMap map) {
270+
holder.terminatingThreadLocals = map;
271+
}
272+
262273
/*
263274
* ThreadLocal values pertaining to this thread. This map is maintained
264275
* by the ThreadLocal class.
265276
*/
266-
ThreadLocal.ThreadLocalMap threadLocals;
277+
private ThreadLocal.ThreadLocalMap threadLocals;
278+
279+
ThreadLocal.ThreadLocalMap threadLocals() {
280+
return threadLocals;
281+
}
282+
283+
void setThreadLocals(ThreadLocal.ThreadLocalMap map) {
284+
threadLocals = map;
285+
}
267286

268287
/*
269288
* InheritableThreadLocal values pertaining to this thread. This map is
270289
* maintained by the InheritableThreadLocal class.
271290
*/
272-
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
291+
private ThreadLocal.ThreadLocalMap inheritableThreadLocals;
292+
293+
ThreadLocal.ThreadLocalMap inheritableThreadLocals() {
294+
return inheritableThreadLocals;
295+
}
296+
297+
void setInheritableThreadLocals(ThreadLocal.ThreadLocalMap map) {
298+
inheritableThreadLocals = map;
299+
}
273300

274301
/*
275302
* Scoped value bindings are maintained by the ScopedValue class.
@@ -1492,7 +1519,7 @@ private void exit() {
14921519
}
14931520

14941521
try {
1495-
if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
1522+
if (terminatingThreadLocals() != null) {
14961523
TerminatingThreadLocal.threadTerminated();
14971524
}
14981525
} finally {

src/java.base/share/classes/java/lang/ThreadLocal.java

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 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
@@ -193,27 +193,6 @@ private T get(Thread t) {
193193
return setInitialValue(t);
194194
}
195195

196-
/**
197-
* Returns {@code true} if there is a value in the current carrier thread's copy of
198-
* this thread-local variable, even if that values is {@code null}.
199-
*
200-
* @return {@code true} if current carrier thread has associated value in this
201-
* thread-local variable; {@code false} if not
202-
*/
203-
boolean isCarrierThreadLocalPresent() {
204-
assert this instanceof CarrierThreadLocal<T>;
205-
return isPresent(Thread.currentCarrierThread());
206-
}
207-
208-
private boolean isPresent(Thread t) {
209-
ThreadLocalMap map = getMap(t);
210-
if (map != null) {
211-
return map.getEntry(this) != null;
212-
} else {
213-
return false;
214-
}
215-
}
216-
217196
/**
218197
* Variant of set() to establish initialValue. Used instead
219198
* of set() in case user has overridden the set() method.
@@ -302,7 +281,11 @@ private void remove(Thread t) {
302281
* @return the map
303282
*/
304283
ThreadLocalMap getMap(Thread t) {
305-
return t.threadLocals;
284+
if (this instanceof TerminatingThreadLocal<T>) {
285+
return t.terminatingThreadLocals();
286+
} else {
287+
return t.threadLocals();
288+
}
306289
}
307290

308291
/**
@@ -313,7 +296,12 @@ ThreadLocalMap getMap(Thread t) {
313296
* @param firstValue value for the initial entry of the map
314297
*/
315298
void createMap(Thread t, T firstValue) {
316-
t.threadLocals = new ThreadLocalMap(this, firstValue);
299+
var map = new ThreadLocalMap(this, firstValue);
300+
if (this instanceof TerminatingThreadLocal<T>) {
301+
t.setTerminatingThreadLocals(map);
302+
} else {
303+
t.setThreadLocals(map);
304+
}
317305
}
318306

319307
/**

src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,12 +546,6 @@ public interface JavaLangAccess {
546546
*/
547547
void removeCarrierThreadLocal(CarrierThreadLocal<?> local);
548548

549-
/**
550-
* Returns {@code true} if there is a value in the current carrier thread's copy of
551-
* thread-local, even if that values is {@code null}.
552-
*/
553-
boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local);
554-
555549
/**
556550
* Returns the current thread's scoped values cache
557551
*/

src/java.base/share/classes/jdk/internal/misc/CarrierThreadLocal.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 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
@@ -49,9 +49,5 @@ public void remove() {
4949
JLA.removeCarrierThreadLocal(this);
5050
}
5151

52-
public boolean isPresent() {
53-
return JLA.isCarrierThreadLocalPresent(this);
54-
}
55-
5652
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
5753
}

src/java.base/share/classes/jdk/internal/misc/TerminatingThreadLocal.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -29,10 +29,9 @@
2929
import java.util.IdentityHashMap;
3030

3131
/**
32-
* A per-carrier-thread-local variable that is notified when a thread terminates and
33-
* it has been initialized in the terminating carrier thread or a virtual thread
34-
* that had the terminating carrier thread as its carrier thread (even if it was
35-
* initialized with a null value).
32+
* A platform thread-local variable that is notified when a platform thread terminates,
33+
* and it has been initialized in the terminating thread (or a mounted virtual thread in
34+
* the case of a carrier thread), and even if it was initialized with a null value.
3635
*/
3736
public class TerminatingThreadLocal<T> extends CarrierThreadLocal<T> {
3837

@@ -44,8 +43,8 @@ public void set(T value) {
4443

4544
@Override
4645
public void remove() {
47-
super.remove();
4846
unregister(this);
47+
super.remove();
4948
}
5049

5150
/**
@@ -80,7 +79,9 @@ public static void threadTerminated() {
8079
* @param tl the ThreadLocal to register
8180
*/
8281
public static void register(TerminatingThreadLocal<?> tl) {
83-
REGISTRY.get().add(tl);
82+
if (tl != REGISTRY) {
83+
REGISTRY.get().add(tl);
84+
}
8485
}
8586

8687
/**
@@ -93,11 +94,11 @@ private static void unregister(TerminatingThreadLocal<?> tl) {
9394
}
9495

9596
/**
96-
* a per-carrier-thread registry of TerminatingThreadLocal(s) that have been registered
97-
* but later not unregistered in a particular carrier-thread.
97+
* A per-platform-thread registry of TerminatingThreadLocal(s). The registry is
98+
* itself a TerminatingThreadLocal to keep it reachable until the thread terminates.
9899
*/
99-
public static final CarrierThreadLocal<Collection<TerminatingThreadLocal<?>>> REGISTRY =
100-
new CarrierThreadLocal<>() {
100+
public static final TerminatingThreadLocal<Collection<TerminatingThreadLocal<?>>> REGISTRY =
101+
new TerminatingThreadLocal<>() {
101102
@Override
102103
protected Collection<TerminatingThreadLocal<?>> initialValue() {
103104
return Collections.newSetFromMap(new IdentityHashMap<>(4));

src/java.base/share/classes/sun/nio/ch/IOVecWrapper.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2000, 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
@@ -79,6 +79,7 @@ protected IOVecWrapper[] initialValue() {
7979
protected void threadTerminated(IOVecWrapper[] cache) {
8080
IOVecWrapper wrapper = cache[0];
8181
if (wrapper != null) {
82+
cache[0] = null;
8283
wrapper.vecArray.free();
8384
}
8485
}

test/jdk/jdk/internal/misc/TerminatingThreadLocal/TestTerminatingThreadLocal.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 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
@@ -24,13 +24,15 @@
2424
import jdk.internal.misc.TerminatingThreadLocal;
2525

2626
import java.lang.reflect.Constructor;
27+
import java.lang.reflect.Field;
2728
import java.lang.reflect.InvocationTargetException;
2829
import java.util.Arrays;
2930
import java.util.List;
3031
import java.util.concurrent.CopyOnWriteArrayList;
3132
import java.util.concurrent.Executor;
3233
import java.util.concurrent.Executors;
3334
import java.util.concurrent.ThreadFactory;
35+
import java.util.concurrent.atomic.AtomicReference;
3436
import java.util.function.Consumer;
3537
import java.util.function.Function;
3638
import java.util.stream.Stream;
@@ -41,7 +43,7 @@
4143

4244
/*
4345
* @test
44-
* @bug 8202788 8291897
46+
* @bug 8202788 8291897 8357637
4547
* @summary TerminatingThreadLocal unit test
4648
* @modules java.base/java.lang:+open java.base/jdk.internal.misc
4749
* @requires vm.continuations
@@ -154,6 +156,53 @@ protected T initialValue() {
154156
assertEquals(terminatedValues, expectedTerminatedValues);
155157
}
156158

159+
/**
160+
* Test TerminatingThreadLocal when thread locals are "cleared" by null'ing the
161+
* threadLocal field of the current Thread.
162+
*/
163+
@Test
164+
public void testClearingThreadLocals() throws Throwable {
165+
var terminatedValues = new CopyOnWriteArrayList<Object>();
166+
167+
var tl = new ThreadLocal<String>();
168+
var ttl = new TerminatingThreadLocal<String>() {
169+
@Override
170+
protected void threadTerminated(String value) {
171+
terminatedValues.add(value);
172+
}
173+
};
174+
var throwableRef = new AtomicReference<Throwable>();
175+
176+
String tlValue = "abc";
177+
String ttlValue = "xyz";
178+
179+
Thread thread = Thread.ofPlatform().start(() -> {
180+
try {
181+
tl.set(tlValue);
182+
ttl.set(ttlValue);
183+
184+
assertEquals(tl.get(), tlValue);
185+
assertEquals(ttl.get(), ttlValue);
186+
187+
// set Thread.threadLocals to null
188+
Field f = Thread.class.getDeclaredField("threadLocals");
189+
f.setAccessible(true);
190+
f.set(Thread.currentThread(), null);
191+
192+
assertNull(tl.get());
193+
assertEquals(ttl.get(), ttlValue);
194+
} catch (Throwable t) {
195+
throwableRef.set(t);
196+
}
197+
});
198+
thread.join();
199+
if (throwableRef.get() instanceof Throwable t) {
200+
throw t;
201+
}
202+
203+
assertEquals(terminatedValues, List.of(ttlValue));
204+
}
205+
157206
/**
158207
* Returns a builder to create virtual threads that use the given scheduler.
159208
*/

0 commit comments

Comments
 (0)