8000 Track Runtime run number. Assert, that PyObjects are only disposed in… · pythonnet/pythonnet@4f657d4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4f657d4

Browse files
committed
Track Runtime run number. Assert, that PyObjects are only disposed in the same run they were created in.
1 parent ac336a8 commit 4f657d4

File tree

3 files changed

+61
-21
lines changed

3 files changed

+61
-21
lines changed

src/runtime/pybuffer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,10 @@ private void Dispose(bool disposing)
236236

237237
~PyBuffer()
238238
{
239-
if (disposedValue)
240-
{
241-
return;
242-
}
239+
Debug.Assert(!disposedValue);
240+
241+
_exporter.CheckRun();
242+
243243
Finalizer.Instance.AddFinalizedObject(ref _view.obj);
244244
}
245245

src/runtime/pyobject.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public partial class PyObject : DynamicObject, IDisposable
2727
#endif
2828

2929
protected internal IntPtr obj = IntPtr.Zero;
30+
readonly int run = Runtime.GetRun();
3031

3132
public static PyObject None => new PyObject(new BorrowedReference(Runtime.PyNone));
3233
internal BorrowedReference Reference => new BorrowedReference(this.obj);
@@ -95,11 +96,15 @@ internal PyObject(in StolenReference reference)
9596
// when the managed wrapper is garbage-collected.
9697
~PyObject()
9798
{
98-
if (obj == IntPtr.Zero)
99-
{
100-
return;
101-
}
99+
Debug.Assert(obj != IntPtr.Zero);
100+
101+
#if TRACE_ALLOC
102+
CheckRun();
103+
#endif
104+
102105
Finalizer.Instance.AddFinalizedObject(ref obj);
106+
107+
Dispose(false);
103108
}
104109

105110

@@ -167,17 +172,6 @@ public object AsManagedObject(Type t)
167172

168173
internal bool IsDisposed => obj == IntPtr.Zero;
169174

170-
/// <summary>
171-
/// Dispose Method
172-
/// </summary>
173-
/// <remarks>
174-
/// The Dispose method provides a way to explicitly release the
175-
/// Python object represented by a PyObject instance. It is a good
176-
/// idea to call Dispose on PyObjects that wrap resources that are
177-
/// limited or need strict lifetime control. Otherwise, references
178-
/// to Python objects will not be released until a managed garbage
179-
/// collection occurs.
180-
/// </remarks>
181175
protected virtual void Dispose(bool disposing)
182176
{
183177
if (this.obj == IntPtr.Zero)
@@ -188,6 +182,8 @@ protected virtual void Dispose(bool disposing)
188182
if (Runtime.Py_IsInitialized() == 0)
189183
throw new InvalidOperationException("Python runtime must be initialized");
190184

185+
CheckRun();
186+
191187
if (!Runtime.IsFinalizing)
192188
{
193189
long refcount = Runtime.Refcount(this.obj);
@@ -221,10 +217,26 @@ protected virtual void Dispose(bool disposing)
221217
this.obj = IntPtr.Zero;
222218
}
223219

220+
/// <summary>
221+
/// The Dispose method provides a way to explicitly release the
222+
/// Python object represented by a PyObject instance. It is a good
223+
/// idea to call Dispose on PyObjects that wrap resources that are
224+
/// limited or need strict lifetime control. Otherwise, references
225+
/// to Python objects will not be released until a managed garbage
226+
/// collection occurs.
227+
/// </summary>
224228
public void Dispose()
225229
{
226-
Dispose(true);
227230
GC.SuppressFinalize(this);
231+
Dispose(true);
232+
}
233+
234+
internal void CheckRun()
235+
{
236+
if (run != Runtime.GetRun())
237+
throw new InvalidOperationException(
238+
"PythonEngine was shut down after this object was created." +
239+
" It is an error to attempt to dispose or to continue using it.");
228240
}
229241

230242
internal BorrowedReference GetPythonTypeReference()

src/runtime/runtime.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ internal static Version PyVersion
8585
}
8686
}
8787

88-
/// <summary>
88+
static int run = 0;
89+
90+
internal static int GetRun()
91+
{
92+
int runNumber = run;
93+
Debug.Assert(runNumber > 0, "This must only be called after Runtime is initialized at least once");
94+
return runNumber;
95+
}
96+
8997
/// Initialize the runtime...
9098
/// </summary>
9199
/// <remarks>Always call this method from the Main thread. After the
@@ -110,6 +118,9 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
110118
if (!interpreterAlreadyInitialized)
111119
{
112120
Py_InitializeEx(initSigs ? 1 : 0);
121+
122+
NewRun();
123+
113124
if (PyEval_ThreadsInitialized() == 0)
114125
{
115126
PyEval_InitThreads();
@@ -130,6 +141,16 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
130141
{
131142
PyGILState_Ensure();
132143
}
144+
145+
BorrowedReference pyRun = PySys_GetObject("__pynet_run__");
146+
if (pyRun != null)
147+
{
148+
run = checked((int)PyLong_AsSignedSize_t(pyRun));
149+
}
150+
else
151+
{
152+
NewRun();
153+
}
133154
}
134155
MainManagedThreadId = Thread.CurrentThread.ManagedThreadId;
135156

@@ -175,6 +196,13 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
175196
inspect = GetModuleLazy("inspect");
176197
}
177198

199+
static void NewRun()
200+
{
201+
run++;
202+
using var pyRun = PyLong_FromLongLong(run);
203+
PySys_SetObject("__pynet_run__", pyRun);
204+
}
205+
178206
private static void InitPyMembers()
179207
{
180208
IntPtr op;

0 commit comments

Comments
 (0)
0