Thanks to visit codestin.com
Credit goes to github.com

Skip to content

REVIEW ONLY: first steps making GC reentrant #913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed

Conversation

dpgeorge
Copy link
Member

This is totally not ready to merge, but I did a fair bit of work on it and want to see what others think before continuing (or abandoning).

The idea is that you want to be able to allocate memory during an interrupt. If we allow this, then you should be able to rely on allocating memory during an interrupt, so long as there is memory available. You can arrange for memory to be available by calling gc.collect() explicitly and often enough (outside the interrupt).

Allowing gc_alloc/gc_realloc/gc_free to work in an interrupt means that these 3 functions, and the garbage collector routine itself, must be reentrant (can be suspended, executed again at a higher priority interrupt, then resumed).

This means making all global state operations atomic, and always leaving the heap in a consistent state.

It is difficult because you can't simply disable IRQs for the whole gc_alloc: it could take some time to find a free piece of the heap, and in this time you might get another IRQ (eg UART character comes in). So you can only disable IRQs for a very short time.

I've tested this patch by creating 3 timers running at 1Hz, 10Hz and 1000Hz, all allocating large chunks of memory. Then I do gc.collect() in the foreground in an infinite loop. It seems to work, but needs a lot more testing.

This really opens a whole can of worms, since other issues start to become apparent. Eg, we need to atomically store to the global dictionary (mp_store_global). And, ctrl-C handler probably needs to be completely redesigned and rewritten so that you can't ctrl-C while in the middle of a gc collection/alloc/realloc/free (in fact, you shouldn't be able to ctrl-C in any HAL function either since they modify global state of the peripherals).

@dpgeorge
Copy link
Member Author

Don't we need to disable collections while inside an interrupt handler?

No actually. The GC can run just fine in an irq (so long as it's not already running, but that's checked for). The only issue is that it might take some time to complete, so you should really know what you are doing if you let the GC run on an irq. You would need to explicitly disable auto-gc (using gc.disable) if you wanted to prevent it running in an irq.

We would need to re-organise the IRQ priorities to help with latency. Eg, if the GC is running in an IRQ, then you still want other IRQs (like USB, UART buffering) to be able to preempt it. Also, would be nice for the user if they can specify priority for callbacks. Something like switch.callback(f, priority=3). The priority scheme would be something like (note that a large IRQ level number is lower priority):

  1. Top-level main thread (no IRQ): foreground code execution.
  2. IRQ levels 8-15: user defined levels for running Python code on an interrupt (switch, timer, extint callbacks).
  3. IRQ levels 0-7: system defined levels for low-level drivers such as USB, UART buffer fill, flash, systick.

This gives the user 9 levels (top level and 8 IRQs) to play with. There are no restrictions on the code that you can run in these levels (eg you can run gc.collect, or have it run automatically).

We could additionally define levels 8-11 to disable automatic GC when they are entered.

@dpgeorge
Copy link
Member Author

Every python object has a HEAD block. The TAIL blocks fill following blocks for the rest of the object (so every python object has 1 head, and 0-n tails).

Yes. It is also true for non Python objects. Any "chunk" of memory allocated on the GC heap has HEAD plus 0-n TAILs.

The ATB is an array of 2-bit fields, one field per block.

Yes. The AT (allocation table) tells 4 states for each block: FREE, HEAD, TAIL, MARK. MARK means marked head for tracing.

@dpgeorge
Copy link
Member Author

I implemented @dhylands' idea about setting gc_last_free_atb_index earlier.

@dpgeorge
Copy link
Member Author

This PR is stale. Some new ideas have surfaced (in my mind at least!) on how to tackle the issue of memory allocation in an interrupt. I think first we should concentrate on improving uPy core so that it doesn't need to allocate heap memory as far as is possible.

@dpgeorge dpgeorge closed this Dec 23, 2014
tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Jun 8, 2018
@dpgeorge dpgeorge deleted the reentrant-gc branch July 8, 2022 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants