当select将非阻塞fd返回EAGAIN报告为可写时,是否可以将write()写入该fd?
我正试图追踪OSX10.8.2中的一些奇怪行为。基本上,我打开一个管道,用数据填充它,直到它无法写入。然而,我发现,根据我试图写入的数据块的大小,有时我会从写入调用中获得EAGAIN,即使select声明管道仍然是可写的。下面是一些测试代码:当select将非阻塞fd返回EAGAIN报告为可写时,是否可以将write()写入该fd?,c,ruby,macos,nonblocking,C,Ruby,Macos,Nonblocking,我正试图追踪OSX10.8.2中的一些奇怪行为。基本上,我打开一个管道,用数据填充它,直到它无法写入。然而,我发现,根据我试图写入的数据块的大小,有时我会从写入调用中获得EAGAIN,即使select声明管道仍然是可写的。下面是一些测试代码: #include <unistd.h> #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/select.h&g
#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");
}
}
请注意,故障情况3是写入调用返回-1,但select仍然报告filehandle是可写的
下面是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还是对规范的误解。想法 您正在使用管道。管道有一个有趣的原子写入属性,写入小于管道4096的字节保证是原子的。因此,使用EAGAIN对管道的写入可能会失败,即使可以向管道写入少量字节
我不确定这是否就是您在这里遇到的情况我没有仔细查看,但这种行为在man 7 pipe中有记录。我不知道这是否有帮助,但在Mac OS X 10.7.5上,我得到了输出抱歉,它必须是一条注释,格式将很糟糕:1:字节计数15873,通过2:字节计数15874,通过3:字节计数15873,通过4:字节计数15876,通过5:字节计数15875,通过6:字节计数15876,通过7:字节计数15876,通过8:字节计数15880,通过9:字节计数15876,通过10:字节计数15880,通过11:字节计数15873,通过12:字节计数15876,通过13:字节计数15873,通过14:字节计数15876,通过15:字节计数15885,通过。select的POSIX规范规定,当对O_NONBLOCK clear的输出函数的调用不会阻塞时,描述符应被视为已准备好写入,函数是否会成功传输数据,但我不确定select如何确定,因为它不知道需要输出多少字节。在我看来,规范应该说。。。调用一个输出函数来传输一个字节,并且O_NONBLOCK clear不会阻塞…我在10.8.2中看到了失败,所以我认为这是山狮中引入的错误。ML还有一个特性,它可以将管道扩展到64kB,这在所有的失败中是很常见的。对于我来说,在64位Linux上也是这样,但是有一个64k的缓冲区@JonathanLeffler@MichaelBurr,或者,规范可以说select在描述符有足够的空间满足描述符的原子性保证时将其描述为就绪,即套接字的空间为1字节,而管道的空间为PIPE_BUF。Linux似乎有这样的解释,因为测试通过了。
(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