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
12 changes: 9 additions & 3 deletions Lib/test/test_devpoll.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,15 @@ def test_devpoll1(self):

self.assertEqual(bufs, [MSG] * NUM_PIPES)

def create_pipe(self):
w, r = os.pipe()
self.addCleanup(os.close, w)
self.addCleanup(os.close, r)
return (w, r)

def test_timeout_overflow(self):
pollster = select.devpoll()
w, r = os.pipe()
w, r = self.create_pipe()
pollster.register(w)

pollster.poll(-1)
Expand Down Expand Up @@ -129,7 +135,7 @@ def test_fd_non_inheritable(self):

def test_events_mask_overflow(self):
pollster = select.devpoll()
w, r = os.pipe()
w, r = self.create_pipe()
pollster.register(w)
# Issue #17919
self.assertRaises(ValueError, pollster.register, 0, -1)
Expand All @@ -141,7 +147,7 @@ def test_events_mask_overflow(self):
def test_events_mask_overflow_c_limits(self):
from _testcapi import USHRT_MAX
pollster = select.devpoll()
w, r = os.pipe()
w, r = self.create_pipe()
pollster.register(w)
# Issue #17919
self.assertRaises(OverflowError, pollster.register, 0, USHRT_MAX + 1)
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,7 @@ def test_empty_select_timeout(self):

@unittest.skipUnless(hasattr(selectors, 'DevpollSelector'),
"Test needs selectors.DevpollSelector")
class DevpollSelectorTestCase(BaseSelectorTestCase, ScalableSelectorMixIn,
unittest.TestCase):
class DevpollSelectorTestCase(BaseSelectorTestCase, unittest.TestCase):

SELECTOR = getattr(selectors, 'DevpollSelector', None)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:mod:`select`: Fixed excessive memory allocation and data race issues in Solaris specific :func:`select.devpoll`. Patch by Jakub Kulik.
96 changes: 83 additions & 13 deletions Modules/selectmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -824,12 +824,17 @@ poll_dealloc(PyObject *op)
#ifdef HAVE_SYS_DEVPOLL_H
static PyMethodDef devpoll_methods[];

#define DEVPOLL_IN_BUFFER_SIZE 128
#define DEVPOLL_OUT_BUFFER_SIZE 1024

typedef struct {
PyObject_HEAD
int fd_devpoll;
int max_n_fds;
int n_fds;
int out_size;
PyObject *registered; // set of registered fds
struct pollfd *fds;
struct pollfd *out_fds;
} devpollObject;

#define devpollObject_CAST(op) ((devpollObject *)(op))
Expand Down Expand Up @@ -878,11 +883,22 @@ internal_devpoll_register(devpollObject *self, int fd,
if (self->fd_devpoll < 0)
return devpoll_err_closed();

// registered.add(fd)
PyObject *fd_obj = PyLong_FromLong(fd);
if (fd_obj == NULL) {
return NULL;
}
int res = PySet_Add(self->registered, fd_obj);
Py_DECREF(fd_obj);
if (res < 0) {
return NULL;
}

if (remove) {
self->fds[self->n_fds].fd = fd;
self->fds[self->n_fds].events = POLLREMOVE;

if (++self->n_fds == self->max_n_fds) {
if (++self->n_fds == DEVPOLL_IN_BUFFER_SIZE) {
if (devpoll_flush(self))
return NULL;
}
Expand All @@ -891,7 +907,7 @@ internal_devpoll_register(devpollObject *self, int fd,
self->fds[self->n_fds].fd = fd;
self->fds[self->n_fds].events = (signed short)events;

if (++self->n_fds == self->max_n_fds) {
if (++self->n_fds == DEVPOLL_IN_BUFFER_SIZE) {
if (devpoll_flush(self))
return NULL;
}
Expand Down Expand Up @@ -960,10 +976,21 @@ select_devpoll_unregister_impl(devpollObject *self, int fd)
if (self->fd_devpoll < 0)
return devpoll_err_closed();

// registered.discard(fd)
PyObject *fd_obj = PyLong_FromLong(fd);
if (fd_obj == NULL) {
return NULL;
}
int res = PySet_Discard(self->registered, fd_obj);
Py_DECREF(fd_obj);
if (res < 0) {
return NULL;
}

self->fds[self->n_fds].fd = fd;
self->fds[self->n_fds].events = POLLREMOVE;

if (++self->n_fds == self->max_n_fds) {
if (++self->n_fds == DEVPOLL_IN_BUFFER_SIZE) {
if (devpoll_flush(self))
return NULL;
}
Expand Down Expand Up @@ -1024,8 +1051,20 @@ select_devpoll_poll_impl(devpollObject *self, PyObject *timeout_obj)
if (devpoll_flush(self))
return NULL;

dvp.dp_fds = self->fds;
dvp.dp_nfds = self->max_n_fds;
/* Ensure the output buffer is large enough to potentially
* fit all registered file descriptors. */
Py_ssize_t registered = PySet_GET_SIZE(self->registered);
if (registered > self->out_size) {
self->out_size = registered + 128;
PyMem_Resize(self->out_fds, struct pollfd, self->out_size);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very suspicious. On failure, it leaks memory and leave the object in inconsistent state.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right. I suggest this fix (untested):

        struct pollfd *new_out_fds = self->out_fds;
        PyMem_Resize(new_out_fds, struct pollfd, self->out_size);
        if (new_out_fds == NULL) {
            PyErr_NoMemory();
            return NULL;
        }
        self->out_fds = new_out_fds;

if (self->out_fds == NULL) {
PyErr_NoMemory();
return NULL;
}
}

dvp.dp_fds = self->out_fds;
dvp.dp_nfds = self->out_size;
dvp.dp_timeout = (int)ms;

if (timeout >= 0) {
Expand Down Expand Up @@ -1069,8 +1108,8 @@ select_devpoll_poll_impl(devpollObject *self, PyObject *timeout_obj)
return NULL;

for (i = 0; i < poll_result; i++) {
num1 = PyLong_FromLong(self->fds[i].fd);
num2 = PyLong_FromLong(self->fds[i].revents);
num1 = PyLong_FromLong(self->out_fds[i].fd);
num2 = PyLong_FromLong(self->out_fds[i].revents);
if ((num1 == NULL) || (num2 == NULL)) {
Py_XDECREF(num1);
Py_XDECREF(num2);
Expand Down Expand Up @@ -1162,12 +1201,12 @@ static devpollObject *
newDevPollObject(PyObject *module)
{
devpollObject *self;
int fd_devpoll, limit_result;
struct pollfd *fds;
int fd_devpoll, limit_result, out_size;
struct pollfd *fds, *out_fds;
struct rlimit limit;

/*
** If we try to process more that getrlimit()
** If we try to process more than getrlimit()
** fds, the kernel will give an error, so
** we set the limit here. It is a dynamic
** value, because we can change rlimit() anytime.
Expand All @@ -1178,27 +1217,56 @@ newDevPollObject(PyObject *module)
return NULL;
}

/*
** If the limit is too high (or RLIM_INFINITY), we might
** allocate huge amounts of memory or even fail to allocate.
*/
out_size = limit.rlim_cur;
if ((rlim_t)out_size > DEVPOLL_OUT_BUFFER_SIZE) {
out_size = DEVPOLL_OUT_BUFFER_SIZE;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really worth it to getrlimit(RLIMIT_NOFILE) if we limit the value to 128 anyway? I suggest to always use 128.

select.devpoll.poll() enlarges outsize on demand anyway.


fd_devpoll = _Py_open("/dev/poll", O_RDWR);
if (fd_devpoll == -1)
return NULL;

fds = PyMem_NEW(struct pollfd, limit.rlim_cur);
fds = PyMem_NEW(struct pollfd, DEVPOLL_IN_BUFFER_SIZE);
if (fds == NULL) {
close(fd_devpoll);
PyErr_NoMemory();
return NULL;
}

out_fds = PyMem_NEW(struct pollfd, out_size);
if (out_fds == NULL) {
close(fd_devpoll);
PyMem_Free(fds);
PyErr_NoMemory();
return NULL;
}

PyObject *registered = PySet_New(NULL);
if (registered == NULL) {
close(fd_devpoll);
PyMem_Free(fds);
PyMem_Free(out_fds);
return NULL;
}

self = PyObject_New(devpollObject, get_select_state(module)->devpoll_Type);
if (self == NULL) {
close(fd_devpoll);
PyMem_Free(fds);
PyMem_Free(out_fds);
Py_DECREF(registered);
return NULL;
}
self->fd_devpoll = fd_devpoll;
self->max_n_fds = limit.rlim_cur;
self->n_fds = 0;
self->fds = fds;
self->registered = registered;
self->out_size = out_size;
self->out_fds = out_fds;

return self;
}
Expand All @@ -1210,6 +1278,8 @@ devpoll_dealloc(PyObject *op)
PyTypeObject *type = Py_TYPE(self);
(void)devpoll_internal_close(self);
PyMem_Free(self->fds);
PyMem_Free(self->out_fds);
Py_DECREF(self->registered);
PyObject_Free(self);
Py_DECREF(type);
}
Expand Down
Loading