Skip to content

Commit ef1419d

Browse files
committed
gh-151364: Fix data race on kqueue file descriptor in free-threaded build
1 parent a4f28a5 commit ef1419d

3 files changed

Lines changed: 49 additions & 7 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import unittest
2+
3+
from test.support import import_helper, threading_helper
4+
from test.support.threading_helper import run_concurrently
5+
6+
select = import_helper.import_module("select")
7+
8+
NTHREADS = 4
9+
10+
11+
@threading_helper.requires_working_threading()
12+
@unittest.skipUnless(hasattr(select, "kqueue"), "test needs select.kqueue()")
13+
class KqueueTests(unittest.TestCase):
14+
def test_close_while_reading(self):
15+
# gh-151364: close() must not race with closed, fileno() and control()
16+
def reader(kq):
17+
for _ in range(100):
18+
kq.closed
19+
try:
20+
kq.fileno()
21+
kq.control(None, 0, 0)
22+
except (ValueError, OSError):
23+
pass
24+
25+
def closer(kq):
26+
kq.close()
27+
28+
for _ in range(20):
29+
kq = select.kqueue()
30+
run_concurrently(
31+
worker_func=[closer] + [reader] * NTHREADS,
32+
args=(kq,),
33+
)
34+
self.assertTrue(kq.closed)
35+
36+
37+
if __name__ == "__main__":
38+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix data races on the file descriptor of :func:`select.kqueue` objects in
2+
the :term:`free-threaded build`. Based on a patch by Victor Stinner.

‎Modules/selectmodule.c‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "Python.h"
1616
#include "pycore_fileutils.h" // _Py_set_inheritable()
17+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_INT_RELAXED()
1718
#include "pycore_time.h" // _PyTime_FromSecondsObject()
1819
#include "pycore_tuple.h" // _PyTuple_FromPairSteal
1920

@@ -2162,7 +2163,7 @@ kqueue_queue_internal_close(kqueue_queue_Object *self)
21622163
int save_errno = 0;
21632164
if (self->kqfd >= 0) {
21642165
int kqfd = self->kqfd;
2165-
self->kqfd = -1;
2166+
FT_ATOMIC_STORE_INT_RELAXED(self->kqfd, -1);
21662167
_selectstate *state = _selectstate_by_type(Py_TYPE(self));
21672168
kqueue_tracking_remove(state, self);
21682169
Py_BEGIN_ALLOW_THREADS
@@ -2276,7 +2277,7 @@ static PyObject *
22762277
kqueue_queue_get_closed(PyObject *op, void *Py_UNUSED(closure))
22772278
{
22782279
kqueue_queue_Object *self = kqueue_queue_Object_CAST(op);
2279-
if (self->kqfd < 0) {
2280+
if (FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd) < 0) {
22802281
Py_RETURN_TRUE;
22812282
}
22822283
Py_RETURN_FALSE;
@@ -2292,9 +2293,10 @@ static PyObject *
22922293
select_kqueue_fileno_impl(kqueue_queue_Object *self)
22932294
/*[clinic end generated code: output=716f46112a4f6e5c input=41911c539ca2b0ca]*/
22942295
{
2295-
if (self->kqfd < 0)
2296+
int kqfd = FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd);
2297+
if (kqfd < 0)
22962298
return kqueue_queue_err_closed();
2297-
return PyLong_FromLong(self->kqfd);
2299+
return PyLong_FromLong(kqfd);
22982300
}
22992301

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

2352-
if (self->kqfd < 0)
2354+
if (FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd) < 0)
23532355
return kqueue_queue_err_closed();
23542356

23552357
if (maxevents < 0) {
@@ -2431,8 +2433,8 @@ select_kqueue_control_impl(kqueue_queue_Object *self, PyObject *changelist,
24312433
do {
24322434
Py_BEGIN_ALLOW_THREADS
24332435
errno = 0;
2434-
gotevents = kevent(self->kqfd, chl, nchanges,
2435-
evl, maxevents, ptimeoutspec);
2436+
gotevents = kevent(FT_ATOMIC_LOAD_INT_RELAXED(self->kqfd),
2437+
chl, nchanges, evl, maxevents, ptimeoutspec);
24362438
Py_END_ALLOW_THREADS
24372439

24382440
if (errno != EINTR)

0 commit comments

Comments
 (0)