Description
libnative does not detect when a read/write fails due to O_NONBLOCK
being set on the fd. It makes the assumption that all of its files never have that flag set, because it never sets that flag on them. Unfortunately, this isn't necessarily the case. FIFOs and character device files (e.g. terminals) will actually share the O_NONBLOCK
flag among all processes that have the same open file description (e.g. the underlying kernel object that backs the fd).
Using a tiny C program that uses fcntl()
to set O_NONBLOCK
on its stdout, a FIFO, and a Rust program that writes 32k of output to stdout, I can reproduce this issue 100% of the time. The invocation looks like
> (./mknblock; ./rust_program) > fifo
and on the reading side I just do
> (sleep 1; cat) < fifo
This causes the rust program to return a "Resource temporarily unavailable" error from stdout.write()
after writing 8k of output. Removing the call to ./mknblock
restores the expected behavior where the rust program will block until the reading side has started consuming input. And further, switching the rust program over to libgreen also causes it to block even with ./mknblock
.
The C program looks like this:
#include <fcntl.h>
#include <stdio.h>
int main() {
if (fcntl(1, F_SETFL, O_NONBLOCK) == -1) {
perror("fcntl");
return 1;
}
return 0;
}
The Rust program is a bit longer, mostly because it prints out information about stdout before it begins writing. It looks like this:
extern crate green;
extern crate rustuv;
use std::io;
use std::io::IoResult;
use std::libc;
use std::os;
use std::mem;
static O_NONBLOCK: libc::c_int = 0x0004;
static O_APPEND: libc::c_int = 0x0008;
static O_ASYNC: libc::c_int = 0x0040;
static F_GETFL: libc::c_int = 3;
unsafe fn print_flags(fd: libc::c_int) -> IoResult<()> {
let mut stat: libc::stat = mem::uninit();
if libc::fstat(fd, &mut stat) < 0 {
try!(writeln!(&mut io::stderr(), "fstat: {}", os::last_os_error()));
libc::exit(1);
}
try!(writeln!(&mut io::stderr(), "stdout: dev={}, ino={}", stat.st_dev, stat.st_ino));
let flags = libc::fcntl(fd, F_GETFL);
if flags == -1 {
try!(writeln!(&mut io::stderr(), "fcntl: {}", os::last_os_error()));
libc::exit(1);
}
let mut v = Vec::new();
if flags & O_NONBLOCK != 0 {
v.push("nonblock");
}
if flags & O_APPEND != 0 {
v.push("append");
}
if flags & O_ASYNC != 0 {
v.push("async");
}
try!(writeln!(&mut io::stderr(), "flags: {}", v.connect(", ")));
Ok(())
}
fn run() -> IoResult<()> {
unsafe { try!(print_flags(1)); }
let mut out = io::stdio::stdout_raw();
for i in range(0u, 32) {
try!(writeln!(&mut io::stderr(), "Writing chunk {}...", i));
let mut buf = ['x' as u8, ..1024];
buf[1023] = '\n' as u8;
match out.write(buf) {
Ok(()) => (),
Err(e) => {
try!(writeln!(&mut io::stderr(), "Error writing chunk {}", i));
return Err(e);
}
}
}
Ok(())
}
fn main() {
match run() {
Err(e) => {
(writeln!(&mut io::stderr(), "Error: {}", e)).unwrap();
os::set_exit_status(1);
}
Ok(()) => ()
}
}
unsafe fn arg_is_dash_g(arg: *u8) -> bool {
*arg == '-' as u8 &&
*arg.offset(1) == 'g' as u8 &&
*arg.offset(2) == 0
}
#[start]
fn start(argc: int, argv: **u8) -> int {
if argc > 1 && unsafe { arg_is_dash_g(*argv.offset(1)) } {
green::start(argc, argv, rustuv::event_loop, main)
} else {
native::start(argc, argv, main)
}
}