Skip to content

Commit 8b24aa3

Browse files
jimmodpgeorge
authored andcommitted
extmod/modselect: Handle growing the pollfds allocation correctly.
The poll_obj_t instances have their pollfd field point into this allocation. So if re-allocating results in a move, we need to update the existing poll_obj_t's. Update the test to cover this case. Fixes issue micropython#12887. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent e9bcd49 commit 8b24aa3

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

extmod/modselect.c

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
#if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
4343

44+
#include <string.h>
4445
#include <poll.h>
4546

4647
#if !((MP_STREAM_POLL_RD) == (POLLIN) && \
@@ -142,14 +143,47 @@ STATIC void poll_obj_set_revents(poll_obj_t *poll_obj, mp_uint_t revents) {
142143
}
143144
}
144145

146+
// How much (in pollfds) to grow the allocation for poll_set->pollfds by.
147+
#define POLL_SET_ALLOC_INCREMENT (4)
148+
145149
STATIC struct pollfd *poll_set_add_fd(poll_set_t *poll_set, int fd) {
146150
struct pollfd *free_slot = NULL;
147151

148152
if (poll_set->used == poll_set->max_used) {
149153
// No free slots below max_used, so expand max_used (and possibly allocate).
150154
if (poll_set->max_used >= poll_set->alloc) {
151-
poll_set->pollfds = m_renew(struct pollfd, poll_set->pollfds, poll_set->alloc, poll_set->alloc + 4);
152-
poll_set->alloc += 4;
155+
size_t new_alloc = poll_set->alloc + POLL_SET_ALLOC_INCREMENT;
156+
// Try to grow in-place.
157+
struct pollfd *new_fds = m_renew_maybe(struct pollfd, poll_set->pollfds, poll_set->alloc, new_alloc, false);
158+
if (!new_fds) {
159+
// Failed to grow in-place. Do a new allocation and copy over the pollfd values.
160+
new_fds = m_new(struct pollfd, new_alloc);
161+
memcpy(new_fds, poll_set->pollfds, sizeof(struct pollfd) * poll_set->alloc);
162+
163+
// Update existing poll_obj_t to update their pollfd field to
164+
// point to the same offset inside the new allocation.
165+
for (mp_uint_t i = 0; i < poll_set->map.alloc; ++i) {
166+
if (!mp_map_slot_is_filled(&poll_set->map, i)) {
167+
continue;
168+
}
169+
170+
poll_obj_t *poll_obj = MP_OBJ_TO_PTR(poll_set->map.table[i].value);
171+
if (!poll_obj) {
172+
// This is the one we're currently adding,
173+
// poll_set_add_obj doesn't assign elem->value until
174+
// afterwards.
175+
continue;
176+
}
177+
178+
poll_obj->pollfd = new_fds + (poll_obj->pollfd - poll_set->pollfds);
179+
}
180+
181+
// Delete the old allocation.
182+
m_del(struct pollfd, poll_set->pollfds, poll_set->alloc);
183+
}
184+
185+
poll_set->pollfds = new_fds;
186+
poll_set->alloc = new_alloc;
153187
}
154188
free_slot = &poll_set->pollfds[poll_set->max_used++];
155189
} else {

tests/extmod/select_poll_fd.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,22 @@
3434
# Poll for input, should return an empty list.
3535
print(poller.poll(0))
3636

37-
# Test registering a very large number of file descriptors.
37+
# Test registering a very large number of file descriptors (will trigger
38+
# EINVAL due to more than OPEN_MAX fds).
3839
poller = select.poll()
3940
for fd in range(6000):
4041
poller.register(fd)
4142
try:
4243
poller.poll()
44+
assert False
4345
except OSError as er:
4446
print(er.errno == errno.EINVAL)
47+
48+
# Register stdout/stderr, plus many extra ones to trigger the fd vector
49+
# resizing. Then unregister the excess ones and verify poll still works.
50+
poller = select.poll()
51+
for fd in range(1, 1000):
52+
poller.register(fd)
53+
for i in range(3, 1000):
54+
poller.unregister(i)
55+
print(sorted(poller.poll()))

0 commit comments

Comments
 (0)