-
-
Notifications
You must be signed in to change notification settings - Fork 130
Issues with Print::print() and partial writes #64
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
Comments
Related issues: |
AFAIR in Arduino ecosystem I see your point that the current API may be interpreted as allowing partial writes, and probably some action should be taken quickly to discourage this usage. Maybe we can reinforce this fact by better commenting the function? |
If write() and all its forms are always blocking and everyone is happy with that, then simply documenting it would solve the issue. Once write() becomes non blocking things start to get a bit more complicated particularly if there is not a way to configure it to be blocking. I had always assumed that write() was not required to be blocking and that is why I was concerned. |
In further thinking and reflecting I think the issue needs a better solution since it can't fully be resolved through documentation - not for the print() functions. If the write() functions are not allowed to do partial i/o, what is the value of returning the number of bytes written? The issue for print() is that the way the print() code is currently working is that even if write() is not allowed to do partial i/o but must return the number of bytes processed, and print() simply returns the number of characters successfully written, that there is no way to tell that a print() call failed or wasn't fully processed because print() simply passes the amount of successfully written characters. If an error does not occur on the very first character, then the caller has no idea that there was an error since the caller does not know the number of characters that should be output.
and foo is an integer and the write() function failed mid way through the output of the characters of the integer, then print() simply returns the number of characters that were written successfully. The caller of print() has no idea that there was an error since print() returned a non zero value but the caller has no idea how many actual characters were supposed to be output. The caller has no way of knowing that all the characters were not written. The print() return value is not really a new issue but is something that has been lingering for quite some time. Here is a nearly fully backward compatible solution that also allows partial writes() in the future: I'm curious about other peoples thoughts on partial i/o and then how to indicate errors vs partial i/o and then how to handle return errors from print() functions. |
One possible usecase, which I've used in the past, is for alignment, when printing an arbitrary value and you need to pad it with spaces up to a certain amount of characters. (No time for going over the rest of your post(s), though) |
matthijskooijman, I'm not following your comment. When disallowing write() from doing partial i/o, I understand the value of doing partial i/o but as I mentioned above, I don't understand the value of write() being required to return the number of bytes written when partial i/o is not allowed. |
Partial writes don't, but returning the number of bytes written helps with padding alignment. You said you didn't know why the number of bytes was returned without supporting partial writes, so I gave a usecase for the return value that did not need partial writes. Now I realize I that this really only applies to Reading your comment, I think I agree - the return value from A slightly tuned down version of your proposal could be:
This allows a caller to detect errors, even when it cannot with the current code. On the other hand, this does throw away other information (namely the number of bytes written before an error occurred). I'm inclined to think that this information is less valuable than knowing if an error occurred, but there might be usecases that need the number of bytes written before the error (I can't think of any just now). |
matthijskooijman, I still don't understand your use case for print() so it is hard to respond to your comments. I still don't follow how returning partial write information from print() helps with padding and alignment. For example, suppose there is a number and the number should be right justified within a character output area/field. Or in another case where you send a string to print() and you want space padding between words to be done to ensure that words don't wrap across a line. If the device returns an "error" or simply a count that is lower than the total output size requested when attempting to write beyond the end of the line and then print() returns the number of characters written on the line, how does that help with the padding to prevent word wrap. The partial word up the wrap point has already been output. Can you be very specific on what you mean by alignment/spacing and provide an exact example of how print() supporting fractional output helps? In terms of return values, what I mentioned earlier was that the return value of print() is not really tied to the return value of write(). print() could be smarter and treat it differently since it knows if all the characters were successfully output and could return a status based on that information, and that is what I was proposing. The real issue is that within the current write() API definition there is no documented way to distinguish between a partial write and an error. What you have proposed is the same as what I proposed when partial writes were not allowed and a non-zero return for print() was used vs a boolean. If partial writes without an error are going to be allowed by write() - then the write() API must be clear on how to support that and clearly indicate the difference between a partial write and error that occurred mid way through the processing. My biggest concern and focus is return status for print(). The reason I want to separate them is that in my view print() is pretty much a simple "fire and forget" interface that may block but yet you might like to know if all your output was successful. write() on the other hand is different beast and callers of write() may want and need more sophisticated things and so things like support for partial writes and better status information may be necessary. The real issue is that when this stuff was tossed in back in 1.0 the the Print class API functions didn't include support for errors. I think print() is easy. write() is the potential challenge, particularly if partial writes are supported as that can affect backward compatibility. |
Like I said, partial write info doesn't help here, but returning the number of characters does. Say I wanted to print:
For the number + aligning spaces part, I can now do:
Without the Serial.print return value, I will have no idea how much characters the printed number has taken, so I can not do alignment (other than predicting how much characters there will be, which is cumbersome, duplicates code, and doesn't generalize well, especially when e.g. Of course, this stuff only helps with left-aligning things, for right-aligning you will need to predict how much characters something is going to take (which probably needs a "print to string" kind of approach, somewhat voiding the need for the print return value). So perhaps this usecase isn't particularly important (but I do think it is valid). Other than that, I agree with your ideas. Print should be blocking (no meaningful way to support partial writes), but write might support partials. Print can just retry writing repeatedly as well (until an error occurs / nothing is written), to bridge the gap between these two. I do wonder about the performance / code complexity impact. If all print functions need to handle retries, things get complicated. We could add a Perhaps we can even invert this, by keeping the existing AFAICS, this would add non-blocking support on an opt-in basis, while staying compatible with the existing APIs completely? One potential challenge is that |
Don't we already have availableForWrite() defined in the Arduino API to solve this non-blocking need? |
I believe that write() should be single character based. It should return at a minimum 0 or 1, possibly -1 for fatal errors. But, adding -1 handling would require a complete para-dine shift. Just returning 0 and having print() fail back creates the ability to identify the failure. The argument that print()'s conversion to Human Readable Data (HRD) renders the count invalid, and therefore count is useless is an oversight. I agree that using the value of count on HRD is useless. But, when I use count as returned by print(), I always have an expected value to compare to. Most Arduino sketch's exist without any error detection or correction. Returning the Actual number of characters written is valuable. The reason I submitted the fail on write bug, was that I had added RTS/CTS handling to HardwareSerial. This created the possibility of an infinite hang on CTS. I added timeout logic to HardwareSerial.write(). In my upper level code I can either use availableForWrite() or comparing count with an expected value. Without the fail, count became meaningless, and I was not able to recover from a buffer full timeout.
We need a style sheet. The expected behavior of print() is that it always works, and the all data is accurately transferred. That is not always the case. Serial() always sends the data but the device may not be able to receive/process it fast enough. Overruns Occur.
Coding is quite easy: Serial.begin( baud, characteristic, ctsPin, rtsPin);
char ch[50];
char chLen=25,i=0;
ch[chLen]='\0';
unsigned long timeout=millis();
//try to send ch[] for up to 10 seconds
while((millis()-timeout)<10000)&&(i<chLen)){
i+=Serial.print((char*)ch[i]);
}
// the RTS pin handshaking will pause the device until I can process the data @matthijskooijman brings up some good points:
But, I think that print() should always respond to a write()==0 as a fail. The virtual write() function should be the one in control of the retry, block on buffer full, fail out.
When an Object uses print() it decides how it implements write() so, write() is custom for the Object. These decision should be left for the custom Object. print() just needs to respond the same way across all of it's methods. Chuck. |
On 01/08/2016 06:38 PM, chuck todd wrote:
There are other potential conditions that are harder to deal with. And that is why dealing with these types of low level issues is normally
There is no way the sketch can really ever recover because the sketch Just like I pointed out in the example above, if you call print() to
I do agree that the return behavior of print() should be fully defined. When you start to talk about receiver overruns, you are starting to talk In systems which have real device drivers, there are multiple modes for In real device drivers, there are raw modes and cooked modes (which do Within the existing print() and write() APIs, --- bill |
@bperrybap , I agree having print() generate HRD reduces the value of count. But having print() ignore write()==0 makes count useless. I agree that print() does not / should not handle retry, error recovery. print() is a generic data provider for the level 2 / level 1 Object write(). Inside the write() function is the only location where device specific retry/timeout/abort logic should exist.
print() must be predictable. The only thing print() must do is to honor the abort when write()==0. To answer you CTS timeout scenario, a call to Serial.availableForWrite() will tell me if the buffer has been emptied, and/or digitalRead(ctsPin) will tell me if the device is busy. Arduino has been designed with a Best Effort model. It assumes everything worked as expected. I do not think write()==0 abort would break this model. For my modified HardwareSerial(), unless I call my modified Serial.begin(), Serial() functions exactly as before. Calling my modified Serial.begin() initializes the CTS/RTS hardware and changes the behavior. These changes are in the level2 write() and level1 interrupt handlers of the HardwareSerial() object. I don't know if the UNO has enough guts to handle print()==-1 error propagation. I am having enough problem implementing a network layered RS232 <-> RS485 <-> nRF24L01 <-> RS485 on MEGA2560. I am using the 9bit hardware and packet checksums, so my upperlevel sketch is ignorant of data transmission internals. It just sends and receives packets all of the retries and validations are handled under the 'hood'. Chuck. |
Recently there was a lengthy conversation on the Arduino Developers Mailing List regarding RTS/CTS. After many messages, the consensus was Arduino would (someday) use Serial.attachRts(pin) and Serial.attachCts(pin) to allow hardware flow control. It was also decided these functions would return boolean, indicating success or failure. Boards not supporting hardware flow control, or attempts to use unsupported pins would return false. I sincerely hope you'll adopt this API, rather than overloading Serial.begin(). Assuming Arduino stays with this already-made decision, following it will mean your code will be compatible with whatever Arduino does in the future. |
quoting @bperrybap here
http://forum.arduino.cc/index.php?topic=357312.msg2481770#msg2481770
The text was updated successfully, but these errors were encountered: