summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--extmod/modselect.c308
-rw-r--r--extmod/vfs_posix_file.c2
-rw-r--r--ports/unix/variants/mpconfigvariant_common.h1
-rw-r--r--py/mpconfig.h7
4 files changed, 284 insertions, 34 deletions
diff --git a/extmod/modselect.c b/extmod/modselect.c
index 7c4e968e8..01b1474d7 100644
--- a/extmod/modselect.c
+++ b/extmod/modselect.c
@@ -35,6 +35,29 @@
#if MICROPY_PY_SELECT
+#if MICROPY_PY_SELECT_SELECT && MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+#error "select.select is not supported with MICROPY_PY_SELECT_POSIX_OPTIMISATIONS"
+#endif
+
+#if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+
+#include <poll.h>
+
+#if !((MP_STREAM_POLL_RD) == (POLLIN) && \
+ (MP_STREAM_POLL_WR) == (POLLOUT) && \
+ (MP_STREAM_POLL_ERR) == (POLLERR) && \
+ (MP_STREAM_POLL_HUP) == (POLLHUP) && \
+ (MP_STREAM_POLL_NVAL) == (POLLNVAL))
+#error "With MICROPY_PY_SELECT_POSIX_OPTIMISATIONS enabled, POLL constants must match"
+#endif
+
+// When non-file-descriptor objects are on the list to be polled (the polling of
+// which involves repeatedly calling ioctl(MP_STREAM_POLL)), this variable sets
+// the period between polling these objects.
+#define MICROPY_PY_SELECT_IOCTL_CALL_PERIOD_MS (1)
+
+#endif
+
// Flags for poll()
#define FLAG_ONESHOT (1)
@@ -42,18 +65,42 @@
typedef struct _poll_obj_t {
mp_obj_t obj;
mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode);
- mp_uint_t flags;
- mp_uint_t flags_ret;
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ // If the pollable object has an associated file descriptor, then pollfd points to an entry
+ // in poll_set_t::pollfds, and the events/revents fields for this object are stored in the
+ // pollfd entry (and the nonfd_* members are unused).
+ // Otherwise the object is a non-file-descriptor object and pollfd==NULL, and the events/
+ // revents fields are stored in the nonfd_* members (which are named as such so that code
+ // doesn't accidentally mix the use of these members when this optimisation is used).
+ struct pollfd *pollfd;
+ uint16_t nonfd_events;
+ uint16_t nonfd_revents;
+ #else
+ mp_uint_t events;
+ mp_uint_t revents;
+ #endif
} poll_obj_t;
// A set of pollable objects.
typedef struct _poll_set_t {
// Map containing a dict with key=object to poll, value=its corresponding poll_obj_t.
mp_map_t map;
+
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ // Array of pollfd entries for objects that have a file descriptor.
+ unsigned short alloc;
+ unsigned short len;
+ struct pollfd *pollfds;
+ #endif
} poll_set_t;
STATIC void poll_set_init(poll_set_t *poll_set, size_t n) {
mp_map_init(&poll_set->map, n);
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ poll_set->alloc = 0;
+ poll_set->len = 0;
+ poll_set->pollfds = NULL;
+ #endif
}
#if MICROPY_PY_SELECT_SELECT
@@ -62,25 +109,141 @@ STATIC void poll_set_deinit(poll_set_t *poll_set) {
}
#endif
-STATIC void poll_set_add_obj(poll_set_t *poll_set, const mp_obj_t *obj, mp_uint_t obj_len, mp_uint_t flags, bool or_flags) {
+#if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+
+STATIC mp_uint_t poll_obj_get_events(poll_obj_t *poll_obj) {
+ assert(poll_obj->pollfd == NULL);
+ return poll_obj->nonfd_events;
+}
+
+STATIC void poll_obj_set_events(poll_obj_t *poll_obj, mp_uint_t events) {
+ if (poll_obj->pollfd != NULL) {
+ poll_obj->pollfd->events = events;
+ } else {
+ poll_obj->nonfd_events = events;
+ }
+}
+
+STATIC mp_uint_t poll_obj_get_revents(poll_obj_t *poll_obj) {
+ if (poll_obj->pollfd != NULL) {
+ return poll_obj->pollfd->revents;
+ } else {
+ return poll_obj->nonfd_revents;
+ }
+}
+
+STATIC void poll_obj_set_revents(poll_obj_t *poll_obj, mp_uint_t revents) {
+ if (poll_obj->pollfd != NULL) {
+ poll_obj->pollfd->revents = revents;
+ } else {
+ poll_obj->nonfd_revents = revents;
+ }
+}
+
+STATIC struct pollfd *poll_set_add_fd(poll_set_t *poll_set, int fd) {
+ struct pollfd *free_slot = NULL;
+ for (unsigned int i = 0; i < poll_set->len; ++i) {
+ struct pollfd *slot = &poll_set->pollfds[i];
+ if (slot->fd == -1) {
+ free_slot = slot;
+ break;
+ }
+ }
+
+ if (free_slot == NULL) {
+ if (poll_set->len >= poll_set->alloc) {
+ poll_set->pollfds = m_renew(struct pollfd, poll_set->pollfds, poll_set->alloc, poll_set->alloc + 4);
+ poll_set->alloc += 4;
+ }
+ free_slot = &poll_set->pollfds[poll_set->len++];
+ }
+
+ free_slot->fd = fd;
+
+ return free_slot;
+}
+
+static inline bool poll_set_all_are_fds(poll_set_t *poll_set) {
+ return poll_set->map.used == poll_set->len;
+}
+
+#else
+
+static inline mp_uint_t poll_obj_get_events(poll_obj_t *poll_obj) {
+ return poll_obj->events;
+}
+
+static inline void poll_obj_set_events(poll_obj_t *poll_obj, mp_uint_t events) {
+ poll_obj->events = events;
+}
+
+static inline mp_uint_t poll_obj_get_revents(poll_obj_t *poll_obj) {
+ return poll_obj->revents;
+}
+
+static inline void poll_obj_set_revents(poll_obj_t *poll_obj, mp_uint_t revents) {
+ poll_obj->revents = revents;
+}
+
+#endif
+
+STATIC void poll_set_add_obj(poll_set_t *poll_set, const mp_obj_t *obj, mp_uint_t obj_len, mp_uint_t events, bool or_events) {
for (mp_uint_t i = 0; i < obj_len; i++) {
mp_map_elem_t *elem = mp_map_lookup(&poll_set->map, mp_obj_id(obj[i]), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
if (elem->value == MP_OBJ_NULL) {
// object not found; get its ioctl and add it to the poll list
- const mp_stream_p_t *stream_p = mp_get_stream_raise(obj[i], MP_STREAM_OP_IOCTL);
+
+ // If an exception is raised below when adding the new object then the map entry for that
+ // object remains unpopulated, and methods like poll() may crash. This case is not handled.
+
poll_obj_t *poll_obj = m_new_obj(poll_obj_t);
poll_obj->obj = obj[i];
+
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ int fd = -1;
+ if (mp_obj_is_int(obj[i])) {
+ // A file descriptor integer passed in as the object, so use it directly.
+ fd = mp_obj_get_int(obj[i]);
+ if (fd < 0) {
+ mp_raise_ValueError(NULL);
+ }
+ poll_obj->ioctl = NULL;
+ } else {
+ // An object passed in. Check if it has a file descriptor.
+ const mp_stream_p_t *stream_p = mp_get_stream_raise(obj[i], MP_STREAM_OP_IOCTL);
+ poll_obj->ioctl = stream_p->ioctl;
+ int err;
+ mp_uint_t res = stream_p->ioctl(obj[i], MP_STREAM_GET_FILENO, 0, &err);
+ if (res != MP_STREAM_ERROR) {
+ fd = res;
+ }
+ }
+ if (fd >= 0) {
+ // Object has a file descriptor so add it to pollfds.
+ poll_obj->pollfd = poll_set_add_fd(poll_set, fd);
+ } else {
+ // Object doesn't have a file descriptor.
+ poll_obj->pollfd = NULL;
+ }
+ #else
+ const mp_stream_p_t *stream_p = mp_get_stream_raise(obj[i], MP_STREAM_OP_IOCTL);
poll_obj->ioctl = stream_p->ioctl;
- poll_obj->flags = flags;
- poll_obj->flags_ret = 0;
+ #endif
+
+ poll_obj_set_events(poll_obj, events);
+ poll_obj_set_revents(poll_obj, 0);
elem->value = MP_OBJ_FROM_PTR(poll_obj);
} else {
- // object exists; update its flags
- if (or_flags) {
- ((poll_obj_t *)MP_OBJ_TO_PTR(elem->value))->flags |= flags;
- } else {
- ((poll_obj_t *)MP_OBJ_TO_PTR(elem->value))->flags = flags;
+ // object exists; update its events
+ poll_obj_t *poll_obj = (poll_obj_t *)MP_OBJ_TO_PTR(elem->value);
+ #if MICROPY_PY_SELECT_SELECT
+ if (or_events) {
+ events |= poll_obj_get_events(poll_obj);
}
+ #else
+ (void)or_events;
+ #endif
+ poll_obj_set_events(poll_obj, events);
}
}
}
@@ -94,9 +257,17 @@ STATIC mp_uint_t poll_set_poll_once(poll_set_t *poll_set, size_t *rwx_num) {
}
poll_obj_t *poll_obj = MP_OBJ_TO_PTR(poll_set->map.table[i].value);
+
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ if (poll_obj->pollfd != NULL) {
+ // Object has file descriptor so will be polled separately by poll().
+ continue;
+ }
+ #endif
+
int errcode;
- mp_int_t ret = poll_obj->ioctl(poll_obj->obj, MP_STREAM_POLL, poll_obj->flags, &errcode);
- poll_obj->flags_ret = ret;
+ mp_int_t ret = poll_obj->ioctl(poll_obj->obj, MP_STREAM_POLL, poll_obj_get_events(poll_obj), &errcode);
+ poll_obj_set_revents(poll_obj, ret);
if (ret == -1) {
// error doing ioctl
@@ -106,6 +277,7 @@ STATIC mp_uint_t poll_set_poll_once(poll_set_t *poll_set, size_t *rwx_num) {
if (ret != 0) {
// object is ready
n_ready += 1;
+ #if MICROPY_PY_SELECT_SELECT
if (rwx_num != NULL) {
if (ret & MP_STREAM_POLL_RD) {
rwx_num[0] += 1;
@@ -117,21 +289,80 @@ STATIC mp_uint_t poll_set_poll_once(poll_set_t *poll_set, size_t *rwx_num) {
rwx_num[2] += 1;
}
}
+ #else
+ (void)rwx_num;
+ #endif
}
}
return n_ready;
}
STATIC mp_uint_t poll_set_poll_until_ready_or_timeout(poll_set_t *poll_set, size_t *rwx_num, mp_uint_t timeout) {
- mp_uint_t start_tick = mp_hal_ticks_ms();
+ mp_uint_t start_ticks = mp_hal_ticks_ms();
+
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+
+ for (;;) {
+ MP_THREAD_GIL_EXIT();
+
+ // Compute the timeout.
+ int t = MICROPY_PY_SELECT_IOCTL_CALL_PERIOD_MS;
+ if (poll_set_all_are_fds(poll_set)) {
+ // All our pollables are file descriptors, so we can use a blocking
+ // poll and let it (the underlying system) handle the timeout.
+ if (timeout == (mp_uint_t)-1) {
+ t = -1;
+ } else {
+ mp_uint_t delta = mp_hal_ticks_ms() - start_ticks;
+ if (delta >= timeout) {
+ t = 0;
+ } else {
+ t = timeout - delta;
+ }
+ }
+ }
+
+ // Call system poll for those objects that have a file descriptor.
+ int n_ready = poll(poll_set->pollfds, poll_set->len, t);
+
+ MP_THREAD_GIL_ENTER();
+
+ // The call to poll() may have been interrupted, but per PEP 475 we must retry if the
+ // signal is EINTR (this implements a special case of calling MP_HAL_RETRY_SYSCALL()).
+ if (n_ready == -1) {
+ int err = errno;
+ if (err != EINTR) {
+ mp_raise_OSError(err);
+ }
+ n_ready = 0;
+ }
+
+ // Explicitly poll any objects that do not have a file descriptor.
+ if (!poll_set_all_are_fds(poll_set)) {
+ n_ready += poll_set_poll_once(poll_set, rwx_num);
+ }
+
+ // Return if an object is ready, or if the timeout expired.
+ if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_ticks >= timeout)) {
+ return n_ready;
+ }
+
+ // This would be MICROPY_EVENT_POLL_HOOK but the call to poll() above already includes a delay.
+ mp_handle_pending(true);
+ }
+
+ #else
+
for (;;) {
// poll the objects
mp_uint_t n_ready = poll_set_poll_once(poll_set, rwx_num);
- if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_tick >= timeout)) {
+ if (n_ready > 0 || (timeout != (mp_uint_t)-1 && mp_hal_ticks_ms() - start_ticks >= timeout)) {
return n_ready;
}
MICROPY_EVENT_POLL_HOOK
}
+
+ #endif
}
#if MICROPY_PY_SELECT_SELECT
@@ -181,13 +412,13 @@ STATIC mp_obj_t select_select(size_t n_args, const mp_obj_t *args) {
continue;
}
poll_obj_t *poll_obj = MP_OBJ_TO_PTR(poll_set.map.table[i].value);
- if (poll_obj->flags_ret & MP_STREAM_POLL_RD) {
+ if (poll_obj->revents & MP_STREAM_POLL_RD) {
((mp_obj_list_t *)MP_OBJ_TO_PTR(list_array[0]))->items[rwx_len[0]++] = poll_obj->obj;
}
- if (poll_obj->flags_ret & MP_STREAM_POLL_WR) {
+ if (poll_obj->revents & MP_STREAM_POLL_WR) {
((mp_obj_list_t *)MP_OBJ_TO_PTR(list_array[1]))->items[rwx_len[1]++] = poll_obj->obj;
}
- if ((poll_obj->flags_ret & ~(MP_STREAM_POLL_RD | MP_STREAM_POLL_WR)) != 0) {
+ if ((poll_obj->revents & ~(MP_STREAM_POLL_RD | MP_STREAM_POLL_WR)) != 0) {
((mp_obj_list_t *)MP_OBJ_TO_PTR(list_array[2]))->items[rwx_len[2]++] = poll_obj->obj;
}
}
@@ -210,13 +441,13 @@ typedef struct _mp_obj_poll_t {
// register(obj[, eventmask])
STATIC mp_obj_t poll_register(size_t n_args, const mp_obj_t *args) {
mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]);
- mp_uint_t flags;
+ mp_uint_t events;
if (n_args == 3) {
- flags = mp_obj_get_int(args[2]);
+ events = mp_obj_get_int(args[2]);
} else {
- flags = MP_STREAM_POLL_RD | MP_STREAM_POLL_WR;
+ events = MP_STREAM_POLL_RD | MP_STREAM_POLL_WR;
}
- poll_set_add_obj(&self->poll_set, &args[1], 1, flags, false);
+ poll_set_add_obj(&self->poll_set, &args[1], 1, events, false);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register);
@@ -224,7 +455,20 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register);
// unregister(obj)
STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) {
mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in);
- mp_map_lookup(&self->poll_set.map, mp_obj_id(obj_in), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
+ mp_map_elem_t *elem = mp_map_lookup(&self->poll_set.map, mp_obj_id(obj_in), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
+
+ #if MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+ if (elem != NULL) {
+ poll_obj_t *poll_obj = (poll_obj_t *)MP_OBJ_TO_PTR(elem->value);
+ if (poll_obj->pollfd != NULL) {
+ poll_obj->pollfd->fd = -1;
+ }
+ elem->value = MP_OBJ_NULL;
+ }
+ #else
+ (void)elem;
+ #endif
+
// TODO raise KeyError if obj didn't exist in map
return mp_const_none;
}
@@ -237,7 +481,7 @@ STATIC mp_obj_t poll_modify(mp_obj_t self_in, mp_obj_t obj_in, mp_obj_t eventmas
if (elem == NULL) {
mp_raise_OSError(MP_ENOENT);
}
- ((poll_obj_t *)MP_OBJ_TO_PTR(elem->value))->flags = mp_obj_get_int(eventmask_in);
+ poll_obj_set_events((poll_obj_t *)MP_OBJ_TO_PTR(elem->value), mp_obj_get_int(eventmask_in));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(poll_modify_obj, poll_modify);
@@ -277,12 +521,12 @@ STATIC mp_obj_t poll_poll(size_t n_args, const mp_obj_t *args) {
continue;
}
poll_obj_t *poll_obj = MP_OBJ_TO_PTR(self->poll_set.map.table[i].value);
- if (poll_obj->flags_ret != 0) {
- mp_obj_t tuple[2] = {poll_obj->obj, MP_OBJ_NEW_SMALL_INT(poll_obj->flags_ret)};
+ if (poll_obj_get_revents(poll_obj) != 0) {
+ mp_obj_t tuple[2] = {poll_obj->obj, MP_OBJ_NEW_SMALL_INT(poll_obj_get_revents(poll_obj))};
ret_list->items[n_ready++] = mp_obj_new_tuple(2, tuple);
if (self->flags & FLAG_ONESHOT) {
- // Don't poll next time, until new event flags will be set explicitly
- poll_obj->flags = 0;
+ // Don't poll next time, until new event mask will be set explicitly
+ poll_obj_set_events(poll_obj, 0);
}
}
}
@@ -320,13 +564,13 @@ STATIC mp_obj_t poll_iternext(mp_obj_t self_in) {
continue;
}
poll_obj_t *poll_obj = MP_OBJ_TO_PTR(self->poll_set.map.table[i].value);
- if (poll_obj->flags_ret != 0) {
+ if (poll_obj_get_revents(poll_obj) != 0) {
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->ret_tuple);
t->items[0] = poll_obj->obj;
- t->items[1] = MP_OBJ_NEW_SMALL_INT(poll_obj->flags_ret);
+ t->items[1] = MP_OBJ_NEW_SMALL_INT(poll_obj_get_revents(poll_obj));
if (self->flags & FLAG_ONESHOT) {
- // Don't poll next time, until new event flags will be set explicitly
- poll_obj->flags = 0;
+ // Don't poll next time, until new event mask will be set explicitly
+ poll_obj_set_events(poll_obj, 0);
}
return MP_OBJ_FROM_PTR(t);
}
diff --git a/extmod/vfs_posix_file.c b/extmod/vfs_posix_file.c
index 41d7622b3..d70bc4738 100644
--- a/extmod/vfs_posix_file.c
+++ b/extmod/vfs_posix_file.c
@@ -188,7 +188,7 @@ STATIC mp_uint_t vfs_posix_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_
return 0;
case MP_STREAM_GET_FILENO:
return o->fd;
- #if MICROPY_PY_SELECT
+ #if MICROPY_PY_SELECT && !MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
case MP_STREAM_POLL: {
#ifdef _WIN32
mp_raise_NotImplementedError(MP_ERROR_TEXT("poll on file not available on win32"));
diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h
index 9e320d584..082938ed5 100644
--- a/ports/unix/variants/mpconfigvariant_common.h
+++ b/ports/unix/variants/mpconfigvariant_common.h
@@ -107,6 +107,7 @@
#endif
// The "select" module is enabled by default, but disable select.select().
+#define MICROPY_PY_SELECT_POSIX_OPTIMISATIONS (1)
#define MICROPY_PY_SELECT_SELECT (0)
// Enable the "websocket" module.
diff --git a/py/mpconfig.h b/py/mpconfig.h
index c617f573b..4a15205ae 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -1485,11 +1485,16 @@ typedef double mp_float_t;
#define MICROPY_PY_ERRNO_ERRORCODE (1)
#endif
-// Whether to provide "select" module (baremetal implementation)
+// Whether to provide "select" module
#ifndef MICROPY_PY_SELECT
#define MICROPY_PY_SELECT (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
#endif
+// Whether to enable POSIX optimisations in the "select" module (requires system poll)
+#ifndef MICROPY_PY_SELECT_POSIX_OPTIMISATIONS
+#define MICROPY_PY_SELECT_POSIX_OPTIMISATIONS (0)
+#endif
+
// Whether to enable the select() function in the "select" module (baremetal
// implementation). This is present for compatibility but can be disabled to
// save space.