Skip to content

Commit 0cc43df

Browse files
[2.7] bpo-28994: Fixed errors handling in atexit._run_exitfuncs(). (GH-2034) (#2123)
The traceback no longer displayed for SystemExit raised in a callback registered by atexit.. (cherry picked from commit 3fd54d4)
1 parent 107949c commit 0cc43df

File tree

3 files changed

+363
-2
lines changed

3 files changed

+363
-2
lines changed

Lib/test/test_atexit.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
from imp import reload
66
from test import test_support
77

8+
9+
def exit():
10+
raise SystemExit
11+
12+
813
class TestCase(unittest.TestCase):
914
def setUp(self):
10-
s = StringIO.StringIO()
1115
self.save_stdout = sys.stdout
1216
self.save_stderr = sys.stderr
13-
sys.stdout = sys.stderr = self.subst_io = s
17+
self.stream = StringIO.StringIO()
18+
sys.stdout = sys.stderr = self.subst_io = self.stream
1419
self.save_handlers = atexit._exithandlers
1520
atexit._exithandlers = []
1621

@@ -55,6 +60,13 @@ def test_raise(self):
5560
atexit.register(self.raise2)
5661
self.assertRaises(TypeError, atexit._run_exitfuncs)
5762

63+
def test_exit(self):
64+
# be sure a SystemExit is handled properly
65+
atexit.register(exit)
66+
67+
self.assertRaises(SystemExit, atexit._run_exitfuncs)
68+
self.assertEqual(self.stream.getvalue(), '')
69+
5870
### helpers
5971
def h1(self):
6072
print "h1"

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ Extension Modules
4949
Library
5050
-------
5151

52+
- bpo-28994: The traceback no longer displayed for SystemExit raised in
53+
a callback registered by atexit.
54+
5255
- bpo-30418: On Windows, subprocess.Popen.communicate() now also ignore EINVAL
5356
on stdin.write() if the child process is still running but closed the pipe.
5457

Modules/atexitmodule.c

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
/*
2+
* atexit - allow programmer to define multiple exit functions to be executed
3+
* upon normal program termination.
4+
*
5+
* Translated from atexit.py by Collin Winter.
6+
+ Copyright 2007 Python Software Foundation.
7+
*/
8+
9+
#include "Python.h"
10+
11+
/* Forward declaration (for atexit_cleanup) */
12+
static PyObject *atexit_clear(PyObject*, PyObject*);
13+
/* Forward declaration of module object */
14+
static struct PyModuleDef atexitmodule;
15+
16+
/* ===================================================================== */
17+
/* Callback machinery. */
18+
19+
typedef struct {
20+
PyObject *func;
21+
PyObject *args;
22+
PyObject *kwargs;
23+
} atexit_callback;
24+
25+
typedef struct {
26+
atexit_callback **atexit_callbacks;
27+
int ncallbacks;
28+
int callback_len;
29+
} atexitmodule_state;
30+
31+
#define GET_ATEXIT_STATE(mod) ((atexitmodule_state*)PyModule_GetState(mod))
32+
33+
34+
static void
35+
atexit_delete_cb(atexitmodule_state *modstate, int i)
36+
{
37+
atexit_callback *cb;
38+
39+
cb = modstate->atexit_callbacks[i];
40+
modstate->atexit_callbacks[i] = NULL;
41+
Py_DECREF(cb->func);
42+
Py_DECREF(cb->args);
43+
Py_XDECREF(cb->kwargs);
44+
PyMem_Free(cb);
45+
}
46+
47+
/* Clear all callbacks without calling them */
48+
static void
49+
atexit_cleanup(atexitmodule_state *modstate)
50+
{
51+
atexit_callback *cb;
52+
int i;
53+
for (i = 0; i < modstate->ncallbacks; i++) {
54+
cb = modstate->atexit_callbacks[i];
55+
if (cb == NULL)
56+
continue;
57+
58+
atexit_delete_cb(modstate, i);
59+
}
60+
modstate->ncallbacks = 0;
61+
}
62+
63+
/* Installed into pylifecycle.c's atexit mechanism */
64+
65+
static void
66+
atexit_callfuncs(void)
67+
{
68+
PyObject *exc_type = NULL, *exc_value, *exc_tb, *r;
69+
atexit_callback *cb;
70+
PyObject *module;
71+
atexitmodule_state *modstate;
72+
int i;
73+
74+
module = PyState_FindModule(&atexitmodule);
75+
if (module == NULL)
76+
return;
77+
modstate = GET_ATEXIT_STATE(module);
78+
79+
if (modstate->ncallbacks == 0)
80+
return;
81+
82+
83+
for (i = modstate->ncallbacks - 1; i >= 0; i--)
84+
{
85+
cb = modstate->atexit_callbacks[i];
86+
if (cb == NULL)
87+
continue;
88+
89+
r = PyObject_Call(cb->func, cb->args, cb->kwargs);
90+
Py_XDECREF(r);
91+
if (r == NULL) {
92+
/* Maintain the last exception, but don't leak if there are
93+
multiple exceptions. */
94+
if (exc_type) {
95+
Py_DECREF(exc_type);
96+
Py_XDECREF(exc_value);
97+
Py_XDECREF(exc_tb);
98+
}
99+
PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
100+
if (!PyErr_GivenExceptionMatches(exc_type, PyExc_SystemExit)) {
101+
PySys_WriteStderr("Error in atexit._run_exitfuncs:\n");
102+
PyErr_NormalizeException(&exc_type, &exc_value, &exc_tb);
103+
PyErr_Display(exc_type, exc_value, exc_tb);
104+
}
105+
}
106+
}
107+
108+
atexit_cleanup(modstate);
109+
110+
if (exc_type)
111+
PyErr_Restore(exc_type, exc_value, exc_tb);
112+
}
113+
114+
/* ===================================================================== */
115+
/* Module methods. */
116+
117+
PyDoc_STRVAR(atexit_register__doc__,
118+
"register(func, *args, **kwargs) -> func\n\
119+
\n\
120+
Register a function to be executed upon normal program termination\n\
121+
\n\
122+
func - function to be called at exit\n\
123+
args - optional arguments to pass to func\n\
124+
kwargs - optional keyword arguments to pass to func\n\
125+
\n\
126+
func is returned to facilitate usage as a decorator.");
127+
128+
static PyObject *
129+
atexit_register(PyObject *self, PyObject *args, PyObject *kwargs)
130+
{
131+
atexitmodule_state *modstate;
132+
atexit_callback *new_callback;
133+
PyObject *func = NULL;
134+
135+
modstate = GET_ATEXIT_STATE(self);
136+
137+
if (modstate->ncallbacks >= modstate->callback_len) {
138+
atexit_callback **r;
139+
modstate->callback_len += 16;
140+
r = (atexit_callback**)PyMem_Realloc(modstate->atexit_callbacks,
141+
sizeof(atexit_callback*) * modstate->callback_len);
142+
if (r == NULL)
143+
return PyErr_NoMemory();
144+
modstate->atexit_callbacks = r;
145+
}
146+
147+
if (PyTuple_GET_SIZE(args) == 0) {
148+
PyErr_SetString(PyExc_TypeError,
149+
"register() takes at least 1 argument (0 given)");
150+
return NULL;
151+
}
152+
153+
func = PyTuple_GET_ITEM(args, 0);
154+
if (!PyCallable_Check(func)) {
155+
PyErr_SetString(PyExc_TypeError,
156+
"the first argument must be callable");
157+
return NULL;
158+
}
159+
160+
new_callback = PyMem_Malloc(sizeof(atexit_callback));
161+
if (new_callback == NULL)
162+
return PyErr_NoMemory();
163+
164+
new_callback->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
165+
if (new_callback->args == NULL) {
166+
PyMem_Free(new_callback);
167+
return NULL;
168+
}
169+
new_callback->func = func;
170+
new_callback->kwargs = kwargs;
171+
Py_INCREF(func);
172+
Py_XINCREF(kwargs);
173+
174+
modstate->atexit_callbacks[modstate->ncallbacks++] = new_callback;
175+
176+
Py_INCREF(func);
177+
return func;
178+
}
179+
180+
PyDoc_STRVAR(atexit_run_exitfuncs__doc__,
181+
"_run_exitfuncs() -> None\n\
182+
\n\
183+
Run all registered exit functions.");
184+
185+
static PyObject *
186+
atexit_run_exitfuncs(PyObject *self, PyObject *unused)
187+
{
188+
atexit_callfuncs();
189+
if (PyErr_Occurred())
190+
return NULL;
191+
Py_RETURN_NONE;
192+
}
193+
194+
PyDoc_STRVAR(atexit_clear__doc__,
195+
"_clear() -> None\n\
196+
\n\
197+
Clear the list of previously registered exit functions.");
198+
199+
static PyObject *
200+
atexit_clear(PyObject *self, PyObject *unused)
201+
{
202+
atexit_cleanup(GET_ATEXIT_STATE(self));
203+
Py_RETURN_NONE;
204+
}
205+
206+
PyDoc_STRVAR(atexit_ncallbacks__doc__,
207+
"_ncallbacks() -> int\n\
208+
\n\
209+
Return the number of registered exit functions.");
210+
211+
static PyObject *
212+
atexit_ncallbacks(PyObject *self, PyObject *unused)
213+
{
214+
atexitmodule_state *modstate;
215+
216+
modstate = GET_ATEXIT_STATE(self);
217+
218+
return PyLong_FromSsize_t(modstate->ncallbacks);
219+
}
220+
221+
static int
222+
atexit_m_traverse(PyObject *self, visitproc visit, void *arg)
223+
{
224+
int i;
225+
atexitmodule_state *modstate;
226+
227+
modstate = GET_ATEXIT_STATE(self);
228+
for (i = 0; i < modstate->ncallbacks; i++) {
229+
atexit_callback *cb = modstate->atexit_callbacks[i];
230+
if (cb == NULL)
231+
continue;
232+
Py_VISIT(cb->func);
233+
Py_VISIT(cb->args);
234+
Py_VISIT(cb->kwargs);
235+
}
236+
return 0;
237+
}
238+
239+
static int
240+
atexit_m_clear(PyObject *self)
241+
{
242+
atexitmodule_state *modstate;
243+
modstate = GET_ATEXIT_STATE(self);
244+
atexit_cleanup(modstate);
245+
return 0;
246+
}
247+
248+
static void
249+
atexit_free(PyObject *m)
250+
{
251+
atexitmodule_state *modstate;
252+
modstate = GET_ATEXIT_STATE(m);
253+
atexit_cleanup(modstate);
254+
PyMem_Free(modstate->atexit_callbacks);
255+
}
256+
257+
PyDoc_STRVAR(atexit_unregister__doc__,
258+
"unregister(func) -> None\n\
259+
\n\
260+
Unregister an exit function which was previously registered using\n\
261+
atexit.register\n\
262+
\n\
263+
func - function to be unregistered");
264+
265+
static PyObject *
266+
atexit_unregister(PyObject *self, PyObject *func)
267+
{
268+
atexitmodule_state *modstate;
269+
atexit_callback *cb;
270+
int i, eq;
271+
272+
modstate = GET_ATEXIT_STATE(self);
273+
274+
for (i = 0; i < modstate->ncallbacks; i++)
275+
{
276+
cb = modstate->atexit_callbacks[i];
277+
if (cb == NULL)
278+
continue;
279+
280+
eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);
281+
if (eq < 0)
282+
return NULL;
283+
if (eq)
284+
atexit_delete_cb(modstate, i);
285+
}
286+
Py_RETURN_NONE;
287+
}
288+
289+
static PyMethodDef atexit_methods[] = {
290+
{"register", (PyCFunction) atexit_register, METH_VARARGS|METH_KEYWORDS,
291+
atexit_register__doc__},
292+
{"_clear", (PyCFunction) atexit_clear, METH_NOARGS,
293+
atexit_clear__doc__},
294+
{"unregister", (PyCFunction) atexit_unregister, METH_O,
295+
atexit_unregister__doc__},
296+
{"_run_exitfuncs", (PyCFunction) atexit_run_exitfuncs, METH_NOARGS,
297+
atexit_run_exitfuncs__doc__},
298+
{"_ncallbacks", (PyCFunction) atexit_ncallbacks, METH_NOARGS,
299+
atexit_ncallbacks__doc__},
300+
{NULL, NULL} /* sentinel */
301+
};
302+
303+
/* ===================================================================== */
304+
/* Initialization function. */
305+
306+
PyDoc_STRVAR(atexit__doc__,
307+
"allow programmer to define multiple exit functions to be executed\
308+
upon normal program termination.\n\
309+
\n\
310+
Two public functions, register and unregister, are defined.\n\
311+
");
312+
313+
314+
static struct PyModuleDef atexitmodule = {
315+
PyModuleDef_HEAD_INIT,
316+
"atexit",
317+
atexit__doc__,
318+
sizeof(atexitmodule_state),
319+
atexit_methods,
320+
NULL,
321+
atexit_m_traverse,
322+
atexit_m_clear,
323+
(freefunc)atexit_free
324+
};
325+
326+
PyMODINIT_FUNC
327+
PyInit_atexit(void)
328+
{
329+
PyObject *m;
330+
atexitmodule_state *modstate;
331+
332+
m = PyModule_Create(&atexitmodule);
333+
if (m == NULL)
334+
return NULL;
335+
336+
modstate = GET_ATEXIT_STATE(m);
337+
modstate->callback_len = 32;
338+
modstate->ncallbacks = 0;
339+
modstate->atexit_callbacks = PyMem_New(atexit_callback*,
340+
modstate->callback_len);
341+
if (modstate->atexit_callbacks == NULL)
342+
return NULL;
343+
344+
_Py_PyAtExit(atexit_callfuncs);
345+
return m;
346+
}

0 commit comments

Comments
 (0)