昨日ちゅんさんと話していた話題。select(2)中に監視対象のディスクリプタを別スレッドからclose(2)したらどうなるの?epoll(2)だと?
以下のような事を試してみた。
thread1 thread2
--------------------------------
(r,w) <-pipe()
select(fdset = {r})
close(r)
close(w)
(r2, w2) <- pipe() // 同じfdになる
write(w2)
select(2)版コード
void* fd_consumer(void *writefd_){
int* pipefd = (int*)writefd_;
sleep(1);
close(pipefd[0]);
close(pipefd[1]);
cerr << "close(2) pipe" << endl;
sleep(1);
if(pipe(pipefd)){ perror("Failed to make pipe"); exit(1); }
cerr << "pipe(2) in another thread: " << pipefd[0] << ", " << pipefd[1] << endl;
sleep(1);
cerr << "write(2) to pipe" << endl;
char buf[] = "a";
write(pipefd[1], buf, 1);
return NULL;
}
int main(void){
int pipefd[2];
if(pipe(pipefd)){
perror("failed to make pipe");
exit(1);
}
cerr << "pipe(2): " << pipefd[0] << ", " << pipefd[1] << endl;
fd_set rfds;
fd_set wfds;
fd_set efds;
pthread_t thread;
pthread_create(&thread, NULL, fd_consumer, (void *)pipefd);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
FD_SET(pipefd[0], &rfds);
FD_SET(pipefd[0], &wfds);
FD_SET(pipefd[0], &efds);
struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0;
int r = select(pipefd[0]+1, &rfds, &wfds, &efds, &tv);
if(r < 0){
cerr << "select(2) error" << endl;
} else if (r == 0){
cerr << "select(2) timedout" << endl;
}else{
cerr << "select(2) catched " << r << " elements" << endl;
}
if(FD_ISSET(pipefd[0], &rfds))
cerr << "pipefd[0] set(rfds)" << endl;
if(FD_ISSET(pipefd[0], &wfds))
cerr << "pipefd[0] set(wfds)" << endl;
if(FD_ISSET(pipefd[0], &efds))
cerr << "pipefd[0] set(efds)" << endl;
return 0;
}
epoll(2)版コード
void* fd_consumer(void *writefd_){
int* pipefd = (int*)writefd_;
sleep(1);
close(pipefd[0]);
close(pipefd[1]);
cerr << "close(2) pipe" << endl;
sleep(1);
if(pipe(pipefd)){ perror("Failed to make pipe"); exit(1); }
cerr << "pipe(2) in another thread: " << pipefd[0] << ", " << pipefd[1] << endl;
sleep(1);
cerr << "write(2) to pipe" << endl;
char buf[] = "a";
write(pipefd[1], buf, 1);
return NULL;
}
int main(void){
int epfd = epoll_create(10);
int pipefd[2];
if(pipe(pipefd)){
perror("failed to make pipe");
exit(1);
}
cerr << "pipe(2): " << pipefd[0] << ", " << pipefd[1] << endl;
struct epoll_event epev;
memset(&epev, 0, sizeof(epev));
epev.events = EPOLLIN;
epev.data.fd = pipefd[0];
epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], &epev);
pthread_t thread;
pthread_create(&thread, NULL, fd_consumer, (void *)pipefd);
struct epoll_event events[10];
int r = epoll_wait(epfd, events, 10, 5000);
if(r < 0){
cerr << "epoll(2) error" << endl;
} else if (r == 0){
cerr << "epoll(2) timedout" << endl;
}else{
cerr << "epoll(2) catched " << r << " elements" << endl;
}
for(int i=0; i<r; i++){
if(events[i].events & EPOLLIN)
cerr << "EPOLLIN" << endl;
if(events[i].events & EPOLLOUT)
cerr << "EPOLLOUT" << endl;
if(events[i].events & EPOLLERR)
cerr << "EPOLLERR" << endl;
if(events[i].events & EPOLLHUP)
cerr << "EPOLLHUP" << endl;
}
return 0;
}
実行結果
mac% g++ seltest.cpp -Wall; time ./a.out
pipe(2): 3, 4
select(2) error
pipefd[0] set(rfds)
pipefd[0] set(wfds)
pipefd[0] set(efds)
./a.out 0.00s user 0.01s system 0% cpu 1.011 total
linux% g++ -Wall seltest.cpp -lpthread; time ./a.out
pipe(2): 3, 4
close(2) pipe
pipe(2) in another thread: 3, 4
write(2) to pipe
select(2) catched 1 elements
pipefd[0] set(rfds)
./a.out 0.00s user 0.00s system 0% cpu 5.007 total
linux% g++ -Wall epolltest.cpp -lpthread; time ./a.out
pipe(2): 4, 5
close(2) pipe
pipe(2) in another thread: 4, 5
write(2) to pipe
epoll(2) timedout
./a.out 0.00s user 0.00s system 0% cpu 5.007 total
結果
select(2)だとMacOS XとLinuxで挙動が違う。未定義動作?
Macの方の動作を意図してselect(2)を使ったプログラムを書いていたので、ちょっと困った。代替となるportableな方法無いかなぁ。