@@ -212,6 +212,7 @@ public void setNativeWrapper(PThreadState nativeWrapper) {
212212 }
213213
214214 public void dispose () {
215+ // This method may be called twice on the same object.
215216 ReleaseHandleNode releaseHandleNode = ReleaseHandleNodeGen .getUncached ();
216217 if (dict != null && dict .getNativeWrapper () != null ) {
217218 releaseHandleNode .execute (dict .getNativeWrapper ());
@@ -854,6 +855,8 @@ public void finalizeContext() {
854855 try (GilNode .UncachedAcquire gil = GilNode .uncachedAcquire ()) {
855856 shutdownThreads ();
856857 runShutdownHooks ();
858+ disposeThreadStates ();
859+ cleanupCApiResources ();
857860 }
858861 }
859862
@@ -891,10 +894,26 @@ public void runShutdownHooks() {
891894 for (ShutdownHook h : shutdownHooks ) {
892895 h .call (this );
893896 }
894- assert threadStateMapping != null ;
895- for (PythonThreadState threadState : threadStateMapping .values ()) {
896- threadState .dispose ();
897+ }
898+
899+ /**
900+ * Release all resources held by the thread states. This function needs to run as long as the
901+ * context is still valid because it may call into LLVM to release handles.
902+ */
903+ @ TruffleBoundary
904+ private void disposeThreadStates () {
905+ for (PythonThreadState ts : threadStateMapping .values ()) {
906+ ts .dispose ();
897907 }
908+ threadStateMapping .clear ();
909+ }
910+
911+ /**
912+ * Release all native wrappers of singletons. This function needs to run as long as the context
913+ * is still valid because it may call into LLVM to release handles.
914+ */
915+ @ TruffleBoundary
916+ private void cleanupCApiResources () {
898917 ReleaseHandleNode releaseHandleNode = ReleaseHandleNodeGen .getUncached ();
899918 for (PythonNativeWrapper singletonNativeWrapper : singletonNativePtrs ) {
900919 if (singletonNativeWrapper != null ) {
0 commit comments