Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Lib/test/test_free_threading/test_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import unittest

from test.support import import_helper, threading_helper
from test.support.threading_helper import run_concurrently

select = import_helper.import_module("select")

NTHREADS = 4


@threading_helper.requires_working_threading()
@unittest.skipUnless(hasattr(select, "kqueue"), "test needs select.kqueue()")
class KqueueTests(unittest.TestCase):
def test_close_while_reading(self):
# gh-151364: close() must not race with closed, fileno() and control()
def reader(kq):
for _ in range(100):
kq.closed
try:
kq.fileno()
kq.control(None, 0, 0)
except (ValueError, OSError):
pass

def closer(kq):
kq.close()

for _ in range(20):
kq = select.kqueue()
run_concurrently(
worker_func=[closer] + [reader] * NTHREADS,
args=(kq,),
)
self.assertTrue(kq.closed)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix data races on the file descriptor of :func:`select.kqueue` objects in
the :term:`free-threaded build`. Based on a patch by Victor Stinner.
16 changes: 9 additions & 7 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include "Python.h"
#include "pycore_fileutils.h" // _Py_set_inheritable()
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_INT_RELAXED()
#include "pycore_time.h" // _PyTime_FromSecondsObject()
#include "pycore_tuple.h" // _PyTuple_FromPairSteal

Expand Down Expand Up @@ -2162,7 +2163,7 @@ kqueue_queue_internal_close(kqueue_queue_Object *self)
int save_errno = 0;
if (self->kqfd >= 0) {
int kqfd = self->kqfd;
self->kqfd = -1;
FT_ATOMIC_STORE_INT_RELAXED(self->kqfd, -1);
_selectstate *state = _selectstate_by_type(Py_TYPE(self));
kqueue_tracking_remove(state, self);
Py_BEGIN_ALLOW_THREADS
Expand Down Expand Up @@ -2276,7 +2277,7 @@ static PyObject *
kqueue_queue_get_closed(PyObject *op, void *Py_UNUSED(closure))
{
kqueue_queue_Object *self = kqueue_queue_Object_CAST(op);
if (self->kqfd < 0) {
if (FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd) < 0) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
Expand All @@ -2292,9 +2293,10 @@ static PyObject *
select_kqueue_fileno_impl(kqueue_queue_Object *self)
/*[clinic end generated code: output=716f46112a4f6e5c input=41911c539ca2b0ca]*/
{
if (self->kqfd < 0)
int kqfd = FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd);
if (kqfd < 0)
return kqueue_queue_err_closed();
return PyLong_FromLong(self->kqfd);
return PyLong_FromLong(kqfd);
}

/*[clinic input]
Expand Down Expand Up @@ -2349,7 +2351,7 @@ select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist,
PyTime_t timeout, deadline = 0;
_selectstate *state = _selectstate_by_type(Py_TYPE(self));

if (self->kqfd < 0)
if (FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd) < 0)
return kqueue_queue_err_closed();

if (maxevents < 0) {
Expand Down Expand Up @@ -2431,8 +2433,8 @@ select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist,
do {
Py_BEGIN_ALLOW_THREADS
errno = 0;
gotevents = kevent(self->kqfd, chl, nchanges,
evl, maxevents, ptimeoutspec);
gotevents = kevent(FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd),
chl, nchanges, evl, maxevents, ptimeoutspec);
Py_END_ALLOW_THREADS

if (errno != EINTR)
Expand Down
Loading