Skip to content

serial_connected never resets to False, causing print() to block when buffer is full #10814

@BMDan

Description

@BMDan

CircuitPython version and board name

Adafruit CircuitPython 10.0.3 on 2025-10-17; FeatherS3 with ESP32S3

Code/REPL

import time
import supervisor

i=0
while True:
    i += 1
    print(f"[{time.monotonic_ns()}] [{supervisor.runtime.serial_connected}] {i}...")
    time.sleep(0.1)

Behavior

This code will run just fine on a fresh start. However, once a serial console has been connected (supervisor.runtime.serial_connected becomes True), even after that console disconnects, print() will become blocking any time the output buffer is full. This will be evidenced by, upon reconnection of the console, multiple older messages filling the screen, and then a large jump in the outputted time.monotonic_ns() as the program execution resumes. You will also note that the outputs that occurred while the console was disconnected still indicate that supervisor.runtime.serial_connected is True.

Description

I suspect this is the true root cause of #9150, and possibly #10500. I've been able to reproduce it with minicom, screen, and even cat as serial consoles.

My workaround is to create a ring buffer, but that's a lot of heavy lifting to paper over the fact that serial_connected apparently never changes back to False.

Additional information

In case anyone wants my ring buffer code; it's not the prettiest, but it works, and allows easy toggling in and out of ring mode; press CTRL-C once to toggle, and optionally quickly press CTRL-C again to fully exit.

_ring_drops: int = 0
_ring: list[tuple] = []
def ring_print(fmt: str, *args) -> None:
    global _ring
    global _ring_drops
    if len(_ring) > RING_SIZE:
        _ring.pop(0)
        _ring_drops += 1
    _ring.append((fmt, args))

# Monkeypatch print()
print("Patching print(); use ^C to see output.")
_orig_print = print
print = ring_print

while True: # Core kernel
    try:
        # <... do stuff...>
    except KeyboardInterrupt as e:
        _out = ""
        _orig_print(f"\033[2m[{time.time()}] Got keyboard interrupt at:")
        traceback.print_exception(e)  # So we know what our CTRL-C interrupted
        _orig_print("\033[0;0m", end="")
        if print is ring_print:
            if _ring_drops:
                _orig_print(f"<... dropped {_ring_drops} messages ...>")
            for fmt, args in _ring:
                _orig_print(fmt, *args)
            _ring = [] 
            _ring_drops = 0
            _out = f"\033[2m[{time.time()}] Switching to live output; press CTRL-C to toggle back to ring.\033[0;0m"      
            print = _orig_print
        else:
            _out = f"\033[2m[{time.time()}] Switching to ring-buffered output; press CTRL-C to toggle back to live.\033[0;0m"
            print = ring_print
        time.sleep(0.5)  # hit CTRL-C again and you'll exit the loop.
        _orig_print(_out)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions