-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability to check whether data is available or a read timeout #122
Comments
This is mostly a feature request for the In |
Hmmm. I'm unsure exactly what problem you're trying to solve here:
|
The place where this come into play is 3D printing. For every command you send to a 3D printer, you should expect a reply, this leads to code that looks something like this:
However, for various reasons, such as bugs in the firmware, or slight mismatch in the serial data rates between the computer and the microcontroller used in the printers, occasionally a reply is never received. This leads to a stall in the print, unless the code is modified as such:
As for the second question, I didn't have a specific use case in mind, except to think that it could possibly be used to solve the problem presented in the first situation. It's possible that indeed it isn't necessary, if a timeout facility was provided. |
To handle timeouts whatwg/streams#1103 is probably what you need. That assumes, however, that you actually want to cancel the call to read(). This isn't strictly necessary. Some piece of the code which is listening for messages can be kept active at all times waiting for it to respond. As written in the original example there's actually a problem because since the read is never cancelled the next chunk, when it arrives, will be discarded. I'm curious if developer using WebSocketStreams are running into similar issues. Ignoring HTTP streaming, for a fetch-based stream you don't find yourself handling discrete chunks of the data over a long time-scale the way you do for a long-lasting connection. |
OK, cool. Yeah, so my personal view of the ideal solution here would be:
Then you would write try {
const { value, done } = await serialReader.read({ signal: AbortSignal.timeout(10_000) });
} catch (e) {
if (e.name === "AbortError") {
// TODO
} else {
throw e;
}
} |
Actually, I believe this criticism applies to my first implementation, which looked like this:
This version is actually broken in precisely the way you explained. This is why I said writing the code correctly is non-trivial. I fixed it by saving the promise object for reuse in the next call to "read()". |
@domenic: This would work for me. The syntax is a bit strange, I would have expected Anyhow, it seems like my request is a duplicate of that other ticket. Glad it's being looked at. Thank you for your feedback! |
In whatwg/streams#1103 folks are pointing out that it's very rare that you want to cancel a read() call without canceling other subsequent read() calls. At first I thought this thread was a counterexample, but now I am not so sure. In particular, @reillyeon's point
indicates a different strategy, which is more like async printLoop(commands) {
while(commands.length) {
let pendingCommand = commands.shift();
await sendCommand(pendingCommand);
let gotReply = false;
setTimeout(() => {
if (!gotReply) {
// Resend the command
sendCommand(pendingCommand);
}
}, 10_000);
await getReply(); // no timeout
gotReply = true;
}
} although that only handles a single retry, hmm... |
@domenic: This is a very simplified example. The actual code for interacting with the printer is much more complicated that this. Of the top of my head, simply resending a command like this would probably not work. Also, I believe that the idea behind async/await is to allow people to write code with a more familiar synchronous paradigm. Adding a timeout like that breaks that abstraction in a way that concerns me a lot. |
Here is an example that still is simplified, but gets closer to what actually needs to happen:
Purging the data on the port is one situation where checking whether bytes are available could help; doing so with a read() with timeout introduces a slight delay, which isn't a problem in this case. |
The point we're trying to make is that you should be able to completely avoid the read-with-timeout pattern, and instead have a concurrent bit of code running which notices that a response hasn't arrived yet, and performs any necessary resets at that time. That way, you maintain the invariant that a single read request always results in a single result, instead of having to issue potentially N read requests, the first N - 1 of which timeout. |
Purging data from the port can be accomplished by calling |
Hummm. I see what you are saying. I'm not arguing that it can't be done that way. But I guess I will counter that at least a certain number of people will be porting serial code that was written in a sequential manner. A lot of serial port code is written that way and will rely on the timeout pattern; forcing that code to be rewritten as concurrent code could present significant problems. |
@reillyeon: That's interesting and somewhat non-obvious. I'll have to give that a try. I guess I'm a bit curious why the Web Serial API is so different from the serial interfaces used in the past. I imagine it will present somewhat of a learning curve for folks transitioning to it from other languages. |
This (and to a lesser extent #123) is a consequence of our choice to use the WHATWG Streams API instead of providing a more POSIX-style interface. I think that was the right decision for consistency with the rest of the web platform but acknowledge the learning curve. |
@reillyeon Okay. By no means do not take my comments as criticism. The work you guys are doing is amazingly awesome and I am very excited about what I've been able to do with it already. I don't know whether you guys have ever done 3D printing, here is what I am currently working on: https://syndaverco.github.io/slicer-beta/?enableSerial=1 Before Web Serial, I had this as an Electron app because it was the only way to access the native serial ports. I am very excited to see it running on a plain old web broswer now. I think it's quite a game changer! |
No offense taken, this is really good feedback to help us make the API better. |
It looks like whatwg/streams#1103 has reached the conclusion that async function readWithTimeout(stream, timeout) {
const reader = stream.getReader();
const timer = setTimeout(() => {
reader.releaseLock();
}, timeout);
const result = await reader.read();
clearTimeout(timer);
reader.releaseLock();
return result;
} This function will reject with a @domenic, do you know when this new behavior will be implemented in Chromium? |
UPD. Unfortunately I misunderstood the read function's code earlier, so please ignore my previous statement. I think therefore we better to do something like this:
|
It looks like the Chromium issue tracking implementing the new behavior for |
Hi all. Sorry, if this is not the appropriate channel to ask questions! I think I am one of the people having that "learning curve" mentioned above. I stumbled across the
where line 96 is the According to the post from above, this is expected:
Can I add a Thanks a lot! |
You want to put it around any place where you call try {
const { value, done } = await readWithTimeout(port.readable, 1000);
// Do something with |value|.
} catch (e) {
if (e instanceof TypeError) {
// Handle the timeout.
} else {
// Handle other errors.
}
} Alternately you can put the try/catch block around the |
Thank you for your help. I am kind of new to JavaScript, especially the asynchronous part (not really familiar with The following does the trick for me and hence, I am sharing it for others:
This should return always return an Edit: does not work fully for me. Chaining multiple Another edit: Commenting out the second |
Right now, when you call
await serial.readable.getReader().read()
, it will block indefinitely if no data is available. This works alright when you know you are expecting data, but it won't work very well if you do not know this for certain or when serial errors cause data to be dropped.It is possible to implement a read timeout using
setTimeout
, but I found it is somewhat tricky to get right and I had to go through several iterations to figure out how to make it work:Having a timeout helps in some situations, but it really seems like a clumsy work around to not being able to check whether data is available before calling
serial.readable.getReader().read()
The text was updated successfully, but these errors were encountered: