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+
11711263int 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