Skip to content

Commit 1c4a6c3

Browse files
committed
atexit module refinements
- add test for atexit module - add callback to gc collection - fix callback memory allocation - execute callback on both code and repl exit
1 parent 4938851 commit 1c4a6c3

File tree

8 files changed

+54
-23
lines changed

8 files changed

+54
-23
lines changed

lib/utils/pyexec.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@
4444
#include "lib/utils/pyexec.h"
4545
#include "genhdr/mpversion.h"
4646

47-
#if CIRCUITPY_ATEXIT
48-
#include "shared-module/atexit/__init__.h"
49-
#endif
50-
5147
pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
5248
int pyexec_system_exit = 0;
5349

@@ -131,10 +127,6 @@ STATIC int parse_compile_execute(const void *source, mp_parse_input_kind_t input
131127
start = mp_hal_ticks_ms();
132128
#endif
133129
mp_call_function_0(module_fun);
134-
// execute exit handlers (if any).
135-
#if CIRCUITPY_ATEXIT
136-
shared_module_atexit_execute();
137-
#endif
138130
mp_hal_set_interrupt_char(-1); // disable interrupt
139131
mp_handle_pending(true); // handle any pending exceptions (and any callbacks)
140132
nlr_pop();

main.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ STATIC bool maybe_run_list(const char * const * filenames, pyexec_result_t* exec
217217
decompress(compressed, decompressed);
218218
mp_hal_stdout_tx_str(decompressed);
219219
pyexec_file(filename, exec_result);
220+
#if CIRCUITPY_ATEXIT
221+
shared_module_atexit_execute();
222+
#endif
220223
return true;
221224
}
222225

@@ -766,6 +769,9 @@ STATIC int run_repl(void) {
766769
} else {
767770
exit_code = pyexec_friendly_repl();
768771
}
772+
#if CIRCUITPY_ATEXIT
773+
shared_module_atexit_execute();
774+
#endif
769775
cleanup_after_vm(heap, MP_OBJ_SENTINEL);
770776
#if CIRCUITPY_STATUS_LED
771777
status_led_init();
@@ -881,6 +887,10 @@ void gc_collect(void) {
881887
common_hal_alarm_gc_collect();
882888
#endif
883889

890+
#if CIRCUITPY_ATEXIT
891+
atexit_gc_collect();
892+
#endif
893+
884894
#if CIRCUITPY_DISPLAYIO
885895
displayio_gc_collect();
886896
#endif

ports/atmel-samd/boards/adafruit_proxlight_trinkey_m0/mpconfigboard.mk

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ CIRCUITPY_ROTARYIO = 0
2020
CIRCUITPY_RTC = 0
2121
CIRCUITPY_USB_MIDI = 0
2222

23-
CIRCUITPY_ATEXIT = 0
2423
CIRCUITPY_PIXELBUF = 1
2524
CIRCUITPY_BUSDEVICE = 1
2625

py/circuitpy_mpconfig.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ CFLAGS += -DCIRCUITPY_ALARM=$(CIRCUITPY_ALARM)
4848
CIRCUITPY_ANALOGIO ?= 1
4949
CFLAGS += -DCIRCUITPY_ANALOGIO=$(CIRCUITPY_ANALOGIO)
5050

51-
CIRCUITPY_ATEXIT ?= 1
51+
CIRCUITPY_ATEXIT ?= $(CIRCUITPY_FULL_BUILD)
5252
CFLAGS += -DCIRCUITPY_ATEXIT=$(CIRCUITPY_ATEXIT)
5353

5454
CIRCUITPY_AUDIOBUSIO ?= $(CIRCUITPY_FULL_BUILD)

shared-bindings/atexit/__init__.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
//| This module defines functions to register and unregister cleanup functions.
3232
//| Functions thus registered are automatically executed upon normal vm termination.
3333
//|
34+
//| These functions are run in the reverse order in which they were registered;
35+
//| if you register ``A``, ``B``, and ``C``, they will be run in the order ``C``, ``B``, ``A``.
36+
//|
3437
//| """
3538
//| ...
3639
//|
@@ -39,13 +42,14 @@
3942
//|
4043
//| """Register func as a function to be executed at termination.
4144
//|
42-
//| Any optional arguments that are to be passed to func must be passed as arguments to register().
45+
//| Any optional arguments that are to be passed to func must be passed as arguments to `register()`.
4346
//| It is possible to register the same function and arguments more than once.
4447
//|
4548
//| At normal program termination (for instance, if `sys.exit()` is called or the vm execution completes),
46-
//| all functions registered are called in order of there registration.
49+
//| all functions registered are called in last in, first out order.
4750
//|
48-
//| If an exception is raised during execution of the exit handlers, a traceback is printed and the execution stops.
51+
//| If an exception is raised during execution of the exit handler,
52+
//| a traceback is printed (unless `SystemExit` is raised) and the execution stops.
4953
//|
5054
//| This function returns func, which makes it possible to use it as a decorator.
5155
//|

shared-module/atexit/__init__.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
#include "py/gc.h"
2728
#include "py/runtime.h"
2829
#include "shared-module/atexit/__init__.h"
2930

@@ -32,15 +33,19 @@ typedef struct _atexit_callback_t {
3233
mp_obj_t func, *args;
3334
} atexit_callback_t;
3435

35-
size_t callback_len = 0;
36-
atexit_callback_t *callback = NULL;
36+
static size_t callback_len = 0;
37+
static atexit_callback_t *callback = NULL;
3738

3839
void atexit_reset(void) {
3940
callback_len = 0;
4041
m_free(callback);
4142
callback = NULL;
4243
}
4344

45+
void atexit_gc_collect(void) {
46+
gc_collect_ptr(callback);
47+
}
48+
4449
void shared_module_atexit_register(mp_obj_t *func, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
4550
if (!mp_obj_is_callable(func)) {
4651
mp_raise_TypeError_varg(translate("'%q' object is not callable"), mp_obj_get_type_qstr(func));
@@ -50,24 +55,22 @@ void shared_module_atexit_register(mp_obj_t *func, size_t n_args, const mp_obj_t
5055
.n_pos = 0,
5156
.n_kw = 0,
5257
.func = func,
53-
.args = ((n_args + n_kw_args) == 0) ? NULL : m_malloc((n_args + (2 * n_kw_args)) * sizeof(mp_obj_t), false)
58+
.args = (n_args + n_kw_args) ? m_malloc((n_args + (n_kw_args * 2)) * sizeof(mp_obj_t), false) : NULL
5459
};
5560
for (; cb.n_pos < n_args; cb.n_pos++) {
5661
cb.args[cb.n_pos] = pos_args[cb.n_pos];
5762
}
5863
for (size_t i = cb.n_pos; cb.n_kw < n_kw_args; i++, cb.n_kw++) {
59-
cb.args[i] = kw_args[cb.n_kw].table->key;
60-
cb.args[i += 1] = kw_args[cb.n_kw].table->value;
61-
}
62-
if (!callback) {
63-
callback = (atexit_callback_t *)m_realloc(callback, sizeof(cb));
64+
cb.args[i] = kw_args->table[cb.n_kw].key;
65+
cb.args[i += 1] = kw_args->table[cb.n_kw].value;
6466
}
67+
callback = (atexit_callback_t *)m_realloc(callback, (callback_len + 1) * sizeof(cb));
6568
callback[callback_len++] = cb;
6669
}
6770

6871
void shared_module_atexit_unregister(const mp_obj_t *func) {
6972
for (size_t i = 0; i < callback_len; i++) {
70-
if (callback[i].func == func) {
73+
if (callback[i].func == *func) {
7174
callback[i].n_pos = 0;
7275
callback[i].n_kw = 0;
7376
callback[i].func = mp_const_none;
@@ -78,7 +81,7 @@ void shared_module_atexit_unregister(const mp_obj_t *func) {
7881

7982
void shared_module_atexit_execute(void) {
8083
if (callback) {
81-
for (size_t i = 0; i < callback_len; i++) {
84+
for (size_t i = callback_len; i-- > 0;) {
8285
if (callback[i].func != mp_const_none) {
8386
mp_call_function_n_kw(callback[i].func, callback[i].n_pos, callback[i].n_kw, callback[i].args);
8487
}

shared-module/atexit/__init__.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "py/obj.h"
3131

3232
extern void atexit_reset(void);
33+
extern void atexit_gc_collect(void);
3334

3435
extern void shared_module_atexit_register(mp_obj_t *func,
3536
size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

tests/circuitpython/atexit_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# test atexit module
2+
3+
try:
4+
import atexit
5+
except ImportError:
6+
print("SKIP")
7+
raise SystemExit
8+
9+
10+
@atexit.register
11+
def skip_at_exit():
12+
print("skip at exit")
13+
14+
15+
@atexit.register
16+
def do_at_exit(*args, **kwargs):
17+
print("done at exit:", args, kwargs)
18+
19+
20+
atexit.unregister(skip_at_exit)
21+
atexit.register(do_at_exit, "ok", 1, arg="2", param=(3, 4))
22+
print("done before exit")

0 commit comments

Comments
 (0)