← index #9233Issue #7977
Related · medium · value 2.105
QUERY · ISSUE

uasyncio StreamWriter concurrency failure

openby peterhinchopened 2022-09-07updated 2022-09-13
enhancementport-stm32

The following script produces unexpected behaviour on a Pyboard 1.1

import uasyncio as asyncio
from machine import UART
import monitor
from pyb import LED
led = LED(1)

uarts = []  # Pins x9, x3, y9, y1
for n in (1, 2, 3, 6):
    uarts.append((UART(n, 9600, timeout=0), n))

async def sender(u, n):
    s = f"Hello from UART {n} "# * 55  # 990 chars
    swriter = asyncio.StreamWriter(u, {})
    while True:
        swriter.write(s)
        await swriter.drain()
        await asyncio.sleep(0)

async def heartbeat():
    while True:
        await asyncio.sleep_ms(100)
        led.toggle()

async def main():
    for v in uarts:
        asyncio.create_task(sender(*v))
    await heartbeat()

asyncio.run(main())

Each instance of swriter.drain appears to block: here is no concurrency.
Image

With 990 character strings (see code comment), the heartbeat LED changes state only once per second, implying that the scheduler is locked for the duration of each write. The LA trace is similar to above, but each burst is ~1s long.

CANDIDATE · ISSUE

rp2: Dual core code behaves non-deterministically driving UART

closedby peterhinchopened 2021-11-05updated 2022-02-25
port-rp2

The following runs for a while before the other thread on the second core stops UART output. The other thread continues to run, as evidenced by the incrementing g value. In my actual application I have seen UART output restart and stop again periodically, sometimes over times of many seconds.

Similar code with multiple uasyncio tasks outputting to the UART but running on one core works as expected.

If you change things in the code such as the type of g, the presence or absence of the sleep_ms(20) or whether foo is started, the UART behaviour changes but not in a way which seems to make sense. In every case the code runs: it is only the UART output which is unexpected.

import _thread
from time import sleep_ms
import uasyncio as asyncio
from machine import UART

u = UART(0, 1_000_000)

lock = _thread.allocate_lock()
g = 1.0  # 'a'

def other():
    global g
    while True:
        u.write('A')
        lock.acquire()
        # Proof of code running.
        g += 0.1  #  g += 'a'
        sleep_ms(20)  # Commenting-out can alter behaviour
        lock.release()
        u.write('a')
        sleep_ms(100)

async def foo():
    while True:
        u.write(b'\x40')  # @
        await asyncio.sleep_ms(0)
        u.write(b'\x60')  # `
        await asyncio.sleep_ms(0)

async def main():
    u.write('z')
    asyncio.create_task(foo())  # Commenting-out can alter behaviour
    _thread.start_new_thread(other, ())
    while True:
        u.write('B')
        while lock.locked():
            await asyncio.sleep_ms(0)
        lock.acquire()
        print(g)
        lock.release()
        u.write('b')
        await asyncio.sleep_ms(103)

asyncio.run(main())

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied