diff --git a/Doc/library/select.rst b/Doc/library/select.rst index 733a91e20b684d..e02a9c0aa648db 100644 --- a/Doc/library/select.rst +++ b/Doc/library/select.rst @@ -114,7 +114,7 @@ The module defines the following: :ref:`kevent-objects` below for the methods supported by kevent objects. -.. function:: select(rlist, wlist, xlist[, timeout]) +.. function:: select(rlist, wlist, xlist[, timeout], *, setsize=FD_SETSIZE) This is a straightforward interface to the Unix :c:func:`select` system call. The first three arguments are sequences of 'waitable objects': either @@ -157,6 +157,9 @@ The module defines the following: library, and does not handle file descriptors that don't originate from WinSock. + .. versionchanged:: 3.9 + Added *setsize* parameter. + .. versionchanged:: 3.5 The function is now retried with a recomputed timeout when interrupted by a signal, except if the signal handler raises an exception (see @@ -164,6 +167,12 @@ The module defines the following: :exc:`InterruptedError`. +.. attribute:: FD_SETSIZE + + Set size used by :func:`select.select` by default. + + .. versionadded:: 3.9 + .. attribute:: PIPE_BUF The minimum number of bytes which can be written without blocking to a pipe diff --git a/Misc/NEWS.d/next/Library/2019-06-05-18-05-58.bpo-28708.jgeS7X.rst b/Misc/NEWS.d/next/Library/2019-06-05-18-05-58.bpo-28708.jgeS7X.rst new file mode 100644 index 00000000000000..ac87bd0be1bb91 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-06-05-18-05-58.bpo-28708.jgeS7X.rst @@ -0,0 +1,3 @@ +Add *setsize* keywoard-only parameter to :func:`select.select`. It becomes +possible to configure the set size used internally by :func:`select.select` +at runtime. diff --git a/Modules/clinic/selectmodule.c.h b/Modules/clinic/selectmodule.c.h index 9015816f80c143..78f0fa3fed20b9 100644 --- a/Modules/clinic/selectmodule.c.h +++ b/Modules/clinic/selectmodule.c.h @@ -3,7 +3,7 @@ preserve [clinic start generated code]*/ PyDoc_STRVAR(select_select__doc__, -"select($module, rlist, wlist, xlist, timeout=None, /)\n" +"select($module, rlist, wlist, xlist, timeout=None, /, *, setsize=-1)\n" "--\n" "\n" "Wait until one or more file descriptors are ready for some kind of I/O.\n" @@ -30,33 +30,53 @@ PyDoc_STRVAR(select_select__doc__, "descriptors can be used."); #define SELECT_SELECT_METHODDEF \ - {"select", (PyCFunction)(void(*)(void))select_select, METH_FASTCALL, select_select__doc__}, + {"select", (PyCFunction)(void(*)(void))select_select, METH_FASTCALL|METH_KEYWORDS, select_select__doc__}, static PyObject * select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, - PyObject *xlist, PyObject *timeout_obj); + PyObject *xlist, PyObject *timeout_obj, int setsize_int); static PyObject * -select_select(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +select_select(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; + static const char * const _keywords[] = {"", "", "", "", "setsize", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "select", 0}; + PyObject *argsbuf[5]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 3; PyObject *rlist; PyObject *wlist; PyObject *xlist; PyObject *timeout_obj = Py_None; + int setsize_int = FD_SETSIZE; - if (!_PyArg_CheckPositional("select", nargs, 3, 4)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 3, 4, 0, argsbuf); + if (!args) { goto exit; } rlist = args[0]; wlist = args[1]; xlist = args[2]; if (nargs < 4) { - goto skip_optional; + goto skip_optional_posonly; } + noptargs--; timeout_obj = args[3]; -skip_optional: - return_value = select_select_impl(module, rlist, wlist, xlist, timeout_obj); +skip_optional_posonly: + if (!noptargs) { + goto skip_optional_kwonly; + } + if (PyFloat_Check(args[4])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + setsize_int = _PyLong_AsInt(args[4]); + if (setsize_int == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_kwonly: + return_value = select_select_impl(module, rlist, wlist, xlist, timeout_obj, setsize_int); exit: return return_value; @@ -1215,4 +1235,4 @@ select_kqueue_control(kqueue_queue_Object *self, PyObject *const *args, Py_ssize #ifndef SELECT_KQUEUE_CONTROL_METHODDEF #define SELECT_KQUEUE_CONTROL_METHODDEF #endif /* !defined(SELECT_KQUEUE_CONTROL_METHODDEF) */ -/*[clinic end generated code: output=03041f3d09b04a3d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c3e31d14dbc86fd9 input=a9049054013a1b77]*/ diff --git a/Modules/selectmodule.c b/Modules/selectmodule.c index ed71d8b0d59852..b1aeacbe47d9f5 100644 --- a/Modules/selectmodule.c +++ b/Modules/selectmodule.c @@ -94,10 +94,10 @@ typedef struct { } pylist; static void -reap_obj(pylist fd2obj[FD_SETSIZE + 1]) +reap_obj(pylist *fd2obj, size_t setsize) { - unsigned int i; - for (i = 0; i < (unsigned int)FD_SETSIZE + 1 && fd2obj[i].sentinel >= 0; i++) { + size_t i; + for (i = 0; i < setsize + 1 && fd2obj[i].sentinel >= 0; i++) { Py_CLEAR(fd2obj[i].obj); } fd2obj[0].sentinel = -1; @@ -108,10 +108,10 @@ reap_obj(pylist fd2obj[FD_SETSIZE + 1]) returns a number >= 0 */ static int -seq2set(PyObject *seq, fd_set *set, pylist fd2obj[FD_SETSIZE + 1]) +seq2set(PyObject *seq, fd_set *set, pylist *fd2obj, size_t setsize) { int max = -1; - unsigned int index = 0; + size_t index = 0; Py_ssize_t i; PyObject* fast_seq = NULL; PyObject* o = NULL; @@ -145,10 +145,17 @@ seq2set(PyObject *seq, fd_set *set, pylist fd2obj[FD_SETSIZE + 1]) if (v > max) max = v; #endif /* _MSC_VER */ + if ((size_t)v >= setsize) { + PyErr_Format(PyExc_ValueError, + "file descriptor %i greater than " + "or equal to setsize=%zu", + v, setsize); + goto finally; + } FD_SET(v, set); /* add object and its file descriptor to the list */ - if (index >= (unsigned int)FD_SETSIZE) { + if (index >= setsize) { PyErr_SetString(PyExc_ValueError, "too many file descriptors in select()"); goto finally; @@ -169,13 +176,13 @@ seq2set(PyObject *seq, fd_set *set, pylist fd2obj[FD_SETSIZE + 1]) /* returns NULL and sets the Python exception if an error occurred */ static PyObject * -set2list(fd_set *set, pylist fd2obj[FD_SETSIZE + 1]) +set2list(fd_set *set, pylist *fd2obj, size_t setsize) { - int i, j, count=0; + size_t i, j, count=0; PyObject *list, *o; SOCKET fd; - for (j = 0; fd2obj[j].sentinel >= 0; j++) { + for (j = 0; j < setsize && fd2obj[j].sentinel >= 0; j++) { if (FD_ISSET(fd2obj[j].fd, set)) count++; } @@ -184,7 +191,7 @@ set2list(fd_set *set, pylist fd2obj[FD_SETSIZE + 1]) return NULL; i = 0; - for (j = 0; fd2obj[j].sentinel >= 0; j++) { + for (j = 0; j < setsize && fd2obj[j].sentinel >= 0; j++) { fd = fd2obj[j].fd; if (FD_ISSET(fd, set)) { o = fd2obj[j].obj; @@ -215,6 +222,8 @@ select.select xlist: object timeout as timeout_obj: object = None / + * + setsize as setsize_int: int(c_default="FD_SETSIZE") = -1 Wait until one or more file descriptors are ready for some kind of I/O. @@ -242,22 +251,11 @@ descriptors can be used. static PyObject * select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, - PyObject *xlist, PyObject *timeout_obj) -/*[clinic end generated code: output=2b3cfa824f7ae4cf input=177e72184352df25]*/ + PyObject *xlist, PyObject *timeout_obj, int setsize_int) +/*[clinic end generated code: output=9563bc11a5b35909 input=32c56b0582ddd47a]*/ { -#ifdef SELECT_USES_HEAP + size_t setsize; pylist *rfd2obj, *wfd2obj, *efd2obj; -#else /* !SELECT_USES_HEAP */ - /* XXX: All this should probably be implemented as follows: - * - find the highest descriptor we're interested in - * - add one - * - that's the size - * See: Stevens, APitUE, $12.5.1 - */ - pylist rfd2obj[FD_SETSIZE + 1]; - pylist wfd2obj[FD_SETSIZE + 1]; - pylist efd2obj[FD_SETSIZE + 1]; -#endif /* SELECT_USES_HEAP */ PyObject *ret = NULL; fd_set ifdset, ofdset, efdset; struct timeval tv, *tvp; @@ -265,6 +263,14 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, int n; _PyTime_t timeout, deadline = 0; + if (setsize_int <= 0) { + PyErr_SetString(PyExc_ValueError, "setsize must be greater than 0"); + return NULL; + } + else { + setsize = (size_t)setsize_int; + } + if (timeout_obj == Py_None) tvp = (struct timeval *)NULL; else { @@ -286,18 +292,16 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, tvp = &tv; } -#ifdef SELECT_USES_HEAP /* Allocate memory for the lists */ - rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1); - wfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1); - efd2obj = PyMem_NEW(pylist, FD_SETSIZE + 1); + rfd2obj = PyMem_NEW(pylist, setsize + 1); + wfd2obj = PyMem_NEW(pylist, setsize + 1); + efd2obj = PyMem_NEW(pylist, setsize + 1); if (rfd2obj == NULL || wfd2obj == NULL || efd2obj == NULL) { if (rfd2obj) PyMem_DEL(rfd2obj); if (wfd2obj) PyMem_DEL(wfd2obj); if (efd2obj) PyMem_DEL(efd2obj); return PyErr_NoMemory(); } -#endif /* SELECT_USES_HEAP */ /* Convert sequences to fd_sets, and get maximum fd number * propagates the Python exception set in seq2set() @@ -305,11 +309,11 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, rfd2obj[0].sentinel = -1; wfd2obj[0].sentinel = -1; efd2obj[0].sentinel = -1; - if ((imax = seq2set(rlist, &ifdset, rfd2obj)) < 0) + if ((imax = seq2set(rlist, &ifdset, rfd2obj, setsize)) < 0) goto finally; - if ((omax = seq2set(wlist, &ofdset, wfd2obj)) < 0) + if ((omax = seq2set(wlist, &ofdset, wfd2obj, setsize)) < 0) goto finally; - if ((emax = seq2set(xlist, &efdset, efd2obj)) < 0) + if ((emax = seq2set(xlist, &efdset, efd2obj, setsize)) < 0) goto finally; max = imax; @@ -361,9 +365,9 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, convenient to test for this after all three calls... but is that acceptable? */ - rlist = set2list(&ifdset, rfd2obj); - wlist = set2list(&ofdset, wfd2obj); - xlist = set2list(&efdset, efd2obj); + rlist = set2list(&ifdset, rfd2obj, setsize); + wlist = set2list(&ofdset, wfd2obj, setsize); + xlist = set2list(&efdset, efd2obj, setsize); if (PyErr_Occurred()) ret = NULL; else @@ -375,14 +379,12 @@ select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist, } finally: - reap_obj(rfd2obj); - reap_obj(wfd2obj); - reap_obj(efd2obj); -#ifdef SELECT_USES_HEAP + reap_obj(rfd2obj, setsize); + reap_obj(wfd2obj, setsize); + reap_obj(efd2obj, setsize); PyMem_DEL(rfd2obj); PyMem_DEL(wfd2obj); PyMem_DEL(efd2obj); -#endif /* SELECT_USES_HEAP */ return ret; } @@ -2495,6 +2497,8 @@ PyInit_select(void) if (m == NULL) return NULL; + PyModule_AddIntConstant(m, "FD_SETSIZE", FD_SETSIZE); + Py_INCREF(PyExc_OSError); PyModule_AddObject(m, "error", PyExc_OSError);