3

我正在尝试追踪 OS X (10.8.2) 中的一些奇怪行为。基本上,我正在打开一个管道,并用数据填充它,直到它不可写为止。然而,我发现,根据我尝试写入的块的大小,有时我会从 write() 调用中获得 EAGAIN,即使 select 声称管道仍然是可写的。这是一些测试代码:

#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");
    }

}

这是输出:

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

请注意,失败案例 3 是 write() 调用返回 -1,但 select 仍然报告文件句柄是可写的。

这是 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

我无法确定这是错误还是对规范的误解。想法?

4

1 回答 1

3

你在这里使用管道。管道有一个有趣的原子写入属性 --- 小于 PIPE_BUF(此处为 4096)字节的写入保证是原子的。因此,即使可以向管道写入较少数量的字节,对管道的写入也会因 EAGAIN 失败。

我不确定这是否是您在这里遇到的(我没有仔细观察),但是这种行为记录在 man 7 pipe 中。

于 2012-11-24T07:50:29.667 回答