mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-14 09:53:18 +00:00
8357637: Native resources cached in thread locals not released when FJP common pool threads clears thread locals
Reviewed-by: vklang
This commit is contained in:
parent
e3eb089d47
commit
ac9af69eee
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1998, 2025, 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
|
||||
@ -80,7 +80,7 @@ public class InheritableThreadLocal<T> extends ThreadLocal<T> {
|
||||
*/
|
||||
@Override
|
||||
ThreadLocalMap getMap(Thread t) {
|
||||
return t.inheritableThreadLocals;
|
||||
return t.inheritableThreadLocals();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,6 +91,6 @@ public class InheritableThreadLocal<T> extends ThreadLocal<T> {
|
||||
*/
|
||||
@Override
|
||||
void createMap(Thread t, T firstValue) {
|
||||
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
|
||||
t.setInheritableThreadLocals(new ThreadLocalMap(this, firstValue));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2254,10 +2254,6 @@ public final class System {
|
||||
((ThreadLocal<?>)local).removeCarrierThreadLocal();
|
||||
}
|
||||
|
||||
public boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local) {
|
||||
return ((ThreadLocal<?>)local).isCarrierThreadLocalPresent();
|
||||
}
|
||||
|
||||
public Object[] scopedValueCache() {
|
||||
return Thread.scopedValueCache();
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ public class Thread implements Runnable {
|
||||
private volatile ClassLoader contextClassLoader;
|
||||
|
||||
// Additional fields for platform threads.
|
||||
// All fields, except task, are accessed directly by the VM.
|
||||
// All fields, except task and terminatingThreadLocals, are accessed directly by the VM.
|
||||
private static class FieldHolder {
|
||||
final ThreadGroup group;
|
||||
final Runnable task;
|
||||
@ -244,6 +244,9 @@ public class Thread implements Runnable {
|
||||
volatile boolean daemon;
|
||||
volatile int threadStatus;
|
||||
|
||||
// This map is maintained by the ThreadLocal class
|
||||
ThreadLocal.ThreadLocalMap terminatingThreadLocals;
|
||||
|
||||
FieldHolder(ThreadGroup group,
|
||||
Runnable task,
|
||||
long stackSize,
|
||||
@ -259,17 +262,41 @@ public class Thread implements Runnable {
|
||||
}
|
||||
private final FieldHolder holder;
|
||||
|
||||
ThreadLocal.ThreadLocalMap terminatingThreadLocals() {
|
||||
return holder.terminatingThreadLocals;
|
||||
}
|
||||
|
||||
void setTerminatingThreadLocals(ThreadLocal.ThreadLocalMap map) {
|
||||
holder.terminatingThreadLocals = map;
|
||||
}
|
||||
|
||||
/*
|
||||
* ThreadLocal values pertaining to this thread. This map is maintained
|
||||
* by the ThreadLocal class.
|
||||
*/
|
||||
ThreadLocal.ThreadLocalMap threadLocals;
|
||||
private ThreadLocal.ThreadLocalMap threadLocals;
|
||||
|
||||
ThreadLocal.ThreadLocalMap threadLocals() {
|
||||
return threadLocals;
|
||||
}
|
||||
|
||||
void setThreadLocals(ThreadLocal.ThreadLocalMap map) {
|
||||
threadLocals = map;
|
||||
}
|
||||
|
||||
/*
|
||||
* InheritableThreadLocal values pertaining to this thread. This map is
|
||||
* maintained by the InheritableThreadLocal class.
|
||||
*/
|
||||
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
|
||||
private ThreadLocal.ThreadLocalMap inheritableThreadLocals;
|
||||
|
||||
ThreadLocal.ThreadLocalMap inheritableThreadLocals() {
|
||||
return inheritableThreadLocals;
|
||||
}
|
||||
|
||||
void setInheritableThreadLocals(ThreadLocal.ThreadLocalMap map) {
|
||||
inheritableThreadLocals = map;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scoped value bindings are maintained by the ScopedValue class.
|
||||
@ -1492,7 +1519,7 @@ public class Thread implements Runnable {
|
||||
}
|
||||
|
||||
try {
|
||||
if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
|
||||
if (terminatingThreadLocals() != null) {
|
||||
TerminatingThreadLocal.threadTerminated();
|
||||
}
|
||||
} finally {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, 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
|
||||
@ -193,27 +193,6 @@ public class ThreadLocal<T> {
|
||||
return setInitialValue(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if there is a value in the current carrier thread's copy of
|
||||
* this thread-local variable, even if that values is {@code null}.
|
||||
*
|
||||
* @return {@code true} if current carrier thread has associated value in this
|
||||
* thread-local variable; {@code false} if not
|
||||
*/
|
||||
boolean isCarrierThreadLocalPresent() {
|
||||
assert this instanceof CarrierThreadLocal<T>;
|
||||
return isPresent(Thread.currentCarrierThread());
|
||||
}
|
||||
|
||||
private boolean isPresent(Thread t) {
|
||||
ThreadLocalMap map = getMap(t);
|
||||
if (map != null) {
|
||||
return map.getEntry(this) != null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of set() to establish initialValue. Used instead
|
||||
* of set() in case user has overridden the set() method.
|
||||
@ -302,7 +281,11 @@ public class ThreadLocal<T> {
|
||||
* @return the map
|
||||
*/
|
||||
ThreadLocalMap getMap(Thread t) {
|
||||
return t.threadLocals;
|
||||
if (this instanceof TerminatingThreadLocal<T>) {
|
||||
return t.terminatingThreadLocals();
|
||||
} else {
|
||||
return t.threadLocals();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,7 +296,12 @@ public class ThreadLocal<T> {
|
||||
* @param firstValue value for the initial entry of the map
|
||||
*/
|
||||
void createMap(Thread t, T firstValue) {
|
||||
t.threadLocals = new ThreadLocalMap(this, firstValue);
|
||||
var map = new ThreadLocalMap(this, firstValue);
|
||||
if (this instanceof TerminatingThreadLocal<T>) {
|
||||
t.setTerminatingThreadLocals(map);
|
||||
} else {
|
||||
t.setThreadLocals(map);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -546,12 +546,6 @@ public interface JavaLangAccess {
|
||||
*/
|
||||
void removeCarrierThreadLocal(CarrierThreadLocal<?> local);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if there is a value in the current carrier thread's copy of
|
||||
* thread-local, even if that values is {@code null}.
|
||||
*/
|
||||
boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local);
|
||||
|
||||
/**
|
||||
* Returns the current thread's scoped values cache
|
||||
*/
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, 2025, 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
|
||||
@ -49,9 +49,5 @@ public class CarrierThreadLocal<T> extends ThreadLocal<T> {
|
||||
JLA.removeCarrierThreadLocal(this);
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return JLA.isCarrierThreadLocalPresent(this);
|
||||
}
|
||||
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, 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,10 +29,9 @@ import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
/**
|
||||
* A per-carrier-thread-local variable that is notified when a thread terminates and
|
||||
* it has been initialized in the terminating carrier thread or a virtual thread
|
||||
* that had the terminating carrier thread as its carrier thread (even if it was
|
||||
* initialized with a null value).
|
||||
* A platform thread-local variable that is notified when a platform thread terminates,
|
||||
* and it has been initialized in the terminating thread (or a mounted virtual thread in
|
||||
* the case of a carrier thread), and even if it was initialized with a null value.
|
||||
*/
|
||||
public class TerminatingThreadLocal<T> extends CarrierThreadLocal<T> {
|
||||
|
||||
@ -44,8 +43,8 @@ public class TerminatingThreadLocal<T> extends CarrierThreadLocal<T> {
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
super.remove();
|
||||
unregister(this);
|
||||
super.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +79,9 @@ public class TerminatingThreadLocal<T> extends CarrierThreadLocal<T> {
|
||||
* @param tl the ThreadLocal to register
|
||||
*/
|
||||
public static void register(TerminatingThreadLocal<?> tl) {
|
||||
REGISTRY.get().add(tl);
|
||||
if (tl != REGISTRY) {
|
||||
REGISTRY.get().add(tl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,11 +94,11 @@ public class TerminatingThreadLocal<T> extends CarrierThreadLocal<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* a per-carrier-thread registry of TerminatingThreadLocal(s) that have been registered
|
||||
* but later not unregistered in a particular carrier-thread.
|
||||
* A per-platform-thread registry of TerminatingThreadLocal(s). The registry is
|
||||
* itself a TerminatingThreadLocal to keep it reachable until the thread terminates.
|
||||
*/
|
||||
public static final CarrierThreadLocal<Collection<TerminatingThreadLocal<?>>> REGISTRY =
|
||||
new CarrierThreadLocal<>() {
|
||||
public static final TerminatingThreadLocal<Collection<TerminatingThreadLocal<?>>> REGISTRY =
|
||||
new TerminatingThreadLocal<>() {
|
||||
@Override
|
||||
protected Collection<TerminatingThreadLocal<?>> initialValue() {
|
||||
return Collections.newSetFromMap(new IdentityHashMap<>(4));
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2000, 2025, 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
|
||||
@ -79,6 +79,7 @@ class IOVecWrapper {
|
||||
protected void threadTerminated(IOVecWrapper[] cache) {
|
||||
IOVecWrapper wrapper = cache[0];
|
||||
if (wrapper != null) {
|
||||
cache[0] = null;
|
||||
wrapper.vecArray.free();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, 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
|
||||
@ -24,6 +24,7 @@
|
||||
import jdk.internal.misc.TerminatingThreadLocal;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -31,6 +32,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
@ -41,7 +43,7 @@ import static org.testng.Assert.*;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8202788 8291897
|
||||
* @bug 8202788 8291897 8357637
|
||||
* @summary TerminatingThreadLocal unit test
|
||||
* @modules java.base/java.lang:+open java.base/jdk.internal.misc
|
||||
* @requires vm.continuations
|
||||
@ -154,6 +156,53 @@ public class TestTerminatingThreadLocal {
|
||||
assertEquals(terminatedValues, expectedTerminatedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test TerminatingThreadLocal when thread locals are "cleared" by null'ing the
|
||||
* threadLocal field of the current Thread.
|
||||
*/
|
||||
@Test
|
||||
public void testClearingThreadLocals() throws Throwable {
|
||||
var terminatedValues = new CopyOnWriteArrayList<Object>();
|
||||
|
||||
var tl = new ThreadLocal<String>();
|
||||
var ttl = new TerminatingThreadLocal<String>() {
|
||||
@Override
|
||||
protected void threadTerminated(String value) {
|
||||
terminatedValues.add(value);
|
||||
}
|
||||
};
|
||||
var throwableRef = new AtomicReference<Throwable>();
|
||||
|
||||
String tlValue = "abc";
|
||||
String ttlValue = "xyz";
|
||||
|
||||
Thread thread = Thread.ofPlatform().start(() -> {
|
||||
try {
|
||||
tl.set(tlValue);
|
||||
ttl.set(ttlValue);
|
||||
|
||||
assertEquals(tl.get(), tlValue);
|
||||
assertEquals(ttl.get(), ttlValue);
|
||||
|
||||
// set Thread.threadLocals to null
|
||||
Field f = Thread.class.getDeclaredField("threadLocals");
|
||||
f.setAccessible(true);
|
||||
f.set(Thread.currentThread(), null);
|
||||
|
||||
assertNull(tl.get());
|
||||
assertEquals(ttl.get(), ttlValue);
|
||||
} catch (Throwable t) {
|
||||
throwableRef.set(t);
|
||||
}
|
||||
});
|
||||
thread.join();
|
||||
if (throwableRef.get() instanceof Throwable t) {
|
||||
throw t;
|
||||
}
|
||||
|
||||
assertEquals(terminatedValues, List.of(ttlValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder to create virtual threads that use the given scheduler.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user