Skip to content

uasyncio: make uasyncio.Event() safe to call from an interrupt (RFC, WIP) #6056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
8 changes: 8 additions & 0 deletions docs/library/micropython.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ Functions
incoming stream of characters that is usually used for the REPL, in case
that stream is used for other purposes.

.. function:: scheduler_lock()

Lock the scheduler. May be nested.

.. function:: scheduler_unlock()

Unlock the scheduler. May be nested.

.. function:: schedule(func, arg)

Schedule the function *func* to be executed "very soon". The function
Expand Down
5 changes: 5 additions & 0 deletions extmod/modlwip.c
Original file line number Diff line number Diff line change
Expand Up @@ -1733,10 +1733,15 @@ MP_DEFINE_CONST_FUN_OBJ_0(lwip_print_pcbs_obj, lwip_print_pcbs);

#if MICROPY_PY_LWIP

MP_DECLARE_CONST_FUN_OBJ_0(mod_socket_socketpair_obj);

STATIC const mp_rom_map_elem_t mp_module_lwip_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_lwip) },
{ MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&mod_lwip_reset_obj) },
{ MP_ROM_QSTR(MP_QSTR_callback), MP_ROM_PTR(&mod_lwip_callback_obj) },
#if MICROPY_PY_USOCKET_SOCKETPAIR
{ MP_ROM_QSTR(MP_QSTR_socketpair), MP_ROM_PTR(&mod_socket_socketpair_obj) },
#endif
{ MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&lwip_getaddrinfo_obj) },
{ MP_ROM_QSTR(MP_QSTR_print_pcbs), MP_ROM_PTR(&lwip_print_pcbs_obj) },
// objects
Expand Down
15 changes: 12 additions & 3 deletions extmod/uasyncio/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,14 @@ def sleep(t):

class IOQueue:
def __init__(self):
import usocket as socket

self.poller = select.poll()
self.map = {} # maps id(stream) to [task_waiting_read, task_waiting_write, stream]
self.pipe_rd, self.pipe_wr = socket.socketpair()
self.pipe_rd.setblocking(False)
self.poller.register(self.pipe_rd, select.POLLIN)
self.buf = bytearray(16)

def _enqueue(self, s, idx):
if id(s) not in self.map:
Expand Down Expand Up @@ -109,8 +115,14 @@ def remove(self, task):
else:
break

def notify_threadsafe(self):
self.pipe_wr.send(b"\x00")

def wait_io_event(self, dt):
for s, ev in self.poller.ipoll(dt):
if s is self.pipe_rd:
s.recv_into(self.buf)
continue
sm = self.map[id(s)]
# print('poll', s, sm, ev)
if ev & ~select.POLLOUT and sm[0] is not None:
Expand Down Expand Up @@ -160,9 +172,6 @@ def run_until_complete(main_task=None):
if t:
# A task waiting on _task_queue; "ph_key" is time to schedule task at
dt = max(0, ticks_diff(t.ph_key, ticks()))
elif not _io_queue.map:
# No tasks can be woken so finished running
return
# print('(poll {})'.format(dt), len(_io_queue.map))
_io_queue.wait_io_event(dt)

Expand Down
15 changes: 15 additions & 0 deletions extmod/uasyncio/event.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# MicroPython uasyncio module
# MIT license; Copyright (c) 2019-2020 Damien P. George

from micropython import scheduler_lock, scheduler_unlock
from . import core

# Event class for primitive events that can be waited on, set, and cleared
# The following methods are safe to call from a scheduled callback: is_set, set, clear
class Event:
def __init__(self):
self.state = False # False=unset; True=set
Expand All @@ -13,19 +15,32 @@ def is_set(self):
return self.state

def set(self):
if self.state:
return
# Event becomes set, schedule any tasks waiting on it
scheduler_lock()
signal = False
while self.waiting.peek():
core._task_queue.push_head(self.waiting.pop_head())
signal = True
if signal:
# signal poll to finish
core._io_queue.notify_threadsafe()
self.state = True
scheduler_unlock()

def clear(self):
self.state = False

async def wait(self):
scheduler_lock()
if not self.state:
# Event not set, put the calling task on the event's waiting queue
self.waiting.push_head(core.cur_task)
# Set calling task's data to the event's queue so it can be removed if needed
core.cur_task.data = self.waiting
scheduler_unlock()
yield
else:
scheduler_unlock()
return True
3 changes: 3 additions & 0 deletions extmod/uasyncio/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# This file contains the core TaskQueue based on a pairing heap, and the core Task class.
# They can optionally be replaced by C implementations.

import micropython
from . import core


Expand Down Expand Up @@ -152,6 +153,7 @@ def cancel(self):
# Can't cancel self (not supported yet).
if self is core.cur_task:
raise RuntimeError("can't cancel self")
micropython.scheduler_lock()
# If Task waits on another task then forward the cancel to the one it's waiting on.
while isinstance(self.data, Task):
self = self.data
Expand All @@ -165,4 +167,5 @@ def cancel(self):
core._task_queue.remove(self)
core._task_queue.push_head(self)
self.data = core.CancelledError
micropython.scheduler_unlock()
return True
142 changes: 142 additions & 0 deletions extmod/usocket_socketpair.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include "py/runtime.h"
#include "py/stream.h"
#include "py/mphal.h"

#if MICROPY_PY_USOCKET_SOCKETPAIR

typedef struct _mp_obj_socketpair_t {
mp_obj_base_t base;
bool blocking;
volatile size_t avail;
uint8_t buf[1];
} mp_obj_socketpair_t;

STATIC mp_uint_t socketpair_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
mp_obj_socketpair_t *self = MP_OBJ_TO_PTR(self_in);
switch (request) {
case MP_STREAM_POLL: {
uintptr_t flags = arg;
if (self->avail) {
return MP_STREAM_POLL_RD & flags;
} else {
return MP_STREAM_POLL_WR & flags;
}
}
default:
*errcode = MP_EINVAL;
return MP_STREAM_ERROR;
}
}

STATIC mp_obj_t socketpair_recv_into(mp_obj_t self_in, mp_obj_t buf_in) {
mp_obj_socketpair_t *self = MP_OBJ_TO_PTR(self_in);

mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE);
if (bufinfo.len == 0) {
return MP_OBJ_NEW_SMALL_INT(0);
}

if (!self->avail) {
if (!self->blocking) {
mp_raise_OSError(MP_EAGAIN);
}
while (!self->avail) {
MICROPY_EVENT_POLL_HOOK;
}
}

((uint8_t *)bufinfo.buf)[0] = self->buf[0];
self->avail = 0;

return MP_OBJ_NEW_SMALL_INT(1);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpair_recv_into_obj, socketpair_recv_into);

STATIC mp_obj_t socketpair_send(mp_obj_t self_in, mp_obj_t buf_in) {
mp_obj_socketpair_t *self = MP_OBJ_TO_PTR(self_in);

mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
if (bufinfo.len == 0) {
return MP_OBJ_NEW_SMALL_INT(0);
}

if (self->avail) {
return MP_OBJ_NEW_SMALL_INT(0);
}

self->buf[0] = ((uint8_t *)bufinfo.buf)[0];
self->avail = 1;

return MP_OBJ_NEW_SMALL_INT(1);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpair_send_obj, socketpair_send);

STATIC mp_obj_t socketpair_setblocking(mp_obj_t self_in, mp_obj_t flag_in) {
mp_obj_socketpair_t *self = MP_OBJ_TO_PTR(self_in);
self->blocking = mp_obj_is_true(flag_in);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(socketpair_setblocking_obj, socketpair_setblocking);

STATIC const mp_rom_map_elem_t socketpair_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_recv_into), MP_ROM_PTR(&socketpair_recv_into_obj) },
{ MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&socketpair_send_obj) },
{ MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&socketpair_setblocking_obj) },
};
STATIC MP_DEFINE_CONST_DICT(socketpair_locals_dict, socketpair_locals_dict_table);

STATIC const mp_stream_p_t socketpair_stream_p = {
.ioctl = socketpair_ioctl,
};

STATIC const mp_obj_type_t mp_type_socketpair = {
{ &mp_type_type },
.name = MP_QSTR_socketpair,
.protocol = &socketpair_stream_p,
.locals_dict = (mp_obj_dict_t *)&socketpair_locals_dict,
};

STATIC mp_obj_t socketpair_new(void) {
mp_obj_socketpair_t *self = m_new_obj(mp_obj_socketpair_t);
self->base.type = &mp_type_socketpair;
self->blocking = true;
self->avail = 0;
return MP_OBJ_FROM_PTR(self);
}

STATIC mp_obj_t mod_socket_socketpair(void) {
mp_obj_t s = socketpair_new();
mp_obj_t tuple[2] = { s, s };
return mp_obj_new_tuple(2, tuple);
}
MP_DEFINE_CONST_FUN_OBJ_0(mod_socket_socketpair_obj, mod_socket_socketpair);

#endif // MICROPY_PY_USOCKET_SOCKETPAIR
5 changes: 5 additions & 0 deletions ports/esp32/modsocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,10 +829,15 @@ STATIC mp_obj_t esp_socket_initialize() {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_socket_initialize_obj, esp_socket_initialize);

MP_DECLARE_CONST_FUN_OBJ_0(mod_socket_socketpair_obj);

STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usocket) },
{ MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_socket_initialize_obj) },
{ MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&socket_type) },
#if MICROPY_PY_USOCKET_SOCKETPAIR
{ MP_ROM_QSTR(MP_QSTR_socketpair), MP_ROM_PTR(&mod_socket_socketpair_obj) },
#endif
{ MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&esp_socket_getaddrinfo_obj) },

{ MP_ROM_QSTR(MP_QSTR_AF_INET), MP_ROM_INT(AF_INET) },
Expand Down
1 change: 1 addition & 0 deletions ports/esp32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
#define MICROPY_PY_WEBREPL (1)
#define MICROPY_PY_FRAMEBUF (1)
#define MICROPY_PY_USOCKET_EVENTS (MICROPY_PY_WEBREPL)
#define MICROPY_PY_USOCKET_SOCKETPAIR (1)
#define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1)
#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ("ESP32")

Expand Down
1 change: 1 addition & 0 deletions ports/esp8266/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
#define MICROPY_PY_UZLIB (1)
#define MICROPY_PY_LWIP (1)
#define MICROPY_PY_LWIP_SOCK_RAW (1)
#define MICROPY_PY_USOCKET_SOCKETPAIR (1)
#define MICROPY_PY_MACHINE (1)
#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new
#define MICROPY_PY_MACHINE_PULSE (1)
Expand Down
5 changes: 5 additions & 0 deletions ports/stm32/modusocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,15 @@ STATIC mp_obj_t mod_usocket_getaddrinfo(mp_obj_t host_in, mp_obj_t port_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_usocket_getaddrinfo_obj, mod_usocket_getaddrinfo);

MP_DECLARE_CONST_FUN_OBJ_0(mod_socket_socketpair_obj);

STATIC const mp_rom_map_elem_t mp_module_usocket_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usocket) },

{ MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&socket_type) },
#if MICROPY_PY_USOCKET_SOCKETPAIR
{ MP_ROM_QSTR(MP_QSTR_socketpair), MP_ROM_PTR(&mod_socket_socketpair_obj) },
#endif
{ MP_ROM_QSTR(MP_QSTR_getaddrinfo), MP_ROM_PTR(&mod_usocket_getaddrinfo_obj) },

// class constants
Expand Down
42 changes: 29 additions & 13 deletions ports/stm32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
#endif
#ifndef MICROPY_PY_USOCKET
#define MICROPY_PY_USOCKET (1)
#define MICROPY_PY_USOCKET_SOCKETPAIR (1)
#endif
#ifndef MICROPY_PY_NETWORK
#define MICROPY_PY_NETWORK (1)
Expand Down Expand Up @@ -376,31 +377,46 @@ static inline mp_uint_t disable_irq(void) {
#define MICROPY_END_ATOMIC_SECTION(state) enable_irq(state)

#if MICROPY_PY_THREAD
#define MICROPY_EVENT_POLL_HOOK \
#define MICROPY_EVENT_WAIT_ATOMIC \
do { \
extern void mp_handle_pending(bool); \
mp_handle_pending(true); \
if (pyb_thread_enabled) { \
MP_THREAD_GIL_EXIT(); \
pyb_thread_yield(); \
MP_THREAD_GIL_ENTER(); \
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); \
if (!mp_sched_any_pending()) { \
if (pyb_thread_enabled) { \
MICROPY_END_ATOMIC_SECTION(atomic_state); \
MP_THREAD_GIL_EXIT(); \
pyb_thread_yield(); \
MP_THREAD_GIL_ENTER(); \
} else { \
__WFI(); \
MICROPY_END_ATOMIC_SECTION(atomic_state); \
} \
} else { \
__WFI(); \
MICROPY_END_ATOMIC_SECTION(atomic_state); \
} \
} while (0);
} while (0)

#define MICROPY_THREAD_YIELD() pyb_thread_yield()
#else

#define MICROPY_EVENT_WAIT_ATOMIC \
do { \
mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); \
if (!mp_sched_any_pending()) { \
__WFI(); \
} \
MICROPY_END_ATOMIC_SECTION(atomic_state); \
} while (0)

#define MICROPY_THREAD_YIELD()
#endif

#define MICROPY_EVENT_POLL_HOOK \
do { \
MICROPY_EVENT_WAIT_ATOMIC; \
extern void mp_handle_pending(bool); \
mp_handle_pending(true); \
__WFI(); \
} while (0);

#define MICROPY_THREAD_YIELD()
#endif

// The LwIP interface must run at a raised IRQ priority
#define MICROPY_PY_LWIP_ENTER uint32_t atomic_state = raise_irq_pri(IRQ_PRI_PENDSV);
#define MICROPY_PY_LWIP_REENTER atomic_state = raise_irq_pri(IRQ_PRI_PENDSV);
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy