diff --git a/Lib/os.py b/Lib/os.py index e293eca..5b9b5e8 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 _thread _names = sys.builtin_module_names @@ -672,6 +673,7 @@ class _Environ(MutableMapping): self.putenv = putenv self.unsetenv = unsetenv self._data = data + self._lock = _thread.RLock() def __getitem__(self, key): try: @@ -685,28 +687,35 @@ class _Environ(MutableMapping): key = self.encodekey(key) value = self.encodevalue(value) self.putenv(key, value) - self._data[key] = value + with self._lock: + self._data[key] = value def __delitem__(self, key): encodedkey = self.encodekey(key) self.unsetenv(encodedkey) try: - del self._data[encodedkey] + with self._lock: + del self._data[encodedkey] except KeyError: # raise KeyError with the original key value 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): return len(self._data) def __repr__(self): + with self._lock: + items = list(self._data.items()) + return 'environ({{{}}})'.format(', '.join( ('{!r}: {!r}'.format(self.decodekey(key), self.decodevalue(value)) - for key, value in self._data.items()))) + for key, value in items))) def copy(self): return dict(self) diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 746b3f8..9cbe4b9 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()."""