[mew-dist 25599] non-blocking connect()
Kazu Yamamoto ( 山本和彦 )
kazu at example.com
2004年 10月 28日 (木) 11:30:00 JST
山本です。
このメールは mew-dist、および関係のありそうな方にお送りしています。下記
のバグを直すために、次のようなお願いをしたいと思います。協力できる方は、
ぜひ協力して下さい。
[みなさんへのお願い]
Emacs current に以下のパッチを当てて、コンパイルし利用して下さ
い。いろいろなプラットフォームで試すことが重要です。BSD では問
題ないと核心していますが、Linux や Solaris がちょっと不安。
そして、問題が生じるか、生じないかを教えて下さい。
報告例:
NetBSD current: connect() の問題に遭遇したことがあり、パッチを
当てたところ、問題は解決した。
FreeBSD x.x: connect() の問題に遭遇したことはない。パッチを当
てた後も変らず良好に動作している。
[jinmei さんへのお願い]
専門家としてコードをレビューして頂けると嬉しいです。
[半田さんへのお願い]
Emacs 21.4 はいつごろ出るか教えて下さい。
また、このコードは取り込まれそうか、取り込まれるなら Emacs
21.4 に間に合いそうか、教えて下さい。
各プラットフォームでの動作報告が集まったら、英訳してフィードバックします。
--かず
[バグ]
src/process.c/make_network_process() の実装ミスにより、
open-network-stream() で TCP コネクションが作成できないことがあ
る
[解析]
make_network_process() でクライアント用の TCP コネクションを開
く際には、alarm() をセットして connect () します。alarm() のタ
イマーはコードを読む限り 0.1 秒のようです。
0.1 秒以内に connect() が完了すれば、問題は生じません。
完了しない場合、connect() は alarm() に割り込まれて、EINTR を返
します。そして、再び alarm() を設定し、「同じソケット」で
connect() を試みます。(retry_connect:)
これは多くの場合、エラーを返します。FreeBSD では EADDRINUSE が
返り、NetBSD では EALREADY が返ります。
EADDRINUSE が返った場合、1 秒 sleep() して、また
retry_connect: に行きます。
少なくとも現在のコードでは、EALREADY に対応していないので、
NetBSD ではコネクションは張れません。これは 3 名が経験していま
す。
FreeBSD でもコネクションを張るのに長い時間がかかることがあるよ
うです。1名経験。
[考察]
EALREADY を処理するコードを足せば、NetBSD でもコネクションが張
れるようになります。
しかし、FreeBSD で見られるような、効率の悪さは残ります。
[解決方法]
正しい解決方法は、non-blocking ソケットを使って、connect() する
ことです。オリジナルのコードには、以下のようなコメントがありま
す。
This turns off all alarm-based interrupts; the
bind_polling_period call above doesn't always turn all the
short-interval ones off, especially if interrupt_input is
set.
It'd be nice to be able to control the connect timeout
though. Would non-blocking connect calls be portable?
Emacs 21.3 にも同様のコメントがあります。
一方で、Emacs current にはすでに non-blockin ソケットのコードが
あるので、Emacs 21.3 時代に書かれた「portable?」という疑問は解
消しているはずです。
そこで、このコードをマージすることは、問題がないと考えます。
[パッチ]
以下のパッチでは、non-blocking ソケットをサポートをしているプラッ
トフォームでは、それを使って connect() します。上記の問題はすべ
て解決されています。
non-blocking ソケットをサポートしていないプラットフォームでは、
オリジナルのコードを使います。
インデントは、Emacs スタイルに合わせてあります。
Index: process.c
===================================================================
RCS file: /cvsroot/emacs/emacs/src/process.c,v
retrieving revision 1.442
diff -c -r1.442 process.c
*** process.c 29 Sep 2004 23:43:08 -0000 1.442
--- process.c 28 Oct 2004 02:05:41 -0000
***************
*** 2727,2732 ****
--- 2727,2737 ----
int is_server = 0, backlog = 5;
int socktype;
int family = -1;
+ #ifdef NON_BLOCKING_CONNECT
+ int flags, len;
+ fd_set rset, wset;
+ struct timeval timeout;
+ #endif
if (nargs == 0)
return Qnil;
***************
*** 3094,3100 ****
--- 3099,3146 ----
break;
}
+ #ifdef NON_BLOCKING_CONNECT
+ flags = fcntl(s, F_GETFL, 0);
+ ret = fcntl(s, F_SETFL, flags | O_NONBLOCK);
+ if (ret < 0) goto next;
+
+ ret = connect (s, lres->ai_addr, lres->ai_addrlen);
+ if (ret == 0)
+ {
+ if (fcntl(s, F_SETFL, flags) < 0)
+ goto next;
+ break;
+ }
+ if ((ret < 0) && (errno != EINPROGRESS)) goto next;
+ FD_ZERO(&rset);
+ FD_SET(s,&rset);
+ wset = rset;
+ timeout.tv_sec=2;
+ timeout.tv_usec=0;
+
+ immediate_quit = 1;
+ QUIT;
+ select(s+1, &rset, &wset, NULL, &timeout);
+ immediate_quit = 0;
+
+ if (FD_ISSET(s, &rset) || FD_ISSET(s, &wset))
+ {
+ len = sizeof(xerrno);
+ if (getsockopt(s, SOL_SOCKET, SO_ERROR, &xerrno, &len) < 0)
+ goto next;
+ if (xerrno != 0) {
+ errno = xerrno;
+ goto next;
+ }
+ if (fcntl(s, F_SETFL, flags) < 0)
+ goto next;
+ break;
+ }
+
+ next:
+ xerrno = errno;
+ #else /* NON_BLOCKING_CONNECT */
retry_connect:
immediate_quit = 1;
***************
*** 3149,3155 ****
retry++;
goto retry_connect;
}
!
/* Discard the unwind protect closing S. */
specpdl_ptr = specpdl + count1;
emacs_close (s);
--- 3195,3201 ----
retry++;
goto retry_connect;
}
! #endif /* NON_BLOCKING_CONNECT */
/* Discard the unwind protect closing S. */
specpdl_ptr = specpdl + count1;
emacs_close (s);
Mew-dist メーリングリストの案内