Very strange (to me) non-deterministic stdio behavior #15913
-
Consider the following sample running on a RP2040 Pi Pico: # Stdin test case
import select
import sys
poll_object = select.poll()
poll_object.register(sys.stdin, 1)
def get_cmd():
if poll_object.poll(0):
ch = sys.stdin.readline()
if ch == "\n":
#print("")
return None
return ch.rstrip()
print("Listening for commands")
while True:
cmd = get_cmd()
if cmd:
print(f"Got {repr(cmd)}") nominally, it should listen for a "command" on stdin (REPL input, I guess), then simply print that and go back to waiting. Of course, many terminals send CRLF instead of just LF, so it's very often the case that a "command" of "myCommand\n" will show up as "myCommand\n","\n". For that reason, if ch=="\n": return None. If you send any non-empty string, this program immediately prints the repr() of it and not the spare \n. Cool. But if you send an EMPTY string, it seems to get stuck for one second per send. It SHOULD just be rapidly doing nothing on each of the two empty lines. That is, enter a bunch of strings rapid fire and it stays completely responsive. Hit "enter" 10x then "foo[enter]" and it'll take about 10s before the program prints "foo\n". As a hint, uncomment line 12 What is going on here with this blocking? |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 2 replies
-
Here's a slightly more complicated case that I imagine has the same root cause, but I don't know: import select
import sys
class cmd_listener():
def __init__(self):
self.poll_object = select.poll()
self.poll_object.register(sys.stdin, 1)
def get_cmd(self):
if self.poll_object.poll(0):
ch = sys.stdin.readline()
# Don't return an empty command. One of these happens every command when sent from a terminal that does CRLF line endings.
# Note: it seems that every .readline() incurrs a 1s delay if there's no associated print() which sounds insane and makes no sense to me.
if ch == "\n":
return None
return ch.rstrip()
def listen(self):
"""Start listening for commands"""
print("Listening for commands")
while True:
cmd = self.get_cmd()
if cmd:
try:
method = getattr(self, cmd)
print("")
method()
except AttributeError:
print("Unknown command")
def foo(self):
print("foo")
listener = cmd_listener()
listener.listen() this is supposed to blindly execute the self."command" it hears over the stdin/REPL. It works as intended, besides the extraneous printed newline, ONLY IF the |
Beta Was this translation helpful? Give feedback.
-
I can't replicate this. I ran the first script on a Pico, firstly using I would hazard a guess that the problem is with your means of accessing the Pico. Thonny, perhaps? |
Beta Was this translation helpful? Give feedback.
-
Very interesting! No, I don’t think so - IIRC it’s been the same across mpremote, the Serial Monitor plugin for vscode, and Putty. But I’ll absolutely try reproducing it across those and across a couple computers. Very interesting that you couldn’t reproduce it. I suppose I should have mentioned the mpy version too. Stay tuned for all these details! |
Beta Was this translation helpful? Give feedback.
-
The unix port works with both examples. Pulled and compiled 5 minutes ago. |
Beta Was this translation helpful? Give feedback.
-
In my experience some problems where the physical interface is USB can be affected by the PC. I have two laptops running different Linux distros: one has problems with the REPL on an ESP32-C2 board while the other is fine. On the "good" machine USB remains something of a black art. |
Beta Was this translation helpful? Give feedback.
I can't replicate this. I ran the first script on a Pico, firstly using
mpremote
thenrshell
. In both cases it handled normal lines as expected, ignored multiple blank lines, and handled subsequent non-blank lines promptly. The second script also worked, with the blank print statement removed.I would hazard a guess that the problem is with your means of accessing the Pico. Thonny, perhaps?