2012-11-24 14 views
3

我試圖追查下OS X(10.8.2)中的一些奇怪的行爲。基本上,我打開一個管道,並填充數據,直到它不可寫。然而,我發現根據我嘗試寫入的塊的大小,有時候我會從write()調用中獲得EAGAIN,即使select聲明管道仍然可寫。下面是一些測試代碼:當select選擇將其報告爲可寫時,可以將()寫入非阻塞fd返回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,但仍然選擇報告的文件句柄是可寫的。

這裏是紅寶石更短的例子,顯示了同樣的故障:

(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 

我不能告訴了肯定,如果這是一個錯誤或規範的誤解。思考?

+1

我不知道它是否有幫助,但在Mac OS X 10.7.5上,我得到了輸出(對不起,它必須是註釋,格式會很糟糕):'1:byte count 15873 ,PASS 2:字節數15874,PASS 3:字節數15873,PASS 4:字節數15876,PASS 5:字節數15875,PASS 6:字節數15876,PASS 7:字節數15876,PASS 8:字節數15880,PASS 9:字節數15876,PASS 10:字節數15880,PASS 11:字節數15873,PASS 12:字節數15876,PASS 13:字節數15873,PASS 14 :字節c ount 15876,PASS 15:字節計數15885,合格 '。 –

+1

對於'select()'的POSIX規範說,當清除O_NONBLOCK的調用對輸出函數的調用不會阻塞,函數是否會成功傳輸數據時,描述符應被視爲已準備好寫入「,但我不確定'select()'是如何做出這個決定的,因爲它不知道要輸出多少個字節。在我看來,規範應該可能會說,「...調用輸出函數傳輸一個字節與O_NONBLOCK清除不會阻止...」。 –

+0

我在10.8.2中看到了失敗,所以我認爲這是在Mountain Lion中引入的錯誤。 ML還有一個「功能」,它將管道擴展到64kB,這在所有故障中似乎都是常見的。 –

回答

3

您在這裏使用管道。管道有一個有趣的原子寫屬性---寫入比PIPE_BUF(4096這裏)小的字節保證是原子的。因此,即使可以向管道寫入較少的字節數,對管道的寫入也可能會因EAGAIN而失敗。

我不確定這是你在這裏遇到的情況(我沒有看得太緊),但是這種行爲記錄在man 7管道中。

相關問題