mirror of
https://github.com/python/cpython.git
synced 2025-09-26 10:19:53 +00:00
Issue #9260: A finer-grained import lock.
Most of the import sequence now uses per-module locks rather than the global import lock, eliminating well-known issues with threads and imports.
This commit is contained in:
parent
5cec9d2ae5
commit
ea3eb88bca
12 changed files with 3777 additions and 3041 deletions
115
Lib/importlib/test/test_locks.py
Normal file
115
Lib/importlib/test/test_locks.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
from importlib import _bootstrap
|
||||
import time
|
||||
import unittest
|
||||
import weakref
|
||||
|
||||
from test import support
|
||||
|
||||
try:
|
||||
import threading
|
||||
except ImportError:
|
||||
threading = None
|
||||
else:
|
||||
from test import lock_tests
|
||||
|
||||
|
||||
LockType = _bootstrap._ModuleLock
|
||||
DeadlockError = _bootstrap._DeadlockError
|
||||
|
||||
|
||||
if threading is not None:
|
||||
class ModuleLockAsRLockTests(lock_tests.RLockTests):
|
||||
locktype = staticmethod(lambda: LockType("some_lock"))
|
||||
|
||||
# _is_owned() unsupported
|
||||
test__is_owned = None
|
||||
# acquire(blocking=False) unsupported
|
||||
test_try_acquire = None
|
||||
test_try_acquire_contended = None
|
||||
# `with` unsupported
|
||||
test_with = None
|
||||
# acquire(timeout=...) unsupported
|
||||
test_timeout = None
|
||||
# _release_save() unsupported
|
||||
test_release_save_unacquired = None
|
||||
|
||||
else:
|
||||
class ModuleLockAsRLockTests(unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
@unittest.skipUnless(threading, "threads needed for this test")
|
||||
class DeadlockAvoidanceTests(unittest.TestCase):
|
||||
|
||||
def run_deadlock_avoidance_test(self, create_deadlock):
|
||||
NLOCKS = 10
|
||||
locks = [LockType(str(i)) for i in range(NLOCKS)]
|
||||
pairs = [(locks[i], locks[(i+1)%NLOCKS]) for i in range(NLOCKS)]
|
||||
if create_deadlock:
|
||||
NTHREADS = NLOCKS
|
||||
else:
|
||||
NTHREADS = NLOCKS - 1
|
||||
barrier = threading.Barrier(NTHREADS)
|
||||
results = []
|
||||
def _acquire(lock):
|
||||
"""Try to acquire the lock. Return True on success, False on deadlock."""
|
||||
try:
|
||||
lock.acquire()
|
||||
except DeadlockError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
def f():
|
||||
a, b = pairs.pop()
|
||||
ra = _acquire(a)
|
||||
barrier.wait()
|
||||
rb = _acquire(b)
|
||||
results.append((ra, rb))
|
||||
if rb:
|
||||
b.release()
|
||||
if ra:
|
||||
a.release()
|
||||
lock_tests.Bunch(f, NTHREADS).wait_for_finished()
|
||||
self.assertEqual(len(results), NTHREADS)
|
||||
return results
|
||||
|
||||
def test_deadlock(self):
|
||||
results = self.run_deadlock_avoidance_test(True)
|
||||
# One of the threads detected a potential deadlock on its second
|
||||
# acquire() call.
|
||||
self.assertEqual(results.count((True, False)), 1)
|
||||
self.assertEqual(results.count((True, True)), len(results) - 1)
|
||||
|
||||
def test_no_deadlock(self):
|
||||
results = self.run_deadlock_avoidance_test(False)
|
||||
self.assertEqual(results.count((True, False)), 0)
|
||||
self.assertEqual(results.count((True, True)), len(results))
|
||||
|
||||
|
||||
class LifetimeTests(unittest.TestCase):
|
||||
|
||||
def test_lock_lifetime(self):
|
||||
name = "xyzzy"
|
||||
self.assertNotIn(name, _bootstrap._module_locks)
|
||||
lock = _bootstrap._get_module_lock(name)
|
||||
self.assertIn(name, _bootstrap._module_locks)
|
||||
wr = weakref.ref(lock)
|
||||
del lock
|
||||
support.gc_collect()
|
||||
self.assertNotIn(name, _bootstrap._module_locks)
|
||||
self.assertIs(wr(), None)
|
||||
|
||||
def test_all_locks(self):
|
||||
support.gc_collect()
|
||||
self.assertEqual(0, len(_bootstrap._module_locks))
|
||||
|
||||
|
||||
@support.reap_threads
|
||||
def test_main():
|
||||
support.run_unittest(ModuleLockAsRLockTests,
|
||||
DeadlockAvoidanceTests,
|
||||
LifetimeTests)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_main()
|
Loading…
Add table
Add a link
Reference in a new issue