Skip to content

Commit 40e1c11

Browse files
projectgusdpgeorge
authored andcommitted
py/gc: Allow gc_free from inside a gc_sweep finalizer.
Do this by tracking being inside gc collection with a separate flag, GC_COLLECT_FLAG. In gc_free(), ignore this flag when determining if the heap is locked. * For finalisers calling gc_free() when heap is otherwise unlocked, this allows memory to be immediately freed (potentially avoiding a MemoryError). * Hard IRQs still can't call gc_free(), as heap will be locked via gc_lock(). * If finalisers are disabled then all of this code can be compiled out to save some code size. Signed-off-by: Angus Gratton <[email protected]>
1 parent 8a2ff2c commit 40e1c11

File tree

3 files changed

+36
-16
lines changed

3 files changed

+36
-16
lines changed

py/gc.c

+21-14
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,12 @@ void gc_lock(void) {
334334
// - each thread has its own gc_lock_depth so there are no races between threads;
335335
// - a hard interrupt will only change gc_lock_depth during its execution, and
336336
// upon return will restore the value of gc_lock_depth.
337-
MP_STATE_THREAD(gc_lock_depth)++;
337+
MP_STATE_THREAD(gc_lock_depth) += (1 << GC_LOCK_DEPTH_SHIFT);
338338
}
339339

340340
void gc_unlock(void) {
341341
// This does not need to be atomic, See comment above in gc_lock.
342-
MP_STATE_THREAD(gc_lock_depth)--;
342+
MP_STATE_THREAD(gc_lock_depth) -= (1 << GC_LOCK_DEPTH_SHIFT);
343343
}
344344

345345
bool gc_is_locked(void) {
@@ -581,13 +581,18 @@ static void gc_sweep(void) {
581581
}
582582
}
583583

584-
void gc_collect_start(void) {
584+
static void gc_collect_start_common(void) {
585585
GC_ENTER();
586-
MP_STATE_THREAD(gc_lock_depth)++;
586+
assert((MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG) == 0);
587+
MP_STATE_THREAD(gc_lock_depth) |= GC_COLLECT_FLAG;
588+
MP_STATE_MEM(gc_stack_overflow) = 0;
589+
}
590+
591+
void gc_collect_start(void) {
592+
gc_collect_start_common();
587593
#if MICROPY_GC_ALLOC_THRESHOLD
588594
MP_STATE_MEM(gc_alloc_amount) = 0;
589595
#endif
590-
MP_STATE_MEM(gc_stack_overflow) = 0;
591596

592597
// Trace root pointers. This relies on the root pointers being organised
593598
// correctly in the mp_state_ctx structure. We scan nlr_top, dict_locals,
@@ -658,14 +663,12 @@ void gc_collect_end(void) {
658663
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
659664
area->gc_last_free_atb_index = 0;
660665
}
661-
MP_STATE_THREAD(gc_lock_depth)--;
666+
MP_STATE_THREAD(gc_lock_depth) &= ~GC_COLLECT_FLAG;
662667
GC_EXIT();
663668
}
664669

665670
void gc_sweep_all(void) {
666-
GC_ENTER();
667-
MP_STATE_THREAD(gc_lock_depth)++;
668-
MP_STATE_MEM(gc_stack_overflow) = 0;
671+
gc_collect_start_common();
669672
gc_collect_end();
670673
}
671674

@@ -902,10 +905,13 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
902905
// force the freeing of a piece of memory
903906
// TODO: freeing here does not call finaliser
904907
void gc_free(void *ptr) {
905-
if (MP_STATE_THREAD(gc_lock_depth) > 0) {
906-
// Cannot free while the GC is locked. However free is an optimisation
907-
// to reclaim the memory immediately, this means it will now be left
908-
// until the next collection.
908+
// Cannot free while the GC is locked, unless we're only doing a gc sweep.
909+
// However free is an optimisation to reclaim the memory immediately, this
910+
// means it will now be left until the next collection.
911+
//
912+
// (We have the optimisation to free immediately from inside a gc sweep so
913+
// that finalisers can free more memory when trying to avoid MemoryError.)
914+
if (MP_STATE_THREAD(gc_lock_depth) & ~GC_COLLECT_FLAG) {
909915
return;
910916
}
911917

@@ -930,7 +936,8 @@ void gc_free(void *ptr) {
930936
#endif
931937

932938
size_t block = BLOCK_FROM_PTR(area, ptr);
933-
assert(ATB_GET_KIND(area, block) == AT_HEAD);
939+
assert(ATB_GET_KIND(area, block) == AT_HEAD
940+
|| (ATB_GET_KIND(area, block) == AT_MARK && (MP_STATE_THREAD(gc_lock_depth) & GC_COLLECT_FLAG)));
934941

935942
#if MICROPY_ENABLE_FINALISER
936943
FTB_CLEAR(area, block);

py/modmicropython.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,13 @@ static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_lock_obj, mp_micropython_he
132132

133133
static mp_obj_t mp_micropython_heap_unlock(void) {
134134
gc_unlock();
135-
return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth));
135+
return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth) >> GC_LOCK_DEPTH_SHIFT);
136136
}
137137
static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_unlock_obj, mp_micropython_heap_unlock);
138138

139139
#if MICROPY_PY_MICROPYTHON_HEAP_LOCKED
140140
static mp_obj_t mp_micropython_heap_locked(void) {
141-
return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth));
141+
return MP_OBJ_NEW_SMALL_INT(MP_STATE_THREAD(gc_lock_depth) >> GC_LOCK_DEPTH_SHIFT);
142142
}
143143
static MP_DEFINE_CONST_FUN_OBJ_0(mp_micropython_heap_locked_obj, mp_micropython_heap_locked);
144144
#endif

py/mpstate.h

+13
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ typedef struct _mp_sched_item_t {
7777
mp_obj_t arg;
7878
} mp_sched_item_t;
7979

80+
// gc_lock_depth field is a combination of the GC_COLLECT_FLAG
81+
// bit and a lock depth shifted GC_LOCK_DEPTH_SHIFT bits left.
82+
#if MICROPY_ENABLE_FINALISER
83+
#define GC_COLLECT_FLAG 1
84+
#define GC_LOCK_DEPTH_SHIFT 1
85+
#else
86+
// If finalisers are disabled then this check doesn't matter, as gc_lock()
87+
// is called anywhere else that heap can't be changed. So save some code size.
88+
#define GC_COLLECT_FLAG 0
89+
#define GC_LOCK_DEPTH_SHIFT 0
90+
#endif
91+
8092
// This structure holds information about a single contiguous area of
8193
// memory reserved for the memory manager.
8294
typedef struct _mp_state_mem_area_t {
@@ -268,6 +280,7 @@ typedef struct _mp_state_thread_t {
268280
#endif
269281

270282
// Locking of the GC is done per thread.
283+
// See GC_LOCK_DEPTH_SHIFT for an explanation of this field.
271284
uint16_t gc_lock_depth;
272285

273286
////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)