Conversation
The wrapper can be used for asynchronous Block IO and Network IO in Linux as an alternative to the standard POSIX system calls. Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
|
CC @envoyproxy/dependency-shepherds: Your approval is needed for changes made to |
|
This is a smaller part of #19082. /assign @antoniovicente |
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
This reverts commit 60d32a7. Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
|
@wrowe @antoniovicente it looks like this is ready for another round of review? |
antoniovicente
left a comment
There was a problem hiding this comment.
Interesting stuff. I have some questions about the ultimate use of this new sophisticated mechanism.
| */ | ||
| virtual void unregisterEventfd() PURE; | ||
|
|
||
| virtual bool isEventfdRegistered() const PURE; |
| enum class IoUringResult { Ok, Busy, Failed }; | ||
|
|
||
| /** | ||
| * Abstract wrapper around `io_uring`. |
There was a problem hiding this comment.
What is the threading model assumed by IoUring? Which methods can be called from any thread and which can only be called from the IoRing thread?
There was a problem hiding this comment.
io_uring docs don't state any requirement regarding thread-safety though it probably should. In case of Envoy every IoUring is coupled with one libevent loop (it doesn't run its own) which is rarely posted from threads other than the one running it. So, I assumed that making IoUring thread local and calling its methods only from the thread it lives in simplifies everything. But perhaps it makes more sense to extend Dispatcher to own IoUrings.
There was a problem hiding this comment.
It may make sense for the dispatcher to own the io_urings eventually instead of providing a getOrCreate factory method. We can get to that as development continues.
There was a problem hiding this comment.
From examples I believe that uring is thread safe. However conceptually there should only one thread emptying the queue. But adding more I/O operations (preparing) into the ring can be done from any thread.
| * and IoUringResult::Ok otherwise. | ||
| */ | ||
| virtual IoUringResult prepareAccept(os_fd_t fd, struct sockaddr* remote_addr, | ||
| socklen_t* remote_addr_len, void* user_data) PURE; |
There was a problem hiding this comment.
Use of void* in the interface seems a bit too low level. What is the ultimate purpose of this interface? Implement an alternate dispatcher.h implementation? I wonder if use of an user_data interface would improve the usability of the resulting class as it would allow for the removal of the CompletionCb argument to forEveryCompletion
There was a problem hiding this comment.
Particularly this interface hides the io_uring syscalls and makes unit testing of the new iohandle (not in this PR) easier a bit. Originally I used a custom type containing the network iohandle instead of *void, but in case of Block IO a different type might be needed. Probably this *void in the interface could be avoided with a template.
I haven't thought of the possibility to implement an alternative Dispatcher, because its interface implies the file readiness model (i.e. Dispatcher::createFileEvent()), whereas io_uring is all about completeness of system operations. CompletionCb is an analog of FileReadyCb, but the former isn't called for a known file descriptor. A system operation and its context (including the arguments) are parts of a completeness event.
There was a problem hiding this comment.
I think it's ok to use void* for now, we may revisit this later as our understanding of this module improves.
I'ld like to better understand how io_uring and dispatcher should interact. I could see some threads being exclusively one or the other. I'm not sure about how they would coexist in a thread while avoiding starvation.
There was a problem hiding this comment.
I see no problem of multiple io_urings coexisting in one dispatcher: all updates in their completion queues are reflected in their respective event notification file descriptors (man eventfd). So, different callback instances are called for different eventfds, only one registered callback per io_uring. The dispatcher is supposed to call these callbacks in the same manner as in the case of connection callbacks.
I have rewritten the tests to run the callbacks through Dispatcher::run().
Though I fail to come up with a simple enough API for Dispatcher to accommodate multiple io_urings if go this way. If we assume only one io_uring per Dispatcher then extending Dispatcher with something like void Dispatcher::registerCompletionEvent(CompletionCb cb) and IoUring& Dispatcher::getIoUring() would be enough, IoUring::registerEventfd() could be dropped, since all manipulations with eventfd would be incapsulated in Dispatcher.
For the case of multiple io_urings Dispatcher::getIoUring() and Dispatcher::registerCompletionEvent() should distinguish io_urings somehow. Maybe something like the following could work:
using IoUringId = uint32_t;
class DispatcherImpl {
public:
...
IoUringId addIoUring(const IoUringFactory& factory);
void registerCompletionEvent(IoUringId id, CompletionCb cb);
IoUring& getIoUring(IoUringId id);
...
private:
std::vector<IoUring> io_urings_;
};
source/common/io/io_uring.h
Outdated
| * `io_uring_enter()` system call. | ||
| * Returns IoUringResult::Ok in case of success and may return | ||
| * IoUringResult::Busy if we over commit the number of requests. In the latter | ||
| * case the application should wait for some completions and try again. |
There was a problem hiding this comment.
How should the application do that?
| "//source/server:server_lib", | ||
| "//source/server:listener_hooks_lib", | ||
| ] + envoy_all_core_extensions() + | ||
| # TODO(rojkov): drop io_uring dependency when it's fully integrated. |
There was a problem hiding this comment.
I'm not sure I follow this TODO.
There was a problem hiding this comment.
The problem is this wrapper doesn't get linked in Envoy's binary. It's not an extension and is not used by the new iohandle currently. If dropped it results in
Invalid build graph structure. deps(//source/...) != deps(//source/exe:envoy_main_common_with_core_extensions_lib) union deps(//source/extensions/...)
in ci/run_envoy_docker.sh 'ci/do_ci.sh deps'. The original PR didn't need this dependency added.
test/common/io/io_uring_impl_test.cc
Outdated
| } | ||
| } | ||
|
|
||
| static thread_local const IoUringFactoryImpl factory_; |
There was a problem hiding this comment.
Does IoUringFactoryImpl need to be thread local? It seems to me that it doesn't need to be given the thread local behavior of its getOrCreate accessor.
There was a problem hiding this comment.
Thanks! This was a mistake. Fixed.
test/common/io/io_uring_impl_test.cc
Outdated
| res = uring.submit(); | ||
| EXPECT_EQ(res, IoUringResult::Ok); | ||
|
|
||
| int ret = epoll_wait(epoll_fd, events, 10, -1); |
There was a problem hiding this comment.
Use of epoll_wait to decide when to drive the IoUring seems like generic code that most uring users will needed. Should this use of epoll_wait be moved to IoUringImpl ?
There was a problem hiding this comment.
Users don't need epoll_wait if they rely on libevent for event handling (via Dispatcher).
There was a problem hiding this comment.
If the intended use is together with the dispatcher, perhaps this test should use the dispatcher for event handling and thus serve as an example of how to use this class.
There was a problem hiding this comment.
Good point. The test doesn't include sys/epoll.h now, event handlers are run by dispatcher instances.
test/common/io/io_uring_impl_test.cc
Outdated
| @@ -0,0 +1,239 @@ | |||
| #include <sys/epoll.h> | |||
There was a problem hiding this comment.
Please look at the test failures in CI:
[2021-12-28 13:52:10.547][15][critical][assert] [source/common/io/io_uring_impl.cc:34] assert failure: ret == 0. Details: unable to initialize io_uring: Operation not permitted
There was a problem hiding this comment.
This is complicated.
The test passes perfectly when run with Azure's managed workers: https://dev.azure.com/dmitryrojkov/meta-helloworld/_build/results?buildId=13&view=logs&j=8c169225-0ae8-53bd-947f-07cb81846cb5&t=6b0ace90-28b2-51d9-2951-b1fd35e67dec
It fails if RBE is active. I suspect that io_uring syscalls are not allow-listed on the workers used as RBEs. Do you happen to know how to reach out to people running those builders?
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
|
@antoniovicente if this isn't waiting can you ping on what happens next or do a review pass? :-) |
|
Blocked on tests on RBE. Could use some help from @yanavlasov /wait |
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
| enum class IoUringResult { Ok, Busy, Failed }; | ||
|
|
||
| /** | ||
| * Abstract wrapper around `io_uring`. |
There was a problem hiding this comment.
From examples I believe that uring is thread safe. However conceptually there should only one thread emptying the queue. But adding more I/O operations (preparing) into the ring can be done from any thread.
|
I tried to use syscalls directly instead of liburing and very soon realized that what I come up with is a copy-paste of the lib basically. And rather complex one which we definitely don't want to maintain. So, I'd propose to proceed with liburing. |
|
Sounds like a good faith effort to explore alternatives and this is the least bad option, at least if we want to target performance, so no objections. |
|
Last call, it sounds like concerns of @htuch , @moderation, @yanavlasov and @antoniovicente are all addressed? If I don't hear otherwise, I'll stamp the deps and merge this Friday morning. |
|
This needs an API stamp from someone. |
| @@ -0,0 +1,35 @@ | |||
| syntax = "proto3"; | |||
There was a problem hiding this comment.
Why isn't this in the bootstrap extension hierarchy? Looking below, I think this is what it is. This hierarchy is introduced in https://github.com/envoyproxy/envoy/pull/19467/files, it seems out bootstrap extensions have been adhoc scattered so far :(
source/common/io/io_uring_impl.cc
Outdated
| IoUring& IoUringFactoryImpl::getOrCreate() const { return tls_->getTyped<IoUringImpl>(); } | ||
|
|
||
| Server::BootstrapExtensionPtr | ||
| IoUringFactoryImpl::createBootstrapExtension(const Protobuf::Message& config, |
There was a problem hiding this comment.
This is a bit strange, this is both in core but also an extension?
There was a problem hiding this comment.
Uh, right. Originally it was not an extension. It was turned into an extension in a later commit to avoid static thread_local and to use the TLS API instead as suggested.
Now I'm confused. If I keep the code to be an extension io_uring won't be usable in core. Otherwise the TLS API seems to be unavailable. I'd rather drop TLS and bootstrap extension as it was originally.
There was a problem hiding this comment.
Now I'm confused, so you're not the only one :) Why can't we use TLS in extensions? Why does this need to be used directly by core?
There was a problem hiding this comment.
Why does this need to be used directly by core?
This comment is the main reason.
Why can't we use TLS in extensions?
That was about (un)availability of TLS in core, not extensions. This concern is moot probably. Still I'm not sure why using TLS is more beneficial than static thread_local in this case, because I bumped into other problems with integration testing.
There was a problem hiding this comment.
OK, then let's make it core and eliminate the use of the extension stuff. We can use TLS from non-extensions, it's used all over core, e.g. see
.I think one of the nice things about using Envoy TLS is you get TLS lifecycle management, which should in theory actually work better for testing etc.
There was a problem hiding this comment.
Thanks!
So far my problem with Envoy TLS in testing has been that the thread controlling integration tests doesn't initialize its TLS slot hence lacks io_uring which is needed to open a client connection. I'll experiment with enabling TLS in the testing thread.
The bootstrap extension is dropped now.
|
/wait |
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
|
@htuch @yanavlasov PTAL |
htuch
left a comment
There was a problem hiding this comment.
Other than nit on extension, no longer any objection on API / extension side.
Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
|
@wrowe this has been waiting on your dependency stamp for 8 days? |
|
/lgtm deps |
|
@yanavlasov - this is blocked on your review, positive confirmation obtained from moderation, htuch, no further concerns from antoniovicente. @rojkov - do you want to take a final pass through yanavlasov's feedback and mark what you believe has been addressed as 'resolved'? |
|
Yep, I've just marked the addressed feedback from Yan as resolved. |
The wrapper can be used for asynchronous Block IO and Network IO in Linux as an alternative to the standard POSIX system calls. Commit Message: io: add io_uring wrapper Additional Description: The wrapper can be used for asynchronous Block IO and Network IO in Linux as an alternative to the standard POSIX system calls. Risk Level: low, new code Testing: unit tests added Docs Changes: N/A Release Notes: N/A, no changes visible to users Platform Specific Features: io_uring wrapper for Linux Contributes to envoyproxy#17922 Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
Commit Message: io: add io_uring wrapper
Additional Description:
The wrapper can be used for asynchronous Block IO and Network IO in Linux
as an alternative to the standard POSIX system calls.
Risk Level: low, new code
Testing: unit tests added
Docs Changes: N/A
Release Notes: N/A, no changes visible to users
Platform Specific Features: io_uring wrapper for Linux
Contributes to #17922