io_uring_prep_accept(3)

πŸ“¦
version 2.15 Β· 2 versions
, in alpine 3.22, alpine 3.23, alpine 3.24, arch rolling, centos-stream 10, centos-stream 9, debian trixie, fedora 43, fedora 44, nixpkgs unstable, ubuntu questing, ubuntu resolute, ubuntu stonking

✨ NAME ✨

okay so bestie, welcome to io_uring_prep_accept(3) πŸ’– this is the one that lives in the liburing Manual, and it's literally just one job: prepare an accept request. that's it. that's the whole vibe. no cap.


🌸 SYNOPSIS 🌸

first you gotta bring the right headers, periodt:

#include <sys/socket.h>
#include <liburing.h>

and then there are four functions in this friend group, not one. yes, four. we love a polycule of prototypes πŸ’…

void io_uring_prep_accept(struct io_uring_sqe *sqe,
                          int sockfd,
                          struct sockaddr *addr,
                          socklen_t *addrlen,
                          int flags);

void io_uring_prep_accept_direct(struct io_uring_sqe *sqe,
                                 int sockfd,
                                 struct sockaddr *addr,
                                 socklen_t *addrlen,
                                 int flags,
                                 unsigned int file_index);

void io_uring_prep_multishot_accept(struct io_uring_sqe *sqe,
                                    int sockfd,
                                    struct sockaddr *addr,
                                    socklen_t *addrlen,
                                    int flags);

void io_uring_prep_multishot_accept_direct(struct io_uring_sqe *sqe,
                                           int sockfd,
                                           struct sockaddr *addr,
                                           socklen_t *addrlen,
                                           int flags);

like. the family tree is tree-ing πŸ¦‹


πŸ’œ DESCRIPTION πŸ’œ

so io_uring_prep_accept(3) and its three variants prepare an accept request, basically the same energy as accept4(2). the submission queue entry sqe gets set up to use the file descriptor sockfd to start accepting a connection request described by the socket address at addr, with structure length addrlen, and using modifier flags in flags. yes the parameter is literally called flags and it holds flags. iconic honestly.

the three variants exist so you can combine the direct file table and multishot features. two great tastes etc etc.


πŸŽ€ direct descriptors, explained πŸŽ€

direct descriptors are io_uring's own private file descriptors. they dodge some of the overhead that comes with thread shared file tables, and you can use them in any io_uring request that takes a file descriptor. slay.

the two direct variants here are the ones that create those direct descriptors. once they exist, you use them by:

  • setting IOSQE_FIXED_FILE in the SQE flags member, and
  • setting the SQE fd field to the direct descriptor value instead of the regular file descriptor.

direct descriptors get managed just like registered files. same rules, same manager, same group chat. πŸ’…


🌈 the direct variants, step by step 🌈

to use an accept direct variant, your app has to first register a file table of whatever size it wants, using io_uring_register_files(3) or io_uring_register_files_sparse(3). no registration, no direct descriptors, fr fr.

once that's registered, io_uring_prep_accept_direct(3) lets you specifically pick an entry in that table through the file_index argument. and heads up bestie:

if the specified entry already contains a file, that file will first be removed from the table and closed β€” consistent with the behavior of updating an existing file with io_uring_register_files_update(3). so like. it's not shy. it will evict. 😭

for this variant, file_index can also be set to IORING_FILE_INDEX_ALLOC, and then an unused table index will be dynamically chosen and returned. likewise, io_uring_prep_multishot_accept_direct will have an unused table index dynamically chosen and returned for each connection accepted β€” one per connection, every time, no cap.

and if you're gonna employ both forms of direct selection, the specific one and the dynamic one, go read io_uring_register_file_alloc_range(3), which sets up the table so that dynamically chosen entries are made against a different range than the one targeted by specific requests. keep them in separate lanes, bestie, it's better for everyone.


🫢 kernel-version drama 🫢

important: old kernels don't check the SQE file_index field, which means applications cannot rely on a -EINVAL CQE res being returned when the kernel is too old. because older kernels may not even recognize that they're being asked to use a direct table slot. so you don't get a cute little error, you get... silence. πŸ’€

and when a direct descriptor accept request asks for a table slot to be dynamically chosen but there are no free entries, -ENFILE is returned as the CQE res. that's your sign to make the table bigger.


πŸ¦‹ multishot variants πŸ¦‹

the multishot variants let an application issue a single accept request, which will repeatedly trigger a CQE whenever a connection request comes in. one request, many completions. main character energy.

like other multishot type requests, the app should look at the CQE flags and see if IORING_CQE_F_MORE is set on completion β€” that's the indication of whether or not the accept request will generate further CQEs. no IORING_CQE_F_MORE means the party's over. πŸŽ€

and now for the cautionary part, because it's giving chaos:

for the multishot variants, setting addr and addrlen may not make a lot of sense, since the same value would be used for every accepted connection. that means the data written to addr may be overwritten by a new connection before the application has had time to process a past connection. πŸ™ˆ if the application knows that a new connection cannot come in before a previous one has been processed, it may be used as expected. otherwise? bestie, don't.

the multishot variants are available since 5.19. ⭐

and if you want the details of the accept function itself, go see the man page accept4(2). it's right there. we're not re-deriving sockets today.


⭐ RETURN VALUE ⭐

None. that's it. nothing returned. this function is a setup girlie, it doesn't hand you results, it hands you a prepared SQE. πŸ’…


πŸ”₯ ERRORS πŸ”₯

the CQE res field will contain the result of the operation. so that's where you look, always.

here's the breakdown, per variant:

and the big one, the one that trips everyone up:

where synchronous system calls will return -1 on failure and set errno to the actual error value, io_uring never uses errno. instead it generates the negated errno directly in the CQE res field. so like, -EINVAL, not errno == EINVAL. do not go looking in errno, she is not there. πŸ’€

🌈 NOTES 🌈

as with any request that passes in data in a struct, that data must remain valid until the request has been successfully submitted. it need not remain valid until completion. once a request has been submitted, the in-kernel state is stable. that's the good stuff.

very early kernels (5.4 and earlier) required state to be stable until the completion occurred. so if you're on ancient vibes, keep your struct alive longer. 😭

applications can test for this behavior by inspecting the IORING_FEAT_SUBMIT_STABLE flag passed back from io_uring_queue_init_params(3). check the flag, know the rules. iykyk.

and one more, because it's a real one:

note that the direct versions of accept do not accept SOCK_CLOEXEC, and setting that flag will result in an -EINVAL error in the CQE. so don't do it. it's not a "maybe it works" situation. it's an -EINVAL situation. πŸ™ˆ

πŸ’– SEE ALSO πŸ’–

go read your siblings, bestie, they're all part of the lore:


and that's the page!! this one came out of liburing-2.2 and it's dated March 13, 2022 β€” so if you're reading this in the future, hi, ily, go check for a newer one. πŸ«Άβœ¨πŸ’…

πŸ“¦ where this page comes from