简体   繁体   English

在选择将其报告为可写时,可以将()写入非阻塞FD Return eagain?

[英]Can write() to a non-blocking fd return EAGAIN when select reports it as writable?

I'm trying to track down some strange behavior in OS X (10.8.2).我正在尝试追踪 OS X (10.8.2) 中的一些奇怪行为。 Basically, I'm opening a pipe, and filling it with data until it is unwritable.基本上,我正在打开一个管道,并用数据填充它直到它不可写。 I'm finding, however, that depending on the size of the chunk I attempt to write, sometime I will get EAGAIN from a write() call even though select claims the pipe is still writable.然而,我发现根据我尝试写入的块的大小,有时我会从 write() 调用中获得 EAGAIN,即使 select 声称管道仍然可写。 Here is some test code:下面是一些测试代码:

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

#define START 1
#define END 16

int is_writeable(int fd) {
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;

    fd_set ws;

    FD_ZERO(&ws);
    FD_SET(fd, &ws);

    if(select(fd+1, NULL, &ws, NULL, &timeout) < 0) {
        return -1;
    }

    if(FD_ISSET(fd, &ws))
        return 1;
    else
        return 0;
}

int main(int argc, char *argv[]) {

    int pipes[2];
    int rp, wp, i, errval, fails, tmp;

    char testbuf[END];
    char destbuf[128000];

    for(i = START; i < END; i++) {
        int byte_count = 0;
        printf("%i: ", i);
        fails = 0;

        pipes[0] = 0;
        pipes[1] = 0;

        if(pipe(pipes) < 0) {
            printf("PIPE FAIL\n");
            break;
        }
        rp = pipes[0];
        wp = pipes[1];

        int flags = fcntl(wp, F_GETFL, 0);
        if(fcntl(wp, F_SETFL, flags | O_NONBLOCK) == -1) {
            fails = 4;
        }

        if(is_writeable(wp) != 1) {
            fails = 1;
        }

        while(!fails) {
            // printf(".");
            if(is_writeable(wp) < 1) {
                break;
            }

            tmp = write(wp, testbuf, i);
            //No bytes written, fail
            if(tmp < 0) {
                if(errno == EAGAIN) {
                    if(is_writeable(wp) == 1) {
                        fails = 3;
                        break;
                    }
                } else {
                    fails = 2;
                    perror("During write");

                    break;
                }

            } else {
                byte_count += tmp;
            }
            //Errno is eagain, fail
        }
        printf("byte count %i, ", byte_count);

        if(fails)
            printf("FAIL, %i\n", fails);
        else
            printf("PASS\n");

        if(close(wp) != 0)
            printf("WP CLOSE FAIL\n");
        if(close(rp) != 0)
            printf("RP CLOSE FAIL\n");
    }

}

Here is the output:这是输出:

1: byte count 16384, PASS
2: byte count 16384, PASS
3: byte count 65535, FAIL 3
4: byte count 16384, PASS
5: byte count 65535, FAIL 3
6: byte count 65532, FAIL 3
7: byte count 65534, FAIL 3
8: byte count 16384, PASS
9: byte count 65529, FAIL 3
10: byte count 65530, FAIL 3
11: byte count 65527, FAIL 3
12: byte count 65532, FAIL 3
13: byte count 65533, FAIL 3
14: byte count 65534, FAIL 3
15: byte count 65535, FAIL 3

Note that failure case 3 is where a write() call returns -1, but select still reports that the filehandle is writable.请注意,失败案例 3 是 write() 调用返回 -1,但 select 仍报告文件句柄可写。

Here is a shorter example in Ruby that shows the same failure:这是 Ruby 中一个较短的示例,显示了相同的失败:

(1..10).each do |i|
  passes = true
  begin
    (rp, wp) = IO.pipe
    wp.write_nonblock ("F" * i) while(select [], [wp], [], 0)
  rescue Errno::EAGAIN
    puts "#{i}: FAIL"
    passes = false
  ensure
    rp.close
    wp.close
  end
  puts "#{i}: PASS" if passes
end

I can't tell for certain if this is a bug or a misinterpretation of the specification.我不能确定这是一个错误还是对规范的误解。 Thoughts?想法?

You're using pipes here.你在这里使用管道。 Pipes have a fun atomic write property --- writes smaller than PIPE_BUF (4096 here) bytes are guaranteed to be atomic.管道有一个有趣的原子写入属性 --- 小于 PIPE_BUF(此处为 4096)字节的写入保证是原子的。 So a write to a pipe can fail with EAGAIN even if it's possible to write a smaller number of bytes to the pipe.因此,即使可以向管道写入较少数量的字节,写入管道也可能会因 EAGAIN 而失败。

I'm not sure if that's what you're running into here (I haven't looked too closely), but this behaviour is documented in man 7 pipe.我不确定这是否是您在这里遇到的情况(我没有仔细观察),但这种行为记录在 man 7 pipe 中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM