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,26 @@ private Finalizer()
8473 {
8574 Enable = true ;
8675 Threshold = 200 ;
87- _collectAction = OnPendingCollect ;
8876 }
8977
90- public void CallPendingFinalizers ( )
78+ public bool CallPendingFinalizers ( )
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+ var ts = PythonEngine . BeginAllowThreads ( ) ;
84+ Instance . _finalizerTask . Wait ( ) ;
85+ PythonEngine . EndAllowThreads ( ts ) ;
86+ return true ;
9587 }
96- Runtime . Py_MakePendingCalls ( ) ;
88+ return false ;
9789 }
9890
9991 public void Collect ( )
10092 {
101- using ( var gilState = new Py . GILState ( ) )
93+ if ( ! Instance . CallPendingFinalizers ( ) )
10294 {
103- DisposeAll ( ) ;
95+ Instance . DisposeAll ( ) ;
10496 }
10597 }
10698
@@ -141,25 +133,10 @@ internal static void Shutdown()
141133 Instance . _objQueue = new ConcurrentQueue < IPyDisposable > ( ) ;
142134 return ;
143135 }
144- Instance . DisposeAll ( ) ;
145- if ( Thread . CurrentThread . ManagedThreadId != Runtime . MainManagedThreadId )
136+ if ( ! Instance . CallPendingFinalizers ( ) )
146137 {
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 ;
138+ Instance . DisposeAll ( ) ;
161139 }
162- Instance . CallPendingFinalizers ( ) ;
163140 }
164141
165142 private void AddPendingCollect ( )
@@ -171,16 +148,14 @@ private void AddPendingCollect()
171148 if ( ! _pending )
172149 {
173150 _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 )
151+ // should already be complete but just in case
152+ _finalizerTask ? . Wait ( ) ;
153+
154+ _finalizerTask = Task . Factory . StartNew ( ( ) =>
179155 {
180- // Full queue, append next time
181- FreePendingArgs ( ) ;
156+ Instance . DisposeAll ( ) ;
182157 _pending = false ;
183- }
158+ } ) ;
184159 }
185160 }
186161 finally
@@ -190,29 +165,6 @@ private void AddPendingCollect()
190165 }
191166 }
192167
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-
216168 private void DisposeAll ( )
217169 {
218170 CollectOnce ? . Invoke ( this , new CollectArgs ( )
@@ -223,46 +175,32 @@ private void DisposeAll()
223175 lock ( _queueLock )
224176#endif
225177 {
178+ using ( Py . GIL ( ) )
179+ {
226180#if FINALIZER_CHECK
227- ValidateRefCount ( ) ;
181+ ValidateRefCount ( ) ;
228182#endif
229- IPyDisposable obj ;
230- while ( _objQueue . TryDequeue ( out obj ) )
231- {
232- try
183+ IPyDisposable obj ;
184+ while ( _objQueue . TryDequeue ( out obj ) )
233185 {
234- obj . Dispose ( ) ;
235- Runtime . CheckExceptionOccurred ( ) ;
236- }
237- catch ( Exception e )
238- {
239- // We should not bother the main thread
240- ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
186+ try
241187 {
242- Error = e
243- } ) ;
188+ obj . Dispose ( ) ;
189+ Runtime . CheckExceptionOccurred ( ) ;
190+ }
191+ catch ( Exception e )
192+ {
193+ // We should not bother the main thread
194+ ErrorHandler ? . Invoke ( this , new ErrorArgs ( )
195+ {
196+ Error = e
197+ } ) ;
198+ }
244199 }
245200 }
246201 }
247202 }
248203
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-
266204#if FINALIZER_CHECK
267205 private void ValidateRefCount ( )
268206 {
0 commit comments