Skip to content

Add io_uring event loop backend#813

Open
Copilot wants to merge 9 commits intomasterfrom
copilot/add-io-uring-support
Open

Add io_uring event loop backend#813
Copilot wants to merge 9 commits intomasterfrom
copilot/add-io-uring-support

Conversation

Copy link
Contributor

Copilot AI commented Mar 10, 2026

  • Create event/io_uring.c - io_uring backend implementation using liburing poll mode (IORING_OP_POLL_ADD)
  • Modify event/iowatcher.h - Add EVENT_IO_URING to backend selection logic
  • Modify event/hloop.c - Add EVENT_IO_URING case to hio_engine() function
  • Modify event/hloop.h - Add EVENT_IO_URING to the engine name comment
  • Modify CMakeLists.txt - Add WITH_IO_URING option (Linux-only) and link liburing
  • Modify hconfig.h.in - Add WITH_IO_URING cmakedefine
  • Modify configure - Add --with-io_uring option and check
  • Modify config.ini - Add WITH_IO_URING=no default
  • Modify Makefile.in - Add io_uring linking when enabled
  • Update event/README.md - Add io_uring.c entry
  • Update docs/PLAN.md - Move io_uring from Plan to Done
  • Restrict WITH_IO_URING option to Linux platform only in CMakeLists.txt
  • Add io_uring_get_sqe_safe() helper to handle SQE exhaustion (submit + retry)
  • Handle iowatcher_init() failure in iowatcher_add_event() (return error)
  • Map poll errors to io->events instead of just HV_READ for proper write-only fd handling
  • Track sqe_queued flag for re-arm submissions instead of relying on nevents
  • Guard -luring with Linux check in Makefile.in
  • Add FATAL_ERROR for non-Linux WITH_IO_URING in CMakeLists.txt
  • Fix build: replace io_uring_for_each_cqe with io_uring_peek_cqe loop
  • Fix build: replace io_uring_cqe_get_data64/io_uring_sqe_set_data64 with portable equivalents
  • Use -std=gnu99 globally on Linux GCC (CMake CMAKE_C_EXTENSIONS ON, Makefile.in -std=gnu99) instead of -std=c99
Original prompt

This section details on the original issue you should resolve

<issue_title>支持linux io_uring的计划</issue_title>
<issue_description>想请问下作者大大,有没有支持linux io_uring的计划。</issue_description>

<agent_instructions>Add io_uring for libhv</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@ithewei 有的,见 [PLAN.md](https://github.com/ithewei/libhv/blob/master/docs/PLAN.md) @ithewei 但时间不定 @ithewei 可以参考这个PR:https://github.com//pull/445

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI and others added 2 commits March 10, 2026 14:05
- Create event/io_uring.c: io_uring backend using IORING_OP_POLL_ADD
- Modify event/iowatcher.h: Add EVENT_IO_URING to backend selection
- Modify event/hloop.c and hloop.h: Add io_uring engine name
- Modify CMakeLists.txt: Add WITH_IO_URING option and liburing linking
- Modify hconfig.h.in: Add WITH_IO_URING cmakedefine
- Modify configure: Add --with-io_uring option
- Modify config.ini: Add WITH_IO_URING=no default
- Modify Makefile.in: Add -luring linking when enabled
- Update event/README.md and docs/PLAN.md

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Copilot AI changed the title [WIP] Add io_uring support for libhv Add io_uring event loop backend Mar 10, 2026
@ithewei ithewei marked this pull request as ready for review March 11, 2026 03:25
Copilot AI review requested due to automatic review settings March 11, 2026 03:25
CMakeLists.txt Outdated
option(WITH_MBEDTLS "with mbedtls library" OFF)

option(WITH_KCP "compile event/kcp" OFF)
option(WITH_IO_URING "with io_uring" OFF)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot 只在linux平台上可以设置WITH_IO_URING

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 6113c31. WITH_IO_URING option and -luring linking are now wrapped in if(CMAKE_SYSTEM_NAME MATCHES "Linux"), matching the pattern used for WITH_WEPOLL on Windows.

Wrap the WITH_IO_URING cmake option and liburing linking inside
CMAKE_SYSTEM_NAME matches "Linux" checks, mirroring how WITH_WEPOLL
is restricted to Windows.

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an io_uring-based event-loop backend for Linux and integrates it into libhv’s existing iowatcher backend selection and build configuration.

Changes:

  • Introduces event/io_uring.c implementing the iowatcher interface using liburing’s poll operations.
  • Adds WITH_IO_URING build/config options and links liburing when enabled (CMake + configure/make).
  • Updates backend auto-selection on Linux and exposes "io_uring" via hio_engine().

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
hconfig.h.in Adds WITH_IO_URING to generated config header.
event/iowatcher.h Auto-selects EVENT_IO_URING on Linux when enabled.
event/io_uring.c New io_uring backend implementation.
event/hloop.h Adds "io_uring" engine string.
event/hloop.c Adds "io_uring" engine string (non-header implementation).
event/README.md Documents the new backend file in the event module tree.
docs/PLAN.md Updates plan/done list to include io_uring.
configure Adds --with-io_uring option handling.
config.ini Adds WITH_IO_URING config knob.
Makefile.in Links -luring when WITH_IO_URING=yes.
CMakeLists.txt Adds WITH_IO_URING option and links uring when enabled.
.gitignore Ignores build-* directories.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

event/io_uring.c Outdated
Comment on lines +64 to +79
struct io_uring_sqe* sqe;
if (io->events != 0) {
// Cancel the existing poll request first
sqe = io_uring_get_sqe(&ctx->ring);
if (sqe == NULL) return -1;
io_uring_prep_poll_remove(sqe, (uint64_t)fd);
io_uring_sqe_set_data64(sqe, IO_URING_CANCEL_TAG);
} else {
ctx->nfds++;
}

// Add poll for the combined events
sqe = io_uring_get_sqe(&ctx->ring);
if (sqe == NULL) return -1;
io_uring_prep_poll_add(sqe, fd, poll_mask);
io_uring_sqe_set_data64(sqe, (uint64_t)fd);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When io_uring_get_sqe returns NULL, this function returns -1, but callers (e.g., hio_add) don't check the return value and will still set io->events. This can silently leave an fd unarmed. Consider ensuring SQE availability (submit/flush + retry, or increase ring size) and/or changing the call path to only update io->events on success.

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +121
// Cancel existing poll
struct io_uring_sqe* sqe = io_uring_get_sqe(&ctx->ring);
if (sqe == NULL) return -1;
io_uring_prep_poll_remove(sqe, (uint64_t)fd);
io_uring_sqe_set_data64(sqe, IO_URING_CANCEL_TAG);

if (poll_mask == 0) {
ctx->nfds--;
} else {
// Re-add with remaining events
sqe = io_uring_get_sqe(&ctx->ring);
if (sqe == NULL) return -1;
io_uring_prep_poll_add(sqe, fd, poll_mask);
io_uring_sqe_set_data64(sqe, (uint64_t)fd);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iowatcher_del_event returns -1 when io_uring_get_sqe fails, but hio_del doesn't check this and will still clear io->events. That can desynchronize the loop's idea of what is armed vs. what's actually registered in io_uring; handle SQE exhaustion here (submit/flush + retry) and avoid returning an unhandled error.

Copilot uses AI. Check for mistakes.
event/io_uring.c Outdated
if (io == NULL) continue;

if (cqe->res < 0) {
io->revents |= HV_READ;
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On cqe->res < 0 (poll request failed), the code sets only HV_READ. If the fd is registered for write-only (e.g. connect uses HV_WRITE), the pending callback will run but hio_handle_events will ignore the event, potentially stalling the connection/error handling. Consider mapping this failure to io->revents |= (io->events ? io->events : HV_RDWR) (or at least set both READ and WRITE) so the appropriate handler runs.

Suggested change
io->revents |= HV_READ;
unsigned ev = io->events ? io->events : HV_RDWR;
io->revents |= ev;

Copilot uses AI. Check for mistakes.
Comment on lines +188 to +205
// io_uring POLL_ADD is one-shot, re-arm for the same events
unsigned remask = 0;
if (io->events & HV_READ) remask |= POLLIN;
if (io->events & HV_WRITE) remask |= POLLOUT;
if (remask) {
struct io_uring_sqe* sqe = io_uring_get_sqe(&ctx->ring);
if (sqe) {
io_uring_prep_poll_add(sqe, fd, remask);
io_uring_sqe_set_data64(sqe, (uint64_t)fd);
}
}
}

io_uring_cq_advance(&ctx->ring, ncqes);

if (nevents > 0) {
io_uring_submit(&ctx->ring);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

io_uring_submit is only called when nevents > 0, but this loop can queue re-arm SQEs even when no HV_READ/HV_WRITE bits were set (e.g., if cqe->res contains only poll bits you don't map to hv events). In that case the re-arm SQEs remain unsubmitted and the loop can block with nfds > 0 but no active polls. Track whether any SQEs were queued and submit based on that instead of nevents.

Copilot uses AI. Check for mistakes.
Comment on lines 195 to +198

ifeq ($(WITH_IO_URING), yes)
LDFLAGS += -luring
endif
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The -luring linker flag is added whenever WITH_IO_URING=yes, regardless of platform. Since liburing is Linux-specific, enabling this flag on non-Linux targets will break linking; consider additionally guarding this block with ifeq ($(OS), Linux) (or equivalent) so the option is only effective on Linux.

Suggested change
ifeq ($(WITH_IO_URING), yes)
LDFLAGS += -luring
endif
ifeq ($(OS), Linux)
ifeq ($(WITH_IO_URING), yes)
LDFLAGS += -luring
endif
endif

Copilot uses AI. Check for mistakes.
@@ -165,6 +169,10 @@ if(WITH_MBEDTLS)
set(LIBS ${LIBS} mbedtls mbedx509 mbedcrypto)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LIBS unconditionally appends uring when WITH_IO_URING is ON. Since liburing is Linux-only, enabling this option on non-Linux platforms will fail at link time; consider guarding this with a Linux check (e.g. if(CMAKE_SYSTEM_NAME STREQUAL "Linux")) and emitting a clear configuration error otherwise.

Suggested change
set(LIBS ${LIBS} mbedtls mbedx509 mbedcrypto)
if(WITH_IO_URING)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "WITH_IO_URING is only supported on Linux because liburing is Linux-only.")
endif()

Copilot uses AI. Check for mistakes.
event/io_uring.c Outdated

int iowatcher_add_event(hloop_t* loop, int fd, int events) {
if (loop->iowatcher == NULL) {
iowatcher_init(loop);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iowatcher_init(loop) can return a negative error (when io_uring_queue_init fails), but iowatcher_add_event ignores that return value and immediately dereferences loop->iowatcher. This can crash on init failure; handle the error (e.g., return it) before using loop->iowatcher.

Suggested change
iowatcher_init(loop);
int ret = iowatcher_init(loop);
if (ret < 0) {
return ret;
}

Copilot uses AI. Check for mistakes.
@ithewei
Copy link
Owner

ithewei commented Mar 11, 2026

@copilot apply changes based on the comments in this thread

Copilot AI and others added 2 commits March 11, 2026 07:16
- Add io_uring_get_sqe_safe() helper that flushes and retries on SQE exhaustion
- Handle iowatcher_init() failure in iowatcher_add_event()
- Map poll errors to io->events (not just HV_READ) so write-only fds get notified
- Track sqe_queued flag for re-arm submissions instead of relying on nevents
- Guard -luring with Linux check in Makefile.in
- Add FATAL_ERROR for non-Linux WITH_IO_URING in CMakeLists.txt

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot apply changes based on the comments in this thread

Applied all review suggestions in 95c4a6d:

  1. SQE exhaustion handling — Added io_uring_get_sqe_safe() helper that flushes pending submissions and retries when io_uring_get_sqe() returns NULL. Used in both iowatcher_add_event and iowatcher_del_event.
  2. Init error handlingiowatcher_add_event now checks iowatcher_init() return value and propagates the error.
  3. Error event mapping — Poll failures now set io->revents to io->events (or HV_RDWR if none registered) instead of just HV_READ, so write-only fds (e.g. connect) get properly notified.
  4. Re-arm submission tracking — Added sqe_queued flag so io_uring_submit is called whenever re-arm SQEs are queued, not just when nevents > 0.
  5. Makefile.in Linux guard-luring is now wrapped in ifeq ($(OS), Linux).
  6. CMakeLists.txt error — Added FATAL_ERROR when WITH_IO_URING is enabled on non-Linux platforms.

@ithewei
Copy link
Owner

ithewei commented Mar 11, 2026

@copilot 编译报错
event/io_uring.c:172:5: error: unknown type name ‘typeof’
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:27: error: expected ‘)’ before ‘&’ token
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^
event/io_uring.c:172:5: error: expected ‘)’ before ‘->’ token
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:5: error: expected ‘)’ before ‘*’ token
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:5: error: expected ‘)’ before ‘(’ token
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:5: note: to match this ‘(’
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:5: error: ‘__atomic_load_ptr’ undeclared (first use in this function); did you mean ‘__atomic_load_n’?
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:172:5: error: argument 1 of ‘__atomic_load’ must be a non-void pointer type
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
^~~~~~~~~~~~~~~~~~~~~
event/io_uring.c:174:25: warning: implicit declaration of function ‘io_uring_cqe_get_data64’; did you mean ‘io_uring_cqe_get_data’? [-Wimplicit-function-declaration]
uint64_t data = io_uring_cqe_get_data64(cqe);
^~~~~~~~~~~~~~~~~~~~~~~

- Replace io_uring_for_each_cqe (uses typeof GNU extension) with
  io_uring_cq_ready + io_uring_peek_cqe loop
- Replace io_uring_cqe_get_data64/io_uring_sqe_set_data64 (liburing 2.2+)
  with io_uring_cqe_get_data/io_uring_sqe_set_data (pointer-based)
- Change IO_URING_CANCEL_TAG from UINT64_MAX to pointer sentinel

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot 编译报错
event/io_uring.c:172:5: error: unknown type name ‘typeof’
io_uring_for_each_cqe(&ctx->ring, head, cqe) {
...

Fixed in 10aba51. The build errors were caused by two liburing compatibility issues:

  1. io_uring_for_each_cqe macro uses typeof (GNU C extension) — replaced with io_uring_cq_ready() + io_uring_peek_cqe() loop
  2. io_uring_cqe_get_data64/io_uring_sqe_set_data64 only exist in liburing ≥ 2.2 — replaced with io_uring_cqe_get_data/io_uring_sqe_set_data (pointer-based, available in all liburing versions)

@ithewei
Copy link
Owner

ithewei commented Mar 11, 2026

@copilot io_uring.c需要使用-std=gnu99才能编译通过,使用-std=c99不行

…ility

liburing headers use GNU C extensions (typeof, statement expressions)
that are not available with -std=c99. Override the C standard to gnu99
specifically for io_uring.c in both CMake and Makefile build systems.

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot io_uring.c需要使用-std=gnu99才能编译通过,使用-std=c99不行

Fixed in bcb6f28. Added -std=gnu99 override specifically for event/io_uring.c in both build systems:

  • CMakeLists.txt: set_source_files_properties(event/io_uring.c PROPERTIES COMPILE_OPTIONS "-std=gnu99") — appended after the project-wide -std=c99 so it takes precedence
  • Makefile.in: explicit compilation rule for event/io_uring.o that appends -std=gnu99 after $(CFLAGS)

Other source files continue to compile with -std=c99 as before.

Makefile.in Outdated
ifeq ($(OS), Linux)
ifeq ($(WITH_IO_URING), yes)
event/io_uring.o: event/io_uring.c
$(CC) $(CPPFLAGS) $(CFLAGS) -std=gnu99 -c $< -o $@
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

单独设置不太好看,能改成如果是linux gcc编译器,就全部使用-std=gnu99吗,cmake里也改下

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot 帮忙改下

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 800e971. Changed to use -std=gnu99 globally for all C files on Linux:

  • CMakeLists.txt: set(CMAKE_C_EXTENSIONS ON) when Linux + GCC, which produces -std=gnu99
  • Makefile.in: -std=gnu99 on Linux, -std=c99 on other platforms

Removed the per-file set_source_files_properties and the explicit io_uring.o rule.

…uring.c

Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

支持linux io_uring的计划

3 participants