我正在尝试追踪OS X(10.8.2)中的一些奇怪行为。基本上,我正在打开一个管道,并填充数据直到它无法写入。然而,我发现,根据我尝试写入的块的大小,有时即使select声称该管道仍然可写,我仍会从write()调用中得到EAGAIN。这是一些测试代码:
#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
我不能确定这是一个bug还是对规范的错误解释。你有什么想法吗?
1: byte count 15873, PASS 2: byte count 15874, PASS 3: byte count 15873, PASS 4: byte count 15876, PASS 5: byte count 15875, PASS 6: byte count 15876, PASS 7: byte count 15876, PASS 8: byte count 15880, PASS 9: byte count 15876, PASS 10: byte count 15880, PASS 11: byte count 15873, PASS 12: byte count 15876, PASS 13: byte count 15873, PASS 14: byte count 15876, PASS 15: byte count 15885, PASS
. - Jonathan Lefflerselect()
的说明是:“当使用一个没有设置O_NONBLOCK标志的输出函数调用不会阻塞时,描述符应被视为准备好写入,无论该函数是否成功传输数据”,但我不确定select()
如何做出这个决定,因为它无法知道请求输出多少字节。在我看来,规范可能应该这样说:“...使用一个没有设置O_NONBLOCK标志的输出函数调用传输一个字节不会阻塞...”。 - Michael Burr