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
|
From 1d028bbb1a1811e976efa8262d713485b70dc437 Mon Sep 17 00:00:00 2001
From: Scott Talbert <swt@techie.net>
Date: Thu, 17 Sep 2026 11:18:02 -0400
Subject: [PATCH 2/3] Preserve pending exception across CurlMulti dealloc
cleanup
do_multi_dealloc() can run while an exception is already propagating,
e.g. when a CurlMulti subclass's __init__ rejects its arguments: the
freshly constructed object's refcount drops to zero and it is torn
down immediately, before the TypeError finishes unwinding. The
cleanup in util_multi_detach_easies() calls back into Python (via
PySequence_List() on easy_object_refs, and close() on any attached
easy handles), which must not run with that exception still set.
On a Python interpreter built with assertions enabled, this trips
_Py_CheckSlotResult and aborts the process (fixes #1071). Save and
restore the pending exception around the dealloc cleanup, matching
the existing pattern in easycb.c.
---
src/multi.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/multi.c b/src/multi.c
index 170bfa098..ae4564116 100644
--- a/src/multi.c
+++ b/src/multi.c
@@ -216,6 +216,15 @@ util_multi_detach_easies(CurlMultiObject *self, int close_handles, int swallow_e
PYCURL_INTERNAL void
do_multi_dealloc(CurlMultiObject *self)
{
+ /* tp_dealloc can run while an exception is propagating (e.g. when
+ * __init__ of a subclass rejects our arguments and the freshly
+ * constructed object is immediately discarded). The cleanup below
+ * calls back into Python (iterating easy_object_refs, calling the
+ * easy objects' close() method), which must not observe that
+ * exception, so stash it for the duration. */
+ PyObject *exc_type, *exc_val, *exc_tb;
+ PyErr_Fetch(&exc_type, &exc_val, &exc_tb);
+
PyObject_GC_UnTrack(self);
Py_TRASHCAN_BEGIN(self, do_multi_dealloc);
@@ -235,6 +244,8 @@ do_multi_dealloc(CurlMultiObject *self)
CurlMulti_Type.tp_free(self);
Py_TRASHCAN_END
+
+ PyErr_Restore(exc_type, exc_val, exc_tb);
}
|