Repository navigation
Open
Labels
topic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Bug report
Bug description:
select.kqueue accesses its internal kqfd file-descriptor field with no synchronization. So kqueue.close() can write the kqfd,
cpython/Modules/selectmodule.c
Lines 2160 to 2165 in b18168c
| kqueue_queue_internal_close(kqueue_queue_Object *self) | |
| { | |
| int save_errno = 0; | |
| if (self->kqfd >= 0) { | |
| int kqfd = self->kqfd; | |
| self->kqfd = -1; |
while the closed getter reads it.
cpython/Modules/selectmodule.c
Lines 2276 to 2279 in b18168c
| kqueue_queue_get_closed(PyObject *op, void *Py_UNUSED(closure)) | |
| { | |
| kqueue_queue_Object *self = kqueue_queue_Object_CAST(op); | |
| if (self->kqfd < 0) { |
Reproducer:
import select
from threading import Thread
slot = [select.kqueue()]
def reader():
for _ in range(200000):
try:
_ = slot[0].closed
except Exception:
pass
def churner():
for _ in range(200000):
kq = select.kqueue()
slot[0] = kq
kq.close()
threads = [Thread(target=reader) for _ in range(4)]
threads += [Thread(target=churner) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()TSAN Report:
==================
WARNING: ThreadSanitizer: data race (pid=28784)
Write of size 4 at 0x00011c040140 by thread T5:
#0 kqueue_queue_internal_close selectmodule.c:2165
#1 select_kqueue_close selectmodule.c.h:1189
#2 method_vectorcall_NOARGS descrobject.c:448
#3 PyObject_Vectorcall call.c:327
#4 _Py_VectorCallInstrumentation_StackRefSteal ceval.c:766
#5 _PyEval_EvalFrameDefault generated_cases.c.h:1846
...
Previous read of size 4 at 0x00011c040140 by thread T4:
#0 kqueue_queue_get_closed selectmodule.c:2279
#1 getset_get descrobject.c:194
#2 _PyObject_GenericGetAttrWithDict object.c
#3 PyObject_GenericGetAttr object.c:2012
#4 _PyObject_GetAttrStackRef object.c
#5 _PyEval_EvalFrameDefault generated_cases.c.h:8312
SUMMARY: ThreadSanitizer: data race selectmodule.c:2165 in kqueue_queue_internal_close
==================
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
Metadata
Metadata
Assignees
Labels
topic-free-threadingtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Activity
pythongh-151364: Add critical sections to kqueue
AHAariyan commentedon Oct 2, 2026
Hi, I'd like to pick this up, continuing from @vstinner's closed GH-151376 as he suggested.
Reproduced on current main (a4f28a5), macOS arm64, free-threaded debug build with
--with-thread-sanitizer. One thread closes aselect.kqueuewhile others read.closedand callfileno(). TSan reports races between:kqueue_queue_internal_close(write tokqfd) andkqueue_queue_get_closed(read)kqueue_queue_internal_close(write tokqfd) andselect_kqueue_fileno(read)Control check: with a critical section on the
closedgetter and@critical_sectiononfileno(), TSan reports nothing in 5/5 runs, versus 2 warnings in each of 5 runs on unpatched main.Plan:
kqfdreads inclosed,fileno()andcontrol().tp_finalizeor__init__.Lib/test/test_free_threading/.keventrepr/richcompare changes for a separate PR, using the relaxed atomic loads @ZeroIntensity suggested.Please let me know if you'd prefer a different approach.