Skip to content

Commit 670e692

Browse files
committed
Patch #1680961: remove sys.exitfunc and replace it with a private C API. Also, reimplement atexit in C so it can take advantage of this private API.
1 parent 450ee81 commit 670e692

File tree

13 files changed

+360
-204
lines changed

13 files changed

+360
-204
lines changed

Doc/lib/libatexit.tex

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
\section{\module{atexit} ---
22
Exit handlers}
33

4-
\declaremodule{standard}{atexit}
4+
\declaremodule{builtin}{atexit}
55
\moduleauthor{Skip Montanaro}{skip@mojam.com}
66
\sectionauthor{Skip Montanaro}{skip@mojam.com}
77
\modulesynopsis{Register and execute cleanup functions.}
88

99
\versionadded{2.0}
1010

11-
The \module{atexit} module defines a single function to register
12-
cleanup functions. Functions thus registered are automatically
13-
executed upon normal interpreter termination.
1411

15-
Note: the functions registered via this module are not called when the program is killed by a
16-
signal, when a Python fatal internal error is detected, or when
17-
\function{os._exit()} is called.
12+
The \module{atexit} module defines functions to register and
13+
unregister cleanup functions. Functions thus registered are
14+
automatically executed upon normal interpreter termination.
1815

19-
This is an alternate interface to the functionality provided by the
20-
\code{sys.exitfunc} variable.
21-
\withsubitem{(in sys)}{\ttindex{exitfunc}}
22-
23-
Note: This module is unlikely to work correctly when used with other code
24-
that sets \code{sys.exitfunc}. In particular, other core Python modules are
25-
free to use \module{atexit} without the programmer's knowledge. Authors who
26-
use \code{sys.exitfunc} should convert their code to use
27-
\module{atexit} instead. The simplest way to convert code that sets
28-
\code{sys.exitfunc} is to import \module{atexit} and register the function
29-
that had been bound to \code{sys.exitfunc}.
16+
Note: the functions registered via this module are not called when
17+
the program is killed by a signal, when a Python fatal internal
18+
error is detected, or when \function{os._exit()} is called.
3019

3120
\begin{funcdesc}{register}{func\optional{, *args\optional{, **kargs}}}
3221
Register \var{func} as a function to be executed at termination. Any
@@ -47,7 +36,16 @@ \section{\module{atexit} ---
4736

4837
\versionchanged[This function now returns \var{func} which makes it
4938
possible to use it as a decorator without binding the
50-
original name to \code{None}]{2.6}
39+
original name to \code{None}]{2.6}
40+
\end{funcdesc}
41+
42+
\begin{funcdesc}{unregister}{func}
43+
Remove a function \var{func} from the list of functions to be run at
44+
interpreter-shutdown. After calling \function{unregister()},
45+
\var{func} is guaranteed not to be called when the interpreter
46+
shuts down.
47+
48+
\versionadded{3.0}
5149
\end{funcdesc}
5250

5351

Doc/lib/libsys.tex

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -218,19 +218,6 @@ \section{\module{sys} ---
218218
program when an error occurs.
219219
\end{funcdesc}
220220

221-
\begin{datadesc}{exitfunc}
222-
This value is not actually defined by the module, but can be set by
223-
the user (or by a program) to specify a clean-up action at program
224-
exit. When set, it should be a parameterless function. This
225-
function will be called when the interpreter exits. Only one
226-
function may be installed in this way; to allow multiple functions
227-
which will be called at termination, use the \refmodule{atexit}
228-
module. \note{The exit function is not called when the program is
229-
killed by a signal, when a Python fatal internal error is detected,
230-
or when \code{os._exit()} is called.}
231-
\deprecated{2.4}{Use \refmodule{atexit} instead.}
232-
\end{datadesc}
233-
234221
\begin{funcdesc}{getcheckinterval}{}
235222
Return the interpreter's ``check interval'';
236223
see \function{setcheckinterval()}.

Include/pythonrun.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ PyAPI_FUNC(void) PyErr_Print(void);
6969
PyAPI_FUNC(void) PyErr_PrintEx(int);
7070
PyAPI_FUNC(void) PyErr_Display(PyObject *, PyObject *, PyObject *);
7171

72+
/* Py_PyAtExit is for the atexit module, Py_AtExit is for low-level
73+
* exit functions.
74+
*/
75+
PyAPI_FUNC(void) _Py_PyAtExit(void (*func)(void));
7276
PyAPI_FUNC(int) Py_AtExit(void (*func)(void));
7377

7478
PyAPI_FUNC(void) Py_Exit(int);

Lib/atexit.py

Lines changed: 0 additions & 65 deletions
This file was deleted.

Lib/test/test___all__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def test_all(self):
4848
self.check_all("StringIO")
4949
self.check_all("UserString")
5050
self.check_all("aifc")
51-
self.check_all("atexit")
5251
self.check_all("audiodev")
5352
self.check_all("base64")
5453
self.check_all("bdb")

Lib/test/test_atexit.py

Lines changed: 94 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,112 @@
44
import atexit
55
from test import test_support
66

7-
class TestCase(unittest.TestCase):
8-
def test_args(self):
9-
# be sure args are handled properly
10-
s = StringIO.StringIO()
11-
sys.stdout = sys.stderr = s
12-
save_handlers = atexit._exithandlers
13-
atexit._exithandlers = []
14-
try:
15-
atexit.register(self.h1)
16-
atexit.register(self.h4)
17-
atexit.register(self.h4, 4, kw="abc")
18-
atexit._run_exitfuncs()
19-
finally:
20-
sys.stdout = sys.__stdout__
21-
sys.stderr = sys.__stderr__
22-
atexit._exithandlers = save_handlers
23-
self.assertEqual(s.getvalue(), "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n")
7+
### helpers
8+
def h1():
9+
print("h1")
2410

25-
def test_order(self):
26-
# be sure handlers are executed in reverse order
27-
s = StringIO.StringIO()
28-
sys.stdout = sys.stderr = s
29-
save_handlers = atexit._exithandlers
30-
atexit._exithandlers = []
31-
try:
32-
atexit.register(self.h1)
33-
atexit.register(self.h2)
34-
atexit.register(self.h3)
35-
atexit._run_exitfuncs()
36-
finally:
37-
sys.stdout = sys.__stdout__
38-
sys.stderr = sys.__stderr__
39-
atexit._exithandlers = save_handlers
40-
self.assertEqual(s.getvalue(), "h3\nh2\nh1\n")
11+
def h2():
12+
print("h2")
4113

42-
def test_sys_override(self):
43-
# be sure a preset sys.exitfunc is handled properly
44-
s = StringIO.StringIO()
45-
sys.stdout = sys.stderr = s
46-
save_handlers = atexit._exithandlers
47-
atexit._exithandlers = []
48-
exfunc = sys.exitfunc
49-
sys.exitfunc = self.h1
50-
reload(atexit)
51-
try:
52-
atexit.register(self.h2)
53-
atexit._run_exitfuncs()
54-
finally:
55-
sys.stdout = sys.__stdout__
56-
sys.stderr = sys.__stderr__
57-
atexit._exithandlers = save_handlers
58-
sys.exitfunc = exfunc
59-
self.assertEqual(s.getvalue(), "h2\nh1\n")
14+
def h3():
15+
print("h3")
6016

61-
def test_raise(self):
62-
# be sure raises are handled properly
63-
s = StringIO.StringIO()
64-
sys.stdout = sys.stderr = s
65-
save_handlers = atexit._exithandlers
66-
atexit._exithandlers = []
67-
try:
68-
atexit.register(self.raise1)
69-
atexit.register(self.raise2)
70-
self.assertRaises(TypeError, atexit._run_exitfuncs)
71-
finally:
72-
sys.stdout = sys.__stdout__
73-
sys.stderr = sys.__stderr__
74-
atexit._exithandlers = save_handlers
17+
def h4(*args, **kwargs):
18+
print("h4", args, kwargs)
19+
20+
def raise1():
21+
raise TypeError
7522

76-
### helpers
77-
def h1(self):
78-
print("h1")
23+
def raise2():
24+
raise SystemError
7925

80-
def h2(self):
81-
print("h2")
26+
class TestCase(unittest.TestCase):
27+
def setUp(self):
28+
self.stream = StringIO.StringIO()
29+
sys.stdout = sys.stderr = self.stream
30+
atexit._clear()
31+
32+
def tearDown(self):
33+
sys.stdout = sys.__stdout__
34+
sys.stderr = sys.__stderr__
35+
atexit._clear()
8236

83-
def h3(self):
84-
print("h3")
37+
def test_args(self):
38+
# be sure args are handled properly
39+
atexit.register(h1)
40+
atexit.register(h4)
41+
atexit.register(h4, 4, kw="abc")
42+
atexit._run_exitfuncs()
8543

86-
def h4(self, *args, **kwargs):
87-
print("h4", args, kwargs)
44+
self.assertEqual(self.stream.getvalue(),
45+
"h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n")
8846

89-
def raise1(self):
90-
raise TypeError
47+
def test_order(self):
48+
# be sure handlers are executed in reverse order
49+
atexit.register(h1)
50+
atexit.register(h2)
51+
atexit.register(h3)
52+
atexit._run_exitfuncs()
53+
54+
self.assertEqual(self.stream.getvalue(), "h3\nh2\nh1\n")
9155

92-
def raise2(self):
93-
raise SystemError
56+
def test_raise(self):
57+
# be sure raises are handled properly
58+
atexit.register(raise1)
59+
atexit.register(raise2)
60+
61+
self.assertRaises(TypeError, atexit._run_exitfuncs)
62+
63+
def test_stress(self):
64+
a = [0]
65+
def inc():
66+
a[0] += 1
67+
68+
for i in range(128):
69+
atexit.register(inc)
70+
atexit._run_exitfuncs()
71+
72+
self.assertEqual(a[0], 128)
73+
74+
def test_clear(self):
75+
a = [0]
76+
def inc():
77+
a[0] += 1
78+
79+
atexit.register(inc)
80+
atexit._clear()
81+
atexit._run_exitfuncs()
82+
83+
self.assertEqual(a[0], 0)
84+
85+
def test_unregister(self):
86+
a = [0]
87+
def inc():
88+
a[0] += 1
89+
def dec():
90+
a[0] -= 1
91+
92+
for i in range(4):
93+
atexit.register(inc)
94+
atexit.register(dec)
95+
atexit.unregister(inc)
96+
atexit._run_exitfuncs()
97+
98+
self.assertEqual(a[0], -1)
99+
100+
def test_bound_methods(self):
101+
l = []
102+
atexit.register(l.append, 5)
103+
atexit._run_exitfuncs()
104+
self.assertEqual(l, [5])
105+
106+
atexit.unregister(l.append)
107+
atexit._run_exitfuncs()
108+
self.assertEqual(l, [5])
109+
94110

95111
def test_main():
96112
test_support.run_unittest(TestCase)
97113

98-
99114
if __name__ == "__main__":
100115
test_main()

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ TO DO
2828
Core and Builtins
2929
-----------------
3030

31+
- Patch #1680961: sys.exitfunc has been removed and replaced with a private
32+
C-level API.
33+
3134
- PEP 3115: new metaclasses: the metaclass is now specified as a
3235
keyword arg in the class statement, which can now use the full syntax of
3336
a parameter list. Also, the metaclass can implement a __prepare__ function
@@ -156,6 +159,8 @@ Extension Modules
156159
Library
157160
-------
158161

162+
- Patch #1680961: atexit has been reimplemented in C.
163+
159164
- Removed all traces of the sets module.
160165

161166
Build

Modules/Setup.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ GLHACK=-Dclear=__GLclear
176176
#collections collectionsmodule.c # Container types
177177
#itertools itertoolsmodule.c # Functions creating iterators for efficient looping
178178
#strop stropmodule.c # String manipulations
179+
#atexit atexitmodule.c # Register functions to be run at interpreter-shutdown
179180

180181
#unicodedata unicodedata.c # static Unicode character database
181182

0 commit comments

Comments
 (0)