Skip to content

macOS: poll() does not report a FIFO close, so a parked read never sees EOF #928

Description

@nohzafk

Note

This issue was investigated and written by an AI agent, at the repository
owner's direction, on the owner's machine. All outputs below come from runs
of the shown commands. The owner read this text before posting.

Summary

On macOS, a computation that parks on a FIFO never returns at end of file.
Both lanes wait with poll(), and poll() does not report a FIFO close on
macOS. The select() call does. So reading a FIFO to the end hangs.

Symptom

An effect that parks on fd 0 reads a pipe and a file to the end. On a FIFO it
stops after the data:

printf 'hello\n' | ./reader      # LINE[hello] LINE[] — done
./reader < /tmp/two.txt          # LINE[a] LINE[b] LINE[] — done
mkfifo /tmp/f; ( sleep 1; echo hi ) > /tmp/f &
./reader < /tmp/f                # LINE[hi], then nothing, forever

The parked call:

return io_wait_on(w, 0, POLLIN, 0, stdin_read_line_more);   // fd 0, no deadline

Scope

One probe waits on a FIFO. The same probe ran on three machines:

poll select
macOS 27.0 (26A428), arm64 never True
Linux 5.15 revents=0x10 True
iSH (a system-call emulator) never never

A named FIFO fails and an anonymous pipe does not. fstat answers S_ISFIFO
for both, so the name is what matters. < file, producer | prog, a pty and
<(cmd) all see EOF.

Where the runtime waits

  • The native lane: while (poll(fds, n, ms) < 0) in io_wait, in the C that
    bend prog.bend -o prog.c emits.
  • The JS lane: io_sys().poll(...) in io_wait, in the emitted JS.

Reproducer, without Bend

#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/select.h>
#include <unistd.h>

int main(int argc, char** argv) {
  int use_select = argc > 1 && argv[1][0] == 's';
  char c;
  for (int i = 0; i < 20; i += 1) {
    int ready;
    if (use_select) {
      fd_set r; FD_ZERO(&r); FD_SET(0, &r);
      struct timeval tv = {0, 400000};
      ready = select(1, &r, NULL, NULL, &tv);
    } else {
      struct pollfd p; p.fd = 0; p.events = POLLIN; p.revents = 0;
      ready = poll(&p, 1, 400);
    }
    printf("%s -> %d\n", use_select ? "select" : "poll", ready);
    if (ready > 0 && read(0, &c, 1) == 0) break;   // 0 from read is EOF
  }
  return 0;
}
clang -o w w.c
rm -f f; mkfifo f; ( sleep 1; echo hi; sleep 1 ) > f &
./w poll   < f    # 0 forever, also after the writer exits
./w select < f    # ready, then read -> 0 (EOF)

The same holds when the program opens the FIFO itself instead of the shell.

Workaround that works

A deadline on the park makes the effect wake and read again:

  • C lane: io_wait_on(w, 0, POLLIN, io_tick() + 250000000ull, more)
  • JS lane: io_park_on(0, false, k, go, performance.now() + 250)

Both lanes then read a FIFO to the end, and their other computations keep
running. tcp_poll.c already uses this shape.

Question

The difference is in Darwin, but Bend decides how it waits. Which do you want?

  1. The runtime handles this case on Darwin, for example with a select wait.
  2. The guide states where a park can wait, and a FIFO as stdin is unsupported.

Environment

Bend 2.0.20 on macOS 27.0 (build 26A428), arm64. I have one macOS machine, so
I cannot say whether other versions behave the same.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions