Skip to content

Commit ca7f30c

Browse files
committed
Dispose thread state on context finalization
1 parent 7bb92b6 commit ca7f30c

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@
251251
import com.oracle.graal.python.nodes.util.CastToJavaStringNode;
252252
import com.oracle.graal.python.runtime.ExecutionContext.IndirectCallContext;
253253
import com.oracle.graal.python.runtime.PythonContext;
254+
import com.oracle.graal.python.runtime.PythonContext.PythonThreadState;
254255
import com.oracle.graal.python.runtime.PythonCore;
255256
import com.oracle.graal.python.runtime.PythonOptions;
256257
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
@@ -1657,12 +1658,18 @@ Object wrap(VirtualFrame frame, Object bufferStructPointer, Object ownerObj, Obj
16571658

16581659
@Builtin(name = "PyThreadState_Get")
16591660
@GenerateNodeFactory
1660-
abstract static class PyThreadState_Get extends NativeBuiltin {
1661+
abstract static class PyThreadStateGet extends NativeBuiltin {
16611662

16621663
@Specialization
16631664
PThreadState get() {
1665+
PythonThreadState threadState = getContext().getThreadState();
1666+
PThreadState nativeWrapper = threadState.getNativeWrapper();
1667+
if (nativeWrapper == null) {
1668+
nativeWrapper = new PThreadState(threadState);
1669+
threadState.setNativeWrapper(nativeWrapper);
1670+
}
16641671
// does not require a 'to_sulong' since it is already a native wrapper type
1665-
return new PThreadState(getContext().getThreadState());
1672+
return nativeWrapper;
16661673
}
16671674
}
16681675

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
6262
import com.oracle.graal.python.builtins.objects.cext.PythonNativeClass;
6363
import com.oracle.graal.python.builtins.objects.cext.capi.CApiContext;
64+
import com.oracle.graal.python.builtins.objects.cext.capi.PThreadState;
65+
import com.oracle.graal.python.builtins.objects.cext.capi.PyTruffleObjectFree.ReleaseHandleNode;
66+
import com.oracle.graal.python.builtins.objects.cext.capi.PyTruffleObjectFreeFactory.ReleaseHandleNodeGen;
6467
import com.oracle.graal.python.builtins.objects.cext.capi.PythonNativeWrapper;
6568
import com.oracle.graal.python.builtins.objects.cext.hpy.GraalHPyContext;
6669
import com.oracle.graal.python.builtins.objects.common.HashingStorage;
@@ -139,6 +142,14 @@ public static final class PythonThreadState {
139142
/* corresponds to 'PyThreadState.dict' */
140143
PDict dict;
141144

145+
/*
146+
* This is the native wrapper object if we need to expose the thread state as PyThreadState
147+
* object. We need to store it here because the wrapper may receive 'toNative' in which case
148+
* a handle is allocated. In order to avoid leaks, the handle needs to be free'd when the
149+
* owning thread (or the whole context) is disposed.
150+
*/
151+
PThreadState nativeWrapper;
152+
142153
/*
143154
* The constructor needs to have this particular signature such that we can use it for
144155
* ContextThreadLocal.
@@ -191,6 +202,26 @@ public PDict getDict() {
191202
public void setDict(PDict dict) {
192203
this.dict = dict;
193204
}
205+
206+
public PThreadState getNativeWrapper() {
207+
return nativeWrapper;
208+
}
209+
210+
public void setNativeWrapper(PThreadState nativeWrapper) {
211+
this.nativeWrapper = nativeWrapper;
212+
}
213+
214+
public void dispose() {
215+
ReleaseHandleNode releaseHandleNode = ReleaseHandleNodeGen.getUncached();
216+
if (dict != null && dict.getNativeWrapper() != null) {
217+
releaseHandleNode.execute(dict.getNativeWrapper());
218+
}
219+
dict = null;
220+
if (nativeWrapper != null) {
221+
releaseHandleNode.execute(nativeWrapper);
222+
nativeWrapper = null;
223+
}
224+
}
194225
}
195226

196227
private static final class AtExitHook {
@@ -245,8 +276,8 @@ private static final class AtExitHook {
245276
*/
246277
private final PythonThreadState buildThreadState;
247278

248-
/* map of thread IDs to indices for array 'threadStates' */
249-
private Map<Thread, PythonThreadState> threadStateMapping = Collections.synchronizedMap(new WeakHashMap<>());
279+
/* map of thread IDs to the corresponding 'threadStates' */
280+
private final Map<Thread, PythonThreadState> threadStateMapping = Collections.synchronizedMap(new WeakHashMap<>());
250281

251282
private final ReentrantLock importLock = new ReentrantLock();
252283
@CompilationFinal private boolean isInitialized = false;
@@ -860,6 +891,16 @@ public void runShutdownHooks() {
860891
for (ShutdownHook h : shutdownHooks) {
861892
h.call(this);
862893
}
894+
assert threadStateMapping != null;
895+
for (PythonThreadState threadState : threadStateMapping.values()) {
896+
threadState.dispose();
897+
}
898+
ReleaseHandleNode releaseHandleNode = ReleaseHandleNodeGen.getUncached();
899+
for (PythonNativeWrapper singletonNativeWrapper : singletonNativePtrs) {
900+
if (singletonNativeWrapper != null) {
901+
releaseHandleNode.execute(singletonNativeWrapper);
902+
}
903+
}
863904
}
864905

865906
@TruffleBoundary
@@ -1166,6 +1207,7 @@ public synchronized void disposeThread(Thread thread) {
11661207
}
11671208
ts.shutdown();
11681209
threadStateMapping.remove(thread);
1210+
ts.dispose();
11691211
releaseSentinelLock(ts.sentinelLock);
11701212
}
11711213

0 commit comments

Comments
 (0)