ruby-1.8.7-p75

ChangeLog

Sun Jan  4 04:45:26 2009  Nobuyoshi Nakada  <nobu@...>

	* win32/win32.c (rb_w32_select): recalc the rest of timeout for each
	  iterations.  [ruby-core:18015]

ソースコードの修正内容

Index: test/ruby/test_sleep.rb
===================================================================
--- test/ruby/test_sleep.rb	(.../v1_8_7_74)	(revision 0)
+++ test/ruby/test_sleep.rb	(.../v1_8_7_75)	(revision 22264)
@@ -0,0 +1,10 @@
+require 'test/unit'
+
+class TestSleep < Test::Unit::TestCase
+  def test_sleep_5sec
+    start = Time.now
+    sleep 5
+    slept = Time.now-start
+    assert_in_delta(5.0, slept, 0.1, "[ruby-core:18015]: longer than expected")
+  end
+end

Property changes on: test/ruby/test_sleep.rb
___________________________________________________________________
Added: svn:eol-style
   + LF

Index: win32/win32.c
===================================================================
--- win32/win32.c	(.../v1_8_7_74)	(revision 22264)
+++ win32/win32.c	(.../v1_8_7_75)	(revision 22264)
@@ -2177,7 +2177,7 @@
 }
 
 static inline int
-subst(struct timeval *rest, const struct timeval *wait)
+subtract(struct timeval *rest, const struct timeval *wait)
 {
     while (rest->tv_usec < wait->tv_usec) {
 	if (rest->tv_sec <= wait->tv_sec) {
@@ -2207,8 +2207,8 @@
 
 #undef Sleep
 long 
-rb_w32_select (int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
-	       struct timeval *timeout)
+rb_w32_select(int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
+	      struct timeval *timeout)
 {
     long r;
     fd_set pipe_rd;
@@ -2216,11 +2216,29 @@
     fd_set else_rd;
     fd_set else_wr;
     int nonsock = 0;
+    struct timeval limit;
 
     if (nfds < 0 || (timeout && (timeout->tv_sec < 0 || timeout->tv_usec < 0))) {
 	errno = EINVAL;
 	return -1;
     }
+
+    if (timeout) {
+	if (timeout->tv_sec < 0 ||
+	    timeout->tv_usec < 0 ||
+	    timeout->tv_usec >= 1000000) {
+	    errno = EINVAL;
+	    return -1;
+	}
+	gettimeofday(&limit, NULL);
+	limit.tv_sec += timeout->tv_sec;
+	limit.tv_usec += timeout->tv_usec;
+	if (limit.tv_usec >= 1000000) {
+	    limit.tv_usec -= 1000000;
+	    limit.tv_sec++;
+	}
+    }
+
     if (!NtSocketsInitialized) {
 	StartSockets();
     }
@@ -2253,10 +2271,9 @@
 	struct timeval rest;
 	struct timeval wait;
 	struct timeval zero;
-	if (timeout) rest = *timeout;
 	wait.tv_sec = 0; wait.tv_usec = 10 * 1000; // 10ms
 	zero.tv_sec = 0; zero.tv_usec = 0;         //  0ms
-	do {
+	for (;;) {
 	    if (nonsock) {
 		// modifying {else,pipe,cons}_rd is safe because
 		// if they are modified, function returns immediately.
@@ -2272,8 +2289,7 @@
 		break;
 	    }
 	    else {
-		struct timeval *dowait =
-		    compare(&rest, &wait) < 0 ? &rest : &wait;
+		struct timeval *dowait = &wait;
 
 		fd_set orig_rd;
 		fd_set orig_wr;
@@ -2287,10 +2303,16 @@
 		if (wr) *wr = orig_wr;
 		if (ex) *ex = orig_ex;
 
-		// XXX: should check the time select spent
+		if (timeout) {
+		    struct timeval now;
+		    gettimeofday(&now, NULL);
+		    rest = limit;
+		    if (!subtract(&rest, &now)) break;
+		    if (compare(&rest, &wait) < 0) dowait = &rest;
+		}
 		Sleep(dowait->tv_sec * 1000 + dowait->tv_usec / 1000);
 	    }
-	} while (!timeout || subst(&rest, &wait));
+	}
     }
 
     return r;

不具合の内容

Windows において、sleep メソッドの結果が期待していたものよりも長い。
sleep(5) により約 6.5 秒の間、待機したと報告されている。

修正の影響や注意点

Windows のみ。ruby の処理系の不具合などで、rb_w32_select の timeout 引き数が不正だった場合、エラーを返すようになっている。
ただし、rb_w32_select がどのように動作し、この修正によりどのような影響があるのか分かっていません。手元に環境がなく、修正前後でどのように動作するかもわかりません。
だれか解説してー。