forked from amos402/pythonnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfinalizer.cs
More file actions
126 lines (111 loc) · 3.41 KB
/
finalizer.cs
File metadata and controls
126 lines (111 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Python.Runtime
{
public class Finalizer
{
public class CollectArgs : EventArgs
{
public int ObjectCount { get; set; }
}
public static readonly Finalizer Instance = new Finalizer();
public event EventHandler<CollectArgs> CollectOnce;
private ConcurrentQueue<IDisposable> _objQueue = new ConcurrentQueue<IDisposable>();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int PedingCall(IntPtr arg);
private readonly PedingCall _collectAction;
private bool _pending = false;
private readonly object _collectingLock = new object();
public int Threshold { get; set; }
public bool Enable { get; set; }
private Finalizer()
{
Enable = true;
Threshold = 200;
_collectAction = OnPendingCollect;
}
public void CallPendingFinalizers()
{
if (Thread.CurrentThread.ManagedThreadId != Runtime.MainManagedThreadId)
{
throw new Exception("PendingCall should execute in main Python thread");
}
Runtime.Py_MakePendingCalls();
}
public void Collect()
{
using (var gilState = new Py.GILState())
{
DisposeAll();
}
}
public List<WeakReference> GetCollectedObjects()
{
return _objQueue.Select(T => new WeakReference(T)).ToList();
}
internal void AddFinalizedObject(IDisposable obj)
{
if (!Enable)
{
return;
}
if (Runtime.Py_IsInitialized() == 0)
{
// XXX: Memory will leak if a PyObject finalized after Python shutdown,
// for avoiding that case, user should call GC.Collect manual before shutdown.
return;
}
_objQueue.Enqueue(obj);
GC.ReRegisterForFinalize(obj);
if (_objQueue.Count >= Threshold)
{
AddPendingCollect();
}
}
internal static void Shutdown()
{
Instance.DisposeAll();
Instance.CallPendingFinalizers();
Runtime.PyErr_Clear();
}
private void AddPendingCollect()
{
lock (_collectingLock)
{
if (_pending)
{
return;
}
_pending = true;
}
IntPtr func = Marshal.GetFunctionPointerForDelegate(_collectAction);
if (Runtime.Py_AddPendingCall(func, IntPtr.Zero) != 0)
{
// Full queue, append next time
_pending = false;
}
}
private int OnPendingCollect(IntPtr arg)
{
DisposeAll();
_pending = false;
return 0;
}
private void DisposeAll()
{
CollectOnce?.Invoke(this, new CollectArgs()
{
ObjectCount = _objQueue.Count
});
IDisposable obj;
while (_objQueue.TryDequeue(out obj))
{
obj.Dispose();
}
}
}
}