11using System ;
22using System . Collections . Concurrent ;
33using System . Collections . Generic ;
4- using System . Diagnostics ;
54using System . Linq ;
6- using System . Runtime . InteropServices ;
75using System . Threading ;
6+ using System . Threading . Tasks ;
87
98namespace Python . Runtime
109{
@@ -28,20 +27,10 @@ public class ErrorArgs : EventArgs
2827 public int Threshold { get ; set ; }
2928 public bool Enable { get ; set ; }
3029
31- [ StructLayout ( LayoutKind . Sequential , CharSet = CharSet . Ansi ) ]
32- struct PendingArgs
33- {
34- public bool cancelled ;
35- }
36-
37- [ UnmanagedFunctionPointer ( CallingConvention . Cdecl ) ]
38- private delegate int PendingCall ( IntPtr arg ) ;
39- private readonly PendingCall _collectAction ;
40-
4130 private ConcurrentQueue < IPyDisposable > _objQueue = new ConcurrentQueue < IPyDisposable > ( ) ;
4231 private bool _pending = false ;
4332 private readonly object _collectingLock = new object ( ) ;
44- private IntPtr _pendingArgs = IntPtr . Zero ;
33+ private Task _finalizerTask ;
4534
4635 #region FINALIZER_CHECK
4736
@@ -84,23 +73,23 @@ private Finalizer()
8473 {
8574 Enable = true ;
8675 Threshold = 200 ;
87- _collectAction = OnPendingCollect ;
8876 }
8977
90- public void CallPendingFinalizers ( )
78+ public void Collect ( bool forceDispose = true )
9179 {
92- if ( Thread . CurrentThread . ManagedThreadId != Runtime . MainManagedThreadId )
80+ if ( Instance . _finalizerTask != null
81+ && ! Instance . _finalizerTask . IsCompleted )
9382 {
94- throw new Exception ( "PendingCall should execute in main Python thread" ) ;
83+ using ( Py . GIL ( ) )
84+ {
85+ var ts = PythonEngine . BeginAllowThreads ( ) ;
86+ Instance . _finalizerTask . Wait ( ) ;
87+ PythonEngine . EndAllowThreads ( ts ) ;
88+ }
9589 }
96- Runtime . Py_MakePendingCalls ( ) ;
97- }
98-
99- public void Collect ( )
100- {
101- using ( var gilState = new Py . GILState ( ) )
90+ else if ( forceDispose )
10291 {
103- DisposeAll ( ) ;
92+ Instance . DisposeAll ( ) ;
10493 }
10594 }
10695
@@ -141,25 +130,7 @@ internal static void Shutdown()
141130 Instance . _objQueue = new ConcurrentQueue < IPyDisposable > ( ) ;
142131 return ;
143132 }
144- Instance . DisposeAll ( ) ;
145- if ( Thread . CurrentThread . ManagedThreadId != Runtime . MainManagedThreadId )
146- {
147- if ( Instance . _pendingArgs == IntPtr . Zero )
148- {
149- Instance . ResetPending ( ) ;
150- return ;
151- }
152- // Not in main thread just cancel the pending operation to avoid error in different domain
153- // It will make a memory leak
154- unsafe
155- {
156- PendingArgs * args = ( PendingArgs * ) Instance . _pendingArgs ;
157- args ->cancelled = true ;
158- }
159- Instance . ResetPending ( ) ;
160- return ;
161- }
162- Instance . CallPendingFinalizers ( ) ;
133+ Instance . Collect ( forceDispose : true ) ;
163134 }
164135
165136 private void AddPendingCollect ( )
@@ -171,16 +142,14 @@ private void AddPendingCollect()
171142 if ( ! _pending )
172143 {
173144 _pending = true ;
174- var args = new PendingArgs { cancelled = false } ;
175- _pendingArgs = Marshal . AllocHGlobal ( Marshal . SizeOf ( typeof ( PendingArgs ) ) ) ;
176- Marshal . StructureToPtr ( args , _pendingArgs , false ) ;
177- IntPtr func = Marshal . GetFunctionPointerForDelegate ( _collectAction ) ;
178- if ( Runtime . Py_AddPendingCall ( func , _pendingArgs ) != 0 )
145+ // should already be complete but just in case
146+ _finalizerTask ? . Wait ( ) ;
147+
148+ _finalizerTask = Task . Factory . StartNew ( ( ) =>
179149 {
180- // Full queue, append next time
181- FreePendingArgs ( ) ;
150+ Instance . DisposeAll ( ) ;
182151 _pending = false ;
183- }
152+ } ) ;
184153 }
185154 }
186155 finally
@@ -190,29 +159,6 @@ private void AddPendingCollect()
190159 }
191160 }
192161
193- private static int OnPendingCollect ( IntPtr arg )
194- {
195- Debug . Assert ( arg == Instance . _pendingArgs ) ;
196- try
197- {
198- unsafe
199- {
200- PendingArgs * pendingArgs = ( PendingArgs * ) arg ;
201- if ( pendingArgs ->cancelled )
202- {
203- return 0 ;
204- }
205- }
206- Instance . DisposeAll ( ) ;
207- }
208- finally
209- {
210- Instance . FreePendingArgs ( ) ;
211- Instance . ResetPending ( ) ;
212- }
213- return 0 ;
214- }
215-
216162 private void DisposeAll ( )
217163 {
218164 CollectOnce ? . Invoke ( this , new CollectArgs ( )
@@ -223,46 +169,32 @@ private void DisposeAll()
223169 lock ( _queueLock )
224170#endif
225171 {
172+ using ( Py . GIL ( ) )
173+ {
226174#if FINALIZER_CHECK
227- ValidateRefCount ( ) ;
175+ ValidateRefCount ( ) ;
228176#endif
229- IPyDisposable obj ;
230- while ( _objQueue . TryDequeue ( out obj ) )
231- {
232- try
233- {
234- obj . Dispose ( ) ;
235- Runtime . CheckExceptionOccurred ( ) ;
236- }
237- catch ( Exception e )
177+ IPyDisposable obj ;
178+ while ( _objQueue . TryDequeue ( out obj ) )
238179 {
239- // We should not bother the main thread
240- ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
180+ try
241181 {
242- Error = e
243- } ) ;
182+ obj . Dispose ( ) ;
183+ Runtime . CheckExceptionOccurred ( ) ;
184+ }
185+ catch ( Exception e )
186+ {
187+ // We should not bother the main thread
188+ ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
189+ {
190+ Error = e
191+ } ) ;
192+ }
244193 }
245194 }
246195 }
247196 }
248197
249- private void FreePendingArgs ( )
250- {
251- if ( _pendingArgs != IntPtr . Zero )
252- {
253- Marshal . FreeHGlobal ( _pendingArgs ) ;
254- _pendingArgs = IntPtr . Zero ;
255- }
256- }
257-
258- private void ResetPending ( )
259- {
260- lock ( _collectingLock )
261- {
262- _pending = false ;
263- }
264- }
265-
266198#if FINALIZER_CHECK
267199 private void ValidateRefCount ( )
268200 {
0 commit comments