diff --git a/asyncio_slow/asyncio_slow.py b/asyncio_slow/asyncio_slow.py new file mode 100644 index 000000000..55f1347f1 --- /dev/null +++ b/asyncio_slow/asyncio_slow.py @@ -0,0 +1,151 @@ +import time +import logging + + +log = logging.getLogger("asyncio") + + +# Workaround for not being able to subclass builtin types +class LoopStop(Exception): + pass + +class InvalidStateError(Exception): + pass + +# Object not matching any other object +_sentinel = [] + + +class EventLoop: + + def __init__(self): + self.q = [] + + def call_soon(self, c, *args): + self.q.append((c, args)) + + def call_later(self, delay, c, *args): + def _delayed(c, args, delay): + yield from sleep(delay) + self.call_soon(c, *args) + Task(_delayed(c, args, delay)) + + def run_forever(self): + while self.q: + c = self.q.pop(0) + try: + c[0](*c[1]) + except LoopStop: + return + # I mean, forever + while True: + time.sleep(1) + + def stop(self): + def _cb(): + raise LoopStop + self.call_soon(_cb) + + def run_until_complete(self, coro): + t = async(coro) + t.add_done_callback(lambda a: self.stop()) + self.run_forever() + + def close(self): + pass + + +_def_event_loop = EventLoop() + + +class Future: + + def __init__(self, loop=_def_event_loop): + self.loop = loop + self.res = _sentinel + self.cbs = [] + + def result(self): + if self.res is _sentinel: + raise InvalidStateError + return self.res + + def add_done_callback(self, fn): + if self.res is _sentinel: + self.cbs.append(fn) + else: + self.loop.call_soon(fn, self) + + def set_result(self, val): + self.res = val + for f in self.cbs: + f(self) + + +class Task(Future): + + def __init__(self, coro, loop=_def_event_loop): + super().__init__() + self.loop = loop + self.c = coro + # upstream asyncio forces task to be scheduled on instantiation + self.loop.call_soon(self) + + def __call__(self): + try: + next(self.c) + self.loop.call_soon(self) + except StopIteration as e: + log.debug("Coro finished: %s", self.c) + self.set_result(None) + + +def get_event_loop(): + return _def_event_loop + + +# Decorator +def coroutine(f): + return f + + +def async(coro): + if isinstance(coro, Future): + return coro + return Task(coro) + + +class _Wait(Future): + + def __init__(self, n): + Future.__init__(self) + self.n = n + + def _done(self): + self.n -= 1 + log.debug("Wait: remaining tasks: %d", self.n) + if not self.n: + self.set_result(None) + + def __call__(self): + pass + + +def wait(coro_list, loop=_def_event_loop): + + w = _Wait(len(coro_list)) + + for c in coro_list: + t = async(c) + t.add_done_callback(lambda val: w._done()) + + return w + + +def sleep(secs): + t = time.time() + log.debug("Started sleep at: %s, targetting: %s", t, t + secs) + while time.time() < t + secs: + time.sleep(0.01) + yield + log.debug("Finished sleeping %ss", secs) diff --git a/asyncio_slow/test_chain.py b/asyncio_slow/test_chain.py new file mode 100644 index 000000000..8d6b9a615 --- /dev/null +++ b/asyncio_slow/test_chain.py @@ -0,0 +1,18 @@ +#https://docs.python.org/3.4/library/asyncio-task.html#example-chain-coroutines +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def compute(x, y): + print("Compute %s + %s ..." % (x, y)) + yield from asyncio.sleep(1.0) + return x + y + +@asyncio.coroutine +def print_sum(x, y): + result = yield from compute(x, y) + print("%s + %s = %s" % (x, y, result)) + +loop = asyncio.get_event_loop() +loop.run_until_complete(print_sum(1, 2)) +loop.close() diff --git a/asyncio_slow/test_future.py b/asyncio_slow/test_future.py new file mode 100644 index 000000000..53026c8d0 --- /dev/null +++ b/asyncio_slow/test_future.py @@ -0,0 +1,15 @@ +#https://docs.python.org/3.4/library/asyncio-task.html#example-chain-coroutines +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def slow_operation(future): + yield from asyncio.sleep(1) + future.set_result('Future is done!') + +loop = asyncio.get_event_loop() +future = asyncio.Future() +asyncio.Task(slow_operation(future)) +loop.run_until_complete(future) +print(future.result()) +loop.close() diff --git a/asyncio_slow/test_future2.py b/asyncio_slow/test_future2.py new file mode 100644 index 000000000..8ba03ef85 --- /dev/null +++ b/asyncio_slow/test_future2.py @@ -0,0 +1,21 @@ +#https://docs.python.org/3.4/library/asyncio-task.html#example-future-with-run-forever +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def slow_operation(future): + yield from asyncio.sleep(1) + future.set_result('Future is done!') + +def got_result(future): + print(future.result()) + loop.stop() + +loop = asyncio.get_event_loop() +future = asyncio.Future() +asyncio.Task(slow_operation(future)) +future.add_done_callback(got_result) +try: + loop.run_forever() +finally: + loop.close() \ No newline at end of file diff --git a/asyncio_slow/test_hello_world.py b/asyncio_slow/test_hello_world.py new file mode 100644 index 000000000..fab558134 --- /dev/null +++ b/asyncio_slow/test_hello_world.py @@ -0,0 +1,12 @@ +#https://docs.python.org/3.4/library/asyncio-task.html#example-hello-world-coroutine +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def greet_every_two_seconds(): + while True: + print('Hello World') + yield from asyncio.sleep(2) + +loop = asyncio.get_event_loop() +loop.run_until_complete(greet_every_two_seconds()) diff --git a/asyncio_slow/test_hello_world_bare.py b/asyncio_slow/test_hello_world_bare.py new file mode 100644 index 000000000..1f8d9702f --- /dev/null +++ b/asyncio_slow/test_hello_world_bare.py @@ -0,0 +1,12 @@ +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def greet_every_two_seconds(): + while True: + print('Hello World') + yield from asyncio.sleep(2) + +loop = asyncio.get_event_loop() +asyncio.Task(greet_every_two_seconds()) +loop.run_forever() diff --git a/asyncio_slow/test_hello_world_callback.py b/asyncio_slow/test_hello_world_callback.py new file mode 100644 index 000000000..9836ffd7b --- /dev/null +++ b/asyncio_slow/test_hello_world_callback.py @@ -0,0 +1,11 @@ +# https://docs.python.org/3.4/library/asyncio-eventloop.html#example-hello-world-callback +#import asyncio +import asyncio_slow as asyncio + +def print_and_repeat(loop): + print('Hello World') + loop.call_later(2, print_and_repeat, loop) + +loop = asyncio.get_event_loop() +loop.call_soon(print_and_repeat, loop) +loop.run_forever() diff --git a/asyncio_slow/test_parallel.py b/asyncio_slow/test_parallel.py new file mode 100644 index 000000000..48a187b87 --- /dev/null +++ b/asyncio_slow/test_parallel.py @@ -0,0 +1,21 @@ +#https://docs.python.org/3.4/library/asyncio-task.html#example-parallel-execution-of-tasks +#import asyncio +import asyncio_slow as asyncio + +@asyncio.coroutine +def factorial(name, number): + f = 1 + for i in range(2, number+1): + print("Task %s: Compute factorial(%s)..." % (name, i)) + yield from asyncio.sleep(1) + f *= i + print("Task %s: factorial(%s) = %s" % (name, number, f)) + +tasks = [ + asyncio.Task(factorial("A", 2)), + asyncio.Task(factorial("B", 3)), + asyncio.Task(factorial("C", 4))] + +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.wait(tasks)) +loop.close() diff --git a/uasyncio/metadata.txt b/uasyncio/metadata.txt new file mode 100644 index 000000000..d0901ec75 --- /dev/null +++ b/uasyncio/metadata.txt @@ -0,0 +1,6 @@ +srctype = micropython-lib +type = module +version = 0.6.1 +author = Paul Sokolovsky +long_desc = Lightweight asyncio-like library built around native Python coroutines, not around un-Python devices like callback mess. +depends = heapq, errno, select, logging diff --git a/uasyncio/setup.py b/uasyncio/setup.py new file mode 100644 index 000000000..0e56c7549 --- /dev/null +++ b/uasyncio/setup.py @@ -0,0 +1,19 @@ +import sys +# Remove current dir from sys.path, otherwise setuptools will peek up our +# module instead of system. +sys.path.pop(0) +from setuptools import setup + + +setup(name='micropython-uasyncio', + version='0.6.1', + description='uasyncio module for MicroPython', + long_description='Lightweight asyncio-like library built around native Python coroutines, not around un-Python devices like callback mess.', + url='https://github.com/micropython/micropython/issues/405', + author='Paul Sokolovsky', + author_email='micro-python@googlegroups.com', + maintainer='MicroPython Developers', + maintainer_email='micro-python@googlegroups.com', + license='MIT', + py_modules=['uasyncio'], + install_requires=['micropython-heapq', 'micropython-errno', 'micropython-select', 'micropython-logging']) diff --git a/uasyncio/test_call_soon.py b/uasyncio/test_call_soon.py new file mode 100644 index 000000000..99ccfefbc --- /dev/null +++ b/uasyncio/test_call_soon.py @@ -0,0 +1,13 @@ +import uasyncio as asyncio +import time + + +def cb(): + print("callback") + time.sleep(0.5) + loop.call_soon(cb) + + +loop = asyncio.get_event_loop() +loop.call_soon(cb) +loop.run_forever() diff --git a/uasyncio/test_http_client.py b/uasyncio/test_http_client.py new file mode 100644 index 000000000..0b4ff83b7 --- /dev/null +++ b/uasyncio/test_http_client.py @@ -0,0 +1,25 @@ +import uasyncio as asyncio + +@asyncio.coroutine +def print_http_headers(url): + reader, writer = yield from asyncio.open_connection(url, 80) + print(reader, writer) + print("================") + query = "GET / HTTP/1.0\r\n\r\n" + yield from writer.awrite(query.encode('latin-1')) + while True: + line = yield from reader.readline() + if not line: + break + if line: + print(line.rstrip()) + +import logging +logging.basicConfig(level=logging.INFO) +url = "google.com" +loop = asyncio.get_event_loop() +#task = asyncio.async(print_http_headers(url)) +#loop.run_until_complete(task) +loop.call_soon(print_http_headers(url)) +loop.run_forever() +loop.close() diff --git a/uasyncio/test_http_server.py b/uasyncio/test_http_server.py new file mode 100644 index 000000000..51654d1d2 --- /dev/null +++ b/uasyncio/test_http_server.py @@ -0,0 +1,21 @@ +import uasyncio as asyncio + +@asyncio.coroutine +def serve(reader, writer): + print(reader, writer) + print("================") + print((yield from reader.read())) + yield from writer.awrite("HTTP/1.0 200 OK\r\n\r\nHello.\r\n") + print("After response write") + yield from writer.close() + print("Finished processing request") + + +import logging +#logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) +loop = asyncio.get_event_loop() +mem_info() +loop.call_soon(asyncio.start_server(serve, "127.0.0.1", 8081)) +loop.run_forever() +loop.close() diff --git a/uasyncio/uasyncio.py b/uasyncio/uasyncio.py new file mode 100644 index 000000000..f34123e65 --- /dev/null +++ b/uasyncio/uasyncio.py @@ -0,0 +1,288 @@ +import __main__ +import time +import heapq +import errno +import logging + + +log = logging.getLogger("asyncio") + +type_gen = type((lambda: (yield))()) + +class EventLoop: + + def __init__(self): + self.q = [] + self.cnt = 0 + + def time(self): + return time.time() + + def call_soon(self, callback, *args): + self.call_at(0, callback, *args) + + def call_later(self, delay, callback, *args): + self.call_at(self.time() + delay, callback, *args) + + def call_at(self, time, callback, *args): + # Including self.cnt is a workaround per heapq docs + log.debug("Scheduling %s", (time, self.cnt, callback, args)) + heapq.heappush(self.q, (time, self.cnt, callback, args)) +# print(self.q) + self.cnt += 1 + + def wait(self, delay): + # Default wait implementation, to be overriden in subclasses + # with IO scheduling + log.debug("Sleeping for: %s", delay) + time.sleep(delay) + + def run_forever(self): + while True: + if self.q: + t, cnt, cb, args = heapq.heappop(self.q) + log.debug("Next coroutine to run: %s", (t, cnt, cb, args)) +# __main__.mem_info() + tnow = self.time() + delay = t - tnow + if delay > 0: + self.wait(delay) + else: + self.wait(-1) + # Assuming IO completion scheduled some tasks + continue + if callable(cb): + cb(*args) + else: + delay = 0 + try: + if args == (): + args = (None,) + log.debug("Coroutine %s send args: %s", cb, args) + ret = cb.send(*args) + log.debug("Coroutine %s yield result: %s", cb, ret) + if isinstance(ret, SysCall): + arg = ret.args[0] + if isinstance(ret, Sleep): + delay = arg + elif isinstance(ret, IORead): +# self.add_reader(ret.obj.fileno(), lambda self, c, f: self.call_soon(c, f), self, cb, ret.obj) +# self.add_reader(ret.obj.fileno(), lambda c, f: self.call_soon(c, f), cb, ret.obj) + self.add_reader(arg.fileno(), lambda cb, f: self.call_soon(cb, f), cb, arg) + continue + elif isinstance(ret, IOWrite): + self.add_writer(arg.fileno(), lambda cb, f: self.call_soon(cb, f), cb, arg) + continue + elif isinstance(ret, IOReadDone): + self.remove_reader(arg.fileno()) + elif isinstance(ret, IOWriteDone): + self.remove_writer(arg.fileno()) + elif isinstance(ret, StopLoop): + return arg + elif isinstance(ret, type_gen): + self.call_soon(ret) + elif ret is None: + # Just reschedule + pass + else: + assert False, "Unsupported coroutine yield value: %r (of type %r)" % (ret, type(ret)) + except StopIteration as e: + log.debug("Coroutine finished: %s", cb) + continue + self.call_later(delay, cb, *args) + + def run_until_complete(self, coro): + def _run_and_stop(): + yield from coro + yield StopLoop(0) + self.call_soon(_run_and_stop()) + self.run_forever() + + def close(self): + pass + + +import select + +class EpollEventLoop(EventLoop): + + def __init__(self): + EventLoop.__init__(self) + self.poller = select.epoll(1) + + def add_reader(self, fd, cb, *args): + log.debug("add_reader%s", (fd, cb, args)) + self.poller.register(fd, select.EPOLLIN, (cb, args)) + + def remove_reader(self, fd): + log.debug("remove_reader(%s)", fd) + self.poller.unregister(fd) + + def add_writer(self, fd, cb, *args): + log.debug("add_writer%s", (fd, cb, args)) + self.poller.register(fd, select.EPOLLOUT, (cb, args)) + + def remove_writer(self, fd): + log.debug("remove_writer(%s)", fd) + self.poller.unregister(fd) + + def wait(self, delay): + log.debug("epoll.wait(%d)", delay) + if delay == -1: + res = self.poller.poll(-1) + else: + res = self.poller.poll(int(delay * 1000)) + log.debug("epoll result: %s", res) + for cb, ev in res: + log.debug("Calling IO callback: %s%s", cb[0], cb[1]) + cb[0](*cb[1]) + + +class SysCall: + + def __init__(self, *args): + self.args = args + + def handle(self): + raise NotImplementedError + +class Sleep(SysCall): + pass + +class StopLoop(SysCall): + pass + +class IORead(SysCall): + pass + +class IOWrite(SysCall): + pass + +class IOReadDone(SysCall): + pass + +class IOWriteDone(SysCall): + pass + + +def get_event_loop(): + return EpollEventLoop() + +def coroutine(f): + return f + +def async(coro): + # We don't have Task bloat, so op is null + return coro + +def sleep(secs): + yield Sleep(secs) + + +import usocket as _socket + +class StreamReader: + + def __init__(self, s): + self.s = s + + def read(self, n=-1): + s = yield IORead(self.s) + while True: + res = self.s.read(n) + if res is not None: + break + log.warn("Empty read") + if not res: + yield IOReadDone(self.s) + return res + + def readline(self): + log.debug("StreamReader.readline()") + s = yield IORead(self.s) + log.debug("StreamReader.readline(): after IORead: %s", s) + while True: + res = self.s.readline() + if res is not None: + break + log.warn("Empty read") + if not res: + yield IOReadDone(self.s) + log.debug("StreamReader.readline(): res: %s", res) + return res + + def __repr__(self): + return "" % self.s + + +class StreamWriter: + + def __init__(self, s): + self.s = s + + def awrite(self, buf): + # This method is called awrite (async write) to not proliferate + # incompatibility with original asyncio. Unlike original asyncio + # whose .write() method is both not a coroutine and guaranteed + # to return immediately (which means it has to buffer all the + # data), this method is a coroutine. + sz = len(buf) + log.debug("StreamWriter.awrite(): spooling %d bytes", sz) + while True: + res = self.s.write(buf) + # If we spooled everything, return immediately + if res == sz: + log.debug("StreamWriter.awrite(): completed spooling %d bytes", res) + return + if res is None: + res = 0 + log.debug("StreamWriter.awrite(): spooled partial %d bytes", res) + assert res < sz + buf = buf[res:] + sz -= res + s = yield IOWrite(self.s) + log.debug("StreamWriter.awrite(): can write more") + + def close(self): + yield IOWriteDone(self.s) + self.s.close() + + def __repr__(self): + return "" % self.s + + +def open_connection(host, port): + log.debug("open_connection(%s, %s)", host, port) + s = _socket.socket() + s.setblocking(False) + ai = _socket.getaddrinfo(host, port) + addr = ai[0][4] + try: + s.connect(addr) + except OSError as e: + if e.args[0] != errno.EINPROGRESS: + raise + log.debug("open_connection: After connect") + s = yield IOWrite(s) + log.debug("open_connection: After iowait: %s", s) + return StreamReader(s), StreamWriter(s) + + +def start_server(client_coro, host, port): + log.debug("start_server(%s, %s)", host, port) + s = _socket.socket() + s.setblocking(False) + + ai = _socket.getaddrinfo(host, port) + addr = ai[0][4] + s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1) + s.bind(addr) + s.listen(10) + while True: + log.debug("start_server: Before accept") + yield IORead(s) + log.debug("start_server: After iowait") + s2, client_addr = s.accept() + s2.setblocking(False) + log.debug("start_server: After accept: %s", s2) + yield client_coro(StreamReader(s2), StreamWriter(s2)) 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