Skip to content
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

sys/stdio: Precision on semantics, and fixes #20364

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

chrysn
Copy link
Member

@chrysn chrysn commented Feb 8, 2024

Contribution description

stdio_write has been used inconsistently with respect to whether or not incomplete writes were returned.

This

  • describes in more detail how stdio_write behaves, clarifying corner cases
  • introduces a helper stdio_write_all for the common case that some code is not prepared to handle incomplete writes
  • applies that helper in a few places (and documents in others why it is not needed)
  • makes the dispatch version retry (because that way it works in the presence of backends that do partial writes)

It does not yet attempt to verify that all stdio implementations behave as desired.

Testing procedure

Most of this is documentation updates and API usage correctness where I wouldn't know how to produce any different behavior. I'd leave it at that and set "CI: run tests" unless reviewers insist (and maybe have concrete suggestions).

Issues/PRs references

This came out of discussions around #19738, and builds on it.

There was earlier discussion on matrix.

@github-actions github-actions bot added Area: network Area: Networking Area: tests Area: tests and testing framework Area: build system Area: Build system Area: pkg Area: External package ports Platform: AVR Platform: This PR/issue effects AVR-based platforms Area: drivers Area: Device drivers Area: arduino API Area: Arduino wrapper API Platform: ESP Platform: This PR/issue effects ESP-based platforms Area: cpu Area: CPU/MCU ports Area: USB Area: Universal Serial Bus Area: sys Area: System labels Feb 8, 2024
@chrysn
Copy link
Member Author

chrysn commented Feb 8, 2024

Checking the existing implementations:

  • Didn't check for any implementation yet how it behaves in interrupts (but it's a "should"...).
  • stdio_rtt, when the buffer is full, will return 0. That has not been allowed before either -- now there is the concrete suggestion that it should block in this case (but the non-blocking stdio_rtt may need overhauling anyway, because discussions indicated that it should be always blocking, unless build-time switched to a mode when it always ever does best effort)
  • similar with the nimble version (if the buffer is full, it will just busyloop when someone tries to write everything)
  • usbus and tinyusb cdc_acm_stdio could do away with the loop (because the caller will do that)

@mguetschow
Copy link
Contributor

It does not yet attempt to verify that all stdio implementations behave as desired.

Should we maybe add a warning to the docs, until the stdio implementations have been confirmed to adhere to the specification?

@github-actions github-actions bot removed Area: network Area: Networking Area: build system Area: Build system Area: pkg Area: External package ports Area: drivers Area: Device drivers Platform: ESP Platform: This PR/issue effects ESP-based platforms Area: USB Area: Universal Serial Bus labels Feb 23, 2024
@chrysn chrysn added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR CI: run tests If set, CI server will run tests on hardware for the labeled PR labels Feb 23, 2024
@chrysn
Copy link
Member Author

chrysn commented Feb 23, 2024

Rebased to address conflicts (now that the base #19738 has been merged).

Should we maybe add a warning to the docs, until the stdio implementations have been confirmed to adhere to the specification?

I think that's adequately covered by the "should" all over the place. <grumpy reason="no lunch yet">If we started placing warnings on all RIOT APIs where there are implementations that subtly misbehave, we'll have very red documentation with very little gained, I'd rather spend the time fixing impls.</grumpy>

@@ -61,6 +68,21 @@ void stdio_close(void) {
#define MAYBE_WEAK __attribute__((weak))
#endif

ssize_t stdio_write_all(const void* buffer, size_t len)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ssize_t stdio_write_all(const void* buffer, size_t len)
int stdio_write_all(const void* buffer, size_t len)

To me, ssize_t sends a lot of "this will return len on success vibes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the vibe check, it's spot on, fixed.

@riot-ci
Copy link

riot-ci commented Feb 23, 2024

Murdock results

FAILED

0bcba79 fixup! treewide: Replace uses of stdio_write with stdio_write_all

Success Failures Total Runtime
224 0 9025 01m:04s

Artifacts

@chrysn chrysn removed the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 23, 2024
@chrysn chrysn added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 23, 2024
@chrysn chrysn added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR and removed CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Feb 23, 2024
break;
}
cursor += written;
}
Copy link
Contributor

@benpicco benpicco Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we say that we don't want to do this because with some backends, this would block forever if stdio is written from ISR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an ISR situation, a backend will (or should) only do one of two things: return an error, or return the full length. Either terminates the loop.

Comment on lines +73 to +81
while (len > 0) {
ssize_t written = stdio_write(buffer, len);
if (written < 0) {
return written;
break;
}
buffer += written;
len -= written;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are already doing this in stdio_write()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only when using the dispatch backend, because it can't practically return incomplete writes. Other backends may still return incomplete writes, and all the callers that were switched to stdio_write_all in this PR previously didn't do this loop manually.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then what do you still need stdio_write() for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this allows applications to decide to slow things down if prints get chunked up too much, or decide to print less.

There is the alternative to make our stdio_write always behave like stdio_write_all, and block all the way through. That would be simpler, just less POSIXy, and I've clung (past tense) to the idea that at some point there'd have been a good reason to do things that way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No application will ever call stdio_write(), this hooks into libc.

@@ -25,6 +25,7 @@
#include <string.h>
#include <unistd.h>

#include "stdio_base.h"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benpicco, going through this in a self-review I've noticed that the previously used stdio_write was extern defined instead of including stdio_base in f518173 -- any memory of why that was?

@benpicco
Copy link
Contributor

I think this PR is a bit odd.
The root problem is that stdio_write() returns anything at all.

If this was solved by calling stdio_write() in a loop, the stdio implementation could have done so already (before there was the high level / low level API split).

There are either stdio implementations that will always write everything (e.g. stdio_uart) or those that fill a buffer and expect another thread to deliver it. In the latter case, a simple loop will do no good if you don't yield control to the other thread - or we keep it the way it is now and accept that with some exotic backends under rare circumstances some stdio can be lost if large amounts of text are printed at once.

To me this would be preferable to introducing weird edge cases that might lead to a hang.

@chrysn
Copy link
Member Author

chrysn commented Feb 23, 2024

The reason the simple loop does work is that stdio_write has never been allowed to return 0 -- at latest when a driver would want to return 0, it'd need to block, and would have needed to block without this PR just as well.

@chrysn
Copy link
Member Author

chrysn commented Feb 23, 2024

I stand corrected: It was allowed to return 0 from the API description, but that case was never actionable. (Like, what is the caller supposed to do?). Not being allowed to return 0 (except when stdiout was closed) is just what one would expect the API to do coming from a POSIX mindset.

@chrysn
Copy link
Member Author

chrysn commented Feb 23, 2024

I see two ways towards an API that can be used:

  • Do this.
  • Declare that stdio_write will always write the full string, block if needed, unless used from an interrupt.
  • Forbid writing from interrupts (in which case the above become similar enough that the distinction barely matters).

As it is now, we're having callers to stdout writing all over the place that ignore error conditions, and may arbitrarily lose data.

@benpicco
Copy link
Contributor

benpicco commented Feb 23, 2024

stdio in RIOT is just a debug tool. It should be reasonably reliable, but still kept as simple as possible. I don't think we need anything on top of that to prevent 'data loss'.

@chrysn
Copy link
Member Author

chrysn commented Feb 23, 2024

It's not just debug output -- that's what the debug.h is.

stdio is also what every single new user comes across when trying out RIOT the first time (as used in the shell), likely part of their first programs, and part of the data extraction of every RIOT based publication I've been involved with so far (with weird bugs showing up there because awk scripts processing the data tripped over gaps).

Having an interface there that changes its behavior depending on the backends, has no clear guidance on how to use it, and is not used consistently throughout the code base, does not make a good first impression, and causes pain down the road as well.

@Teufelchen1
Copy link
Contributor

Any chance to move this forward for the upcoming release?

@benpicco
Copy link
Contributor

In the current form, this PR will cause regressions when stdio is used from ISR.

@maribu maribu mentioned this pull request May 10, 2024
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: arduino API Area: Arduino wrapper API Area: cpu Area: CPU/MCU ports Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR CI: run tests If set, CI server will run tests on hardware for the labeled PR Platform: AVR Platform: This PR/issue effects AVR-based platforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants