-
-
Notifications
You must be signed in to change notification settings - Fork 700
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
fix(stdio): allow usage of file writer on shared non-blocking file descriptors #8725
base: master
Are you sure you want to change the base?
Conversation
…scriptors Signed-off-by: Luís Ferreira <[email protected]>
Thanks for your pull request, @ljmf00! Bugzilla referencesYour PR doesn't reference any Bugzilla issue. If your PR contains non-trivial changes, please reference a Bugzilla issue or create a manual changelog. Testing this PR locallyIf you don't have a local development environment setup, you can use Digger to test this PR: dub run digger -- build "master + phobos#8725" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just need to fix the failed style check.
I'm not sure that looping is the best way to handle this situation. It will put the program into a busyloop which keeps asking the OS to write data and the OS refusing immediately, until the situation is resolved (which may take any amount of time). Though the program will work, the situation will now manifest as a performance problem. Waiting for the socket to be writable with I think we should instead ensure that it's easy to create an object which can be manipulated with the appropriate APIs, whether that's converting a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right.
Duplicating the file descriptor will share the blocking properties, so that's not an option. Despite the added complexity, wouldn't This won't change the behavior if the file descriptor is blocking, and our write API doesn't have a way to know how many bytes we wrote, so the API is not really prepared for non-blocking operations, therefore, we should probably act as if it was blocking. I couldn't find any better solution for this problem, but this fails if the user is interacting with some program that sets the |
Hm, yeah.
How so?
No, I think throwing is appropriate. We should most definitely not try to smooth over exceptional situations with DWIM hacks which guess what the user wants.
I think the situation where standard streams are non-blocking falls well beyond what a standard library should reasonably support. It is in the same area as the standard streams being completed closed (so the first file opened gets fd 0 etc.). |
I think I misinterpreted select. I thought that listening to the same descriptor it wont notify both. Well, could be an option. How would you suggest to use it?
The problem is, when the user doesn't want it to throw? Should they try/catch? EAGAIN is meant to be run again, so, on those situations the user has to try/catch, which is not the workflow to be used with exceptions.
I think it should. After-all its a shared file descriptor, we should be ready for side effects applied by other programs, unrelated to ours. |
Hypothetically, on an EAGAIN, check if the fd is non-blocking. If it is, call But, I don't think we should do this.
Then they should use the
No, and we should not take that stance with anything. DWIM in a general-purpose library, much more in a language's standard library, is a recipe in disaster. The contracts of our APIs should be simple and as short as possible; if you start adding circumstances which our functions "helpfully" try to handle, and then you document all the situations and the possible outcomes of such multiplied with all interactions of each other, the result is a mess that no one can sensibly design a system with predictable behavior out of. Let's take this to an extreme. Consider the possibility that a root process (or even non-root with No. If so, why yes to this? Scaling it back, where do we draw the line? How exactly do you define, in unambiguous technical terms, where the line is drawn? Another way to look at this would be: what other languages' standard libraries do the same thing as what this change is proposing? For std.stdio specifically, one easy argument to make against this proposal is that, because it wraps the C |
Since the file descriptors are shared you can't control them, so, in theory, every program should handle these cases, it just happens to be an edge case, in most situations.
Yeah, but this is clearly an impractical situation. The presented solution is not. I understand your points there, but I do think that our API is not designed to defer errors to the user to handle, specifically, So if we shift from the proposed implementation and have some other alternative: Should we then, like, create an exception that has the written size so the user can catch and proceed? Otherwise, to cover these cases, we can't use the stdio API, which is a bit unfortunate. My only problem, then, is that exceptions for control flow is fundamentally wrong and slow. Therefore, I don't have another solution for it other than changing the API to be able to defer Hypothetically, would adding the size as a return value be as breaking change? ABI signature will change but this already happens across versions. |
The correct way to handle this situation is to signal an error.
OK, let's extrapolate to a less extreme situation. What if file descriptor 0 is a socket, should we detect that and switch to using
If we were to reach the conclusion that
We also can't use the stdio API to work with sockets, even though on POSIX they are also file descriptors. I just don't think this is something that
If we were to reach the conclusion that
On one hand, adding a return value would make it more symmetric with |
Philosophy aside, here are some blockers which I think would make sense to address before we decide that we should support this situation in
|
@ljmf00 what are your plans with this PR? |
Co-authored-by: Paul Backus <[email protected]>
@rikkimax You're responsible for this one now. |
We'll probably need a way to disable this behaviour and possibly a max retry count, but otherwise this guarantees synchronous behaviors. Please add this configurability and once CI is green can be merged. |
I strongly recommend against inventing ad-hoc workarounds in our standard I/O module which cause us to spin the CPU, with no evidence to support that this what the rest of the world (that D programs live in and interact with) expects us to do or does this as well. |
A reasonable argument, to not make it on by default. Opt-in only. |
When dealing with shared file descriptors like
stdout
,stderr
andstdin
, we should be ready to handle, whatever is their state. In the case of set it as a non-blocking file descriptor, we should retry on EAGAIN, as per POSIX documentation. We should also check for the stream error before assuming it's an error.Since our API assume everything is written (no non-blocking behaviour), we should retry and not just write whatever is possible.