librdmacm: extend rsocket for Redis, iperf3, memcached and more Linux APIsRsocket upstream#1702
librdmacm: extend rsocket for Redis, iperf3, memcached and more Linux APIsRsocket upstream#1702BatshevaBlack wants to merge 14 commits intolinux-rdma:masterfrom
Conversation
227fd60 to
959cb7e
Compare
This commit introduces epoll_create functionality to support a centralized thread for managing all epoll instances. The epoll_create call creates an epoll_inst struct and two epoll file descriptors: a "regular epfd" for handling real file descriptors and another epfd that includes the "regular epfd" added using epoll_ctl. The latter epfd is returned from the epoll_create function. Additionally, the new epoll instance is registered with a global thread that processes all instances in a round-robin fashion, efficiently handling events for both regular and rsocket file descriptors. The global thread manages polling in two steps for each epoll instance. First, it iterates through the list of rsocket fds in the epoll struct, polling each one to check for events. Second, it calls epoll_wait on the "regular epfd" to gather events from the real file descriptors. The thread keeps the events in the struct, and proceeds to the next epoll instance. Signed-off-by: Batsheva Black <bblack@nvidia.com>
This commit implements epoll_ctl with tailored handling for real and rsocket file descriptors. For regular file descriptors, epoll_ctl directly operates on the "regular epfd". For rsocket file descriptors, they are added to a dedicated list maintained in the epoll instance struct. This list ensures that the global thread can handle these file descriptors during its polling cycle. epoll_ctl triggers the thread to reprocess the epoll instance to update the ready list. Reflecting any events on the newly added file descriptors. Signed-off-by: Batsheva Black <bblack@nvidia.com>
bee544e to
7e7f2b6
Compare
This commit implements epoll_wait to retrieve events processed by the centralized thread for an epoll instance. When epoll_wait is called, it copies the events collected by the global thread from the ready list in the epoll instance to the user-provided events buffer. If no events are available in the `revents` field, the function triggers the thread to recheck for events. Epoll_wait returns the total number of ready events. Signed-off-by: Batsheva Black <bblack@nvidia.com>
in case of timeout which causes poll to return, clear all signals that arrived by calling rs_poll_exit. Signed-off-by: Batsheva Black <bblack@nvidia.com>
0db3217 to
fbb8d04
Compare
1b6eb75 to
f774322
Compare
e3a3c6d to
840c6d1
Compare
|
poll support is non-trivial. Is there a reason why epoll support was implemented over rpoll? |
shefty
left a comment
There was a problem hiding this comment.
See comments. I didn't review the epoll code in detail, as I would have expected the implementation to leverage to rpoll() path.
Without the user_fds mapping, select() could set bits for internal fds instead of the user fds the application passed in, so the wrong (or no) sockets were reported as ready. Keep the list of the fds that are sent to poll in order to know which fd belongs to each rfd when returning the revents to the fds list. Signed-off-by: Batsheva Black <bblack@nvidia.com>
The accept4 implementation extends accept to support the additional atomic flag-setting functionality provided by accept4. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Add preload interception for fcntl64 so rsocket file descriptors support the same flag semantics as the glibc fcntl64 API. Signed-off-by: Batsheva Black <bblack@nvidia.com>
getsockopt: TCP_INFO, TCP_CONGESTION, SO_BROADCAST & IP_TOS. setsockopt: IP_TOS & TCP_CONGESTION. Signed-off-by: Batsheva Black <bblack@nvidia.com>
rfcntl keeps the files flags all in the fd_flags argument. Adding the new field fs_flags to the rs struct allows the fcntl function to keep the file status flags separately from the file descriptor flags. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Add preload interception for sendfile64 so applications using the 64-bit offset sendfile64 API work correctly with rsocket file descriptors. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Add preload interception for dup so that duplicating an rsocket file descriptor produces another rsocket fd that refers to the same connection. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Previously we only added it when rconnect() returned EINPROGRESS. Now also add when connect succeeds so the progress thread can drive state and handle disconnects. Signed-off-by: Batsheva Black <bblack@nvidia.com>
The changes to rpoll to use a signaling fd to wake up blocked threads, combined with suspending polling while rsockets states may be changing _should_ prevent any threads from blocking indefinitely in rpoll() when a desired state change occurs. We periodically wake up any polling thread, so that it can recheck its rsocket states. The sleeping interval was set to an arbitrary value of 5 seconds, this interval is too long for apps that request a connection and are dependent on the thread waking up, so it's changed now to 0.5 seconds, but can be overridden using config files. Signed-off-by: Batsheva Black <bblack@nvidia.com>
Updated type checks to identify socket types even when additional flags are present in the type field. Changed the comparison to use bitwise AND for more accurate detection. Signed-off-by: Batsheva Black <bblack@nvidia.com>
840c6d1 to
4d794fa
Compare
@shefty |
|
If you can separate the epoll changes into another PR, that should help merge the other changes quicker. This epoll implementation doesn't behave the same as poll. For example, if you look at rpoll(), loops are required for proper event handling. Similar loops are missing from repoll_wait(). I'll walk through what rpoll() is doing below. Here are links to an epoll implementation over poll (called ofi_pollfds). The license is suitable for rdma-core. Note that the implementation targets windows, mac os, and freebsd, so there are extra abstractions that linux only support may not need. https://github.com/ofiwg/libfabric/blob/main/include/ofi_epoll.h#L276 I can walk through what this code is doing if needed. The abstraction itself is NOT full epoll support. It does not handle attaching an epoll fd to another epoll fd, for example. Trying to support that would add significant complexity. The rpoll() implementation has 2 while loops. The first loop busy waits for a small period of time before moving to a blocking call. Technically, the use of a loop here isn't needed, as it's an optimization. However, the loop drives progress across the rsockets and checks for events, which is needed. We need to drive progress because an rocket may already have data to read, sitting in the local buffer. If that's the case, the rsocket must be marked for POLLOUT. There's no guarantee additional completions will be written to the rsocket's CQ or events added to its completion channel. The call to rs_poll_check() from rpoll() drives progress on the rsocket, so events aren't lost. rs_poll_check() checks all fd's, including non-rsocket fd's. This ensures that if we find an event on an rsocket, we don't miss also reporting events on non-rsocket fd's. The second loop goes through the motions are arming the rsocket's CQ prior to blocking in poll(). Note that we call rs_poll_check() again to avoid dropping completions. We have to recheck the CQ for completions after it's been armed to avoid leaving entries on the CQ. If we have any completions to report, we return them, so we don't end up blocked in poll(). Once we're in poll(), if an event occurs, we need to process it. The occurrence of an event doesn't mean that it's an event that should be reported to a user. For example, the user could be waiting for POLLOUT, but we instead receive data. Because the user isn't checking POLLIN, we queue the data, then continue looping until POLLOUT is satisfied. Looking at repoll_wait() and epoll_wait(), I don't see the above functionality. There's no attempt to drive progress. I don't see where CQ completion channels are armed or where the CQs are being polled. I don't see how multiple threads calling repoll_wait is handled (e.g. see rs_poll_enter / rs_poll_exit). There's a lot of complexity being handled by rpoll() which isn't there for repoll, which makes me question the implementation. That's why layering epoll over rpoll may be a better option, so we don't have to duplicate that complexity. (Yes, layering epoll over poll isn't exactly trivial either...) Any epoll implementation we have will likely be limited relative to true epoll support. Those limits should be documented. |
|
If I'm understand the code, epoll spawns a thread that spins in epoll_thread(). Is that correct? repoll_wait() also never blocks, effectively ignoring the timeout, which could result in spinning the application thread. If the above is correct, then I think the changes needed for epoll are to block the thread calling epoll_wait and avoid the epoll thread from spinning. If we implement epoll using a thread, that thread should probably be idle until epoll_wait is called (to avoid contention driving progress on the same rsocket from 2 threads). The epoll thread should reuse more of the rpoll calls, rather than duplicating a large function like rs_poll_rs() with epoll_rs(). Repacing epoll_rs() with rs_poll_rs() is likely trivial. But I suspect we'll want to leverage additional calls to avoid spinning. It's okay if the rpoll flow needs to change some to make this happen. |
Summary
Extend the rsocket implementation in librdmacm so that applications such as Redis, iperf3, and memcached can use rsocket transparently via LD_PRELOAD (librspreload), and so rsocket aligns with more standard Linux socket and I/O behavior.
Motivation
The rsocket library did not fully support several POSIX/Linux interfaces (epoll, select, accept4, sendfile, fcntl64, and various socket options). Applications that rely on these either failed or fell back to TCP. This change extends the rsocket implementation to implement or fix those interfaces, so the preload can intercept them and route traffic over RDMA.
Changes