Skip to content

Commit

Permalink
fix(posix): use fcntl to check if a fd is nonblocking (#10080)
Browse files Browse the repository at this point in the history
The FileReader is responsible for deciding what sort of file descriptor
has been passed to it. On MacOS, when stdin is provided to it the
FileReader assumes that it is nonblocking even if the descriptor is not
as it passes the pollable and not a tty check, this then results in
hangs as the reader expects `EAGAIN`.

To fix this we now check the file descriptor to see if it is nonblocking
rather than using assumptions.
  • Loading branch information
alexkornitzer committed Apr 16, 2024
1 parent fbe2fe0 commit debc9bf
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/bun.js/webcore/streams.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3513,7 +3513,7 @@ pub const FileReader = struct {
this.file_type = .nonblocking_pipe;
}

this.nonblocking = is_nonblocking_tty or (this.pollable and !(file.is_atty orelse false));
this.nonblocking = is_nonblocking_tty or bun.isNonBlocking(fd.int());

if (this.nonblocking and this.file_type == .pipe) {
this.file_type = .nonblocking_pipe;
Expand Down
4 changes: 4 additions & 0 deletions src/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ pub fn ensureNonBlocking(fd: anytype) void {
_ = std.os.fcntl(fd, std.os.F.SETFL, current | std.os.O.NONBLOCK) catch 0;
}

pub fn isNonBlocking(fd: anytype) bool {
return (std.os.fcntl(fd, std.os.F.GETFL, 0) catch 0 & std.os.O.NONBLOCK) == std.os.O.NONBLOCK;
}

const global_scope_log = sys.syslog;
pub fn isReadable(fd: FileDescriptor) PollFlag {
if (comptime Environment.isWindows) {
Expand Down
29 changes: 29 additions & 0 deletions test/regression/issue/10080.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { test, expect } from "bun:test";
import { bunEnv, bunExe, isPosix } from "harness";
import { tmpdir } from "os";
import { join } from "path";

test.if(isPosix)("10080 - ensure blocking stdio is treated as such in FileReader", async () => {
const expected = "foobar\n";
const filename = join(tmpdir(), "bun.test.stream." + Date.now() + ".js");
const contents = "for await (const line of console) {console.log(`foo${line}`)}"
await Bun.write(filename, contents);
const shellCommand = `exec &> >(${bunExe()} ${filename}); echo "bar"; while read -r line; do echo $line; done`;

const proc = Bun.spawn(["bash", "-c", shellCommand], {
stdin: "inherit",
stdout: "pipe",
stderr: "inherit",
env: bunEnv,
});
const { value }= await proc.stdout.getReader().read();
const output = new TextDecoder().decode(value);
if (output !== expected) {
expect(output).toEqual(expected);
throw new Error("Output didn't match!\n");
}

proc.kill(9);
await proc.exited;
expect(proc.killed).toBeTrue();
}, 1000);

0 comments on commit debc9bf

Please sign in to comment.