mirror of
https://github.com/python/cpython.git
synced 2025-09-14 04:37:29 +00:00
Patch #1731049: make threading.py use a proper "raise" when checking internal state, rather than assert statements (which get stripped out by -O).
This commit is contained in:
parent
956f0f71f9
commit
50b79ce8e6
3 changed files with 102 additions and 39 deletions
|
@ -174,11 +174,14 @@ until a call to \method{release()} in another thread changes it to
|
||||||
unlocked, then the \method{acquire()} call resets it to locked and
|
unlocked, then the \method{acquire()} call resets it to locked and
|
||||||
returns. The \method{release()} method should only be called in the
|
returns. The \method{release()} method should only be called in the
|
||||||
locked state; it changes the state to unlocked and returns
|
locked state; it changes the state to unlocked and returns
|
||||||
immediately. When more than one thread is blocked in
|
immediately. If an attempt is made to release an unlocked lock, a
|
||||||
\method{acquire()} waiting for the state to turn to unlocked, only one
|
\exception{RuntimeError} will be raised.
|
||||||
thread proceeds when a \method{release()} call resets the state to
|
|
||||||
unlocked; which one of the waiting threads proceeds is not defined,
|
When more than one thread is blocked in \method{acquire()} waiting for
|
||||||
and may vary across implementations.
|
the state to turn to unlocked, only one thread proceeds when a
|
||||||
|
\method{release()} call resets the state to unlocked; which one of the
|
||||||
|
waiting threads proceeds is not defined, and may vary across
|
||||||
|
implementations.
|
||||||
|
|
||||||
All methods are executed atomically.
|
All methods are executed atomically.
|
||||||
|
|
||||||
|
@ -257,8 +260,9 @@ become unlocked, allow exactly one of them to proceed. If after the
|
||||||
decrement the recursion level is still nonzero, the lock remains
|
decrement the recursion level is still nonzero, the lock remains
|
||||||
locked and owned by the calling thread.
|
locked and owned by the calling thread.
|
||||||
|
|
||||||
Only call this method when the calling thread owns the lock.
|
Only call this method when the calling thread owns the lock. A
|
||||||
Do not call this method when the lock is unlocked.
|
\exception{RuntimeError} is raised if this method is called when the
|
||||||
|
lock is unlocked.
|
||||||
|
|
||||||
There is no return value.
|
There is no return value.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
@ -275,7 +279,8 @@ A condition variable has \method{acquire()} and \method{release()}
|
||||||
methods that call the corresponding methods of the associated lock.
|
methods that call the corresponding methods of the associated lock.
|
||||||
It also has a \method{wait()} method, and \method{notify()} and
|
It also has a \method{wait()} method, and \method{notify()} and
|
||||||
\method{notifyAll()} methods. These three must only be called when
|
\method{notifyAll()} methods. These three must only be called when
|
||||||
the calling thread has acquired the lock.
|
the calling thread has acquired the lock, otherwise a
|
||||||
|
\exception{RuntimeError} is raised.
|
||||||
|
|
||||||
The \method{wait()} method releases the lock, and then blocks until it
|
The \method{wait()} method releases the lock, and then blocks until it
|
||||||
is awakened by a \method{notify()} or \method{notifyAll()} call for
|
is awakened by a \method{notify()} or \method{notifyAll()} call for
|
||||||
|
@ -343,9 +348,9 @@ lock; there is no return value.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{wait}{\optional{timeout}}
|
\begin{methoddesc}{wait}{\optional{timeout}}
|
||||||
Wait until notified or until a timeout occurs.
|
Wait until notified or until a timeout occurs. If the calling thread
|
||||||
This must only be called when the calling thread has acquired the
|
has not acquired the lock when this method is called, a
|
||||||
lock.
|
\exception{RuntimeError} is raised.
|
||||||
|
|
||||||
This method releases the underlying lock, and then blocks until it is
|
This method releases the underlying lock, and then blocks until it is
|
||||||
awakened by a \method{notify()} or \method{notifyAll()} call for the
|
awakened by a \method{notify()} or \method{notifyAll()} call for the
|
||||||
|
@ -367,9 +372,10 @@ when the lock is reacquired.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{notify}{}
|
\begin{methoddesc}{notify}{}
|
||||||
Wake up a thread waiting on this condition, if any.
|
Wake up a thread waiting on this condition, if any. Wait until
|
||||||
This must only be called when the calling thread has acquired the
|
notified or until a timeout occurs. If the calling thread has not
|
||||||
lock.
|
acquired the lock when this method is called, a
|
||||||
|
\exception{RuntimeError} is raised.
|
||||||
|
|
||||||
This method wakes up one of the threads waiting for the condition
|
This method wakes up one of the threads waiting for the condition
|
||||||
variable, if any are waiting; it is a no-op if no threads are waiting.
|
variable, if any are waiting; it is a no-op if no threads are waiting.
|
||||||
|
@ -386,7 +392,9 @@ Note: the awakened thread does not actually return from its
|
||||||
|
|
||||||
\begin{methoddesc}{notifyAll}{}
|
\begin{methoddesc}{notifyAll}{}
|
||||||
Wake up all threads waiting on this condition. This method acts like
|
Wake up all threads waiting on this condition. This method acts like
|
||||||
\method{notify()}, but wakes up all waiting threads instead of one.
|
\method{notify()}, but wakes up all waiting threads instead of one. If
|
||||||
|
the calling thread has not acquired the lock when this method is
|
||||||
|
called, a \exception{RuntimeError} is raised.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
|
|
||||||
|
@ -404,8 +412,9 @@ finds that it is zero, it blocks, waiting until some other thread
|
||||||
calls \method{release()}.
|
calls \method{release()}.
|
||||||
|
|
||||||
\begin{classdesc}{Semaphore}{\optional{value}}
|
\begin{classdesc}{Semaphore}{\optional{value}}
|
||||||
The optional argument gives the initial value for the internal
|
The optional argument gives the initial \var{value} for the internal
|
||||||
counter; it defaults to \code{1}.
|
counter; it defaults to \code{1}. If the \var{value} given is less
|
||||||
|
than 0, \exception{ValueError} is raised.
|
||||||
\end{classdesc}
|
\end{classdesc}
|
||||||
|
|
||||||
\begin{methoddesc}{acquire}{\optional{blocking}}
|
\begin{methoddesc}{acquire}{\optional{blocking}}
|
||||||
|
@ -586,9 +595,12 @@ before doing anything else to the thread.
|
||||||
\begin{methoddesc}{start}{}
|
\begin{methoddesc}{start}{}
|
||||||
Start the thread's activity.
|
Start the thread's activity.
|
||||||
|
|
||||||
This must be called at most once per thread object. It
|
It must be called at most once per thread object. It arranges for the
|
||||||
arranges for the object's \method{run()} method to be invoked in a
|
object's \method{run()} method to be invoked in a separate thread of
|
||||||
separate thread of control.
|
control.
|
||||||
|
|
||||||
|
This method will raise a \exception{RuntimeException} if called more
|
||||||
|
than once on the same thread object.
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{run}{}
|
\begin{methoddesc}{run}{}
|
||||||
|
@ -618,11 +630,10 @@ operation will block until the thread terminates.
|
||||||
|
|
||||||
A thread can be \method{join()}ed many times.
|
A thread can be \method{join()}ed many times.
|
||||||
|
|
||||||
A thread cannot join itself because this would cause a
|
\method{join()} may throw a \exception{RuntimeError}, if an attempt is
|
||||||
deadlock.
|
made to join the current thread as that would cause a deadlock. It is
|
||||||
|
also an error to \method{join()} a thread before it has been started
|
||||||
It is an error to attempt to \method{join()} a thread before it has
|
and attempts to do so raises same exception.
|
||||||
been started.
|
|
||||||
\end{methoddesc}
|
\end{methoddesc}
|
||||||
|
|
||||||
\begin{methoddesc}{getName}{}
|
\begin{methoddesc}{getName}{}
|
||||||
|
@ -651,7 +662,8 @@ Return the thread's daemon flag.
|
||||||
|
|
||||||
\begin{methoddesc}{setDaemon}{daemonic}
|
\begin{methoddesc}{setDaemon}{daemonic}
|
||||||
Set the thread's daemon flag to the Boolean value \var{daemonic}.
|
Set the thread's daemon flag to the Boolean value \var{daemonic}.
|
||||||
This must be called before \method{start()} is called.
|
This must be called before \method{start()} is called, otherwise
|
||||||
|
\exception{RuntimeError} is raised.
|
||||||
|
|
||||||
The initial value is inherited from the creating thread.
|
The initial value is inherited from the creating thread.
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import test.test_support
|
import test.test_support
|
||||||
from test.test_support import verbose
|
from test.test_support import verbose
|
||||||
import random
|
import random
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import thread
|
import thread
|
||||||
import time
|
import time
|
||||||
|
@ -201,8 +202,47 @@ class ThreadTests(unittest.TestCase):
|
||||||
t.join()
|
t.join()
|
||||||
# else the thread is still running, and we have no way to kill it
|
# else the thread is still running, and we have no way to kill it
|
||||||
|
|
||||||
|
class ThreadingExceptionTests(unittest.TestCase):
|
||||||
|
# A RuntimeError should be raised if Thread.start() is called
|
||||||
|
# multiple times.
|
||||||
|
def test_start_thread_again(self):
|
||||||
|
thread = threading.Thread()
|
||||||
|
thread.start()
|
||||||
|
self.assertRaises(RuntimeError, thread.start)
|
||||||
|
|
||||||
|
def test_releasing_unacquired_rlock(self):
|
||||||
|
rlock = threading.RLock()
|
||||||
|
self.assertRaises(RuntimeError, rlock.release)
|
||||||
|
|
||||||
|
def test_waiting_on_unacquired_condition(self):
|
||||||
|
cond = threading.Condition()
|
||||||
|
self.assertRaises(RuntimeError, cond.wait)
|
||||||
|
|
||||||
|
def test_notify_on_unacquired_condition(self):
|
||||||
|
cond = threading.Condition()
|
||||||
|
self.assertRaises(RuntimeError, cond.notify)
|
||||||
|
|
||||||
|
def test_semaphore_with_negative_value(self):
|
||||||
|
self.assertRaises(ValueError, threading.Semaphore, value = -1)
|
||||||
|
self.assertRaises(ValueError, threading.Semaphore, value = -sys.maxint)
|
||||||
|
|
||||||
|
def test_joining_current_thread(self):
|
||||||
|
currentThread = threading.currentThread()
|
||||||
|
self.assertRaises(RuntimeError, currentThread.join);
|
||||||
|
|
||||||
|
def test_joining_inactive_thread(self):
|
||||||
|
thread = threading.Thread()
|
||||||
|
self.assertRaises(RuntimeError, thread.join)
|
||||||
|
|
||||||
|
def test_daemonize_active_thread(self):
|
||||||
|
thread = threading.Thread()
|
||||||
|
thread.start()
|
||||||
|
self.assertRaises(RuntimeError, thread.setDaemon, True)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
test.test_support.run_unittest(ThreadTests)
|
test.test_support.run_unittest(ThreadTests,
|
||||||
|
ThreadingExceptionTests)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
|
@ -111,8 +111,8 @@ class _RLock(_Verbose):
|
||||||
__enter__ = acquire
|
__enter__ = acquire
|
||||||
|
|
||||||
def release(self):
|
def release(self):
|
||||||
me = currentThread()
|
if self.__owner is not currentThread():
|
||||||
assert self.__owner is me, "release() of un-acquire()d lock"
|
raise RuntimeError("cannot release un-aquired lock")
|
||||||
self.__count = count = self.__count - 1
|
self.__count = count = self.__count - 1
|
||||||
if not count:
|
if not count:
|
||||||
self.__owner = None
|
self.__owner = None
|
||||||
|
@ -204,7 +204,8 @@ class _Condition(_Verbose):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def wait(self, timeout=None):
|
def wait(self, timeout=None):
|
||||||
assert self._is_owned(), "wait() of un-acquire()d lock"
|
if not self._is_owned():
|
||||||
|
raise RuntimeError("cannot wait on un-aquired lock")
|
||||||
waiter = _allocate_lock()
|
waiter = _allocate_lock()
|
||||||
waiter.acquire()
|
waiter.acquire()
|
||||||
self.__waiters.append(waiter)
|
self.__waiters.append(waiter)
|
||||||
|
@ -245,7 +246,8 @@ class _Condition(_Verbose):
|
||||||
self._acquire_restore(saved_state)
|
self._acquire_restore(saved_state)
|
||||||
|
|
||||||
def notify(self, n=1):
|
def notify(self, n=1):
|
||||||
assert self._is_owned(), "notify() of un-acquire()d lock"
|
if not self._is_owned():
|
||||||
|
raise RuntimeError("cannot notify on un-aquired lock")
|
||||||
__waiters = self.__waiters
|
__waiters = self.__waiters
|
||||||
waiters = __waiters[:n]
|
waiters = __waiters[:n]
|
||||||
if not waiters:
|
if not waiters:
|
||||||
|
@ -273,7 +275,8 @@ class _Semaphore(_Verbose):
|
||||||
# After Tim Peters' semaphore class, but not quite the same (no maximum)
|
# After Tim Peters' semaphore class, but not quite the same (no maximum)
|
||||||
|
|
||||||
def __init__(self, value=1, verbose=None):
|
def __init__(self, value=1, verbose=None):
|
||||||
assert value >= 0, "Semaphore initial value must be >= 0"
|
if value < 0:
|
||||||
|
raise ValueError("semaphore initial value must be >= 0")
|
||||||
_Verbose.__init__(self, verbose)
|
_Verbose.__init__(self, verbose)
|
||||||
self.__cond = Condition(Lock())
|
self.__cond = Condition(Lock())
|
||||||
self.__value = value
|
self.__value = value
|
||||||
|
@ -424,8 +427,10 @@ class Thread(_Verbose):
|
||||||
return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status)
|
return "<%s(%s, %s)>" % (self.__class__.__name__, self.__name, status)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
assert self.__initialized, "Thread.__init__() not called"
|
if not self.__initialized:
|
||||||
assert not self.__started, "thread already started"
|
raise RuntimeError("thread.__init__() not called")
|
||||||
|
if self.__started:
|
||||||
|
raise RuntimeError("thread already started")
|
||||||
if __debug__:
|
if __debug__:
|
||||||
self._note("%s.start(): starting thread", self)
|
self._note("%s.start(): starting thread", self)
|
||||||
_active_limbo_lock.acquire()
|
_active_limbo_lock.acquire()
|
||||||
|
@ -545,9 +550,13 @@ class Thread(_Verbose):
|
||||||
_active_limbo_lock.release()
|
_active_limbo_lock.release()
|
||||||
|
|
||||||
def join(self, timeout=None):
|
def join(self, timeout=None):
|
||||||
assert self.__initialized, "Thread.__init__() not called"
|
if not self.__initialized:
|
||||||
assert self.__started, "cannot join thread before it is started"
|
raise RuntimeError("Thread.__init__() not called")
|
||||||
assert self is not currentThread(), "cannot join current thread"
|
if not self.__started:
|
||||||
|
raise RuntimeError("cannot join thread before it is started")
|
||||||
|
if self is currentThread():
|
||||||
|
raise RuntimeError("cannot join current thread")
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
if not self.__stopped:
|
if not self.__stopped:
|
||||||
self._note("%s.join(): waiting until thread stops", self)
|
self._note("%s.join(): waiting until thread stops", self)
|
||||||
|
@ -590,8 +599,10 @@ class Thread(_Verbose):
|
||||||
return self.__daemonic
|
return self.__daemonic
|
||||||
|
|
||||||
def setDaemon(self, daemonic):
|
def setDaemon(self, daemonic):
|
||||||
assert self.__initialized, "Thread.__init__() not called"
|
if not self.__initialized:
|
||||||
assert not self.__started, "cannot set daemon status of active thread"
|
raise RuntimeError("Thread.__init__() not called")
|
||||||
|
if self.__started:
|
||||||
|
raise RuntimeError("cannot set daemon status of active thread");
|
||||||
self.__daemonic = daemonic
|
self.__daemonic = daemonic
|
||||||
|
|
||||||
# The timer class was contributed by Itamar Shtull-Trauring
|
# The timer class was contributed by Itamar Shtull-Trauring
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue