Skip to content

Commit f68bd0f

Browse files
committed
gh-156544: fix free-threading race in UserDict.get() and __getitem__().
When UserDict.__delitem__, pop(), __popitem__(), or clear() are called concurrently with get() or __getitem__ a race can occur in that get() or __getitem__ can see the item as being contained and then they use self.data[key] to return the item. However, if the item is removed after the containment check this subscript access will raise KeyError and get() and __getitem__() will propogate it, resulting in get() not returning default and __getitem() incorrectly skipping the call it should make to __missing__ (if defined). This fix detects this race condition by ignoring the KeyError after containment checks to allow default to be returned or __missing__ to be called. It is implemented in this unusual way that preserves the containment check rather than relying solely on KeyError in order to preserve existing semantics and performance characteristics. Removing the call to __contains__ on self.data would change how UserData interacts with it and could potentially break code that relies on this call. In addition to preserving the current semantics, get() with a sentinel is not used to atomically detect if the item is missing. Changing 'in' and '[key]' to a single call to get() would replace these optimized operators with a more expensive function call. Because exceptions incur overhead only when raised, this implementation preserves existing performance for all cases except when the race actually occurs, but ensures correct functionality when it does. It also preserves the interaction semantics with the wrapped dict.
1 parent 9f675a6 commit f68bd0f

1 file changed

Lines changed: 8 additions & 2 deletions

File tree

‎Lib/collections/__init__.py‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,10 @@ def __len__(self):
11871187

11881188
def __getitem__(self, key):
11891189
if key in self.data:
1190-
return self.data[key]
1190+
try:
1191+
return self.data[key]
1192+
except KeyError:
1193+
pass
11911194
if hasattr(self.__class__, "__missing__"):
11921195
return self.__class__.__missing__(self, key)
11931196
raise KeyError(key)
@@ -1208,7 +1211,10 @@ def __contains__(self, key):
12081211

12091212
def get(self, key, default=None):
12101213
if key in self:
1211-
return self[key]
1214+
try:
1215+
return self[key]
1216+
except KeyError:
1217+
pass
12121218
return default
12131219

12141220

0 commit comments

Comments
 (0)