|
7 | 7 | import stat |
8 | 8 | import string |
9 | 9 | import sys |
| 10 | +import threading |
10 | 11 | import time |
11 | 12 | import unittest |
12 | 13 | import warnings |
@@ -736,64 +737,73 @@ def temp_umask(umask): |
736 | 737 |
|
737 | 738 |
|
738 | 739 | class EnvironmentVarGuard(collections.abc.MutableMapping): |
739 | | - """Class to help protect the environment variable properly. |
740 | | -
|
| 740 | + """Thread-safe class to help protect environment variables. |
741 | 741 | Can be used as a context manager. |
742 | 742 | """ |
743 | 743 |
|
744 | 744 | def __init__(self): |
745 | 745 | self._environ = os.environ |
746 | 746 | self._changed = {} |
| 747 | + self._lock = threading.RLock() |
747 | 748 |
|
748 | 749 | def __getitem__(self, envvar): |
749 | | - return self._environ[envvar] |
| 750 | + with self._lock: |
| 751 | + return self._environ[envvar] |
750 | 752 |
|
751 | 753 | 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 |
756 | 759 |
|
757 | 760 | 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) |
763 | 766 |
|
764 | 767 | def keys(self): |
765 | | - return self._environ.keys() |
| 768 | + with self._lock: |
| 769 | + return list(self._environ.keys()) |
766 | 770 |
|
767 | 771 | def __iter__(self): |
768 | | - return iter(self._environ) |
| 772 | + with self._lock: |
| 773 | + return iter(dict(self._environ)) |
769 | 774 |
|
770 | 775 | def __len__(self): |
771 | | - return len(self._environ) |
| 776 | + with self._lock: |
| 777 | + return len(self._environ) |
772 | 778 |
|
773 | 779 | def set(self, envvar, value): |
774 | 780 | self[envvar] = value |
775 | 781 |
|
776 | 782 | def unset(self, envvar, /, *envvars): |
777 | 783 | """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] |
780 | 787 |
|
781 | 788 | def copy(self): |
782 | | - # We do what os.environ.copy() does. |
783 | | - return dict(self) |
| 789 | + with self._lock: |
| 790 | + return dict(self._environ) |
784 | 791 |
|
785 | 792 | def __enter__(self): |
786 | 793 | return self |
787 | 794 |
|
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),)) |
796 | 797 |
|
| 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 |
797 | 807 |
|
798 | 808 | try: |
799 | 809 | if support.MS_WINDOWS: |
|
0 commit comments