syscall: refactor address APIs for deeper errno latching#3897
syscall: refactor address APIs for deeper errno latching#3897ggreenway merged 7 commits intoenvoyproxy:masterfrom
Conversation
The errno set by a syscall can be overwritten by code (e.g. logging) as it propagates up through the call stack. This commit refactors the bind and connect methods in the address API to allow for returning the errno from deeper down the call stack i.e. as soon as a syscall is performed. Signed-off-by: Venil Noronha <veniln@vmware.com>
alyssawilk
left a comment
There was a problem hiding this comment.
Cool - safer and safer :-)
test/test_common/network_utility.cc
Outdated
| const int fd = addr->socket(type); | ||
| int err; | ||
| if (fd < 0) { | ||
| err = errno; |
There was a problem hiding this comment.
I think it'd be nice to have socket return a tuple for consistency.
This commit ensures that address implementations return the errno along with the file descriptor in socket method calls. Signed-off-by: Venil Noronha <veniln@vmware.com>
include/envoy/network/address.h
Outdated
| * successful, errno shouldn't be used. | ||
| */ | ||
| virtual int bind(int fd) const PURE; | ||
| virtual std::tuple<int, int> bind(int fd) const PURE; |
There was a problem hiding this comment.
would absl::optional be a better return type? (ditto for connect)
There was a problem hiding this comment.
Would it be better to have a custom type with rc (or fd) and the errno instead? Or, maybe, a generic for the first value?
There was a problem hiding this comment.
I would prefer a struct for socket rather than a tuple, it is clearer which field is fd or errno.
There was a problem hiding this comment.
+1 for a struct instead of tuple, for all of these.
There was a problem hiding this comment.
Single struct or multiple? As, the returned values could be rc or fd depending on the call.
There was a problem hiding this comment.
I think a single struct is fine, with members rc and errno. It's always up to the caller to know what the function is returning and how to interpret it.
This commit adds a struct named Result which holds the rc and errno values resulting from a syscall. Also, the address API is refactored to use the new Result struct instead of std::tuple<int, int>. Signed-off-by: Venil Noronha <veniln@vmware.com>
include/envoy/network/address.h
Outdated
| /** | ||
| * The return code from the system call. | ||
| */ | ||
| int rc; |
There was a problem hiding this comment.
Struct/Class member variables have a _ postfix (e.g., int foo_;).
https://github.com/envoyproxy/envoy/blob/master/STYLE.md
mattklein123
left a comment
There was a problem hiding this comment.
Thanks for switching this over to a struct. I prefer this also.
include/envoy/network/address.h
Outdated
| /** | ||
| * Result holds the rc and errno values resulting from a system call. | ||
| */ | ||
| struct Result { |
There was a problem hiding this comment.
Q: Is this really address specific? Should we potentially put this in https://github.com/envoyproxy/envoy/blob/master/include/envoy/api/os_sys_calls.h as SyscalResult or something like that? I don't feel that strongly but throwing it out there.
There was a problem hiding this comment.
That makes perfect sense. I wanted to use this struct in other places (buffer, etc.) anyway.
| Result Ipv4Instance::bind(int fd) const { | ||
| return {::bind(fd, reinterpret_cast<const sockaddr*>(&ip_.ipv4_.address_), | ||
| sizeof(ip_.ipv4_.address_)), | ||
| errno}; |
There was a problem hiding this comment.
It's quite possible that C++ guarantees that the ::bind call happens first, and then errno is captured, but TBH it's not very obvious to me that is the case. I would prefer that we do the call into a local variable and then return the result and errno. Same elsewhere. Thoughts?
There was a problem hiding this comment.
You're right. The c++14 documentation states:
The order of evaluation of function arguments is unspecified.
I'll fix this.
There was a problem hiding this comment.
@venilnoronha You're referring to function arguments, but this is list-initialization, and C++14 guarantees the order:
https://timsong-cpp.github.io/cppwp/n4140/dcl.init.list#4
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions ([temp.variadic]), are evaluated in the order in which they appear.
There was a problem hiding this comment.
@lizan my bad, I just realized that we switched over to a struct. Thanks for sharing!
I think I'd still have it explicit, at least for readability.
* Fixes struct styling * Moves address/Result to os_sys_calls/SysCallResult Signed-off-by: Venil Noronha <veniln@vmware.com>
737eabf to
bb2841e
Compare
Signed-off-by: Venil Noronha <veniln@vmware.com>
| #endif | ||
|
|
||
| return fd; | ||
| return {fd, errno}; |
There was a problem hiding this comment.
In the __APPLE__ case, I think that fcntl could be resetting errno, so you need to latch it earlier.
On the other hand, there's already a RELEASE_ASSERT that an error did not occur (fd != -1). So should this function even return errno?
There was a problem hiding this comment.
@ggreenway good catch! What do you suggest? Should we still keep the function return type as Api::SysCallResult for consistency, and to provide a way for future error handling?
There was a problem hiding this comment.
I'd say leave this function as returning an int, and make sure the docs mention that this will never fail.
RELEASE_ASSERT statements in the socket implementation ensure that the routine returns only on success. This commit does the following: * Revert function return type to int * Update socket documentation * Remove redundant error handling Signed-off-by: Venil Noronha <veniln@vmware.com>
mattklein123
left a comment
There was a problem hiding this comment.
LGTM, nice. Will defer to @ggreenway for final review.
| if (source_address != nullptr) { | ||
| const int rc = source_address->bind(fd()); | ||
| if (rc < 0) { | ||
| Api::SysCallResult result = source_address->bind(fd()); |
There was a problem hiding this comment.
nit: result here and below can be const.
ggreenway
left a comment
There was a problem hiding this comment.
This is looking good. After fixing the one small comment from me, and the one from Matt, I think this is ready.
| ASSERT(fd_ != -1); | ||
|
|
||
| int rc = address->connect(fd_); | ||
| const int rc = address->connect(fd_).rc_; |
There was a problem hiding this comment.
I'd prefer to keep the SysCallResult instead of an int, so that if the assert fires in a test it's easier in a debugger to see the value of errno.
* Use const Api::SysCallResult in connection_impl.cc * Capture result value in place of rc_ for better debugging Signed-off-by: Venil Noronha <veniln@vmware.com>
|
@mattklein123 @ggreenway @alyssawilk @lizan thanks for reviewing/merging! |
Description:
The
errnoset by a syscall can be overwritten by code (e.g. logging) as it propagates up through the call stack. This PR refactors thebind,connectandsocketmethods in theaddressAPI to allow for returning theerrnofrom deeper down the call stack i.e. as soon as a syscall is performed. See #3819 for more information.Signed-off-by: Venil Noronha veniln@vmware.com
Risk Level: Low
Testing: Existing tests suffice
Docs Changes: N/A
Release Notes: N/A
/cc @alyssawilk @mattklein123