This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: "RuntimeError: release unlocked lock" when starting a thread
Type: Stage:
Components: Library (Lib) Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: hongweipeng, maggyero, pitrou, radek_kujawa, serhiy.storchaka, uosiu, v2m, vstinner, xiang.zhang
Priority: normal Keywords:

Created on 2018-08-24 14:02 by uosiu, last changed 2022-04-11 14:59 by admin.

Files
File name Uploaded Description Edit
start_threads.py uosiu, 2018-08-24 14:02
test.sh uosiu, 2018-08-24 14:03
cond_race.py vstinner, 2018-08-27 12:31
Messages (10)
msg323998 - (view) Author: uosiu (uosiu) * Date: 2018-08-24 14:02
Sometimes when thread is starting it raises "RuntimeError: release unlocked lock". That is when signal handler is invoked in the same time.

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 551, in wait
    signaled = self._cond.wait(timeout)
  File "/usr/lib/python3.6/threading.py", line 304, in wait
    self._acquire_restore(saved_state)
  File "start_threads.py", line 12, in sighandler
    raise MyException("got signal")
__main__.MyException: got signal

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "start_threads.py", line 28, in <module>
    thread.start()
  File "/usr/lib/python3.6/threading.py", line 851, in start
    self._started.wait()
  File "/usr/lib/python3.6/threading.py", line 552, in wait
    return signaled
  File "/usr/lib/python3.6/threading.py", line 243, in __exit__
    return self._lock.__exit__(*args)
RuntimeError: release unlocked lock

I have attached to files that reproduce the problem. First runs python and starts noop threads in the loop. It uses signal handler that raises exception. Second is a bash script to run python script and send signal to it. After some time, when signal is handled in unfortunate place, python script breaks due to unreleased lock.
msg323999 - (view) Author: uosiu (uosiu) * Date: 2018-08-24 14:03
This scripts runs start_threads.py and send a signal to it. After some number of loops problem can be reproduced.
msg324170 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2018-08-27 12:31
cond_race.py: simplified example using threading.Lock and threading.Condition: the issue seems to be related to threading.Event which doesn't handle properly signals.
msg324490 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2018-09-03 03:47
It looks to me pure Python implementation could not handle such cases. Put aside other possible bugs, threading.Condition.wait re-acquire the lock using threading.Condition._acquire_restore which could be interrupted and not able to acquire the lock. So it seems in any way using Pure Python, it's not possible to guarantee the lock is acquired.
msg324655 - (view) Author: Vladimir Matveev (v2m) * Date: 2018-09-06 00:54
I agree. From code in threading.Condition.wait looks like if it is interrupted either after calling _release_save and before entering try block or in finally block before calling _acquire_restore - it will leave the lock in non-acquired state. 

First part in theory can be solved if _release_save is moved into try block and instead of returning saved_state as a result it will accept reference to saved_state local and set it in C code.

Second part looks more interesting ... :)
msg324785 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-09-07 19:02
This looks similar to to problems with the "with" statement: see issue29988 and issue34067. Except that there is no a "with" statement, and resolving that issues will not solve this issue.
msg324891 - (view) Author: hongweipeng (hongweipeng) * Date: 2018-09-09 16:35
It seems to me the problem is related to nested `finally` or `with`, which can't handle signals well.

class Event:
    ...
    def wait(self, timeout=None):
        self._cond.__enter__()
        signaled = self._flag
        if not signaled:
            signaled = self._cond.wait(timeout)
        self._cond.__exit__()
        return signaled


Reducing one layer of nesting can solve this issue,but it can't pass `test` module.
msg324893 - (view) Author: Xiang Zhang (xiang.zhang) * (Python committer) Date: 2018-09-09 17:11
It's not right to replace with statement by manually calling __enter__ and __exit__. If there is any exception after __enter__ and before __exit__, __exit__ method will be skipped. That's not what we want.
msg324896 - (view) Author: Vladimir Matveev (v2m) * Date: 2018-09-09 17:47
To bring in analogy: C# has lock statement that allow to run a block of code holding a mutual-exclusion lock on some object. 
```
lock(o) 
{  
}
```
is compiled as 
```
object _lock = o;
bool _lockTaken = false;
try
{
   System.Threading.Monitor.Enter(_lock, out _lockTaken);
   ...
}
finally 
{
   if (_lockTaken) 
   {
      System.Threading.Monitor.Exit(_lock);
   }
}
```
In C# System.ThreadAbortException can be raised in arbitrary point in code and yet lock statement needs to to enforce the invariant "if lock is taken it will be released". In order to do so:
- lock acquisition is performed inside the try block, as a side effect it sets the value of '_lockTaken' passed as out parameter - these two actions are performed atomically and cannot be interrupted by the asynchronous exception
- lock is released in finally block only if lock was previously taken. Also finally blocks in .NET has a property that they cannot be interrupted by asynchronous exceptions so call to Monitor.Exit is guaranteed to be run if control flow has entered matching try block.

I feel that something similar can be used to solve this issue as well. Discussions for issue29988 has already mentioned adding special semantic to __enter__/__exit__ methods or marking bytecode ranges as atomic to make sure that they are not interrupted. While the former case is specific to with statements, the latter one can probably be generalized to support finally blocks as well.
msg336070 - (view) Author: Radek (radek_kujawa) Date: 2019-02-20 11:11
Any progress on this topic? I think I've encountered this (or similar) issue:

>Traceback (most recent call last):
>  File "logging/__init__.py", line 1944, in shutdown
>  File "logging/__init__.py", line 813, in acquire
>  File "site-packages/utils/signals.py", line 58, in signal_wrapper
>  File "utils/utils.py", line 147, in sigterm_handler
>SystemExit: 0
>
>During handling of the above exception, another exception occurred:
>
>Traceback (most recent call last):
>  File "logging/__init__.py", line 1954, in shutdown
>  File "logging/__init__.py", line 821, in release
>RuntimeError: cannot release un-acquired lock
History
Date User Action Args
2022-04-11 14:59:05adminsetgithub: 78667
2022-02-03 17:42:54maggyerosetnosy: + maggyero
2019-02-20 11:11:51radek_kujawasetnosy: + radek_kujawa
messages: + msg336070
2018-09-09 17:47:15v2msetmessages: + msg324896
2018-09-09 17:11:42xiang.zhangsetmessages: + msg324893
2018-09-09 16:35:44hongweipengsetnosy: + hongweipeng
messages: + msg324891
2018-09-07 19:02:32serhiy.storchakasetmessages: + msg324785
2018-09-06 00:54:33v2msetnosy: + v2m
messages: + msg324655
2018-09-03 03:47:10xiang.zhangsetmessages: + msg324490
2018-08-27 12:31:41vstinnersetfiles: + cond_race.py
nosy: + pitrou, serhiy.storchaka
messages: + msg324170

2018-08-25 07:26:44xiang.zhangsetnosy: + vstinner, xiang.zhang
2018-08-24 14:03:41uosiusetfiles: + test.sh

messages: + msg323999
2018-08-24 14:02:35uosiucreate