Skip to content

Commit 4a5e79f

Browse files
authored
Merge pull request #7777 from grondo/issue#7774
libsubprocess: fix busy loop on unreadable fd
2 parents f38478a + 65f41da commit 4a5e79f

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

src/common/libsubprocess/fbuf_watcher.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <sys/stat.h>
1616
#include <fcntl.h>
1717
#include <unistd.h>
18+
#include <errno.h>
1819
#include <flux/core.h>
1920

2021
#include "src/common/libutil/log.h"
@@ -188,8 +189,17 @@ static void rbwatcher_fd_cb (flux_reactor_t *r,
188189
if ((space = fbuf_space (rbw->fbuf)) < 0)
189190
return;
190191

191-
if ((ret = fbuf_write_from_fd (rbw->fbuf, rbw->fd, space)) < 0)
192+
if ((ret = fbuf_write_from_fd (rbw->fbuf, rbw->fd, space)) < 0) {
193+
/* EAGAIN and EWOULDBLOCK are transient errors: return and
194+
* try again. Otherwise, stop the watcher and return
195+
* FLUX_POLLERR to catch permanent errors like EBADF.
196+
*/
197+
if (errno == EAGAIN || errno == EWOULDBLOCK)
198+
return;
199+
flux_watcher_stop (fd_w);
200+
watcher_call (w, FLUX_POLLERR);
192201
return;
202+
}
193203

194204
if (!ret) {
195205
fbuf_read_watcher_decref (w);

src/common/libsubprocess/test/fbuf_watcher.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#endif
1414
#include <sys/types.h>
1515
#include <sys/socket.h>
16+
#include <fcntl.h>
1617
#include <flux/core.h>
1718

1819
#include "src/common/libutil/fdutils.h"
@@ -1168,6 +1169,97 @@ static void test_buffer_corner_case (flux_reactor_t *reactor)
11681169
close (fd[1]);
11691170
}
11701171

1172+
/* Regression test for issue #7774.
1173+
*
1174+
* A read buffer watcher on a fd that polls ready but returns a persistent
1175+
* error on read() (e.g. EBADF) must stop the watcher and notify the user
1176+
* with FLUX_POLLERR, rather than spinning at 100% CPU re-reading the fd.
1177+
*/
1178+
1179+
struct buffer_read_error {
1180+
int count;
1181+
int revents;
1182+
};
1183+
1184+
static void buffer_read_error_cb (flux_reactor_t *r,
1185+
flux_watcher_t *w,
1186+
int revents,
1187+
void *arg)
1188+
{
1189+
struct buffer_read_error *err = arg;
1190+
err->count++;
1191+
err->revents = revents;
1192+
flux_watcher_stop (w);
1193+
flux_reactor_stop (r);
1194+
}
1195+
1196+
/* Watchdog: if the watcher busy loops, the reactor never returns on its
1197+
* own. This timer fires to break out and let the test report failure
1198+
* instead of hanging.
1199+
*/
1200+
static void buffer_read_error_timeout (flux_reactor_t *r,
1201+
flux_watcher_t *w,
1202+
int revents,
1203+
void *arg)
1204+
{
1205+
int *timed_out = arg;
1206+
*timed_out = 1;
1207+
flux_reactor_stop (r);
1208+
}
1209+
1210+
static void test_buffer_read_error (flux_reactor_t *reactor)
1211+
{
1212+
int fd;
1213+
flux_watcher_t *w;
1214+
flux_watcher_t *timer;
1215+
struct buffer_read_error err = { .count = 0, .revents = 0 };
1216+
int timed_out = 0;
1217+
1218+
/* Open /dev/null write-only: the fd is valid and can be made
1219+
* nonblocking (so watcher creation succeeds), but read() on it
1220+
* returns EBADF, modeling the sandboxed fd 0 from issue #7774.
1221+
*/
1222+
ok ((fd = open ("/dev/null", O_WRONLY)) >= 0,
1223+
"buffer read error: opened /dev/null O_WRONLY");
1224+
ok (fd_set_nonblocking (fd) >= 0,
1225+
"buffer read error: set fd nonblocking");
1226+
1227+
w = fbuf_read_watcher_create (reactor,
1228+
fd,
1229+
1024,
1230+
buffer_read_error_cb,
1231+
0,
1232+
&err);
1233+
ok (w != NULL,
1234+
"buffer read error: read watcher created on EBADF fd");
1235+
1236+
timer = flux_timer_watcher_create (reactor,
1237+
5.,
1238+
0.,
1239+
buffer_read_error_timeout,
1240+
&timed_out);
1241+
if (!timer)
1242+
BAIL_OUT ("could not create timer watcher");
1243+
1244+
flux_watcher_start (w);
1245+
flux_watcher_start (timer);
1246+
1247+
flux_reactor_run (reactor, 0);
1248+
1249+
ok (!timed_out,
1250+
"buffer read error: watcher stopped instead of busy looping");
1251+
ok (err.count == 1,
1252+
"buffer read error: user callback called exactly once");
1253+
ok ((err.revents & FLUX_POLLERR),
1254+
"buffer read error: user callback got FLUX_POLLERR");
1255+
1256+
flux_watcher_stop (timer);
1257+
flux_watcher_destroy (timer);
1258+
flux_watcher_stop (w);
1259+
flux_watcher_destroy (w);
1260+
close (fd);
1261+
}
1262+
11711263
int main (int argc, char *argv[])
11721264
{
11731265
flux_reactor_t *reactor;
@@ -1182,6 +1274,7 @@ int main (int argc, char *argv[])
11821274
test_buffer (reactor);
11831275
test_buffer_refcnt (reactor);
11841276
test_buffer_corner_case (reactor);
1277+
test_buffer_read_error (reactor);
11851278

11861279
flux_reactor_destroy (reactor);
11871280

0 commit comments

Comments
 (0)