Skip to content
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

static analysis finds problem in gc_never_free #2204

Closed
jepler opened this issue Oct 8, 2019 · 8 comments
Closed

static analysis finds problem in gc_never_free #2204

jepler opened this issue Oct 8, 2019 · 8 comments
Labels
Milestone

Comments

@jepler
Copy link
Member

jepler commented Oct 8, 2019

The full report from scan-build-7 is here: http://media.unpythonic.net/emergent-files/sandbox/report-3d3d49.html#LN954

Basically, in the case where the first block of 3 "never free" objects is filled, the code needs to allocate a new block and hook it into the linked list that goes via the [0] index of each block. The logic to chain the blocks together erroneously tries to use current_reference_block[0] but current_reference_block is guaranteed to be NULL at this point, by the condition of the while loop.

This bug isn't triggering in practice at this time, as gc_never_free sees only limited use from displayio and I think this means at most one entry is ever used.

@dhalbert dhalbert added the bug label Oct 31, 2019
@dhalbert dhalbert added this to the 5.0.0 milestone Oct 31, 2019
@jepler jepler modified the milestones: 5.0.0, Long term Feb 3, 2020
@furbrain
Copy link

I've managed to trigger this bug with this code

import displayio
import adafruit_displayio_sh1106
import time
import busio
import board

def setup():
    print("initialising i2c")
    i2c = busio.I2C(board.D8, board.D7)
    print("initialising bus")
    bus = displayio.I2CDisplay(i2c, device_address=0x3c)
    print("initialising display")
    disp = adafruit_displayio_sh1106.SH1106(bus, width=132,height=64)
    time.sleep(0.1)
    displayio.release_displays()
    i2c.deinit()
    time.sleep(0.1)
    
for i in range(10):
    print(f"loop {i}")
    time.sleep(0.5)
    setup()

This gives the following output:

[tio 21:22:04] Connected
loop 0
free: 140176
initialising i2c
initialising bus
initialising display
loop 1
free: 140144
initialising i2c
initialising bus
initialising display
loop 2
free: 140128
initialising i2c
initialising bus
initialising display
loop 3
free: 140112
initialising i2c
initialising bus

[tio 21:22:12] Disconnected
[tio 21:22:14] Connected
Auto-reload is off.
Running in safe mode! Not running saved code.

You are in safe mode because:
CircuitPython core code crashed hard. Whoops!
Fault detected by hardware.
Please file an issue with your program at https://github.com/adafruit/circuitpython/issues.
Press reset to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

As you can see this fits with the above issue - we successfully create the first block of pointers, and can allocate three items to the never_free list, but it then crashes when we try to create a second block of pointers

furbrain added a commit to furbrain/circuitpython that referenced this issue May 15, 2023
@furbrain
Copy link

I've submitted a fix for this, and now my code works as expected:

code.py output:
loop 0
free: 140176
initialising i2c
initialising bus
initialising display
loop 1
free: 140144
initialising i2c
initialising bus
initialising display
loop 2
free: 140128
initialising i2c
initialising bus
initialising display
loop 3
free: 140112
initialising i2c
initialising bus
initialising display
loop 4
free: 140080
initialising i2c
initialising bus
initialising display
loop 5
free: 140064
initialising i2c
initialising bus
initialising display
loop 6
free: 140048
initialising i2c
initialising bus
initialising display
loop 7
free: 139984
initialising i2c
initialising bus
initialising display
loop 8
free: 139968
initialising i2c
initialising bus
initialising display
loop 9
free: 139952
initialising i2c
initialising bus
initialising display

Code done running.

Press any key to enter the REPL. Use CTRL-D to reload.

There is however another issue: there is now a memory leak as we add a new i2c bus reference for each time around my loop

My application will maybe do 50 loops before getting a hard reset, so I can live with this for now. Fixing would likely require a new function like gc_allow_free(ptr) to remove the pointer from the list - this could be called by displayio.release_displays at the relevant point

@furbrain
Copy link

I can work on creating gc_allow_free if there is an appetite for it

@tannewt
Copy link
Member

tannewt commented May 16, 2023

I can work on creating gc_allow_free if there is an appetite for it

I'm happy to review if you think it'll help these situations.

tannewt added a commit that referenced this issue May 16, 2023
@tannewt
Copy link
Member

tannewt commented May 16, 2023

I can work on creating gc_allow_free if there is an appetite for it

After reviewing the PR, I think it'd be better to remove gc_never_free() and have the display code manage collecting those pointers through displayio_gc_collect().

@jepler
Copy link
Member Author

jepler commented May 16, 2023

thanks @furbrain! did you encounter this problem "organically" or did you go looking for it?

@dhalbert
Copy link
Collaborator

Fixed by #7983.

@furbrain
Copy link

@jepler I encountered it organically - I have a display that i need to regularly power down and then reinitialise and was encountering this bug.

@tannewt I agree - I think you said that only one display is actually used by CP internals for displaying error messages etc - so may be better just to keep a reference to that one display.

furbrain added a commit to furbrain/circuitpython that referenced this issue May 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants