diff --git a/CHANGES.rst b/CHANGES.rst index ff3621f..ff0e417 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,8 @@ For changes before version 3.0, see ``HISTORY.rst``. 5.4 (unreleased) ---------------- -- Nothing changed yet. +- Prevent race condition in guarded_import + (see `#123 `_) 5.3 (2022-02-25) diff --git a/src/AccessControl/SecurityInfo.py b/src/AccessControl/SecurityInfo.py index 6f4f896..8443828 100644 --- a/src/AccessControl/SecurityInfo.py +++ b/src/AccessControl/SecurityInfo.py @@ -270,7 +270,12 @@ def secureModule(mname, *imp): if imp: __import__(mname, *imp) - del _moduleSecurity[mname] + try: + del _moduleSecurity[mname] + except KeyError: + # In case of access from multiple threads, the del might fail, but that + # is OK. + pass module = sys.modules[mname] modsec.apply(module.__dict__) _appliedModuleSecurity[mname] = modsec diff --git a/src/AccessControl/tests/testModuleSecurity.py b/src/AccessControl/tests/testModuleSecurity.py index 7d48448..631f32e 100644 --- a/src/AccessControl/tests/testModuleSecurity.py +++ b/src/AccessControl/tests/testModuleSecurity.py @@ -70,6 +70,27 @@ def testPublicModule(self): self.assertAuth('AccessControl.tests.public_module.submodule', ('pub',)) + def testPublicModuleThreaded(self): + """ + Import the same module from two threads simultaneously, checking that + this does not result in a race condition. + """ + import threading + finished = [] + + def threaded_run(): + self.assertAuth('AccessControl.tests.public_module', ()) + finished.append(True) + + threads = [threading.Thread(target=threaded_run) for _ in range(2)] + + for t in threads: + t.start() + for t in threads: + t.join() + + self.assertEqual(len(finished), 2) + def test_public_module_asterisk_not_allowed(self): self.assertUnauth('AccessControl.tests.public_module', ('*',))