diff --git a/Lib/os.py b/Lib/os.py index e293ecae7f..7699663884 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -25,6 +25,7 @@ and opendir), and leave all pathname manipulation to os.path import abc import sys, errno import stat as st +import threading _names = sys.builtin_module_names @@ -672,6 +673,7 @@ class _Environ(MutableMapping): self.putenv = putenv self.unsetenv = unsetenv self._data = data + self._lock = threading.Lock() def __getitem__(self, key): try: @@ -697,7 +699,9 @@ class _Environ(MutableMapping): raise KeyError(key) from None def __iter__(self): - for key in self._data: + with self._lock: + keys = list(self._data) + for key in keys: yield self.decodekey(key) def __len__(self): diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 746b3f8be8..9cbe4b90d1 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -828,6 +828,21 @@ class EnvironTests(mapping_tests.BasicTestMappingProtocol): self.assertIs(cm.exception.args[0], missing) self.assertTrue(cm.exception.__suppress_context__) + def test_iter_error_when_os_environ_changes(self): + def _iter_environ_change(): + for key, value in os.environ.items(): + yield key, value + + iter_environ = _iter_environ_change() + key, value = next(iter_environ) # start iteration over os.environ + + # add a new key in os.environ mapping + new_key = "__{}".format(key) + os.environ[new_key] = value + + next(iter_environ) # force iteration over modified mapping + self.assertEqual(os.environ[new_key], value) + class WalkTests(unittest.TestCase): """Tests for os.walk()."""