1414
1515#include "Python.h"
1616#include "pycore_fileutils.h" // _Py_set_inheritable()
17+ #include "pycore_import.h" // _PyImport_GetModuleAttrString()
1718#include "pycore_time.h" // _PyTime_t
1819
20+ #include <stdbool.h>
1921#include <stddef.h> // offsetof()
2022#ifndef MS_WINDOWS
2123# include <unistd.h> // close()
@@ -75,13 +77,26 @@ extern void bzero(void *, int);
7577# define POLLPRI 0
7678#endif
7779
80+ #ifdef HAVE_KQUEUE
81+ // Linked list to track kqueue objects with an open fd, so
82+ // that we can invalidate them at fork;
83+ typedef struct _kqueue_list_item {
84+ struct kqueue_queue_Object * obj ;
85+ struct _kqueue_list_item * next ;
86+ } _kqueue_list_item , * _kqueue_list ;
87+ #endif
88+
7889typedef struct {
7990 PyObject * close ;
8091 PyTypeObject * poll_Type ;
8192 PyTypeObject * devpoll_Type ;
8293 PyTypeObject * pyEpoll_Type ;
94+ #ifdef HAVE_KQUEUE
8395 PyTypeObject * kqueue_event_Type ;
8496 PyTypeObject * kqueue_queue_Type ;
97+ _kqueue_list kqueue_open_list ;
98+ bool kqueue_tracking_initialized ;
99+ #endif
85100} _selectstate ;
86101
87102static struct PyModuleDef selectmodule ;
@@ -1754,7 +1769,7 @@ typedef struct {
17541769
17551770#define kqueue_event_Check (op , state ) (PyObject_TypeCheck((op), state->kqueue_event_Type))
17561771
1757- typedef struct {
1772+ typedef struct kqueue_queue_Object {
17581773 PyObject_HEAD
17591774 SOCKET kqfd ; /* kqueue control fd */
17601775} kqueue_queue_Object ;
@@ -1940,13 +1955,116 @@ kqueue_queue_err_closed(void)
19401955 return NULL ;
19411956}
19421957
1958+ static PyObject *
1959+ kqueue_tracking_after_fork (PyObject * module ) {
1960+ _selectstate * state = get_select_state (module );
1961+ _kqueue_list_item * item = state -> kqueue_open_list ;
1962+ state -> kqueue_open_list = NULL ;
1963+ while (item ) {
1964+ // Safety: we hold the GIL, and references are removed from this list
1965+ // before the object is deallocated.
1966+ kqueue_queue_Object * obj = item -> obj ;
1967+ assert (obj -> kqfd != -1 );
1968+ obj -> kqfd = -1 ;
1969+ _kqueue_list_item * next = item -> next ;
1970+ PyMem_Free (item );
1971+ item = next ;
1972+ }
1973+ Py_RETURN_NONE ;
1974+ }
1975+
1976+ static PyMethodDef kqueue_tracking_after_fork_def = {
1977+ "kqueue_tracking_after_fork" , (PyCFunction )kqueue_tracking_after_fork ,
1978+ METH_NOARGS , "Invalidate open select.kqueue objects after fork."
1979+ };
1980+
1981+ static void
1982+ kqueue_tracking_init (PyObject * module ) {
1983+ _selectstate * state = get_select_state (module );
1984+ assert (state -> kqueue_open_list == NULL );
1985+ // Register a callback to invalidate kqueues with open fds after fork.
1986+ PyObject * register_at_fork = NULL , * cb = NULL , * args = NULL ,
1987+ * kwargs = NULL , * result = NULL ;
1988+ register_at_fork = _PyImport_GetModuleAttrString ("posix" ,
1989+ "register_at_fork" );
1990+ if (register_at_fork == NULL ) {
1991+ goto finally ;
1992+ }
1993+ cb = PyCFunction_New (& kqueue_tracking_after_fork_def , module );
1994+ if (cb == NULL ) {
1995+ goto finally ;
1996+ }
1997+ args = PyTuple_New (0 );
1998+ assert (args != NULL );
1999+ kwargs = Py_BuildValue ("{sO}" , "after_in_child" , cb );
2000+ if (kwargs == NULL ) {
2001+ goto finally ;
2002+ }
2003+ result = PyObject_Call (register_at_fork , args , kwargs );
2004+
2005+ finally :
2006+ if (PyErr_Occurred ()) {
2007+ // There are a few reasons registration can fail, especially if someone
2008+ // touched posix.register_at_fork. But everything else still works so
2009+ // instead of raising we issue a warning and move along.
2010+ PyObject * exc = PyErr_GetRaisedException ();
2011+ PyObject * exctype = (PyObject * )Py_TYPE (exc );
2012+ PyErr_WarnFormat (PyExc_RuntimeWarning , 1 ,
2013+ "An exception of type %S was raised while registering an "
2014+ "after-fork handler for select.kqueue objects: %S" , exctype , exc );
2015+ Py_DECREF (exc );
2016+ }
2017+ Py_XDECREF (register_at_fork );
2018+ Py_XDECREF (cb );
2019+ Py_XDECREF (args );
2020+ Py_XDECREF (kwargs );
2021+ Py_XDECREF (result );
2022+ state -> kqueue_tracking_initialized = true;
2023+ }
2024+
2025+ static int
2026+ kqueue_tracking_add (_selectstate * state , kqueue_queue_Object * self ) {
2027+ if (!state -> kqueue_tracking_initialized ) {
2028+ kqueue_tracking_init (PyType_GetModule (Py_TYPE (self )));
2029+ }
2030+ assert (self -> kqfd >= 0 );
2031+ _kqueue_list_item * item = PyMem_New (_kqueue_list_item , 1 );
2032+ if (item == NULL ) {
2033+ PyErr_NoMemory ();
2034+ return -1 ;
2035+ }
2036+ item -> obj = self ;
2037+ item -> next = state -> kqueue_open_list ;
2038+ state -> kqueue_open_list = item ;
2039+ return 0 ;
2040+ }
2041+
2042+ static void
2043+ kqueue_tracking_remove (_selectstate * state , kqueue_queue_Object * self ) {
2044+ _kqueue_list * listptr = & state -> kqueue_open_list ;
2045+ while (* listptr != NULL ) {
2046+ _kqueue_list_item * item = * listptr ;
2047+ if (item -> obj == self ) {
2048+ * listptr = item -> next ;
2049+ PyMem_Free (item );
2050+ return ;
2051+ }
2052+ listptr = & item -> next ;
2053+ }
2054+ // The item should be in the list when we remove it,
2055+ // and it should only be removed once at close time.
2056+ assert (0 );
2057+ }
2058+
19432059static int
19442060kqueue_queue_internal_close (kqueue_queue_Object * self )
19452061{
19462062 int save_errno = 0 ;
19472063 if (self -> kqfd >= 0 ) {
19482064 int kqfd = self -> kqfd ;
19492065 self -> kqfd = -1 ;
2066+ _selectstate * state = _selectstate_by_type (Py_TYPE (self ));
2067+ kqueue_tracking_remove (state , self );
19502068 Py_BEGIN_ALLOW_THREADS
19512069 if (close (kqfd ) < 0 )
19522070 save_errno = errno ;
@@ -1987,6 +2105,13 @@ newKqueue_Object(PyTypeObject *type, SOCKET fd)
19872105 return NULL ;
19882106 }
19892107 }
2108+
2109+ _selectstate * state = _selectstate_by_type (type );
2110+ if (kqueue_tracking_add (state , self ) < 0 ) {
2111+ Py_DECREF (self );
2112+ return NULL ;
2113+ }
2114+
19902115 return (PyObject * )self ;
19912116}
19922117
@@ -2017,13 +2142,11 @@ select_kqueue_impl(PyTypeObject *type)
20172142}
20182143
20192144static void
2020- kqueue_queue_dealloc (kqueue_queue_Object * self )
2145+ kqueue_queue_finalize (kqueue_queue_Object * self )
20212146{
2022- PyTypeObject * type = Py_TYPE ( self );
2147+ PyObject * error = PyErr_GetRaisedException ( );
20232148 kqueue_queue_internal_close (self );
2024- freefunc kqueue_free = PyType_GetSlot (type , Py_tp_free );
2025- kqueue_free ((PyObject * )self );
2026- Py_DECREF ((PyObject * )type );
2149+ PyErr_SetRaisedException (error );
20272150}
20282151
20292152/*[clinic input]
@@ -2357,11 +2480,11 @@ static PyMethodDef kqueue_queue_methods[] = {
23572480};
23582481
23592482static PyType_Slot kqueue_queue_Type_slots [] = {
2360- {Py_tp_dealloc , kqueue_queue_dealloc },
23612483 {Py_tp_doc , (void * )select_kqueue__doc__ },
23622484 {Py_tp_getset , kqueue_queue_getsetlist },
23632485 {Py_tp_methods , kqueue_queue_methods },
23642486 {Py_tp_new , select_kqueue },
2487+ {Py_tp_finalize , kqueue_queue_finalize },
23652488 {0 , 0 },
23662489};
23672490
@@ -2406,8 +2529,11 @@ _select_traverse(PyObject *module, visitproc visit, void *arg)
24062529 Py_VISIT (state -> poll_Type );
24072530 Py_VISIT (state -> devpoll_Type );
24082531 Py_VISIT (state -> pyEpoll_Type );
2532+ #ifdef HAVE_KQUEUE
24092533 Py_VISIT (state -> kqueue_event_Type );
24102534 Py_VISIT (state -> kqueue_queue_Type );
2535+ // state->kqueue_open_list only holds borrowed refs
2536+ #endif
24112537 return 0 ;
24122538}
24132539
@@ -2420,8 +2546,10 @@ _select_clear(PyObject *module)
24202546 Py_CLEAR (state -> poll_Type );
24212547 Py_CLEAR (state -> devpoll_Type );
24222548 Py_CLEAR (state -> pyEpoll_Type );
2549+ #ifdef HAVE_KQUEUE
24232550 Py_CLEAR (state -> kqueue_event_Type );
24242551 Py_CLEAR (state -> kqueue_queue_Type );
2552+ #endif
24252553 return 0 ;
24262554}
24272555
@@ -2570,6 +2698,8 @@ _select_exec(PyObject *m)
25702698 } while (0)
25712699
25722700#ifdef HAVE_KQUEUE
2701+ state -> kqueue_open_list = NULL ;
2702+
25732703 state -> kqueue_event_Type = (PyTypeObject * )PyType_FromModuleAndSpec (
25742704 m , & kqueue_event_Type_spec , NULL );
25752705 if (state -> kqueue_event_Type == NULL ) {
0 commit comments