Skip to content

Commit 6d738bf

Browse files
committed
Run callbacks registered by atexit at Shutdown on soft-shutdown mode
1 parent e877b33 commit 6d738bf

3 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/embed_tests/pyinitialize.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,46 @@ public void ShutdownHandlers()
136136
// Wrong: (4 * 2) + 1 + 1 + 1 = 11
137137
Assert.That(shutdown_count, Is.EqualTo(12));
138138
}
139+
140+
[Test]
141+
public static void TestRunExitFuncs()
142+
{
143+
if (Runtime.Runtime.GetDefaultShutdownMode() == ShutdownMode.Normal)
144+
{
145+
// If the runtime using the normal mode,
146+
// callback registered by atexit will be called after we release the clr information,
147+
// thus there's no chance we can check it here.
148+
Assert.Ignore("Skip on normal mode");
149+
}
150+
Runtime.Runtime.Initialize();
151+
PyObject atexit;
152+
try
153+
{
154+
atexit = Py.Import("atexit");
155+
}
156+
catch (PythonException e)
157+
{
158+
string msg = e.ToString();
159+
Runtime.Runtime.Shutdown();
160+
161+
if (e.IsMatches(Exceptions.ImportError))
162+
{
163+
Assert.Ignore("no atexit module");
164+
}
165+
else
166+
{
167+
Assert.Fail(msg);
168+
}
169+
return;
170+
}
171+
bool called = false;
172+
Action callback = () =>
173+
{
174+
called = true;
175+
};
176+
atexit.InvokeMethod("register", callback.ToPython());
177+
Runtime.Runtime.Shutdown();
178+
Assert.True(called);
179+
}
139180
}
140181
}

src/runtime/pythonexception.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ public string Format()
187187
return res;
188188
}
189189

190+
public bool IsMatches(IntPtr exc)
191+
{
192+
return Runtime.PyErr_GivenExceptionMatches(PyType, exc) != 0;
193+
}
194+
190195
/// <summary>
191196
/// Dispose Method
192197
/// </summary>

src/runtime/runtime.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ internal static void Shutdown()
317317
PyGILState_Ensure();
318318

319319
var mode = ShutdownMode;
320+
if (mode != ShutdownMode.Normal)
321+
{
322+
RunExitFuncs();
323+
}
320324
#if !NETSTANDARD
321325
if (mode == ShutdownMode.Reload)
322326
{
@@ -388,6 +392,34 @@ internal static int AtExit()
388392
return 0;
389393
}
390394

395+
private static void RunExitFuncs()
396+
{
397+
PyObject atexit;
398+
try
399+
{
400+
atexit = Py.Import("atexit");
401+
}
402+
catch (PythonException e)
403+
{
404+
if (!e.IsMatches(Exceptions.ImportError))
405+
{
406+
throw;
407+
}
408+
e.Dispose();
409+
// The runtime may not provided `atexit` module.
410+
return;
411+
}
412+
try
413+
{
414+
atexit.InvokeMethod("_run_exitfuncs").Dispose();
415+
}
416+
catch (PythonException e)
417+
{
418+
Console.Error.WriteLine(e);
419+
e.Dispose();
420+
}
421+
}
422+
391423
private static void SetPyMember(ref IntPtr obj, IntPtr value, Action onRelease)
392424
{
393425
// XXX: For current usages, value should not be null.
@@ -1849,7 +1881,7 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n)
18491881
private static extern IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n);
18501882

18511883
/// <summary>
1852-
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type¡¯s base class. Return 0 on success, or return -1 and sets an exception on error.
1884+
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a types base class. Return 0 on success, or return -1 and sets an exception on error.
18531885
/// </summary>
18541886
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
18551887
internal static extern int PyType_Ready(IntPtr type);

0 commit comments

Comments
 (0)