(note: not an overflow but I don't know how else to succinctly describe whatever is happening here...)
My goal is to use a uasyncio.ThreadSafeFlag object to limit the rate at which an output task runs. The output task may have a variable workload and so when that load is small I'd like to give CPU time to other processes, but when that load is high I expect the output task to complete its own processing and then (per uasyncio design) allow other coroutines to be run before coming back.
It took me quite a lot of paring down and hypothesizing to boil the issue down from my full application to this demo code:
Code: Select all
import uasyncio
import time
from machine import Timer
timer_id = 0 # edit to suit your platform
stats = {}
def start_stat(tag):
now = time.ticks_us()
try:
s = stats[tag]
s['start'] = now
s['stop'] = None
s['updated'] = now
except KeyError:
stats[tag] = {'start': now, 'updated': now, 'history': [], 'avg': None}
def stop_stat(tag):
now = time.ticks_us()
s = stats[tag]
s['updated'] = now
elapsed = now - s['start']
hist = s['history']
hist.append(elapsed)
if len(hist) > 10:
hist = hist[-10:]
s['history'] = hist
s['avg'] = sum(hist) / len(hist)
async def print_stats_task():
while True:
await uasyncio.sleep_ms(1000)
now = time.ticks_us()
for tag, s in stats.items():
print('\t', tag, 'avg (ms):', s['avg']/1000, 'age (s):', (now-s['updated'])/1000000)
print('\n')
async def output_task():
timer = Timer(timer_id)
timer_flag = uasyncio.ThreadSafeFlag()
timer_period_ms = 30
load_period_ms = timer_period_ms - 1 # this works reliably on esp32
# load_period_ms = timer_period_ms # fails, likely same mode as longer delays but due to overhead
# load_period_ms = timer_period_ms + 1 # seems to always fail
def timer_cb(self):
timer_flag.set()
timer.init(period=timer_period_ms, callback=timer_cb)
while True:
start_stat('t_total')
start_stat('t_timer')
await timer_flag.wait()
stop_stat('t_timer')
start_stat(' t_load')
time.sleep_ms(load_period_ms)
stop_stat(' t_load')
stop_stat('t_total')
loop = uasyncio.get_event_loop()
loop.create_task(print_stats_task())
loop.create_task(output_task())
loop.run_forever()
This was tested on ESP32 with timer 0
I'm truly at a loss... Is this expected behavior for uasyncio? If so it feels very surprising. If not then this seems like a bug - the proper behavior would be for the flag to be set (perhaps multiple times while processing is occurring) and then for it to be unset the next time around.
Thanks for looking!