python / python/cpython

Race between UserDict.__getitem__ and __delitem__ can result in __missing_ not being called when item is missing.

Đang mở
#156,544 5 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

stdlib topic-free-threading type-bug
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

https://github.com/python/cpython/blob/71cb9e8b64c4a89971955d3ce8ff393f283aeace/Lib/collections/__init__.py#L1189

I believe there is a race if __getitem__ sees the key exists in self.data and the GIL is released before getting the item. If another thread then calls __delitem__ which removes the item from self.data, when __getitem__ resumes it will attempt to get and return the item which no longer exists resulting in KeyError being raised out of __getitem__. If the class has __missing__ defined it should be called if the item is missing, but that won't occur in this sequence of events.
This is a theoretical bug report based solely on code inspection prompted by a DPO discussion about a different race the GIL does protect against (https://discuss.python.org/t/pep-805-safe-parallel-python/108670/49).

The only concern is that __missing__ will not be called, not that the item is initially seen then not seen.
I have not done a thorough review yet of the rest of the UserDict methods that do a containment check followed by lookup, but my cursory inspection they look to delegate to __getitem__. I am a bit concerned that get() does not call __missing__ if the initial check does not see the item but will if it sees the item and this bug is fixed as proposed below will result in __missing__ being called. This should be consistent.
https://github.com/python/cpython/pull/17910 seems related and I will look into the details of that fix to ensure this fix is consistent (if I end up working on this issue).

I offer a strawman proposal for fixing this by changing __getitem__ to use a single inspection of self.data with something like:
item = self.data.get(key, MISSING) if item is not MISSING: return item ...
Where MISSING is an internal object guaranteed to not be a key in self.data (adding it if something suitable doesn't already exist).

I volunteer to work on this issue. To point me in the right direction are there existing tests (either in collections or elsewhere) that force GIL timing races I can look at as an example of how to write a test to verify this issue is fixed? The challenge is reliably interleaving the initial containment check, then __delitem__, then the subscript access. I'm not sure if this level of testing is feasible based on previous experience with similar issues...the fix usually invalidates the test steps to interleave things properly by removing the ability for them to be interleaved. Any guidance on this would be very appreciated if my offer to work on this is taken up.

Linked PRs
  • gh-156621
  • gh-157287

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu tại Lib/collections/init.py, khoảng dòng 1189, và kiểm tra hành vi tra cứu và xóa của UserDict được mô tả trong báo cáo. Xem xét các PR được liên kết gh-156621 và gh-157287 để nắm hướng hiện tại, sau đó kiểm tra xem các test collections hiện có đã bao phủ missing và interleaving liên quan hay chưa. Công việc được xem là hoàn tất khi hành vi đối với mục bị thiếu được báo cáo đã được giải quyết nhất quán với get() và được bao phủ bởi các test phù hợp.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
backend
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Đình trệ
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.