io_uring_prep_recv(3)

✨ io_uring_prep_recv ✨

okay bestie, gather round, we're doing liburing today πŸ’– this one's io_uring_prep_recv β€” the prep helper from liburing that gets your async receive requests all cute and queued up.


🌸 SYNOPSIS 🌸

#include <liburing.h>

void io_uring_prep_recv(struct io_uring_sqe *sqe,
                        int sockfd,
                        void *buf,
                        size_t len,
                        int flags);

void io_uring_prep_recv_multishot(struct io_uring_sqe *sqe,
                                  int sockfd,
                                  void *buf,
                                  size_t len,
                                  int flags);

πŸ’… DESCRIPTION πŸ’…

So io_uring_prep_recv(3) literally prepares a recv request. That's it, that's the job. It sets up the submission queue entry sqe so it'll use the file descriptor sockfd to start receiving data into the destination buffer buf (size len, obviously) with the modifier flags flags. slay.

This is preparing an async recv(2) request β€” so if you want the deets on what all those arguments actually mean, go read that man page, because this helper is just the glow-up wrapper around it. ✨

πŸ¦‹ the multishot version πŸ¦‹

io_uring_prep_recv_multishot is the main character version: your app issues one single receive request and it keeps posting a CQE whenever data shows up. Iconic. But there are rules, no cap:

  • the length must be 0 (0, zero, nada)
  • the IOSQE_BUFFER_SELECT flag must be set
  • the MSG_WAITALL flag must not be set

Because of that, every single CQE grabs a buffer out of a provided buffer pool for the receive. And listen β€” you should check the flags on each CQE, no matter what its result is. If a posted CQE doesn't have the IORING_CQE_F_MORE flag set, that means the multishot receive is done, and if you still wanna receive data from that socket you have to issue a brand new request. Fr fr. Multishot variants are available since kernel 6.0. πŸ‘‘

πŸ”₯ internal modifier flags πŸ”₯

After you call this function, io_uring might set extra internal modifier flags in the SQE ioprio field. These are the supported ones:

  • IORING_RECVSEND_POLL_FIRST β€” if this is set, io_uring will assume the socket is currently empty and that trying to receive data will fail. In that case, io_uring arms internal poll and triggers a receive of the data once the socket actually has data to be read. The initial receive attempt is kinda wasteful when you already expect the socket to be empty, so setting this flag skips that first attempt and goes straight to arming poll. And if poll does say data is ready to be received, the operation proceeds. You can use this with the CQE IORING_CQE_F_SOCK_NONEMPTY flag, which io_uring sets on CQEs after a recv(2) or recvmsg(2) operation. If that's set, the socket still had data to be read after the operation finished. Both these flags are available since 5.19. πŸ’–
  • IORING_RECVSEND_BUNDLE β€” if this is set and provided buffers are used with IOSQE_BUFFER_SELECT, the receive operation will try to fill multiple buffers instead of just picking one single buffer to fill. To receive multiple buffers in one receive, the buffer group ID set in the SQE must be of the ring provided type. If it's set, the CQE res field tells you the total number of bytes received, and the buffer ID returned in the CQE flags field indicates the first buffer in the receive operation. Your app has to process the indicated initial buffer ID and keep going until all res bytes have been seen, so you know which one is the last buffer in the receive. The buffers consumed are contiguous from the initial buffer, in the order they appear in the buffer ring. Now β€” the CQE struct does not contain the position of the buffer in the buffer ring, so to identify the buffers contained in the bundle, it's advised to maintain the cached head index per buffer ring. That uint16_t index is the position of the next buffer to be consumed within the ring, and when a receive operation completes, the cached head index should be incremented accordingly. Receiving in bundles can improve performance when more than one chunk of data is available to receive, because it eliminates redundant round trips through the networking stack. 🌈 Receive bundles can be used by both single shot and multishot receive operations. Note that internally, bundles rely on the networking stack passing back how much data is left in the socket after the initial receive β€” which means the initial receive may contain fewer buffers than what's available, with the followup receive(s) containing more buffers. Available since 6.10. 🫢

πŸŽ€ RETURN VALUE πŸŽ€

None. That's it. Nothing. Zip. Nada. void energy. πŸ’€


😭 ERRORS 😭

The CQE res field will contain the result of the operation. Go check the related man page for the possible values, bestie.

not me explaining errno again β€” where synchronous system calls return -1 on failure and set errno to the actual error value, io_uring never uses errno. Instead it returns the negated errno directly in the CQE res field. Write that down. πŸ“

πŸ‘‘ SEE ALSO πŸ‘‘


that's the whole page babes, you ate it πŸ“ go forth and receive your data asynchronously, the vibes are immaculate βœ¨πŸ’œ

signing off from liburing-2.2, dated March 12, 2022 β€” stay slaying, bestie πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from