wip: io-uring

This commit is contained in:
Daniel Eklöf 2025-05-08 09:23:01 +02:00
parent 9b0d5e7c96
commit ae8394b0c1
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
5 changed files with 250 additions and 9 deletions

View file

@ -237,6 +237,152 @@ static bool cursor_blink_rearm_timer(struct terminal *term);
/* Externally visible, but not declared in terminal.h, to enable pgo
* to call this function directly */
#if defined(FOOT_IO_URING)
static bool
fdm_ptmx_eventfd(struct fdm *fdm, int fd, int events, void *data)
{
struct terminal *term = data;
/* Prevent blinking while typing */
if (term->cursor_blink.fd >= 0) {
term->cursor_blink.state = CURSOR_BLINK_ON;
cursor_blink_rearm_timer(term);
}
if (unlikely(term->interactive_resizing.grid != NULL)) {
/*
* Don't consume PTMX while we're doing an interactive resize,
* since the 'normal' grid we're currently using is a
* temporary one - all changes done to it will be lost when
* the interactive resize ends.
*/
return true;
}
//const size_t max_iterations = !hup ? 10 : SIZE_MAX;
const size_t max_iterations = 100;
for (size_t i = 0; i < max_iterations; i++) {
struct io_uring_cqe *cqe;
int ret = io_uring_peek_cqe(&term->uring.ring, &cqe);
if (unlikely(ret < 0)) {
if (-ret == EAGAIN)
break;
LOG_ERRNO_P(-ret, "failed to get CQE from io-uring");
return false;
}
if (!(cqe->flags & IORING_CQE_F_MORE)) {
if (cqe->res >= 0 || -cqe->res == ENOBUFS) {
struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring);
io_uring_prep_read_multishot(sqe, term->ptmx, 0, -1, 123);
io_uring_submit(&term->uring.ring);
}
}
if (unlikely(cqe->res < 0)) {
if (-cqe->res == EAGAIN || -cqe->res == EIO || -cqe->res == ENOBUFS) {
/*
* EAGAIN: no more to read - FDM will trigger us again
* EIO: assume PTY was closed - we already have, or will get, a EPOLLHUP
*/
io_uring_cqe_seen(&term->uring.ring, cqe);
return true;
}
LOG_ERRNO_P(-cqe->res, "failed to read from pseudo terminal");
io_uring_cqe_seen(&term->uring.ring, cqe);
return false;
}
xassert(cqe->flags & IORING_CQE_F_BUFFER);
const int buffer_id = cqe->flags >> IORING_CQE_BUFFER_SHIFT;
const ssize_t count = cqe->res;
const uint8_t *buf = term->uring.buffers[buffer_id];
xassert(term->interactive_resizing.grid == NULL);
vt_from_slave(term, buf, count);
io_uring_buf_ring_add(term->uring.buf_ring, (void *)buf, 24 * 1024, buffer_id, io_uring_buf_ring_mask(128), 0);
io_uring_buf_ring_cq_advance(&term->uring.ring, term->uring.buf_ring, 1);
}
if (!term->render.app_sync_updates.enabled) {
/*
* We likely need to re-render. But, we don't want to do it
* immediately. Often, a single client update is done through
* multiple writes. This could lead to us rendering one frame with
* "intermediate" state.
*
* For example, we might end up rendering a frame
* where the client just erased a line, while in the
* next frame, the client wrote to the same line. This
* causes screen "flickering".
*
* Mitigate by always incuring a small delay before
* rendering the next frame. This gives the client
* some time to finish the operation (and thus gives
* us time to receive the last writes before doing any
* actual rendering).
*
* We incur this delay *every* time we receive
* input. To ensure we don't delay rendering
* indefinitely, we start a second timer that is only
* reset when we render.
*
* Note that when the client is producing data at a
* very high pace, we're rate limited by the wayland
* compositor anyway. The delay we introduce here only
* has any effect when the renderer is idle.
*/
uint64_t lower_ns = term->conf->tweak.delayed_render_lower_ns;
uint64_t upper_ns = term->conf->tweak.delayed_render_upper_ns;
if (lower_ns > 0 && upper_ns > 0) {
#if PTMX_TIMING
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (last.tv_sec > 0 || last.tv_nsec > 0) {
struct timespec diff;
timespec_sub(&now, &last, &diff);
LOG_INFO("waited %lds %ldns for more input",
(long)diff.tv_sec, diff.tv_nsec);
}
last = now;
#endif
xassert(lower_ns < 1000000000);
xassert(upper_ns < 1000000000);
xassert(upper_ns > lower_ns);
timerfd_settime(
term->delayed_render_timer.lower_fd, 0,
&(struct itimerspec){.it_value = {.tv_nsec = lower_ns}},
NULL);
/* Second timeout - only reset when we render. Set to one
* frame (assuming 60Hz) */
if (!term->delayed_render_timer.is_armed) {
timerfd_settime(
term->delayed_render_timer.upper_fd, 0,
&(struct itimerspec){.it_value = {.tv_nsec = upper_ns}},
NULL);
term->delayed_render_timer.is_armed = true;
}
} else
render_refresh(term);
}
return true;
}
#endif
bool
fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
{
@ -292,6 +438,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
xassert(term->interactive_resizing.grid == NULL);
vt_from_slave(term, buf, count);
break;
}
if (!term->render.app_sync_updates.enabled) {
@ -1406,6 +1553,16 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.active_notifications = tll_init(),
};
#if defined(FOOT_IO_URING)
{
int io_ret = io_uring_queue_init(2, &term->uring.ring, 0);
if (io_ret < 0) {
LOG_ERRNO_P(-io_ret, "failed to initialize io_uring queue");
goto close_fds;
}
}
#endif
pixman_region32_init(&term->render.last_overlay_clip);
term_update_ascii_printer(term);
@ -1503,7 +1660,39 @@ term_window_configured(struct terminal *term)
/* Enable ptmx FDM callback */
if (!term->shutdown.in_progress) {
xassert(term->window->is_configured);
#if defined(FOOT_IO_URING)
{
const unsigned short bid = 123;
const size_t total_size = 3 * 1024 * 1024;
const size_t buf_size = 24 * 1024;
const size_t buf_count = total_size / buf_size;
xassert(buf_count * buf_size == total_size);
int ret;
term->uring.buf_ring = io_uring_setup_buf_ring(
&term->uring.ring, buf_count, bid, 0, &ret);
term->uring.buffers = xmalloc(buf_count * sizeof(term->uring.buffers[0]));
for (unsigned short id = 0; id < buf_count; id++) {
void *buffer = xmalloc(buf_size);
io_uring_buf_ring_add(
term->uring.buf_ring, buffer, buf_size, id,
io_uring_buf_ring_mask(buf_count), id);
term->uring.buffers[id] = buffer;
}
io_uring_buf_ring_advance(term->uring.buf_ring, buf_count);
struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring);
io_uring_prep_read_multishot(sqe, term->ptmx, 0, -1, bid);
io_uring_submit(&term->uring.ring);
}
fdm_add(term->fdm, term->uring.ring.enter_ring_fd, EPOLLIN, &fdm_ptmx_eventfd, term);
fdm_add(term->fdm, term->ptmx, 0, &fdm_ptmx, term);
#else
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
#endif
const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf);
LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled");
@ -1723,9 +1912,10 @@ term_shutdown(struct terminal *term)
del_utmp_record(term->conf, term->reaper, term->ptmx);
if (term->window != NULL && term->window->is_configured)
if (term->window != NULL && term->window->is_configured) {
fdm_del(term->fdm, term->ptmx);
else
//fdm_del(term->fdm, term->io_uring.enter_ring_fd);
} else
close(term->ptmx);
if (!term->shutdown.client_has_terminated) {
@ -1832,6 +2022,38 @@ term_destroy(struct terminal *term)
if (term->shutdown.terminate_timeout_fd >= 0)
fdm_del(term->fdm, term->shutdown.terminate_timeout_fd);
#if defined(FOOT_IO_URING)
fdm_del(term->fdm, term->uring.ring.enter_ring_fd);
{
/* Cancel all pending io-uring requests */
struct io_uring_sqe *sqe = io_uring_get_sqe(&term->uring.ring);
io_uring_prep_cancel(sqe, NULL, IORING_ASYNC_CANCEL_ALL | IORING_ASYNC_CANCEL_ANY);
io_uring_submit(&term->uring.ring);
while (true) {
struct io_uring_cqe *cqe;
int ret = io_uring_peek_cqe(&term->uring.ring, &cqe);
if (ret < 0) {
if (-ret != EAGAIN)
LOG_ERRNO_P(-ret, "failed to pull CQE from io-uring");
break;
}
io_uring_cqe_seen(&term->uring.ring, cqe);
}
/* Shutdown io-uring */
io_uring_queue_exit(&term->uring.ring);
/* Free io-uring buffers */
for (size_t i = 0; i < 128; i++)
free(term->uring.buffers[i]);
free(term->uring.buffers);
}
#endif
if (term->window != NULL) {
wayl_win_destroy(term->window);
term->window = NULL;