Skip to content

Commit a6fd0c4

Browse files
committed
add zcrx docs
1 parent aac398c commit a6fd0c4

1 file changed

Lines changed: 112 additions & 4 deletions

File tree

docs/guide.md

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,6 @@ condy::Coro<void> session(int session_fd) {
643643
}
644644
```
645645
646-
> [!NOTE]
647-
> io_uring also supports Zero Copy Rx. Condy will support this feature in the future.
648-
649646
### File Registration
650647
651648
io_uring allows you to register files with the kernel. Normally, each asynchronous operation increments/decrements the file's reference count, but registering files with the kernel can skip this process and improve performance.
@@ -832,7 +829,7 @@ condy::Coro<int> co_main() {
832829
break;
833830
}
834831
835-
// Error handling (excluding buffer pool full)
832+
// Error handling (excluding buffer pool empty)
836833
if (res < 0 && res != -ENOBUFS) {
837834
ch.push_close();
838835
co_return 1;
@@ -845,6 +842,117 @@ condy::Coro<int> co_main() {
845842
int main() { return condy::sync_wait(co_main()); }
846843
```
847844

845+
### Zero Copy Rx
846+
847+
Similar to Zero Copy Tx, io_uring also supports receiving data directly into user-space memory, allowing the NIC to write incoming data without kernel-to-user copies. Condy provides the `condy::ZeroCopyRxBufferPool` type to encapsulate this feature.
848+
849+
`condy::ZeroCopyRxBufferPool` manages a set of pre-registered buffers. When used with `condy::async_recv_multishot()`, each received packet is placed into a buffer from the pool, and the coroutine receives a `condy::ZeroCopyRxBuffer` — an RAII type that automatically returns the buffer to the pool when it goes out of scope.
850+
851+
Constructing a `condy::ZeroCopyRxBufferPool` requires:
852+
- A network interface index (`if_idx`) and receive queue index (`if_rxq`).
853+
- The number of receive queue entries (`rq_entries`).
854+
- A `condy::ZeroCopyRxArea` describing the memory region, or a `condy::ZeroCopyRxDMABufArea` for DMA-BUF scenarios.
855+
856+
> [!NOTE]
857+
> See https://docs.kernel.org/networking/iou-zcrx.html for more information.
858+
859+
The following example demonstrates a echo server using Zero Copy Rx. The structure is similar to the Provided Buffers example above — `condy::ZeroCopyRxBufferPool` and `condy::ProvidedBufferPool` share the same usage pattern with `condy::async_recv_multishot()`.
860+
861+
```cpp
862+
#include <arpa/inet.h>
863+
#include <condy.hpp>
864+
865+
// Background coroutine: handle buffers from the Channel
866+
condy::Coro<void>
867+
handle_buffers(condy::Channel<std::pair<int, condy::ZeroCopyRxBuffer>> &ch,
868+
int session_fd) {
869+
while (true) {
870+
auto [r, data] = co_await ch.pop();
871+
if (r == -EPIPE) {
872+
break;
873+
}
874+
assert(r == 0);
875+
auto &[n, buffer] = data;
876+
co_await condy::async_write(session_fd,
877+
condy::buffer(buffer.data(), n), 0);
878+
}
879+
}
880+
881+
// Main coroutine: receive data and use zero-copy buffer pool
882+
condy::Coro<int> co_main() {
883+
sockaddr_in server_addr{};
884+
server_addr.sin_family = AF_INET;
885+
server_addr.sin_port = htons(8080);
886+
inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
887+
888+
int server_fd = co_await condy::async_socket(AF_INET, SOCK_STREAM, 0, 0);
889+
if (server_fd < 0) {
890+
co_return 1;
891+
}
892+
893+
int r =
894+
::bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
895+
if (r < 0) {
896+
co_await condy::async_close(server_fd);
897+
co_return 1;
898+
}
899+
900+
r = ::listen(server_fd, 10);
901+
if (r < 0) {
902+
co_await condy::async_close(server_fd);
903+
co_return 1;
904+
}
905+
906+
// Accept one client connection
907+
sockaddr_in client_addr;
908+
socklen_t client_addr_len = sizeof(client_addr);
909+
int session_fd = co_await condy::async_accept(
910+
server_fd, (struct sockaddr *)&client_addr, &client_addr_len, 0);
911+
if (session_fd < 0) {
912+
co_await condy::async_close(server_fd);
913+
co_return 1;
914+
}
915+
916+
// Create zero-copy receive buffer pool.
917+
// For demonstration, we use the device-less constructor so the example
918+
// runs without requiring a specific NIC. In production, provide if_idx
919+
// and if_rxq matching your hardware.
920+
condy::ZeroCopyRxBufferPool pool(condy::current_runtime(), 256,
921+
condy::ZeroCopyRxArea{.size = 8ul * 4096});
922+
condy::Channel<std::pair<int, condy::ZeroCopyRxBuffer>> ch(16);
923+
924+
// Spawn background task to handle writes
925+
condy::co_spawn(handle_buffers(ch, session_fd)).detach();
926+
927+
while (true) {
928+
// Multishot receive with zero-copy: callback handles normal results;
929+
// coroutine resumes on final error or termination
930+
auto [res, buf] = co_await condy::async_recv_multishot(
931+
session_fd, pool, 0, condy::will_push(ch));
932+
933+
if (res == 0) {
934+
// Termination signal
935+
ch.push_close();
936+
break;
937+
}
938+
939+
// Error handling (excluding buffer pool empty)
940+
if (res < 0 && res != -ENOMEM) {
941+
ch.push_close();
942+
co_return 1;
943+
}
944+
}
945+
946+
co_return 0;
947+
}
948+
949+
int main() {
950+
condy::Runtime runtime(
951+
condy::RuntimeOptions().enable_cqe32().enable_defer_taskrun());
952+
return condy::sync_wait(runtime, co_main());
953+
}
954+
```
955+
848956
### Initialization Options
849957
850958
As mentioned earlier, the `condy::Runtime` type can accept a `condy::RuntimeOptions` object, which contains a series of configurable initialization parameters for `condy::Runtime`. These parameters can be set using chained calls as shown below:

0 commit comments

Comments
 (0)