Skip to content

Commit a8a295f

Browse files
committed
Make EnvironmentVarGuard thread safe
1 parent bfc292a commit a8a295f

File tree

1 file changed

+37
-27
lines changed

1 file changed

+37
-27
lines changed

Lib/test/support/os_helper.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import stat
88
import string
99
import sys
10+
import threading
1011
import time
1112
import unittest
1213
import warnings
@@ -736,64 +737,73 @@ def temp_umask(umask):
736737

737738

738739
class EnvironmentVarGuard(collections.abc.MutableMapping):
739-
"""Class to help protect the environment variable properly.
740-
740+
"""Thread-safe class to help protect environment variables.
741741
Can be used as a context manager.
742742
"""
743743

744744
def __init__(self):
745745
self._environ = os.environ
746746
self._changed = {}
747+
self._lock = threading.RLock()
747748

748749
def __getitem__(self, envvar):
749-
return self._environ[envvar]
750+
with self._lock:
751+
return self._environ[envvar]
750752

751753
def __setitem__(self, envvar, value):
752-
# Remember the initial value on the first access
753-
if envvar not in self._changed:
754-
self._changed[envvar] = self._environ.get(envvar)
755-
self._environ[envvar] = value
754+
with self._lock:
755+
# Remember the initial value on the first access
756+
if envvar not in self._changed:
757+
self._changed[envvar] = self._environ.get(envvar)
758+
self._environ[envvar] = value
756759

757760
def __delitem__(self, envvar):
758-
# Remember the initial value on the first access
759-
if envvar not in self._changed:
760-
self._changed[envvar] = self._environ.get(envvar)
761-
if envvar in self._environ:
762-
del self._environ[envvar]
761+
with self._lock:
762+
# Remember the initial value on the first access
763+
if envvar not in self._changed:
764+
self._changed[envvar] = self._environ.get(envvar)
765+
self._environ.pop(envvar, None)
763766

764767
def keys(self):
765-
return self._environ.keys()
768+
with self._lock:
769+
return list(self._environ.keys())
766770

767771
def __iter__(self):
768-
return iter(self._environ)
772+
with self._lock:
773+
return iter(dict(self._environ))
769774

770775
def __len__(self):
771-
return len(self._environ)
776+
with self._lock:
777+
return len(self._environ)
772778

773779
def set(self, envvar, value):
774780
self[envvar] = value
775781

776782
def unset(self, envvar, /, *envvars):
777783
"""Unset one or more environment variables."""
778-
for ev in (envvar, *envvars):
779-
del self[ev]
784+
with self._lock:
785+
for ev in (envvar, *envvars):
786+
del self[ev]
780787

781788
def copy(self):
782-
# We do what os.environ.copy() does.
783-
return dict(self)
789+
with self._lock:
790+
return dict(self._environ)
784791

785792
def __enter__(self):
786793
return self
787794

788-
def __exit__(self, *ignore_exc):
789-
for (k, v) in self._changed.items():
790-
if v is None:
791-
if k in self._environ:
792-
del self._environ[k]
793-
else:
794-
self._environ[k] = v
795-
os.environ = self._environ
795+
def __reduce__(self):
796+
return (dict, (dict(self),))
796797

798+
def __exit__(self, *ignore_exc):
799+
with self._lock:
800+
for (k, v) in self._changed.items():
801+
if v is None:
802+
self._environ.pop(k, None)
803+
else:
804+
self._environ[k] = v
805+
self._changed.clear()
806+
os.environ = self._environ
797807

798808
try:
799809
if support.MS_WINDOWS:

0 commit comments

Comments
 (0)