# HG changeset patch # User Torsten Landschoff # Date 1266618770 -3600 # Branch py3k # Node ID 59f8723d1371fa3e7ec3e73fb76d5ffeb7c2d1d3 # Parent eaa113d8b268ae1b6101969d2ac972cb1f36c83e New optional timeout parameter for Semaphore.acquire(). Updated patch based on the patch by Dmitry Vasiliev in from issue 850728, including test cases and documentation. diff -r eaa113d8b268 -r 59f8723d1371 Doc/library/threading.rst --- a/Doc/library/threading.rst Fri Feb 19 17:04:36 2010 +0100 +++ b/Doc/library/threading.rst Fri Feb 19 23:32:50 2010 +0100 @@ -572,7 +572,7 @@ defaults to ``1``. If the *value* given is less than 0, :exc:`ValueError` is raised. - .. method:: acquire(blocking=True) + .. method:: acquire(blocking=True, timeout=None) Acquire a semaphore. @@ -583,14 +583,20 @@ interlocking so that if multiple :meth:`acquire` calls are blocked, :meth:`release` will wake exactly one of them up. The implementation may pick one at random, so the order in which blocked threads are awakened - should not be relied on. There is no return value in this case. + should not be relied on. Returns true (or blocks indefinitely). - When invoked with *blocking* set to true, do the same thing as when called + If a *timeout* other than None is passed and *blocking* is true (or not + given at all), it will block for at most *timeout* seconds. If acquire + does not complete successfully in that interval, return false. Return + true otherwise. + + When invoked with *blocking* set to false, do not block (equivalent to + passing a zero *timeout*). If a call without an argument would block, + return false immediately; otherwise, do the same thing as when called without arguments, and return true. - When invoked with *blocking* set to false, do not block. If a call - without an argument would block, return false immediately; otherwise, do - the same thing as when called without arguments, and return true. + .. versionchanged:: 3.1.2 + The *timeout* parameter is new. .. method:: release() diff -r eaa113d8b268 -r 59f8723d1371 Lib/test/lock_tests.py --- a/Lib/test/lock_tests.py Fri Feb 19 17:04:36 2010 +0100 +++ b/Lib/test/lock_tests.py Fri Feb 19 23:32:50 2010 +0100 @@ -487,6 +487,30 @@ # ordered. self.assertEqual(sorted(results), [False] * 7 + [True] * 3 ) + def test_acquire_timeout(self): + sem = self.semtype(2) + self.assertTrue(sem.acquire(timeout=0.005)) + self.assertTrue(sem.acquire(timeout=0.005)) + self.assertFalse(sem.acquire(timeout=0.005)) + sem.release() + self.assertTrue(sem.acquire(timeout=0.005)) + + timeout = 1.0 + fuzz = 2.0 + t0 = time.time() + self.assertFalse(sem.acquire(timeout=timeout)) + delta = time.time() - t0 + self.assertTrue(delta >= timeout and delta < timeout+fuzz) + + def test_acquire_timeout_nonblock(self): + sem = self.semtype(0) + timeout = 1.0 + fuzz = 0.1 + t0 = time.time() + self.assertFalse(sem.acquire(blocking=False, timeout=timeout)) + dt = time.time() - t0 + self.assertTrue(dt <= fuzz * timeout) + def test_default_value(self): # The default initial value is 1. sem = self.semtype() diff -r eaa113d8b268 -r 59f8723d1371 Lib/threading.py --- a/Lib/threading.py Fri Feb 19 17:04:36 2010 +0100 +++ b/Lib/threading.py Fri Feb 19 23:32:50 2010 +0100 @@ -301,7 +301,7 @@ self._cond = Condition(Lock()) self._value = value - def acquire(self, blocking=True): + def acquire(self, blocking=True, timeout=None): rc = False self._cond.acquire() while self._value == 0: @@ -310,7 +310,13 @@ if __debug__: self._note("%s.acquire(%s): blocked waiting, value=%s", self, blocking, self._value) - self._cond.wait() + if timeout is not None: + endtime = _time() + timeout + self._cond.wait(timeout) + if timeout is not None and self._value == 0: + timeout = endtime - _time() + if timeout <= 0: + break else: self._value = self._value - 1 if __debug__: