1414
1515#include "Python.h"
1616#include "pycore_fileutils.h" // _Py_set_inheritable()
17+ #include "pycore_import.h" // _PyImport_GetModuleAttrString()
18+ #include "pycore_time.h" // _PyTime_t
1719#include "structmember.h" // PyMemberDef
1820
21+ #include <stdbool.h>
22+ #include <stddef.h> // offsetof()
23+ #ifndef MS_WINDOWS
24+ # include <unistd.h> // close()
25+ #endif
26+
1927#ifdef HAVE_SYS_DEVPOLL_H
2028#include <sys/resource.h>
2129#include <sys/devpoll.h>
@@ -70,13 +78,26 @@ extern void bzero(void *, int);
7078# define POLLPRI 0
7179#endif
7280
81+ #ifdef HAVE_KQUEUE
82+ // Linked list to track kqueue objects with an open fd, so
83+ // that we can invalidate them at fork;
84+ typedef struct _kqueue_list_item {
85+ struct kqueue_queue_Object * obj ;
86+ struct _kqueue_list_item * next ;
87+ } _kqueue_list_item , * _kqueue_list ;
88+ #endif
89+
7390typedef struct {
7491 PyObject * close ;
7592 PyTypeObject * poll_Type ;
7693 PyTypeObject * devpoll_Type ;
7794 PyTypeObject * pyEpoll_Type ;
95+ #ifdef HAVE_KQUEUE
7896 PyTypeObject * kqueue_event_Type ;
7997 PyTypeObject * kqueue_queue_Type ;
98+ _kqueue_list kqueue_open_list ;
99+ bool kqueue_tracking_initialized ;
100+ #endif
80101} _selectstate ;
81102
82103static struct PyModuleDef selectmodule ;
@@ -1749,7 +1770,7 @@ typedef struct {
17491770
17501771#define kqueue_event_Check (op , state ) (PyObject_TypeCheck((op), state->kqueue_event_Type))
17511772
1752- typedef struct {
1773+ typedef struct kqueue_queue_Object {
17531774 PyObject_HEAD
17541775 SOCKET kqfd ; /* kqueue control fd */
17551776} kqueue_queue_Object ;
@@ -1935,13 +1956,116 @@ kqueue_queue_err_closed(void)
19351956 return NULL ;
19361957}
19371958
1959+ static PyObject *
1960+ kqueue_tracking_after_fork (PyObject * module ) {
1961+ _selectstate * state = get_select_state (module );
1962+ _kqueue_list_item * item = state -> kqueue_open_list ;
1963+ state -> kqueue_open_list = NULL ;
1964+ while (item ) {
1965+ // Safety: we hold the GIL, and references are removed from this list
1966+ // before the object is deallocated.
1967+ kqueue_queue_Object * obj = item -> obj ;
1968+ assert (obj -> kqfd != -1 );
1969+ obj -> kqfd = -1 ;
1970+ _kqueue_list_item * next = item -> next ;
1971+ PyMem_Free (item );
1972+ item = next ;
1973+ }
1974+ Py_RETURN_NONE ;
1975+ }
1976+
1977+ static PyMethodDef kqueue_tracking_after_fork_def = {
1978+ "kqueue_tracking_after_fork" , (PyCFunction )kqueue_tracking_after_fork ,
1979+ METH_NOARGS , "Invalidate open select.kqueue objects after fork."
1980+ };
1981+
1982+ static void
1983+ kqueue_tracking_init (PyObject * module ) {
1984+ _selectstate * state = get_select_state (module );
1985+ assert (state -> kqueue_open_list == NULL );
1986+ // Register a callback to invalidate kqueues with open fds after fork.
1987+ PyObject * register_at_fork = NULL , * cb = NULL , * args = NULL ,
1988+ * kwargs = NULL , * result = NULL ;
1989+ register_at_fork = _PyImport_GetModuleAttrString ("posix" ,
1990+ "register_at_fork" );
1991+ if (register_at_fork == NULL ) {
1992+ goto finally ;
1993+ }
1994+ cb = PyCFunction_New (& kqueue_tracking_after_fork_def , module );
1995+ if (cb == NULL ) {
1996+ goto finally ;
1997+ }
1998+ args = PyTuple_New (0 );
1999+ assert (args != NULL );
2000+ kwargs = Py_BuildValue ("{sO}" , "after_in_child" , cb );
2001+ if (kwargs == NULL ) {
2002+ goto finally ;
2003+ }
2004+ result = PyObject_Call (register_at_fork , args , kwargs );
2005+
2006+ finally :
2007+ if (PyErr_Occurred ()) {
2008+ // There are a few reasons registration can fail, especially if someone
2009+ // touched posix.register_at_fork. But everything else still works so
2010+ // instead of raising we issue a warning and move along.
2011+ PyObject * exc = PyErr_GetRaisedException ();
2012+ PyObject * exctype = (PyObject * )Py_TYPE (exc );
2013+ PyErr_WarnFormat (PyExc_RuntimeWarning , 1 ,
2014+ "An exception of type %S was raised while registering an "
2015+ "after-fork handler for select.kqueue objects: %S" , exctype , exc );
2016+ Py_DECREF (exc );
2017+ }
2018+ Py_XDECREF (register_at_fork );
2019+ Py_XDECREF (cb );
2020+ Py_XDECREF (args );
2021+ Py_XDECREF (kwargs );
2022+ Py_XDECREF (result );
2023+ state -> kqueue_tracking_initialized = true;
2024+ }
2025+
2026+ static int
2027+ kqueue_tracking_add (_selectstate * state , kqueue_queue_Object * self ) {
2028+ if (!state -> kqueue_tracking_initialized ) {
2029+ kqueue_tracking_init (PyType_GetModule (Py_TYPE (self )));
2030+ }
2031+ assert (self -> kqfd >= 0 );
2032+ _kqueue_list_item * item = PyMem_New (_kqueue_list_item , 1 );
2033+ if (item == NULL ) {
2034+ PyErr_NoMemory ();
2035+ return -1 ;
2036+ }
2037+ item -> obj = self ;
2038+ item -> next = state -> kqueue_open_list ;
2039+ state -> kqueue_open_list = item ;
2040+ return 0 ;
2041+ }
2042+
2043+ static void
2044+ kqueue_tracking_remove (_selectstate * state , kqueue_queue_Object * self ) {
2045+ _kqueue_list * listptr = & state -> kqueue_open_list ;
2046+ while (* listptr != NULL ) {
2047+ _kqueue_list_item * item = * listptr ;
2048+ if (item -> obj == self ) {
2049+ * listptr = item -> next ;
2050+ PyMem_Free (item );
2051+ return ;
2052+ }
2053+ listptr = & item -> next ;
2054+ }
2055+ // The item should be in the list when we remove it,
2056+ // and it should only be removed once at close time.
2057+ assert (0 );
2058+ }
2059+
19382060static int
19392061kqueue_queue_internal_close (kqueue_queue_Object * self )
19402062{
19412063 int save_errno = 0 ;
19422064 if (self -> kqfd >= 0 ) {
19432065 int kqfd = self -> kqfd ;
19442066 self -> kqfd = -1 ;
2067+ _selectstate * state = _selectstate_by_type (Py_TYPE (self ));
2068+ kqueue_tracking_remove (state , self );
19452069 Py_BEGIN_ALLOW_THREADS
19462070 if (close (kqfd ) < 0 )
19472071 save_errno = errno ;
@@ -1982,6 +2106,13 @@ newKqueue_Object(PyTypeObject *type, SOCKET fd)
19822106 return NULL ;
19832107 }
19842108 }
2109+
2110+ _selectstate * state = _selectstate_by_type (type );
2111+ if (kqueue_tracking_add (state , self ) < 0 ) {
2112+ Py_DECREF (self );
2113+ return NULL ;
2114+ }
2115+
19852116 return (PyObject * )self ;
19862117}
19872118
@@ -2012,13 +2143,11 @@ select_kqueue_impl(PyTypeObject *type)
20122143}
20132144
20142145static void
2015- kqueue_queue_dealloc (kqueue_queue_Object * self )
2146+ kqueue_queue_finalize (kqueue_queue_Object * self )
20162147{
2017- PyTypeObject * type = Py_TYPE ( self );
2148+ PyObject * error = PyErr_GetRaisedException ( );
20182149 kqueue_queue_internal_close (self );
2019- freefunc kqueue_free = PyType_GetSlot (type , Py_tp_free );
2020- kqueue_free ((PyObject * )self );
2021- Py_DECREF ((PyObject * )type );
2150+ PyErr_SetRaisedException (error );
20222151}
20232152
20242153/*[clinic input]
@@ -2352,11 +2481,11 @@ static PyMethodDef kqueue_queue_methods[] = {
23522481};
23532482
23542483static PyType_Slot kqueue_queue_Type_slots [] = {
2355- {Py_tp_dealloc , kqueue_queue_dealloc },
23562484 {Py_tp_doc , (void * )select_kqueue__doc__ },
23572485 {Py_tp_getset , kqueue_queue_getsetlist },
23582486 {Py_tp_methods , kqueue_queue_methods },
23592487 {Py_tp_new , select_kqueue },
2488+ {Py_tp_finalize , kqueue_queue_finalize },
23602489 {0 , 0 },
23612490};
23622491
@@ -2401,8 +2530,11 @@ _select_traverse(PyObject *module, visitproc visit, void *arg)
24012530 Py_VISIT (state -> poll_Type );
24022531 Py_VISIT (state -> devpoll_Type );
24032532 Py_VISIT (state -> pyEpoll_Type );
2533+ #ifdef HAVE_KQUEUE
24042534 Py_VISIT (state -> kqueue_event_Type );
24052535 Py_VISIT (state -> kqueue_queue_Type );
2536+ // state->kqueue_open_list only holds borrowed refs
2537+ #endif
24062538 return 0 ;
24072539}
24082540
@@ -2415,8 +2547,10 @@ _select_clear(PyObject *module)
24152547 Py_CLEAR (state -> poll_Type );
24162548 Py_CLEAR (state -> devpoll_Type );
24172549 Py_CLEAR (state -> pyEpoll_Type );
2550+ #ifdef HAVE_KQUEUE
24182551 Py_CLEAR (state -> kqueue_event_Type );
24192552 Py_CLEAR (state -> kqueue_queue_Type );
2553+ #endif
24202554 return 0 ;
24212555}
24222556
@@ -2550,6 +2684,8 @@ _select_exec(PyObject *m)
25502684#endif /* HAVE_EPOLL */
25512685
25522686#ifdef HAVE_KQUEUE
2687+ state -> kqueue_open_list = NULL ;
2688+
25532689 state -> kqueue_event_Type = (PyTypeObject * )PyType_FromModuleAndSpec (
25542690 m , & kqueue_event_Type_spec , NULL );
25552691 if (state -> kqueue_event_Type == NULL ) {
0 commit comments