uos.dupterm() behavior in underspecified, implementations broken
While not directly related to HW API, https://github.com/micropython/micropython/wiki/Hardware-API#os-module-additions specifies os.dupterm(), used for:
os.dupterm([stream_obj]) get or set duplication of controlling terminal, so that REPL can be redirected to UART or other (note: could allow to duplicate to multiple stream objs but that's arguably overkill and could anyway be done in Python by making a stream multiplexer)
Unfortunately, this description is not clear/detailed enough, or alternatively, current implementations of it in stmhal and cc3200 ports are not general or just broken. First of all, it should be made clear if the talk is about duplicating "controlling terminal", or replacing it. If there's talk about duplicating, then there should be immediate talk about non-blocking streams. Otherwise this line https://github.com/micropython/micropython/blob/master/cc3200/hal/cc3200_hal.c#L187 will simply hang the entire REPL, as it will wait until a character is available on that particular stream, ignoring all other input sources.
And then if there's requirement to non-blocking streams, it should be kept in mind that all other REPL sources should be non-blocking too, otherwise standard source can block custom installed one. But requirement of non-blocking sources is merely a minimal condition to get it all working, but working quite inefficiently and requiring weird workaround to not make it burn CPU cycles (and a battery of battery-powered device), e.g. https://github.com/micropython/micropython/blob/master/cc3200/hal/cc3200_hal.c#L196. So, next step is making requirement that REPL sources are pollable, and actually poll them.
That's quite a bunch of requirements to get it working in the general case, sure.
Uaysncio v3 Stream Writer Drain Bug
The new implementation of Stream will write redundant frames out it's socket/serial connection if used asynchronously.
Observed on the STM32 UART.
Expected/Actual Behavior & Steps to replicate:
This pseudo-code should write N packets out the uart,
streamWriter = Stream(uart...
async def foo():
streamWriter.write(packet)
await streamWriter.drain()
for i in range(0,N):
asyncio.create_task(foo())
but instead will likely write the first packet N times, the second packet N-1 times, ... the last packet one time (not in that order).
Root cause:
In the drain method of Stream, it pauses at the yield and allows the other coroutines to start, initiating N drain operations where the variable 'off' is 0 in each, and thus the entire buffer is written for each operation. Thus the drain method is not "thread safe" or idempotent in the sense that the 'off' variable is not independent of other concurrent operations; it is thus susceptible to race conditions.
Proposed Fix:
The drain operation should be idempotent, either maintaining some sort of stateful buffer reading in the Stream instance, or limiting itself to processing only one write out loop at a time.
Temporary Workaround:
Comment out the yield operation in drain (yield core._io_queue.queue_write(self.s)). This was tested and seems to be working, but wouldn't consider it safe or reliable since I'm not sure how the entire IO queuing process works and if it's safe to disable.