Skip to content

Commit edd6189

Browse files
authored
Merge pull request #2041 from jnovy/crun_fix
channel_fd_pair: fix CPU busy loop when output pipe is blocked
2 parents 66f1405 + 00206a8 commit edd6189

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

src/libcrun/utils.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,8 +2798,11 @@ channel_fd_pair_process (struct channel_fd_pair *channel, int epollfd, libcrun_e
27982798
size_t used = ring_buffer_get_data_available (channel->rb);
27992799
int events;
28002800

2801-
/* If there is space available in the buffer, we want to read more. */
2802-
events = (available > 0) ? (EPOLLIN | (is_input_eagain ? EPOLLET : 0)) : 0;
2801+
/* If there is space available in the buffer and the output is not
2802+
blocked, we want to read more. When the output got EAGAIN, stop
2803+
reading until the output fd becomes writable again to avoid a
2804+
busy loop when the consumer is not draining the pipe. */
2805+
events = (available > 0 && ! is_output_eagain) ? (EPOLLIN | (is_input_eagain ? EPOLLET : 0)) : 0;
28032806
if (events != channel->infd_epoll_events)
28042807
{
28052808
ret = epoll_helper_toggle (epollfd, channel->in_fd, events, err);

tests/tests_libcrun_utils.c

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
#include <libcrun/cgroup.h>
2222
#include <libcrun/cgroup-systemd.h>
2323
#include <sys/types.h>
24+
#include <sys/epoll.h>
2425
#include <unistd.h>
2526
#include <string.h>
27+
#include <fcntl.h>
2628

2729
typedef int (*test) ();
2830

@@ -629,6 +631,73 @@ test_crun_ensure_directory ()
629631
return 0;
630632
}
631633

634+
static int
635+
test_channel_fd_pair_no_busy_loop_on_blocked_output ()
636+
{
637+
/* When the output pipe is blocked, channel_fd_pair_process must not register
638+
EPOLLIN on the input fd. Verify by writing new data to the input AFTER
639+
processing and checking that epoll_wait does not fire. */
640+
libcrun_error_t err = NULL;
641+
cleanup_close int infd0 = -1, infd1 = -1, outfd0 = -1, outfd1 = -1;
642+
cleanup_close int epollfd = -1;
643+
cleanup_channel_fd_pair struct channel_fd_pair *channel = NULL;
644+
struct epoll_event ev;
645+
char buf[4096];
646+
int input_pipe[2], output_pipe[2];
647+
int ret;
648+
649+
if (pipe (input_pipe) < 0 || pipe (output_pipe) < 0)
650+
return -1;
651+
652+
infd0 = input_pipe[0];
653+
infd1 = input_pipe[1];
654+
outfd0 = output_pipe[0];
655+
outfd1 = output_pipe[1];
656+
657+
fcntl (input_pipe[0], F_SETFL, O_NONBLOCK);
658+
fcntl (input_pipe[1], F_SETFL, O_NONBLOCK);
659+
fcntl (output_pipe[0], F_SETFL, O_NONBLOCK);
660+
fcntl (output_pipe[1], F_SETFL, O_NONBLOCK);
661+
662+
/* Fill the output pipe so writes return EAGAIN. */
663+
memset (buf, 'X', sizeof (buf));
664+
while (write (output_pipe[1], buf, sizeof (buf)) > 0)
665+
;
666+
667+
/* Seed some input data so channel_fd_pair_process has work to do. */
668+
if (write (input_pipe[1], "A", 1) != 1)
669+
return -1;
670+
671+
epollfd = epoll_create1 (EPOLL_CLOEXEC);
672+
if (epollfd < 0)
673+
return -1;
674+
675+
channel = channel_fd_pair_new (input_pipe[0], output_pipe[1], 4096);
676+
677+
ret = channel_fd_pair_process (channel, epollfd, &err);
678+
if (ret < 0)
679+
{
680+
crun_error_release (&err);
681+
return -1;
682+
}
683+
684+
/* Write new data so that an edge-triggered EPOLLIN (if registered) fires. */
685+
if (write (input_pipe[1], "B", 1) != 1)
686+
return -1;
687+
688+
/* With the fix EPOLLIN is not registered, so epoll_wait must time out.
689+
Without the fix EPOLLIN|EPOLLET is registered and the new data triggers
690+
an immediate wake-up. */
691+
ret = epoll_wait (epollfd, &ev, 1, 0);
692+
if (ret > 0 && ev.data.fd == input_pipe[0])
693+
{
694+
fprintf (stderr, "epoll fired on input fd while output is blocked\n");
695+
return -1;
696+
}
697+
698+
return 0;
699+
}
700+
632701
static void
633702
run_and_print_test_result (const char *name, int id, test t)
634703
{
@@ -652,9 +721,9 @@ main ()
652721
{
653722
int id = 1;
654723
#ifdef HAVE_SYSTEMD
655-
printf ("1..16\n");
724+
printf ("1..17\n");
656725
#else
657-
printf ("1..13\n");
726+
printf ("1..14\n");
658727
#endif
659728
RUN_TEST (test_crun_path_exists);
660729
RUN_TEST (test_write_read_file);
@@ -669,6 +738,7 @@ main ()
669738
RUN_TEST (test_str_join_array);
670739
RUN_TEST (test_get_current_timestamp);
671740
RUN_TEST (test_crun_ensure_directory);
741+
RUN_TEST (test_channel_fd_pair_no_busy_loop_on_blocked_output);
672742
#ifdef HAVE_SYSTEMD
673743
RUN_TEST (test_parse_sd_array);
674744
RUN_TEST (test_get_scope_path);

0 commit comments

Comments
 (0)