22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
44using System . ComponentModel ;
5+ using System . Diagnostics ;
56using System . Linq ;
67using System . Threading ;
78using System . Threading . Tasks ;
@@ -30,10 +31,12 @@ public class ErrorArgs : EventArgs
3031 [ DefaultValue ( DefaultThreshold ) ]
3132 public int Threshold { get ; set ; } = DefaultThreshold ;
3233
34+ volatile bool started ;
35+
3336 [ DefaultValue ( true ) ]
3437 public bool Enable { get ; set ; } = true ;
3538
36- private ConcurrentQueue < IntPtr > _objQueue = new ConcurrentQueue < IntPtr > ( ) ;
39+ private ConcurrentQueue < PendingFinalization > _objQueue = new ( ) ;
3740 private int _throttled ;
3841
3942 #region FINALIZER_CHECK
@@ -79,6 +82,8 @@ internal IncorrectRefCountException(IntPtr ptr)
7982
8083 internal void ThrottledCollect ( )
8184 {
85+ if ( ! started ) throw new InvalidOperationException ( $ "{ nameof ( PythonEngine ) } is not initialized") ;
86+
8287 _throttled = unchecked ( this . _throttled + 1 ) ;
8388 if ( ! Enable || _throttled < Threshold ) return ;
8489 _throttled = 0 ;
@@ -87,12 +92,13 @@ internal void ThrottledCollect()
8792
8893 internal List < IntPtr > GetCollectedObjects ( )
8994 {
90- return _objQueue . ToList ( ) ;
95+ return _objQueue . Select ( o => o . PyObj ) . ToList ( ) ;
9196 }
9297
93- internal void AddFinalizedObject ( ref IntPtr obj )
98+ internal void AddFinalizedObject ( ref IntPtr obj , int run )
9499 {
95- if ( ! Enable || obj == IntPtr . Zero )
100+ Debug . Assert ( obj != IntPtr . Zero ) ;
101+ if ( ! Enable )
96102 {
97103 return ;
98104 }
@@ -101,14 +107,20 @@ internal void AddFinalizedObject(ref IntPtr obj)
101107 lock ( _queueLock )
102108#endif
103109 {
104- this . _objQueue . Enqueue ( obj ) ;
110+ this . _objQueue . Enqueue ( new PendingFinalization { PyObj = obj , RuntimeRun = run } ) ;
105111 }
106112 obj = IntPtr . Zero ;
107113 }
108114
115+ internal static void Initialize ( )
116+ {
117+ Instance . started = true ;
118+ }
119+
109120 internal static void Shutdown ( )
110121 {
111122 Instance . DisposeAll ( ) ;
123+ Instance . started = false ;
112124 }
113125
114126 private void DisposeAll ( )
@@ -124,36 +136,31 @@ private void DisposeAll()
124136#if FINALIZER_CHECK
125137 ValidateRefCount ( ) ;
126138#endif
127- IntPtr obj ;
128139 Runtime . PyErr_Fetch ( out var errType , out var errVal , out var traceback ) ;
129140
141+ int run = Runtime . GetRun ( ) ;
142+
130143 try
131144 {
132145 while ( ! _objQueue . IsEmpty )
133146 {
134- if ( ! _objQueue . TryDequeue ( out obj ) )
147+ if ( ! _objQueue . TryDequeue ( out var obj ) )
148+ continue ;
149+
150+ if ( obj . RuntimeRun != run )
151+ {
152+ HandleFinalizationException ( obj . PyObj , new RuntimeShutdownException ( obj . PyObj ) ) ;
135153 continue ;
154+ }
136155
137- Runtime . XDecref ( obj ) ;
156+ Runtime . XDecref ( obj . PyObj ) ;
138157 try
139158 {
140159 Runtime . CheckExceptionOccurred ( ) ;
141160 }
142161 catch ( Exception e )
143162 {
144- var errorArgs = new ErrorArgs
145- {
146- Error = e ,
147- } ;
148-
149- ErrorHandler ? . Invoke ( this , errorArgs ) ;
150-
151- if ( ! errorArgs . Handled )
152- {
153- throw new FinalizationException (
154- "Python object finalization failed" ,
155- disposable : obj , innerException : e ) ;
156- }
163+ HandleFinalizationException ( obj . PyObj , e ) ;
157164 }
158165 }
159166 }
@@ -166,6 +173,23 @@ private void DisposeAll()
166173 }
167174 }
168175
176+ void HandleFinalizationException ( IntPtr obj , Exception cause )
177+ {
178+ var errorArgs = new ErrorArgs
179+ {
180+ Error = cause ,
181+ } ;
182+
183+ ErrorHandler ? . Invoke ( this , errorArgs ) ;
184+
185+ if ( ! errorArgs . Handled )
186+ {
187+ throw new FinalizationException (
188+ "Python object finalization failed" ,
189+ disposable : obj , innerException : cause ) ;
190+ }
191+ }
192+
169193#if FINALIZER_CHECK
170194 private void ValidateRefCount ( )
171195 {
@@ -235,6 +259,12 @@ private void ValidateRefCount()
235259#endif
236260 }
237261
262+ struct PendingFinalization
263+ {
264+ public IntPtr PyObj ;
265+ public int RuntimeRun ;
266+ }
267+
238268 public class FinalizationException : Exception
239269 {
240270 public IntPtr Handle { get ; }
@@ -259,5 +289,21 @@ public FinalizationException(string message, IntPtr disposable, Exception innerE
259289 if ( disposable == IntPtr . Zero ) throw new ArgumentNullException ( nameof ( disposable ) ) ;
260290 this . Handle = disposable ;
261291 }
292+
293+ protected FinalizationException ( string message , IntPtr disposable )
294+ : base ( message )
295+ {
296+ if ( disposable == IntPtr . Zero ) throw new ArgumentNullException ( nameof ( disposable ) ) ;
297+ this . Handle = disposable ;
298+ }
299+ }
300+
301+ public class RuntimeShutdownException : FinalizationException
302+ {
303+ public RuntimeShutdownException ( IntPtr disposable )
304+ : base ( "Python runtime was shut down after this object was created." +
305+ " It is an error to attempt to dispose or to continue using it even after restarting the runtime." , disposable )
306+ {
307+ }
262308 }
263309}
0 commit comments