classification
Title: dummy_threading: .is_alive method returns True after execution has completed
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.7, Python 3.6
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: Jeffrey.Kintscher, njatkinson, pitrou, xiang.zhang, xtreak
Priority: normal Keywords:

Created on 2018-06-06 02:00 by njatkinson, last changed 2019-12-30 11:10 by methane. This issue is now closed.

Messages (8)
msg318795 - (view) Author: Nate Atkinson (njatkinson) Date: 2018-06-06 02:00
Here's what I expect to happen (Python2 behavior):


Python 2.7.14+ (default, Dec  5 2017, 15:17:02)
[GCC 7.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from dummy_threading import Thread
>>> def f(): print 'foo'
...
>>> t = Thread(target=f)
>>> t.start()
foo
>>> t.is_alive()
False
>>>




Here's what actually happens (Python3.6):


Python 3.6.4 (default, Jan  5 2018, 02:13:53)
[GCC 7.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dummy_threading import Thread
>>> def f(): print('foo')
...
>>> t = Thread(target=f)
>>> t.start()
foo
>>> t.is_alive()
True
>>>



After completion of the target function, I would expect .is_alive() to return False for an instance of dummy_thread.Thread. Instead, it returns True until the .join() method of the instance of dummy_thread.Thread is called.
msg318796 - (view) Author: Nate Atkinson (njatkinson) Date: 2018-06-06 02:02
I notice that I maybe have inadvertently assigned this to the wrong group. I suspect that this should apply to the "Library" rather than "Core". Sorry!
msg340596 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-04-21 06:05
https://bugs.python.org/issue29376#msg286873 . Is this an intentional behavior for dummy_threading to always return True for is_alive?
msg340601 - (view) Author: Nate Atkinson (njatkinson) Date: 2019-04-21 07:17
To be clear-- is_alive() doesn't *always* return True. It returns True until .join() is called.


Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dummy_threading import Thread
>>> def f(): print('foo')
...
>>> t = Thread(target=f)
>>> t.start()
foo
>>> t.is_alive()
True
>>> t.join()
>>> t.is_alive()
False



I would expect is_alive to return True while the target function is executing and return False after the execution has completed. Instead, .is_alive is continuing to return True after execution of the target function has completed.
msg341368 - (view) Author: Jeffrey Kintscher (Jeffrey.Kintscher) * Date: 2019-05-04 01:27
This behavior still exists in 3.7.3:

Python 3.7.3 (default, May  1 2019, 00:00:47) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from dummy_threading import Thread
>>> def f(): print('foo')
... 
>>> t = Thread(target=f)
>>> t.start()
foo
>>> t.is_alive()
True
>>> t.join()
>>> t.is_alive()
False


It is inconsistent with the threading module behavior (which matches the 2.x behavior):


Python 3.7.3 (default, May  1 2019, 00:00:47) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from threading import Thread
>>> def f(): print('foo')
... 
>>> t = Thread(target=f)
>>> t.start()
foo
>>> t.is_alive()
False
>>> t.join()
>>> t.is_alive()
False

I would classify this as a bug since the documentation claims the dummy_threading module is supposed to be a drop-in replacement for the threading module.
msg341369 - (view) Author: Jeffrey Kintscher (Jeffrey.Kintscher) * Date: 2019-05-04 01:39
I also see inconsistent behaviorbetween the enumerate() functions in threading and dummy_threading:


Python 3.7.3 (default, May  1 2019, 00:00:47) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from threading import Thread
>>> from threading import enumerate
>>> enumerate()
[<_MainThread(MainThread, started 4618048960)>]
>>> def f(): print('foo')
... 
>>> t = Thread(target=f)
>>> enumerate()
[<_MainThread(MainThread, started 4618048960)>]
>>> t.start()
foo
>>> enumerate()
[<_MainThread(MainThread, started 4618048960)>]
>>> t.is_alive()
False
>>> enumerate()
[<_MainThread(MainThread, started 4618048960)>]
>>> t.join()
>>> enumerate()
[<_MainThread(MainThread, started 4618048960)>]


Python 3.7.3 (default, May  1 2019, 00:00:47) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from dummy_threading import Thread
>>> from dummy_threading import enumerate
>>> enumerate()
[<_MainThread(MainThread, started 1)>]
>>> def f(): print('foo')
... 
>>> t = Thread(target=f)
>>> enumerate()
[<_MainThread(MainThread, started 1)>]
>>> t.start()
foo
>>> enumerate()
[]
>>> t.is_alive()
True
>>> enumerate()
[]
>>> t.join()
>>> enumerate()
[<_DummyThread(Dummy-2, started daemon 1)>]
msg341695 - (view) Author: Jeffrey Kintscher (Jeffrey.Kintscher) * Date: 2019-05-07 07:54
It looks like the problem is that Thread._tstate_lock doesn't get released until Thread.join() is called. _tstate_lock is of type LockType, which has different definitions when using the threading module or the dummy_threading module. It is defined in Modules/_threadmodule.c when using the threading module, and in Lib/_dummy_thread.py when using the dummy_threading module.

The lock is acquired when the new thread starts and is supposed to be released when the thread exits. Thread.is_alive() and Thread.join() both call Thread._wait_for_tstate_lock(), which in turn calls Thread._tstate_lock.acquire(). When Thread._wait_for_tstate_lock() successfully acquires the lock, it calls Thread._stop() to set the Thread._is_stop flag to indicate that the thread has exited. The Thread._is_stop flag is checked by Thread.is_alive() before trying to acquire the lock.

Thread.is_alive() passes False to Thread._wait_for_tstate_lock(), while Thread.join() effectively passes True as the default parameter. Thread._wait_for_tstate_lock() then passes the parameter value to Thread._tstate_lock.acquire() to indicate whether to block until the lock is acquired (True) or try to acquire the lock and return immediately (False).

The return value of the LockType.acquire() function indicates whether (True) or not (False) the lock was acquired. The function defined in the dummy_threading module always returns True when passed True and False when passed False. Since Thread.is_alive() passes False to Thread._wait_for_tstate_lock() and onwards to Thread._tstate_lock.acquire(), Thread._tstate_lock.acquire() returns False which causes Thread._wait_for_tstate_lock() to skip calling Thread._stop() and the Thread._is_stop flag doesn't get set. Thread.is_alive() returns "not self._is_stop", so it always returns True.

Thread.join() passes True, so Thread._tstate_lock.acquire() returns True and Thread._wait_for_tstate_lock() calls Thread._stop to set the Thread._is_stop flag. Subsequent calls to Thread.is_alive() see that Thread._is_stop flag is set and return False (i.e. "not self._is_stop") without checking the lock.

In a nutshell, the way the code is written, Thread.is_alive() always returns True until after Thread.join() is called.
msg359033 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-12-30 10:52
FWIW, dummy_threading and _dummy_thread were removed with 8bf08ee45b7c2341f0d0175b91892843a37c23da in Python 3.9.
History
Date User Action Args
2019-12-30 11:10:32methanesetstatus: open -> closed
resolution: wont fix
stage: resolved
2019-12-30 10:52:22xtreaksetmessages: + msg359033
2019-05-07 07:54:52Jeffrey.Kintschersetmessages: + msg341695
versions: + Python 3.7
2019-05-04 01:39:49Jeffrey.Kintschersetmessages: + msg341369
2019-05-04 01:27:55Jeffrey.Kintschersetnosy: + Jeffrey.Kintscher
messages: + msg341368
2019-04-21 07:17:53njatkinsonsetmessages: + msg340601
2019-04-21 06:05:06xtreaksetnosy: + xiang.zhang, xtreak, pitrou
messages: + msg340596
2018-06-06 19:04:03brett.cannonsetcomponents: + Library (Lib), - Interpreter Core
2018-06-06 02:02:31njatkinsonsetmessages: + msg318796
2018-06-06 02:00:05njatkinsoncreate