A recursive lock implementation which is able to detect deadlocks (e.g. thread 1 trying to take locks A then B, and thread 2 trying to take locks B then A).
| 224 | |
| 225 | |
| 226 | class _ModuleLock: |
| 227 | """A recursive lock implementation which is able to detect deadlocks |
| 228 | (e.g. thread 1 trying to take locks A then B, and thread 2 trying to |
| 229 | take locks B then A). |
| 230 | """ |
| 231 | |
| 232 | def __init__(self, name): |
| 233 | # Create an RLock for protecting the import process for the |
| 234 | # corresponding module. Since it is an RLock, a single thread will be |
| 235 | # able to take it more than once. This is necessary to support |
| 236 | # re-entrancy in the import system that arises from (at least) signal |
| 237 | # handlers and the garbage collector. Consider the case of: |
| 238 | # |
| 239 | # import foo |
| 240 | # -> ... |
| 241 | # -> importlib._bootstrap._ModuleLock.acquire |
| 242 | # -> ... |
| 243 | # -> <garbage collector> |
| 244 | # -> __del__ |
| 245 | # -> import foo |
| 246 | # -> ... |
| 247 | # -> importlib._bootstrap._ModuleLock.acquire |
| 248 | # -> _BlockingOnManager.__enter__ |
| 249 | # |
| 250 | # If a different thread than the running one holds the lock then the |
| 251 | # thread will have to block on taking the lock, which is what we want |
| 252 | # for thread safety. |
| 253 | self.lock = _thread.RLock() |
| 254 | self.wakeup = _thread.allocate_lock() |
| 255 | |
| 256 | # The name of the module for which this is a lock. |
| 257 | self.name = name |
| 258 | |
| 259 | # Can end up being set to None if this lock is not owned by any thread |
| 260 | # or the thread identifier for the owning thread. |
| 261 | self.owner = None |
| 262 | |
| 263 | # Represent the number of times the owning thread has acquired this lock |
| 264 | # via a list of True. This supports RLock-like ("re-entrant lock") |
| 265 | # behavior, necessary in case a single thread is following a circular |
| 266 | # import dependency and needs to take the lock for a single module |
| 267 | # more than once. |
| 268 | # |
| 269 | # Counts are represented as a list of True because list.append(True) |
| 270 | # and list.pop() are both atomic and thread-safe in CPython and it's hard |
| 271 | # to find another primitive with the same properties. |
| 272 | self.count = [] |
| 273 | |
| 274 | # This is a count of the number of threads that are blocking on |
| 275 | # self.wakeup.acquire() awaiting to get their turn holding this module |
| 276 | # lock. When the module lock is released, if this is greater than |
| 277 | # zero, it is decremented and `self.wakeup` is released one time. The |
| 278 | # intent is that this will let one other thread make more progress on |
| 279 | # acquiring this module lock. This repeats until all the threads have |
| 280 | # gotten a turn. |
| 281 | # |
| 282 | # This is incremented in self.acquire() when a thread notices it is |
| 283 | # going to have to wait for another thread to finish. |
no outgoing calls
no test coverage detected
searching dependent graphs…