From 5a0f6e2c70c95d535bb85916b5d811e653545436 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 12:21:50 -0500 Subject: [PATCH 01/12] Add docstrings to library --- asyncio/core.py | 64 ++++++++++++++++++++++++++++++++++++++ asyncio/event.py | 17 +++++++++++ asyncio/funcs.py | 25 +++++++++++++++ asyncio/lock.py | 20 ++++++++++++ asyncio/stream.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++ asyncio/task.py | 13 ++++++++ 6 files changed, 217 insertions(+) diff --git a/asyncio/core.py b/asyncio/core.py index 31a890d..3db0c03 100644 --- a/asyncio/core.py +++ b/asyncio/core.py @@ -26,10 +26,14 @@ class CancelledError(BaseException): + """Injected into a task when calling `Task.cancel()`""" + pass class TimeoutError(Exception): + """Raised when waiting for a task longer than the specified timeout.""" + pass @@ -65,6 +69,11 @@ def __next__(self): # Pause task execution for the given time (integer in milliseconds, uPy extension) # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): + """Sleep for `t` milliseconds. + + This is a coroutine, and a MicroPython extension. + """ + assert sgen.state is None, "Check for a missing `await` in your code" sgen.state = ticks_add(ticks(), max(0, t)) return sgen @@ -72,6 +81,11 @@ def sleep_ms(t, sgen=SingletonGenerator()): # Pause task execution for the given time (in seconds) def sleep(t): + """Sleep for `t` seconds + + This is a coroutine. + """ + return sleep_ms(int(t * 1000)) @@ -152,6 +166,11 @@ def _promote_to_task(aw): # Create and schedule a new task from a coroutine def create_task(coro): + """Create a new task from the given coroutine and schedule it to run. + + Returns the corresponding `Task` object. + """ + if not hasattr(coro, "send"): raise TypeError("coroutine expected") t = Task(coro, globals()) @@ -161,6 +180,8 @@ def create_task(coro): # Keep scheduling tasks until there are none left to schedule def run_until_complete(main_task=None): + """Run the given _main_task_ until it completes.""" + global cur_task excs_all = (CancelledError, Exception) # To prevent heap allocation in loop excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop @@ -232,6 +253,11 @@ def run_until_complete(main_task=None): # Create a new task from a coroutine and run it until it finishes def run(coro): + """Create a new task from the given coroutine and run it until it completes. + + Returns the value returned be *coro*. + """ + return run_until_complete(create_task(coro)) @@ -247,21 +273,33 @@ async def _stopper(): class Loop: + """Class representing the event loop""" + _exc_handler = None def create_task(coro): + """Create a task from the given *coro* and return the new `Task` object.""" + return create_task(coro) def run_forever(): + """Run the event loop until `stop()` is called.""" + global _stop_task _stop_task = Task(_stopper(), globals()) run_until_complete(_stop_task) # TODO should keep running until .stop() is called, even if there're no tasks left def run_until_complete(aw): + """Run the given *awaitable* until it completes. If *awaitable* is not a task then + it will be promoted to one. + """ + return run_until_complete(_promote_to_task(aw)) def stop(): + """Stop the event loop""" + global _stop_task if _stop_task is not None: _task_queue.push_head(_stop_task) @@ -269,32 +307,58 @@ def stop(): _stop_task = None def close(): + """Close the event loop.""" + pass def set_exception_handler(handler): + """Set the exception handler to call when a Task raises an exception that is not + caught. The *handler* should accept two arguments: ``(loop, context)`` + """ + Loop._exc_handler = handler def get_exception_handler(): + """Get the current exception handler. Returns the handler, or ``None`` if no + custom handler is set. + """ + return Loop._exc_handler def default_exception_handler(loop, context): + """The default exception handler that is called.""" + exc = context["exception"] traceback.print_exception(None, exc, exc.__traceback__) def call_exception_handler(context): + """Call the current exception handler. The argument *context* is passed through + and is a dictionary containing keys: + ``'message'``, ``'exception'``, ``'future'`` + """ (Loop._exc_handler or Loop.default_exception_handler)(Loop, context) # The runq_len and waitq_len arguments are for legacy uasyncio compatibility def get_event_loop(runq_len=0, waitq_len=0): + """Return the event loop used to schedule and run tasks. See `Loop`.""" + return Loop def current_task(): + """Return the `Task` object associated with the currently running task.""" + return cur_task def new_event_loop(): + """Reset the event loop and return it. + + **NOTE**: Since MicroPython only has a single event loop, this function just resets + the loop's state, it does not create a new one + """ + global _task_queue, _io_queue # TaskQueue of Task instances _task_queue = TaskQueue() diff --git a/asyncio/event.py b/asyncio/event.py index 346b974..fc92c2e 100644 --- a/asyncio/event.py +++ b/asyncio/event.py @@ -15,6 +15,10 @@ # Event class for primitive events that can be waited on, set, and cleared class Event: + """Create a new event which can be used to synchronize tasks. Events + start in the cleared state. + """ + def __init__(self): self.state = False # False=unset; True=set self.waiting = ( @@ -22,9 +26,14 @@ def __init__(self): ) # Queue of Tasks waiting on completion of this event def is_set(self): + """Returns ``True`` if the event is set, ``False`` otherwise.""" + return self.state def set(self): + """Set the event. Any tasks waiting on the event will be scheduled to run. + """ + # Event becomes set, schedule any tasks waiting on it # Note: This must not be called from anything except the thread running # the asyncio loop (i.e. neither hard or soft IRQ, or a different thread). @@ -33,9 +42,17 @@ def set(self): self.state = True def clear(self): + """Clear the event.""" + self.state = False async def wait(self): + """Wait for the event to be set. If the event is already set then it returns + immediately. + + This is a coroutine. + """ + if not self.state: # Event not set, put the calling task on the event's waiting queue self.waiting.push_head(core.cur_task) diff --git a/asyncio/funcs.py b/asyncio/funcs.py index 01fe551..8dad9df 100644 --- a/asyncio/funcs.py +++ b/asyncio/funcs.py @@ -15,6 +15,18 @@ async def wait_for(aw, timeout, sleep=core.sleep): + """Wait for the *aw* awaitable to complete, but cancel if it takes longer + than *timeout* seconds. If *aw* is not a task then a task will be created + from it. + + If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: + this should be trapped by the caller. + + Returns the return vaalue of *aw*. + + This is a coroutine. + """ + aw = core._promote_to_task(aw) if timeout is None: return await aw @@ -60,10 +72,23 @@ def runner(waiter, aw): def wait_for_ms(aw, timeout): + """Similar to `wait_for` but *timeout* is an integer in milliseconds. + + This is a coroutine, and a MicroPython extension. + """ + return wait_for(aw, timeout, core.sleep_ms) async def gather(*aws, return_exceptions=False): + """Run all *aws* awaitables concurrently. Any *aws* that are not tasks + are promoted to tasks. + + Returns a list of return values of all *aws* + + This is a coroutine. + """ + ts = [core._promote_to_task(aw) for aw in aws] for i in range(len(ts)): try: diff --git a/asyncio/lock.py b/asyncio/lock.py index 0a93872..b6bc335 100644 --- a/asyncio/lock.py +++ b/asyncio/lock.py @@ -15,6 +15,13 @@ # Lock class for primitive mutex capability class Lock: + """Create a new lock which can be used to coordinate tasks. Locks start in + the unlocked state. + + In addition to the methods below, locks can be used in an ``async with`` + statement. + """ + def __init__(self): # The state can take the following values: # - 0: unlocked @@ -25,9 +32,16 @@ def __init__(self): self.waiting = core.TaskQueue() def locked(self): + """Returns ``True`` if the lock is locked, otherwise ``False``.""" + return self.state == 1 def release(self): + """Release the lock. If any tasks are waiting on the lock then the next + one in the queue is scheduled to run and the lock remains locked. Otherwise, + no tasks are waiting and the lock becomes unlocked. + """ + if self.state != 1: raise RuntimeError("Lock not acquired") if self.waiting.peek(): @@ -39,6 +53,12 @@ def release(self): self.state = 0 async def acquire(self): + """Wait for the lock to be in the unlocked state and then lock it in an + atomic way. Only one task can acquire the lock at any one time. + + This is a coroutine. + """ + if self.state != 0: # Lock unavailable, put the calling Task on the waiting queue self.waiting.push_head(core.cur_task) diff --git a/asyncio/stream.py b/asyncio/stream.py index 41baeb2..0fc2c16 100644 --- a/asyncio/stream.py +++ b/asyncio/stream.py @@ -15,12 +15,21 @@ class Stream: + """This represents a TCP stream connection. To minimise code this class + implements both a reader and a writer, and both ``StreamReader`` and + ``StreamWriter`` alias to this class. + """ + def __init__(self, s, e={}): self.s = s self.e = e self.out_buf = b"" def get_extra_info(self, v): + """Get extra information about the stream, given by *v*. The valid + values for *v* are: ``peername``. + """ + return self.e[v] async def __aenter__(self): @@ -33,18 +42,43 @@ def close(self): pass async def wait_closed(self): + """Wait for the stream to close. + + This is a coroutine. + """ + # TODO yield? self.s.close() async def read(self, n): + """Read up to *n* bytes and return them. + + This is a coroutine. + """ + yield core._io_queue.queue_read(self.s) return self.s.read(n) async def readinto(self, buf): + """Read up to n bytes into *buf* with n being equal to the length of *buf* + + Return the number of bytes read into *buf* + + This is a coroutine, and a MicroPython extension. + """ + yield core._io_queue.queue_read(self.s) return self.s.readinto(buf) async def readexactly(self, n): + """Read exactly *n* bytes and return them as a bytes object. + + Raises an ``EOFError`` exception if the stream ends before reading + *n* bytes. + + This is a coroutine. + """ + r = b"" while n: yield core._io_queue.queue_read(self.s) @@ -57,6 +91,11 @@ async def readexactly(self, n): return r async def readline(self): + """Read a line and return it. + + This is a coroutine. + """ + l = b"" while True: yield core._io_queue.queue_read(self.s) @@ -66,9 +105,19 @@ async def readline(self): return l def write(self, buf): + """Accumulated *buf* to the output buffer. The data is only flushed when + `Stream.drain` is called. It is recommended to call `Stream.drain` + immediately after calling this function. + """ + self.out_buf += buf async def drain(self): + """Drain (write) all buffered output data out to the stream. + + This is a coroutine. + """ + mv = memoryview(self.out_buf) off = 0 while off < len(mv): @@ -86,6 +135,15 @@ async def drain(self): # Create a TCP stream connection to a remote host async def open_connection(host, port): + """Open a TCP connection to the given *host* and *port*. The *host* address will + be resolved using `socket.getaddrinfo`, which is currently a blocking call. + + Returns a pair of streams: a reader and a writer stream. Will raise a socket-specific + ``OSError`` if the host could not be resolved or if the connection could not be made. + + This is a coroutine. + """ + from uerrno import EINPROGRESS import usocket as socket @@ -106,6 +164,10 @@ async def open_connection(host, port): # Class representing a TCP stream server, can be closed and used in "async with" class Server: + """This represents the server class returned from `start_server`. It can be used in + an ``async with`` statement to close the server upon exit. + """ + async def __aenter__(self): return self @@ -114,9 +176,16 @@ async def __aexit__(self, exc_type, exc, tb): await self.wait_closed() def close(self): + """Close the server.""" + self.task.cancel() async def wait_closed(self): + """Wait for the server to close. + + This is a coroutine. + """ + await self.task async def _serve(self, s, cb): @@ -141,6 +210,15 @@ async def _serve(self, s, cb): # Helper function to start a TCP stream server, running as a new task # TODO could use an accept-callback on socket read activity instead of creating a task async def start_server(cb, host, port, backlog=5): + """Start a TCP server on the given *host* and *port*. The *cb* callback wil be + called with incoming, accepted connections, and be passed 2 arguments: reader + writer streams for the connection. + + Returns a `Server` object. + + This is a coroutine. + """ + import usocket as socket # Create and bind server socket. diff --git a/asyncio/task.py b/asyncio/task.py index 2bc8bbd..f86639c 100644 --- a/asyncio/task.py +++ b/asyncio/task.py @@ -130,6 +130,13 @@ def remove(self, v): # Task class representing a coroutine, can be waited on and cancelled. class Task: + """This object wraps a coroutine into a running task. Tasks can be waited on + using ``await task``, which will wait for the task to complete and reutnr the + return value of the task. + + Tasks should not be created directly, rather use `create_task` to create them. + """ + def __init__(self, coro, globals=None): self.coro = coro # Coroutine of this Task self.data = None # General data for queue it is waiting on @@ -162,9 +169,15 @@ def __next__(self): core.cur_task.data = self def done(self): + """Whether the task is complete.""" + return not self.state def cancel(self): + """Cancel the task by injecting a ``CancelledError`` into it. The task + may or may not ignore this exception. + """ + # Check if task is already finished. if not self.state: return False From 1ee7776e4adbc7b9b83d2deff6401479a2be003a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 12:27:44 -0500 Subject: [PATCH 02/12] Trim trailing whitespace per pre-commit --- asyncio/core.py | 12 ++++++------ asyncio/funcs.py | 6 +++--- asyncio/lock.py | 4 ++-- asyncio/stream.py | 24 ++++++++++++------------ asyncio/task.py | 4 ++-- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/asyncio/core.py b/asyncio/core.py index 3db0c03..4ecbc7a 100644 --- a/asyncio/core.py +++ b/asyncio/core.py @@ -70,7 +70,7 @@ def __next__(self): # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): """Sleep for `t` milliseconds. - + This is a coroutine, and a MicroPython extension. """ @@ -82,7 +82,7 @@ def sleep_ms(t, sgen=SingletonGenerator()): # Pause task execution for the given time (in seconds) def sleep(t): """Sleep for `t` seconds - + This is a coroutine. """ @@ -167,7 +167,7 @@ def _promote_to_task(aw): # Create and schedule a new task from a coroutine def create_task(coro): """Create a new task from the given coroutine and schedule it to run. - + Returns the corresponding `Task` object. """ @@ -254,10 +254,10 @@ def run_until_complete(main_task=None): # Create a new task from a coroutine and run it until it finishes def run(coro): """Create a new task from the given coroutine and run it until it completes. - + Returns the value returned be *coro*. """ - + return run_until_complete(create_task(coro)) @@ -354,7 +354,7 @@ def current_task(): def new_event_loop(): """Reset the event loop and return it. - + **NOTE**: Since MicroPython only has a single event loop, this function just resets the loop's state, it does not create a new one """ diff --git a/asyncio/funcs.py b/asyncio/funcs.py index 8dad9df..016151b 100644 --- a/asyncio/funcs.py +++ b/asyncio/funcs.py @@ -73,7 +73,7 @@ def runner(waiter, aw): def wait_for_ms(aw, timeout): """Similar to `wait_for` but *timeout* is an integer in milliseconds. - + This is a coroutine, and a MicroPython extension. """ @@ -83,9 +83,9 @@ def wait_for_ms(aw, timeout): async def gather(*aws, return_exceptions=False): """Run all *aws* awaitables concurrently. Any *aws* that are not tasks are promoted to tasks. - + Returns a list of return values of all *aws* - + This is a coroutine. """ diff --git a/asyncio/lock.py b/asyncio/lock.py index b6bc335..0e80d6d 100644 --- a/asyncio/lock.py +++ b/asyncio/lock.py @@ -17,7 +17,7 @@ class Lock: """Create a new lock which can be used to coordinate tasks. Locks start in the unlocked state. - + In addition to the methods below, locks can be used in an ``async with`` statement. """ @@ -55,7 +55,7 @@ def release(self): async def acquire(self): """Wait for the lock to be in the unlocked state and then lock it in an atomic way. Only one task can acquire the lock at any one time. - + This is a coroutine. """ diff --git a/asyncio/stream.py b/asyncio/stream.py index 0fc2c16..804d9b0 100644 --- a/asyncio/stream.py +++ b/asyncio/stream.py @@ -43,7 +43,7 @@ def close(self): async def wait_closed(self): """Wait for the stream to close. - + This is a coroutine. """ @@ -52,7 +52,7 @@ async def wait_closed(self): async def read(self, n): """Read up to *n* bytes and return them. - + This is a coroutine. """ @@ -61,9 +61,9 @@ async def read(self, n): async def readinto(self, buf): """Read up to n bytes into *buf* with n being equal to the length of *buf* - + Return the number of bytes read into *buf* - + This is a coroutine, and a MicroPython extension. """ @@ -72,7 +72,7 @@ async def readinto(self, buf): async def readexactly(self, n): """Read exactly *n* bytes and return them as a bytes object. - + Raises an ``EOFError`` exception if the stream ends before reading *n* bytes. @@ -92,7 +92,7 @@ async def readexactly(self, n): async def readline(self): """Read a line and return it. - + This is a coroutine. """ @@ -114,7 +114,7 @@ def write(self, buf): async def drain(self): """Drain (write) all buffered output data out to the stream. - + This is a coroutine. """ @@ -137,10 +137,10 @@ async def drain(self): async def open_connection(host, port): """Open a TCP connection to the given *host* and *port*. The *host* address will be resolved using `socket.getaddrinfo`, which is currently a blocking call. - + Returns a pair of streams: a reader and a writer stream. Will raise a socket-specific ``OSError`` if the host could not be resolved or if the connection could not be made. - + This is a coroutine. """ @@ -182,7 +182,7 @@ def close(self): async def wait_closed(self): """Wait for the server to close. - + This is a coroutine. """ @@ -213,9 +213,9 @@ async def start_server(cb, host, port, backlog=5): """Start a TCP server on the given *host* and *port*. The *cb* callback wil be called with incoming, accepted connections, and be passed 2 arguments: reader writer streams for the connection. - + Returns a `Server` object. - + This is a coroutine. """ diff --git a/asyncio/task.py b/asyncio/task.py index f86639c..c09f2cb 100644 --- a/asyncio/task.py +++ b/asyncio/task.py @@ -133,7 +133,7 @@ class Task: """This object wraps a coroutine into a running task. Tasks can be waited on using ``await task``, which will wait for the task to complete and reutnr the return value of the task. - + Tasks should not be created directly, rather use `create_task` to create them. """ @@ -170,7 +170,7 @@ def __next__(self): def done(self): """Whether the task is complete.""" - + return not self.state def cancel(self): From 4cc4bdb3a77793295becad104f5de5cadb405aac Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 13:14:18 -0500 Subject: [PATCH 03/12] Update api.rst to show documentation --- docs/api.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index b8c4c04..2414146 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -8,5 +8,24 @@ .. automodule:: asyncio :members: -.. toctree:: - :hidden: +.. automodule:: asyncio.core + :members: + :exclude-members: SingletonGenerator, IOQueue + +.. automodule:: asyncio.event + :members: + :exclude-members: ThreadSafeFlag + +.. automodule:: asyncio.funcs + :members: + +.. automodule:: asyncio.lock + :members: + +.. automodule:: asyncio.stream + :members: + :exclude-members: stream_awrite + +.. automodule:: asyncio.task + :members: + :exclude-members: ph_meld, ph_pairing, ph_delete, TaskQueue From 28936b4435d73e72232af31106ff4a30ddf09d5f Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 17:08:34 -0500 Subject: [PATCH 04/12] Change yield to sleep(0) Per @dhalbert's recommendation --- asyncio/event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/event.py b/asyncio/event.py index fc92c2e..a890070 100644 --- a/asyncio/event.py +++ b/asyncio/event.py @@ -58,7 +58,7 @@ async def wait(self): 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 - yield + core.sleep(0) return True From ebd52518ab3a095641940496926a79956d447d0e Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 18:01:55 -0500 Subject: [PATCH 05/12] Add async keyword to runner() --- asyncio/funcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/funcs.py b/asyncio/funcs.py index 016151b..17070f3 100644 --- a/asyncio/funcs.py +++ b/asyncio/funcs.py @@ -31,7 +31,7 @@ async def wait_for(aw, timeout, sleep=core.sleep): if timeout is None: return await aw - def runner(waiter, aw): + async def runner(waiter, aw): nonlocal status, result try: result = await aw From e96a12a4ef828953583068c729c15fad63ed924a Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Wed, 16 Feb 2022 18:05:54 -0500 Subject: [PATCH 06/12] Change yield in Lock.acquire() to asyncio.sleep(0) --- asyncio/lock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/lock.py b/asyncio/lock.py index 0e80d6d..656a8d0 100644 --- a/asyncio/lock.py +++ b/asyncio/lock.py @@ -65,7 +65,7 @@ async def acquire(self): # Set calling task's data to the lock's queue so it can be removed if needed core.cur_task.data = self.waiting try: - yield + core.sleep(0) except core.CancelledError as er: if self.state == core.cur_task: # Cancelled while pending on resume, schedule next waiting Task From fbb18cf051e9ac9dd50850c09f5e42225cdbf097 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 17 Feb 2022 09:11:20 -0500 Subject: [PATCH 07/12] Change and fix yields to await core.sleep(0) --- asyncio/event.py | 2 +- asyncio/lock.py | 2 +- asyncio/stream.py | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/asyncio/event.py b/asyncio/event.py index a890070..427688e 100644 --- a/asyncio/event.py +++ b/asyncio/event.py @@ -58,7 +58,7 @@ async def wait(self): 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 - core.sleep(0) + await core.sleep(0) return True diff --git a/asyncio/lock.py b/asyncio/lock.py index 656a8d0..17ed27f 100644 --- a/asyncio/lock.py +++ b/asyncio/lock.py @@ -65,7 +65,7 @@ async def acquire(self): # Set calling task's data to the lock's queue so it can be removed if needed core.cur_task.data = self.waiting try: - core.sleep(0) + await core.sleep(0) except core.CancelledError as er: if self.state == core.cur_task: # Cancelled while pending on resume, schedule next waiting Task diff --git a/asyncio/stream.py b/asyncio/stream.py index 804d9b0..77675f6 100644 --- a/asyncio/stream.py +++ b/asyncio/stream.py @@ -56,7 +56,8 @@ async def read(self, n): This is a coroutine. """ - yield core._io_queue.queue_read(self.s) + core._io_queue.queue_read(self.s) + await core.sleep(0) return self.s.read(n) async def readinto(self, buf): @@ -67,7 +68,8 @@ async def readinto(self, buf): This is a coroutine, and a MicroPython extension. """ - yield core._io_queue.queue_read(self.s) + core._io_queue.queue_read(self.s) + await core.sleep(0) return self.s.readinto(buf) async def readexactly(self, n): @@ -81,7 +83,8 @@ async def readexactly(self, n): r = b"" while n: - yield core._io_queue.queue_read(self.s) + core._io_queue.queue_read(self.s) + await core.sleep(0) r2 = self.s.read(n) if r2 is not None: if not len(r2): @@ -98,7 +101,8 @@ async def readline(self): l = b"" while True: - yield core._io_queue.queue_read(self.s) + core._io_queue.queue_read(self.s) + await core.sleep(0) l2 = self.s.readline() # may do multiple reads but won't block l += l2 if not l2 or l[-1] == 10: # \n (check l in case l2 is str) @@ -158,7 +162,8 @@ async def open_connection(host, port): except OSError as er: if er.errno != EINPROGRESS: raise er - yield core._io_queue.queue_write(s) + core._io_queue.queue_write(s) + await core.sleep(0) return ss, ss From a8350db6938f63e7866f165f46a198ce94f2d3fe Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 17 Feb 2022 09:13:33 -0500 Subject: [PATCH 08/12] Fix errors in docstrings --- asyncio/core.py | 8 ++++---- asyncio/task.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/asyncio/core.py b/asyncio/core.py index 4ecbc7a..f53cf98 100644 --- a/asyncio/core.py +++ b/asyncio/core.py @@ -69,7 +69,7 @@ def __next__(self): # Pause task execution for the given time (integer in milliseconds, uPy extension) # Use a SingletonGenerator to do it without allocating on the heap def sleep_ms(t, sgen=SingletonGenerator()): - """Sleep for `t` milliseconds. + """Sleep for *t* milliseconds. This is a coroutine, and a MicroPython extension. """ @@ -81,7 +81,7 @@ def sleep_ms(t, sgen=SingletonGenerator()): # Pause task execution for the given time (in seconds) def sleep(t): - """Sleep for `t` seconds + """Sleep for *t* seconds This is a coroutine. """ @@ -180,7 +180,7 @@ def create_task(coro): # Keep scheduling tasks until there are none left to schedule def run_until_complete(main_task=None): - """Run the given _main_task_ until it completes.""" + """Run the given *main_task* until it completes.""" global cur_task excs_all = (CancelledError, Exception) # To prevent heap allocation in loop @@ -283,7 +283,7 @@ def create_task(coro): return create_task(coro) def run_forever(): - """Run the event loop until `stop()` is called.""" + """Run the event loop until `Loop.stop()` is called.""" global _stop_task _stop_task = Task(_stopper(), globals()) diff --git a/asyncio/task.py b/asyncio/task.py index c09f2cb..6fcddc5 100644 --- a/asyncio/task.py +++ b/asyncio/task.py @@ -131,10 +131,10 @@ def remove(self, v): # Task class representing a coroutine, can be waited on and cancelled. class Task: """This object wraps a coroutine into a running task. Tasks can be waited on - using ``await task``, which will wait for the task to complete and reutnr the + using ``await task``, which will wait for the task to complete and return the return value of the task. - Tasks should not be created directly, rather use `create_task` to create them. + Tasks should not be created directly, rather use ``create_task`` to create them. """ def __init__(self, coro, globals=None): From da4b8f94cb654330207d9c2f2022696bef5b0ea7 Mon Sep 17 00:00:00 2001 From: Alec Delaney Date: Thu, 17 Feb 2022 09:18:02 -0500 Subject: [PATCH 09/12] Add basic module docstrings --- asyncio/core.py | 4 ++++ asyncio/event.py | 4 ++++ asyncio/funcs.py | 5 +++++ asyncio/lock.py | 4 ++++ asyncio/stream.py | 4 ++++ asyncio/task.py | 4 ++++ 6 files changed, 25 insertions(+) diff --git a/asyncio/core.py b/asyncio/core.py index f53cf98..9142b5f 100644 --- a/asyncio/core.py +++ b/asyncio/core.py @@ -10,6 +10,10 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Core +==== +""" from adafruit_ticks import ticks_ms as ticks, ticks_diff, ticks_add import sys, select, traceback diff --git a/asyncio/event.py b/asyncio/event.py index 427688e..04f6e15 100644 --- a/asyncio/event.py +++ b/asyncio/event.py @@ -10,6 +10,10 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Events +====== +""" from . import core diff --git a/asyncio/funcs.py b/asyncio/funcs.py index 17070f3..1ff5085 100644 --- a/asyncio/funcs.py +++ b/asyncio/funcs.py @@ -10,6 +10,11 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Functions +========= +""" + from . import core diff --git a/asyncio/lock.py b/asyncio/lock.py index 17ed27f..3b93e6a 100644 --- a/asyncio/lock.py +++ b/asyncio/lock.py @@ -10,6 +10,10 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Locks +===== +""" from . import core diff --git a/asyncio/stream.py b/asyncio/stream.py index 77675f6..42bd6b7 100644 --- a/asyncio/stream.py +++ b/asyncio/stream.py @@ -10,6 +10,10 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Streams +======= +""" from . import core diff --git a/asyncio/task.py b/asyncio/task.py index 6fcddc5..9a76497 100644 --- a/asyncio/task.py +++ b/asyncio/task.py @@ -10,6 +10,10 @@ # pylint or black. # pylint: skip-file # fmt: off +""" +Tasks +===== +""" # This file contains the core TaskQueue based on a pairing heap, and the core Task class. # They can optionally be replaced by C implementations. From 485c1b283bfd6d98e5d48e4f95a36283c3b62ad1 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:04:02 -0400 Subject: [PATCH 10/12] Fix typo in docstring in core.py --- asyncio/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/core.py b/asyncio/core.py index 9142b5f..fe595d6 100644 --- a/asyncio/core.py +++ b/asyncio/core.py @@ -259,7 +259,7 @@ def run_until_complete(main_task=None): def run(coro): """Create a new task from the given coroutine and run it until it completes. - Returns the value returned be *coro*. + Returns the value returned by *coro*. """ return run_until_complete(create_task(coro)) From 8218c6b5d535daa5f9300abe2c62ba81357dab25 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:04:42 -0400 Subject: [PATCH 11/12] Fix typo in docstring of funcs.py --- asyncio/funcs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/funcs.py b/asyncio/funcs.py index 1ff5085..2289d33 100644 --- a/asyncio/funcs.py +++ b/asyncio/funcs.py @@ -27,7 +27,7 @@ async def wait_for(aw, timeout, sleep=core.sleep): If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: this should be trapped by the caller. - Returns the return vaalue of *aw*. + Returns the return value of *aw*. This is a coroutine. """ From 1ff831391f1d3035363b8fec347c1c9ce9897280 Mon Sep 17 00:00:00 2001 From: Alec Delaney <89490472+tekktrik@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:05:25 -0400 Subject: [PATCH 12/12] Fix typo in docstring of stream.py --- asyncio/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/stream.py b/asyncio/stream.py index 42bd6b7..97dcf6a 100644 --- a/asyncio/stream.py +++ b/asyncio/stream.py @@ -219,7 +219,7 @@ async def _serve(self, s, cb): # Helper function to start a TCP stream server, running as a new task # TODO could use an accept-callback on socket read activity instead of creating a task async def start_server(cb, host, port, backlog=5): - """Start a TCP server on the given *host* and *port*. The *cb* callback wil be + """Start a TCP server on the given *host* and *port*. The *cb* callback will be called with incoming, accepted connections, and be passed 2 arguments: reader writer streams for the connection.