-
-
Notifications
You must be signed in to change notification settings - Fork 7k
make serial.available, peek, read atomic #3848
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
Conversation
when SERIAL_RX_BUFFER_SIZE is defined as greater than 256, the current HardwareSerial.cpp code almost works correctly. but not quite. the function serial.available may return an incorrect number of characters waiting (though when treated purely as a boolean it seems not to fault).serial.peek has a similar issue detecting if a character is waiting, while serial.read may also leave _rx_buffer_tail corrupt to an interrupt occurring mid-update (this corruption may cause the ISR to be mistaken about how much free is available in the Rx buffer). the proposed changes to these three functions add cli/sti pairings around the critical pieces of code. i have tested and verified the change made to serial.available as correcting the problem, while the change to serial.peek follows the exact same pattern. the changes to serial.read have been confirmed as not breaking the function, but i have not been in a position to test the failure of the original (non-atomic) version to make comparisons. multiple testing was conducted at 115,200 baud and 500,000 baud using data streams of 1,000,000 characters sent over a 2 minute interval. any error causing loss of character would have resulted in a catastrophic (ie, very obvious) failure. cheers, rob :-)
Thanks for picking this up, this was a known flaw in the code (and I believe no PR for it existed yet). I do have some remarks:
For the last three points, see this comment for more info and some proposed code. The last point might be a bit tricky to understand, feel free to ask more questions if my point isn't entirely clear :-) (also, I think fixing that one might be more tricky than just adding an |
One more thing: You should probably remove this comment from the code in your commit as well. |
please do advise your suggestions and i shall amend the code accordingly. cheers, |
have updated the three changed functions to match the same style as used in serial.availableForWrite so now is conditional on buffer being > 256 bytes. also brought tail within the atomicity. again, serial.available and serial.read confirmed as working. serial.peek assumed to be ok as is almost identical in structure to serial.available. cheers, |
Yeah, separate variables would be preferable to keep interrupts disabled as short as possible. Also, then we won't have to worry about the typing :-)
The gain is not really in saving a few bytes of code, it's about not disabling interrupts when not needed. Disabling interrrupts can increase the time it takes for interrupts to run, so it should not happen if not strictly needed.
Ah, good point. However, if we want to also make reading and writing from an ISR safe, this assumption no longer holds, and all accesses need to be made atomic.
I don't actually think this is true, and it would be elegant if this would just work. Especially reading from serial in an ISR should be doable. writing is a bit more tricky, since write can potentially block, of course.
In the interest of readability, could you see if you can use the Overall, I think that:
If you locally edit your commits, you can use a force push to overwrite the commit in this pull-request, without having to create a new PR. Thanks! |
i've asked about ATOMIC_BLOCK on the developers forum, and received quite a bit of feedback. it seems like a rather complicated macro that not everyone understands the workings of, and at this point i'd not feel comfortable using it myself - i would far rather leave it for someone else to add in a later revision to the code. similarly, as you say, write() is a much more complicated issue and safest left well alone at this stage. my personal interest is only in the Rx buffer anyway. so, in terms of functionality (ignoring style), is there anything else that needs changing? cheers, |
Good thing to aks! I would say most responses are positive about
Well, it seems your changes sufficiently fix atomicity issues caused by having two-byte index variables. However, you're doing more than that, partially (but not completely) fixing the "read() called in ISR" atomicity issue as well. I guess this should either happen completely, or not at all. Some new things I realized:
Not sure whether supporting read/write in an ISR is worth the trouble. It would be nice to have things just work, though with the current code it will at least usually work (and, AFAICS cause random reads and writes in the worst case, never locking up). @cmaglie, any idea about this? |
my intention in modifying Serial.read was purely with regard to the issues with two-byte index variables; while i've not observed it, the current Serial.read could potentially misbehave with the Rx buffer size >256 bytes when called from loop() - ie, not from within an ISR. any other 'improvements' are unintended side effects. the whole idea of calling from within an ISR adds a layer of complexity that i'd rather leave to someone else to address. if it were my choice, i'd just be bailing (with a -1 error) from the routines if they were called while the interrupt enable flag is cleared. this would help discourage users from trying to do something that i consider bad programming practice. but this is just my opinion. cheers, |
You should probably also add an atomic check around the first statement inside the if (16 bit only): This fix would suit better in here, instead of my PR. |
what line number do you mean? |
The linked one. 220. |
aha, highlighted in brown, yes? that line is in Serial.write my proposed changes are strictly limited to the serial received routines only. i'm not trying to produce a perfect solution that fixes all problems in the serial library, but just to fix some specific issues that i have encountered and produce code that is an incremental improvement on what is already there. cheers, |
We should fix this somewhere anyways. I've added this to my PR now. |
Edit: moved here 95bb83e |
when SERIAL_RX_BUFFER_SIZE is defined as greater than 256, the current HardwareSerial.cpp code almost works correctly. but not quite. the function serial.available may return an incorrect number of characters waiting (though when treated purely as a boolean it seems not to fault).serial.peek has a similar issue detecting if a character is waiting, while serial.read may also leave _rx_buffer_tail corrupt to an interrupt occurring mid-update (this corruption may cause the ISR to be mistaken about how much free is available in the Rx buffer). the proposed changes to these three functions add cli/sti pairings around the critical pieces of code. i have tested and verified the change made to serial.available as correcting the problem, while the change to serial.peek follows the exact same pattern. the changes to serial.read have been confirmed as not breaking the function, but i have not been in a position to test the failure of the original (non-atomic) version to make comparisons. multiple testing was conducted at 115,200 baud and 500,000 baud using data streams of 1,000,000 characters sent over a 2 minute interval. any error causing loss of character would have resulted in a catastrophic (ie, very obvious) failure. cheers, rob :-) -> Squash and rebase of arduino/Arduino#3848
I don't actually think that #6855 includes these changes, see #6855 (comment) and #6855 (comment) But note that these fixes should probably use the new @johnholman, if you're going to create that PR, this one might be a good one to check for where the changes need to be applied. @facchinm, I think this PR can still remain closed, though it might be good to have an issue open for this (not sure if there is one already). |
Hi @matthijskooijman , |
after 2 years of inaction, is it entirely surprising that the original author of the edits had thrown his hands in the air and walked away? he probably decided it was less frustrating to just fix bugs in his own (local) copy of the arduino code rather than sharing with the community. not wishing to criticise, but it does seem that the human brain is wired to reject a partial solution to a problem, preferring to live with no solution rather than adopt a solution that is 'merely' an incremental improvement. just dug around in my local archives, attached is a .txt file containing what were most probably the edits. cheers, |
Hi @matthijskooijman @facchinm Yes #6855 does not address the large buffer atomicity issues, although it does tidy up the one place in available_for_write() where there was already a guard by using the TX_BUFFER_ATOMIC_BLOCK macro. I'm just looking at @robert-rozee's code and think the macro would help avoid a lot of extra code clutter that is otherwise needed. Should I go ahead and create a new PR along those lines or @facchinm are you planning to do it? Also should it now be against ArduinoCore-avr rather than the Arduino repo? |
@robert-rozee, I actually think the changes can still be seen in this PR (under the "Commits" tab), it's just that the PR cannot be re-opened. In any case, even though this has been stale for so long, it's good you created the PR back then, since now we'll hopefully see some movement on this issue :-) |
I've created a new PR #5 against ArduinoCore-avr repository to address atomicity problems with Dealing with large write buffers might be more tricky and for a later commit. The new pull request is similiar to this one but with these differences:
|
when SERIAL_RX_BUFFER_SIZE is defined as greater than 256, the current HardwareSerial.cpp code almost works correctly. but not quite. the function serial.available may return an incorrect number of characters waiting (though when treated purely as a boolean it seems not to fault).serial.peek has a similar issue detecting if a character is waiting, while serial.read may also leave _rx_buffer_tail corrupt to an interrupt occurring mid-update (this corruption may cause the ISR to be mistaken about how much free space is available in the Rx buffer).
the proposed changes to these three functions add cli/sti pairings around the critical pieces of code. i have tested and verified the change made to serial.available as correcting the problem, while the change to serial.peek follows the exact same pattern. the changes to serial.read have been confirmed as not breaking the function, but i have not been in a position to test the failure of the original (non-atomic) version to make comparisons.
multiple testing was conducted at 115,200 baud and 500,000 baud using data streams of 1,000,000 characters sent over a 2 minute interval. any error causing loss of character would have resulted in a catastrophic (ie, very obvious) failure.
cheers,
rob :-)