Skip to content

Commit ca2e9e7

Browse files
Copilotithewei
andauthored
Add io_uring event loop backend (#813)
* Initial plan * Add io_uring event loop backend support using liburing - 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> * Address code review: fix sqe variable scope, improve error message Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com> * Restrict WITH_IO_URING option to Linux platform only 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> * Apply review suggestions to io_uring backend - 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> * Add clarifying comment for error event mapping in io_uring backend Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com> * Fix io_uring.c build: use portable liburing APIs for older versions - 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> * Compile event/io_uring.c with -std=gnu99 for liburing header compatibility 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> * Use -std=gnu99 globally on Linux instead of per-file override for io_uring.c Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ithewei <26049660+ithewei@users.noreply.github.com>
1 parent 35342db commit ca2e9e7

File tree

12 files changed

+278
-3
lines changed

12 files changed

+278
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ dist
6464
test
6565
*_test
6666
build
67+
build-*
6768
config.mk
6869
hconfig.h
6970
html/uploads

CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ option(WITH_MBEDTLS "with mbedtls library" OFF)
3030

3131
option(WITH_KCP "compile event/kcp" OFF)
3232

33+
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
34+
option(WITH_IO_URING "with io_uring" OFF)
35+
endif()
36+
3337
if(WIN32 OR MINGW)
3438
option(WITH_WEPOLL "compile event/wepoll -> use iocp" ON)
3539
option(ENABLE_WINDUMP "Windows MiniDumpWriteDump" OFF)
@@ -100,6 +104,9 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hconfig.h.in ${CMAKE_CURRENT_SOURCE_D
100104
# see Makefile.in
101105
set(CMAKE_C_STANDARD 99)
102106
set(CMAKE_C_STANDARD_REQUIRED True)
107+
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
108+
set(CMAKE_C_EXTENSIONS ON)
109+
endif()
103110
set(CMAKE_CXX_STANDARD 11)
104111
set(CMAKE_CXX_STANDARD_REQUIRED True)
105112

@@ -165,6 +172,13 @@ if(WITH_MBEDTLS)
165172
set(LIBS ${LIBS} mbedtls mbedx509 mbedcrypto)
166173
endif()
167174

175+
if(WITH_IO_URING)
176+
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
177+
message(FATAL_ERROR "WITH_IO_URING is only supported on Linux because liburing is Linux-only.")
178+
endif()
179+
set(LIBS ${LIBS} uring)
180+
endif()
181+
168182
if(WIN32 OR MINGW)
169183
add_definitions(-DWIN32_LEAN_AND_MEAN -D_CRT_SECURE_NO_WARNINGS -D_WIN32_WINNT=0x0600)
170184
set(LIBS ${LIBS} secur32 crypt32 winmm iphlpapi ws2_32)

Makefile.in

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,12 @@ endif
8787
endif
8888

8989
ifeq ($(findstring -std, $(CFLAGS)), )
90+
ifeq ($(OS), Linux)
91+
override CFLAGS += -std=gnu99
92+
else
9093
override CFLAGS += -std=c99
9194
endif
95+
endif
9296

9397
ifeq ($(findstring -std, $(CXXFLAGS)), )
9498
override CXXFLAGS += -std=c++11
@@ -193,6 +197,13 @@ endif
193197
endif
194198
endif
195199

200+
ifeq ($(OS), Linux)
201+
ifeq ($(WITH_IO_URING), yes)
202+
LDFLAGS += -luring
203+
endif
204+
endif
205+
206+
196207
LDFLAGS += $(addprefix -L, $(LIBDIRS))
197208
LDFLAGS += $(addprefix -l, $(LIBS))
198209

config.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ WITH_MBEDTLS=no
3838

3939
# rudp
4040
WITH_KCP=no
41+
42+
# event
43+
WITH_IO_URING=no

configure

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ dependencies:
4545
rudp:
4646
--with-kcp compile with kcp? (DEFAULT: $WITH_KCP)
4747
48+
event:
49+
--with-io_uring compile with io_uring? (DEFAULT: $WITH_IO_URING)
50+
4851
END
4952
}
5053

@@ -298,6 +301,7 @@ option=WITH_MBEDTLS && check_option
298301
option=ENABLE_UDS && check_option
299302
option=USE_MULTIMAP && check_option
300303
option=WITH_KCP && check_option
304+
option=WITH_IO_URING && check_option
301305

302306
# end confile
303307
cat << END >> $confile

docs/PLAN.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Done
22

33
- base: cross platfrom infrastructure
4-
- event: select/poll/epoll/wepoll/kqueue/port
4+
- event: select/poll/epoll/wepoll/kqueue/port/io_uring
55
- ssl: openssl/gnutls/mbedtls/wintls/appletls
66
- rudp: KCP
77
- evpp: c++ EventLoop interface similar to muduo and evpp
@@ -22,7 +22,6 @@
2222
- hrpc = libhv + protobuf
2323
- rudp: FEC, ARQ, UDT, QUIC
2424
- kcptun
25-
- have a taste of io_uring
2625
- coroutine
2726
- cppsocket.io
2827
- IM-libhv

event/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
├── select.c EVENT_SELECT实现
1212
├── poll.c EVENT_POLL实现
1313
├── epoll.c EVENT_EPOLL实现 (for OS_LINUX)
14+
├── io_uring.c EVENT_IO_URING实现 (for OS_LINUX, with liburing)
1415
├── iocp.c EVENT_IOCP实现 (for OS_WIN)
1516
├── kqueue.c EVENT_KQUEUE实现(for OS_BSD/OS_MAC)
1617
├── evport.c EVENT_PORT实现 (for OS_SOLARIS)

event/hloop.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,8 @@ const char* hio_engine() {
771771
return "iocp";
772772
#elif defined(EVENT_PORT)
773773
return "evport";
774+
#elif defined(EVENT_IO_URING)
775+
return "io_uring";
774776
#else
775777
return "noevent";
776778
#endif

event/hloop.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@ const char* hio_engine() {
233233
return "iocp";
234234
#elif defined(EVENT_PORT)
235235
return "evport";
236+
#elif defined(EVENT_IO_URING)
237+
return "io_uring";
236238
#else
237239
return "noevent";
238240
#endif

event/io_uring.c

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#include "iowatcher.h"
2+
3+
#ifdef EVENT_IO_URING
4+
#include "hplatform.h"
5+
#include "hdef.h"
6+
#include "hevent.h"
7+
8+
#include <liburing.h>
9+
#include <poll.h>
10+
11+
#define IO_URING_ENTRIES 1024
12+
#define IO_URING_CANCEL_TAG ((void*)(uintptr_t)-1)
13+
14+
typedef struct io_uring_ctx_s {
15+
struct io_uring ring;
16+
int nfds;
17+
} io_uring_ctx_t;
18+
19+
int iowatcher_init(hloop_t* loop) {
20+
if (loop->iowatcher) return 0;
21+
io_uring_ctx_t* ctx;
22+
HV_ALLOC_SIZEOF(ctx);
23+
int ret = io_uring_queue_init(IO_URING_ENTRIES, &ctx->ring, 0);
24+
if (ret < 0) {
25+
HV_FREE(ctx);
26+
return ret;
27+
}
28+
ctx->nfds = 0;
29+
loop->iowatcher = ctx;
30+
return 0;
31+
}
32+
33+
int iowatcher_cleanup(hloop_t* loop) {
34+
if (loop->iowatcher == NULL) return 0;
35+
io_uring_ctx_t* ctx = (io_uring_ctx_t*)loop->iowatcher;
36+
io_uring_queue_exit(&ctx->ring);
37+
HV_FREE(loop->iowatcher);
38+
return 0;
39+
}
40+
41+
static struct io_uring_sqe* io_uring_get_sqe_safe(struct io_uring* ring) {
42+
struct io_uring_sqe* sqe = io_uring_get_sqe(ring);
43+
if (sqe == NULL) {
44+
// SQ is full, flush pending submissions and retry
45+
io_uring_submit(ring);
46+
sqe = io_uring_get_sqe(ring);
47+
}
48+
return sqe;
49+
}
50+
51+
int iowatcher_add_event(hloop_t* loop, int fd, int events) {
52+
if (loop->iowatcher == NULL) {
53+
int ret = iowatcher_init(loop);
54+
if (ret < 0) {
55+
return ret;
56+
}
57+
}
58+
io_uring_ctx_t* ctx = (io_uring_ctx_t*)loop->iowatcher;
59+
hio_t* io = loop->ios.ptr[fd];
60+
61+
unsigned poll_mask = 0;
62+
// pre events
63+
if (io->events & HV_READ) {
64+
poll_mask |= POLLIN;
65+
}
66+
if (io->events & HV_WRITE) {
67+
poll_mask |= POLLOUT;
68+
}
69+
// now events
70+
if (events & HV_READ) {
71+
poll_mask |= POLLIN;
72+
}
73+
if (events & HV_WRITE) {
74+
poll_mask |= POLLOUT;
75+
}
76+
77+
struct io_uring_sqe* sqe;
78+
if (io->events != 0) {
79+
// Cancel the existing poll request first
80+
sqe = io_uring_get_sqe_safe(&ctx->ring);
81+
if (sqe == NULL) return -1;
82+
io_uring_prep_poll_remove(sqe, (uint64_t)fd);
83+
io_uring_sqe_set_data(sqe, IO_URING_CANCEL_TAG);
84+
} else {
85+
ctx->nfds++;
86+
}
87+
88+
// Add poll for the combined events
89+
sqe = io_uring_get_sqe_safe(&ctx->ring);
90+
if (sqe == NULL) return -1;
91+
io_uring_prep_poll_add(sqe, fd, poll_mask);
92+
io_uring_sqe_set_data(sqe, (void*)(uintptr_t)fd);
93+
94+
io_uring_submit(&ctx->ring);
95+
return 0;
96+
}
97+
98+
int iowatcher_del_event(hloop_t* loop, int fd, int events) {
99+
io_uring_ctx_t* ctx = (io_uring_ctx_t*)loop->iowatcher;
100+
if (ctx == NULL) return 0;
101+
hio_t* io = loop->ios.ptr[fd];
102+
103+
// Calculate remaining events
104+
unsigned poll_mask = 0;
105+
// pre events
106+
if (io->events & HV_READ) {
107+
poll_mask |= POLLIN;
108+
}
109+
if (io->events & HV_WRITE) {
110+
poll_mask |= POLLOUT;
111+
}
112+
// now events
113+
if (events & HV_READ) {
114+
poll_mask &= ~POLLIN;
115+
}
116+
if (events & HV_WRITE) {
117+
poll_mask &= ~POLLOUT;
118+
}
119+
120+
// Cancel existing poll
121+
struct io_uring_sqe* sqe = io_uring_get_sqe_safe(&ctx->ring);
122+
if (sqe == NULL) return -1;
123+
io_uring_prep_poll_remove(sqe, (uint64_t)fd);
124+
io_uring_sqe_set_data(sqe, IO_URING_CANCEL_TAG);
125+
126+
if (poll_mask == 0) {
127+
ctx->nfds--;
128+
} else {
129+
// Re-add with remaining events
130+
sqe = io_uring_get_sqe_safe(&ctx->ring);
131+
if (sqe == NULL) return -1;
132+
io_uring_prep_poll_add(sqe, fd, poll_mask);
133+
io_uring_sqe_set_data(sqe, (void*)(uintptr_t)fd);
134+
}
135+
136+
io_uring_submit(&ctx->ring);
137+
return 0;
138+
}
139+
140+
int iowatcher_poll_events(hloop_t* loop, int timeout) {
141+
io_uring_ctx_t* ctx = (io_uring_ctx_t*)loop->iowatcher;
142+
if (ctx == NULL) return 0;
143+
if (ctx->nfds == 0) return 0;
144+
145+
struct __kernel_timespec ts;
146+
struct __kernel_timespec* tp = NULL;
147+
if (timeout != INFINITE) {
148+
ts.tv_sec = timeout / 1000;
149+
ts.tv_nsec = (timeout % 1000) * 1000000LL;
150+
tp = &ts;
151+
}
152+
153+
struct io_uring_cqe* cqe;
154+
int ret;
155+
if (tp) {
156+
ret = io_uring_wait_cqe_timeout(&ctx->ring, &cqe, tp);
157+
} else {
158+
ret = io_uring_wait_cqe(&ctx->ring, &cqe);
159+
}
160+
if (ret < 0) {
161+
if (ret == -ETIME || ret == -EINTR) {
162+
return 0;
163+
}
164+
perror("io_uring_wait_cqe");
165+
return ret;
166+
}
167+
168+
int nevents = 0;
169+
int sqe_queued = 0;
170+
unsigned nready = io_uring_cq_ready(&ctx->ring);
171+
unsigned i;
172+
for (i = 0; i < nready; ++i) {
173+
if (io_uring_peek_cqe(&ctx->ring, &cqe) != 0) break;
174+
void* data = io_uring_cqe_get_data(cqe);
175+
if (data == IO_URING_CANCEL_TAG) {
176+
io_uring_cqe_seen(&ctx->ring, cqe);
177+
continue;
178+
}
179+
180+
int fd = (int)(uintptr_t)data;
181+
if (fd < 0 || fd >= loop->ios.maxsize) {
182+
io_uring_cqe_seen(&ctx->ring, cqe);
183+
continue;
184+
}
185+
hio_t* io = loop->ios.ptr[fd];
186+
if (io == NULL) {
187+
io_uring_cqe_seen(&ctx->ring, cqe);
188+
continue;
189+
}
190+
191+
if (cqe->res < 0) {
192+
// Poll request failed: notify registered events, or both if none registered
193+
io->revents |= (io->events ? io->events : HV_RDWR);
194+
EVENT_PENDING(io);
195+
++nevents;
196+
} else {
197+
int revents = cqe->res;
198+
if (revents & (POLLIN | POLLHUP | POLLERR)) {
199+
io->revents |= HV_READ;
200+
}
201+
if (revents & (POLLOUT | POLLHUP | POLLERR)) {
202+
io->revents |= HV_WRITE;
203+
}
204+
if (io->revents) {
205+
EVENT_PENDING(io);
206+
++nevents;
207+
}
208+
}
209+
210+
io_uring_cqe_seen(&ctx->ring, cqe);
211+
212+
// io_uring POLL_ADD is one-shot, re-arm for the same events
213+
unsigned remask = 0;
214+
if (io->events & HV_READ) remask |= POLLIN;
215+
if (io->events & HV_WRITE) remask |= POLLOUT;
216+
if (remask) {
217+
struct io_uring_sqe* sqe = io_uring_get_sqe_safe(&ctx->ring);
218+
if (sqe) {
219+
io_uring_prep_poll_add(sqe, fd, remask);
220+
io_uring_sqe_set_data(sqe, (void*)(uintptr_t)fd);
221+
sqe_queued = 1;
222+
}
223+
}
224+
}
225+
226+
if (sqe_queued) {
227+
io_uring_submit(&ctx->ring);
228+
}
229+
230+
return nevents;
231+
}
232+
#endif

0 commit comments

Comments
 (0)