[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 メーリングリストの案内