micropython/uasyncio: Remove uasyncio-v2.

Superceded by uasyncio-v3 in the main repo.

Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
pull/376/head
Jim Mussared 2021-05-27 16:52:16 +10:00
rodzic fa13cbbc8b
commit bc2b6b0b7f
42 zmienionych plików z 0 dodań i 1872 usunięć

Wyświetl plik

@ -1,17 +0,0 @@
import uasyncio.core as asyncio
import time
import logging
logging.basicConfig(level=logging.DEBUG)
# asyncio.set_debug(True)
def cb():
print("callback")
time.sleep(0.5)
loop.call_soon(cb)
loop = asyncio.get_event_loop()
loop.call_soon(cb)
loop.run_forever()

Wyświetl plik

@ -1,6 +0,0 @@
srctype = micropython-lib
type = package
version = 2.0
author = Paul Sokolovsky
desc = Lightweight asyncio-like library for MicroPython, built around native Python coroutines. (Core event loop).
long_desc = Lightweight asyncio-like library for MicroPython, built around native Python coroutines. (Core event loop).

Wyświetl plik

@ -1,24 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio.core",
version="2.0",
description="Lightweight asyncio-like library for MicroPython, built around native Python coroutines. (Core event loop).",
long_description="Lightweight asyncio-like library for MicroPython, built around native Python coroutines. (Core event loop).",
url="https://github.com/micropython/micropython-lib",
author="Paul Sokolovsky",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio"],
)

Wyświetl plik

@ -1,80 +0,0 @@
import time
try:
import uasyncio.core as asyncio
is_uasyncio = True
except ImportError:
import asyncio
is_uasyncio = False
import logging
# logging.basicConfig(level=logging.DEBUG)
# asyncio.set_debug(True)
output = []
cancelled = False
def print1(msg):
print(msg)
output.append(msg)
def looper1(iters):
global cancelled
try:
for i in range(iters):
print1("ping1")
# sleep() isn't properly cancellable
# yield from asyncio.sleep(1.0)
t = time.time()
while time.time() - t < 1:
yield from asyncio.sleep(0)
return 10
except asyncio.CancelledError:
print1("cancelled")
cancelled = True
def looper2(iters):
for i in range(iters):
print1("ping2")
# sleep() isn't properly cancellable
# yield from asyncio.sleep(1.0)
t = time.time()
while time.time() - t < 1:
yield from asyncio.sleep(0)
return 10
def run_to():
coro = looper1(10)
task = loop.create_task(coro)
yield from asyncio.sleep(3)
if is_uasyncio:
asyncio.cancel(coro)
else:
task.cancel()
# Need another eventloop iteration for cancellation to be actually
# processed and to see side effects of the cancellation.
yield from asyncio.sleep(0)
assert cancelled
coro = looper2(10)
task = loop.create_task(coro)
yield from asyncio.sleep(2)
if is_uasyncio:
asyncio.cancel(coro)
else:
task.cancel()
yield from asyncio.sleep(0)
# Once saw 3 ping3's output on CPython 3.5.2
assert output == ["ping1", "ping1", "ping1", "cancelled", "ping2", "ping2"]
loop = asyncio.get_event_loop()
loop.run_until_complete(run_to())

Wyświetl plik

@ -1,16 +0,0 @@
try:
import uasyncio.core as asyncio
except:
import asyncio
def cb(a, b):
assert a == "test"
assert b == "test2"
loop.stop()
loop = asyncio.get_event_loop()
loop.call_soon(cb, "test", "test2")
loop.run_forever()
print("OK")

Wyświetl plik

@ -1,39 +0,0 @@
# Test that uasyncio scheduling is fair, i.e. gives all
# coroutines equal chance to run (this specifically checks
# round-robin scheduling).
import uasyncio.core as asyncio
COROS = 10
ITERS = 20
result = []
test_finished = False
async def coro(n):
for i in range(ITERS):
result.append(n)
yield
async def done():
global test_finished
while True:
if len(result) == COROS * ITERS:
# print(result)
assert result == list(range(COROS)) * ITERS
test_finished = True
return
yield
loop = asyncio.get_event_loop()
for n in range(COROS):
loop.create_task(coro(n))
loop.run_until_complete(done())
assert test_finished

Wyświetl plik

@ -1,56 +0,0 @@
# Test that coros scheduled to run at some time don't run prematurely
# in case of I/O completion before that.
import uasyncio.core as uasyncio
import logging
logging.basicConfig(level=logging.DEBUG)
# uasyncio.set_debug(True)
class MockEventLoop(uasyncio.EventLoop):
def __init__(self):
super().__init__()
self.t = 0
self.msgs = []
def time(self):
return self.t
def pass_time(self, delta):
self.t += delta
def wait(self, delay):
# print("%d: wait(%d)" % (self.t, delay))
self.pass_time(100)
if self.t == 100:
def cb_1st():
self.msgs.append("I should be run first, time: %s" % self.time())
self.call_soon(cb_1st)
if self.t == 1000:
raise StopIteration
loop = MockEventLoop()
def cb_2nd():
loop.msgs.append("I should be run second, time: %s" % loop.time())
loop.call_later_ms(500, cb_2nd)
try:
loop.run_forever()
except StopIteration:
pass
print(loop.msgs)
# .wait() is now called on each loop iteration, and for our mock case, it means that
# at the time of running, self.time() will be skewed by 100 virtual time units.
assert loop.msgs == ["I should be run first, time: 100", "I should be run second, time: 500"], str(
loop.msgs
)

Wyświetl plik

@ -1,47 +0,0 @@
try:
import uasyncio.core as asyncio
except ImportError:
import asyncio
import logging
# logging.basicConfig(level=logging.DEBUG)
# asyncio.set_debug(True)
def looper(iters):
for i in range(iters):
print("ping")
yield from asyncio.sleep(1.0)
return 10
def run_to():
try:
ret = yield from asyncio.wait_for(looper(2), 1)
print("result:", ret)
assert False
except asyncio.TimeoutError:
print("Coro timed out")
print("=================")
try:
ret = yield from asyncio.wait_for(looper(2), 2)
print("result:", ret)
assert False
except asyncio.TimeoutError:
print("Coro timed out")
print("=================")
try:
ret = yield from asyncio.wait_for(looper(2), 3)
print("result:", ret)
except asyncio.TimeoutError:
print("Coro timed out")
assert False
loop = asyncio.get_event_loop()
loop.run_until_complete(run_to())
loop.run_until_complete(asyncio.sleep(1))

Wyświetl plik

@ -1,332 +0,0 @@
import utime as time
import utimeq
import ucollections
type_gen = type((lambda: (yield))())
DEBUG = 0
log = None
def set_debug(val):
global DEBUG, log
DEBUG = val
if val:
import logging
log = logging.getLogger("uasyncio.core")
class CancelledError(Exception):
pass
class TimeoutError(CancelledError):
pass
class EventLoop:
def __init__(self, runq_len=16, waitq_len=16):
self.runq = ucollections.deque((), runq_len, True)
self.waitq = utimeq.utimeq(waitq_len)
# Current task being run. Task is a top-level coroutine scheduled
# in the event loop (sub-coroutines executed transparently by
# yield from/await, event loop "doesn't see" them).
self.cur_task = None
def time(self):
return time.ticks_ms()
def create_task(self, coro):
# CPython 3.4.2
self.call_later_ms(0, coro)
# CPython asyncio incompatibility: we don't return Task object
def call_soon(self, callback, *args):
if __debug__ and DEBUG:
log.debug("Scheduling in runq: %s", (callback, args))
self.runq.append(callback)
if not isinstance(callback, type_gen):
self.runq.append(args)
def call_later(self, delay, callback, *args):
self.call_at_(time.ticks_add(self.time(), int(delay * 1000)), callback, args)
def call_later_ms(self, delay, callback, *args):
if not delay:
return self.call_soon(callback, *args)
self.call_at_(time.ticks_add(self.time(), delay), callback, args)
def call_at_(self, time, callback, args=()):
if __debug__ and DEBUG:
log.debug("Scheduling in waitq: %s", (time, callback, args))
self.waitq.push(time, callback, args)
def wait(self, delay):
# Default wait implementation, to be overriden in subclasses
# with IO scheduling
if __debug__ and DEBUG:
log.debug("Sleeping for: %s", delay)
time.sleep_ms(delay)
def run_forever(self):
cur_task = [0, 0, 0]
while True:
# Expire entries in waitq and move them to runq
tnow = self.time()
while self.waitq:
t = self.waitq.peektime()
delay = time.ticks_diff(t, tnow)
if delay > 0:
break
self.waitq.pop(cur_task)
if __debug__ and DEBUG:
log.debug("Moving from waitq to runq: %s", cur_task[1])
self.call_soon(cur_task[1], *cur_task[2])
# Process runq
l = len(self.runq)
if __debug__ and DEBUG:
log.debug("Entries in runq: %d", l)
while l:
cb = self.runq.popleft()
l -= 1
args = ()
if not isinstance(cb, type_gen):
args = self.runq.popleft()
l -= 1
if __debug__ and DEBUG:
log.info("Next callback to run: %s", (cb, args))
cb(*args)
continue
if __debug__ and DEBUG:
log.info("Next coroutine to run: %s", (cb, args))
self.cur_task = cb
delay = 0
try:
if args is ():
ret = next(cb)
else:
ret = cb.send(*args)
if __debug__ and DEBUG:
log.info("Coroutine %s yield result: %s", cb, ret)
if isinstance(ret, SysCall1):
arg = ret.arg
if isinstance(ret, SleepMs):
delay = arg
elif isinstance(ret, IORead):
cb.pend_throw(False)
self.add_reader(arg, cb)
continue
elif isinstance(ret, IOWrite):
cb.pend_throw(False)
self.add_writer(arg, cb)
continue
elif isinstance(ret, IOReadDone):
self.remove_reader(arg)
elif isinstance(ret, IOWriteDone):
self.remove_writer(arg)
elif isinstance(ret, StopLoop):
return arg
else:
assert False, "Unknown syscall yielded: %r (of type %r)" % (
ret,
type(ret),
)
elif isinstance(ret, type_gen):
self.call_soon(ret)
elif isinstance(ret, int):
# Delay
delay = ret
elif ret is None:
# Just reschedule
pass
elif ret is False:
# Don't reschedule
continue
else:
assert False, "Unsupported coroutine yield value: %r (of type %r)" % (
ret,
type(ret),
)
except StopIteration as e:
if __debug__ and DEBUG:
log.debug("Coroutine finished: %s", cb)
continue
except CancelledError as e:
if __debug__ and DEBUG:
log.debug("Coroutine cancelled: %s", cb)
continue
# Currently all syscalls don't return anything, so we don't
# need to feed anything to the next invocation of coroutine.
# If that changes, need to pass that value below.
if delay:
self.call_later_ms(delay, cb)
else:
self.call_soon(cb)
# Wait until next waitq task or I/O availability
delay = 0
if not self.runq:
delay = -1
if self.waitq:
tnow = self.time()
t = self.waitq.peektime()
delay = time.ticks_diff(t, tnow)
if delay < 0:
delay = 0
self.wait(delay)
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 stop(self):
self.call_soon((lambda: (yield StopLoop(0)))())
def close(self):
pass
class SysCall:
def __init__(self, *args):
self.args = args
def handle(self):
raise NotImplementedError
# Optimized syscall with 1 arg
class SysCall1(SysCall):
def __init__(self, arg):
self.arg = arg
class StopLoop(SysCall1):
pass
class IORead(SysCall1):
pass
class IOWrite(SysCall1):
pass
class IOReadDone(SysCall1):
pass
class IOWriteDone(SysCall1):
pass
_event_loop = None
_event_loop_class = EventLoop
def get_event_loop(runq_len=16, waitq_len=16):
global _event_loop
if _event_loop is None:
_event_loop = _event_loop_class(runq_len, waitq_len)
return _event_loop
def sleep(secs):
yield int(secs * 1000)
# Implementation of sleep_ms awaitable with zero heap memory usage
class SleepMs(SysCall1):
def __init__(self):
self.v = None
self.arg = None
def __call__(self, arg):
self.v = arg
# print("__call__")
return self
def __iter__(self):
# print("__iter__")
return self
def __next__(self):
if self.v is not None:
# print("__next__ syscall enter")
self.arg = self.v
self.v = None
return self
# print("__next__ syscall exit")
_stop_iter.__traceback__ = None
raise _stop_iter
_stop_iter = StopIteration()
sleep_ms = SleepMs()
def cancel(coro):
prev = coro.pend_throw(CancelledError())
if prev is False:
_event_loop.call_soon(coro)
class TimeoutObj:
def __init__(self, coro):
self.coro = coro
def wait_for_ms(coro, timeout):
def waiter(coro, timeout_obj):
res = yield from coro
if __debug__ and DEBUG:
log.debug("waiter: cancelling %s", timeout_obj)
timeout_obj.coro = None
return res
def timeout_func(timeout_obj):
if timeout_obj.coro:
if __debug__ and DEBUG:
log.debug("timeout_func: cancelling %s", timeout_obj.coro)
prev = timeout_obj.coro.pend_throw(TimeoutError())
# print("prev pend", prev)
if prev is False:
_event_loop.call_soon(timeout_obj.coro)
timeout_obj = TimeoutObj(_event_loop.cur_task)
_event_loop.call_later_ms(timeout, timeout_func, timeout_obj)
return (yield from waiter(coro, timeout_obj))
def wait_for(coro, timeout):
return wait_for_ms(coro, int(timeout * 1000))
def coroutine(f):
return f
#
# The functions below are deprecated in uasyncio, and provided only
# for compatibility with CPython asyncio
#
def ensure_future(coro, loop=_event_loop):
_event_loop.call_soon(coro)
# CPython asyncio incompatibility: we don't return Task object
return coro
# CPython asyncio incompatibility: Task is a function, not a class (for efficiency)
def Task(coro, loop=_event_loop):
# Same as async()
_event_loop.call_soon(coro)

Wyświetl plik

@ -1,5 +0,0 @@
srctype = micropython-lib
type = package
version = 0.1.2
long_desc = Port of asyncio.queues to uasyncio.
depends = uasyncio.core, collections.deque

Wyświetl plik

@ -1,25 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio.queues",
version="0.1.2",
description="uasyncio.queues module for MicroPython",
long_description="Port of asyncio.queues to uasyncio.",
url="https://github.com/micropython/micropython-lib",
author="micropython-lib Developers",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio"],
install_requires=["micropython-uasyncio.core", "micropython-collections.deque"],
)

Wyświetl plik

@ -1,57 +0,0 @@
from unittest import TestCase, run_class
import sys
sys.path.insert(0, "../uasyncio")
import queues
class QueueTestCase(TestCase):
def _val(self, gen):
"""Returns val from generator."""
while True:
try:
gen.send(None)
except StopIteration as e:
return e.value
def test_get_put(self):
q = queues.Queue(maxsize=1)
self._val(q.put(42))
self.assertEqual(self._val(q.get()), 42)
def test_get_put_nowait(self):
q = queues.Queue(maxsize=1)
q.put_nowait(12)
try:
q.put_nowait(42)
self.assertTrue(False)
except Exception as e:
self.assertEqual(type(e), queues.QueueFull)
self.assertEqual(q.get_nowait(), 12)
try:
q.get_nowait()
self.assertTrue(False)
except Exception as e:
self.assertEqual(type(e), queues.QueueEmpty)
def test_qsize(self):
q = queues.Queue()
for n in range(10):
q.put_nowait(10)
self.assertEqual(q.qsize(), 10)
def test_empty(self):
q = queues.Queue()
self.assertTrue(q.empty())
q.put_nowait(10)
self.assertFalse(q.empty())
def test_full(self):
q = queues.Queue(maxsize=1)
self.assertFalse(q.full())
q.put_nowait(10)
self.assertTrue(q.full())
if __name__ == "__main__":
run_class(QueueTestCase)

Wyświetl plik

@ -1,95 +0,0 @@
from collections.deque import deque
from uasyncio.core import sleep
class QueueEmpty(Exception):
"""Exception raised by get_nowait()."""
class QueueFull(Exception):
"""Exception raised by put_nowait()."""
class Queue:
"""A queue, useful for coordinating producer and consumer coroutines.
If maxsize is less than or equal to zero, the queue size is infinite. If it
is an integer greater than 0, then "yield from put()" will block when the
queue reaches maxsize, until an item is removed by get().
Unlike the standard library Queue, you can reliably know this Queue's size
with qsize(), since your single-threaded uasyncio application won't be
interrupted between calling qsize() and doing an operation on the Queue.
"""
_attempt_delay = 0.1
def __init__(self, maxsize=0):
self.maxsize = maxsize
self._queue = deque()
def _get(self):
return self._queue.popleft()
def get(self):
"""Returns generator, which can be used for getting (and removing)
an item from a queue.
Usage::
item = yield from queue.get()
"""
while not self._queue:
yield from sleep(self._attempt_delay)
return self._get()
def get_nowait(self):
"""Remove and return an item from the queue.
Return an item if one is immediately available, else raise QueueEmpty.
"""
if not self._queue:
raise QueueEmpty()
return self._get()
def _put(self, val):
self._queue.append(val)
def put(self, val):
"""Returns generator which can be used for putting item in a queue.
Usage::
yield from queue.put(item)
"""
while self.qsize() >= self.maxsize and self.maxsize:
yield from sleep(self._attempt_delay)
self._put(val)
def put_nowait(self, val):
"""Put an item into the queue without blocking.
If no free slot is immediately available, raise QueueFull.
"""
if self.qsize() >= self.maxsize and self.maxsize:
raise QueueFull()
self._put(val)
def qsize(self):
"""Number of items in the queue."""
return len(self._queue)
def empty(self):
"""Return True if the queue is empty, False otherwise."""
return not self._queue
def full(self):
"""Return True if there are maxsize items in the queue.
Note: if the Queue was initialized with maxsize=0 (the default),
then full() is never True.
"""
if self.maxsize <= 0:
return False
else:
return self.qsize() >= self.maxsize

Wyświetl plik

@ -1,27 +0,0 @@
try:
import uasyncio.core as asyncio
from uasyncio.synchro import Lock
except ImportError:
import asyncio
from asyncio import Lock
def task(i, lock):
print(lock)
while 1:
yield from lock.acquire()
print("Acquired lock in task", i)
yield from asyncio.sleep(0.5)
# yield lock.release()
lock.release()
loop = asyncio.get_event_loop()
lock = Lock()
loop.create_task(task(1, lock))
loop.create_task(task(2, lock))
loop.create_task(task(3, lock))
loop.run_forever()

Wyświetl plik

@ -1,5 +0,0 @@
srctype = micropython-lib
type = package
version = 0.1.1
desc = Synchronization primitives for uasyncio.
depends = uasyncio.core

Wyświetl plik

@ -1,25 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio.synchro",
version="0.1.1",
description="Synchronization primitives for uasyncio.",
long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.",
url="https://github.com/micropython/micropython-lib",
author="micropython-lib Developers",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio"],
install_requires=["micropython-uasyncio.core"],
)

Wyświetl plik

@ -1,28 +0,0 @@
from uasyncio import core
class Lock:
def __init__(self):
self.locked = False
self.wlist = []
def release(self):
assert self.locked
self.locked = False
if self.wlist:
# print(self.wlist)
coro = self.wlist.pop(0)
core.get_event_loop().call_soon(coro)
def acquire(self):
# As release() is not coro, assume we just released and going to acquire again
# so, yield first to let someone else to acquire it first
yield
# print("acquire:", self.locked)
while 1:
if not self.locked:
self.locked = True
return True
# print("putting", core.get_event_loop().cur_task, "on waiting list")
self.wlist.append(core.get_event_loop().cur_task)
yield False

Wyświetl plik

@ -1,28 +0,0 @@
# This example is intended to run with dnsmasq running on localhost
# (Ubuntu comes configured like that by default). Dnsmasq, receiving
# some junk, is still kind to reply something back, which we employ
# here.
import uasyncio
import uasyncio.udp
import usocket
def udp_req(addr):
s = uasyncio.udp.socket()
print(s)
yield from uasyncio.udp.sendto(s, b"!eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", addr)
try:
resp = yield from uasyncio.wait_for(uasyncio.udp.recv(s, 1024), 1)
print(resp)
except uasyncio.TimeoutError:
print("timed out")
import logging
logging.basicConfig(level=logging.INFO)
addr = usocket.getaddrinfo("127.0.0.1", 53)[0][-1]
loop = uasyncio.get_event_loop()
loop.run_until_complete(udp_req(addr))
loop.close()

Wyświetl plik

@ -1,6 +0,0 @@
srctype = micropython-lib
type = package
version = 0.1.1
author = Paul Sokolovsky
desc = UDP support for MicroPython's uasyncio
depends = uasyncio

Wyświetl plik

@ -1,25 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio.udp",
version="0.1.1",
description="UDP support for MicroPython's uasyncio",
long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.",
url="https://github.com/micropython/micropython-lib",
author="Paul Sokolovsky",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio"],
install_requires=["micropython-uasyncio"],
)

Wyświetl plik

@ -1,64 +0,0 @@
import usocket
from uasyncio import core
DEBUG = 0
log = None
def set_debug(val):
global DEBUG, log
DEBUG = val
if val:
import logging
log = logging.getLogger("uasyncio.udp")
def socket(af=usocket.AF_INET):
s = usocket.socket(af, usocket.SOCK_DGRAM)
s.setblocking(False)
return s
def recv(s, n):
try:
yield core.IORead(s)
return s.recv(n)
except:
# print("recv: exc, cleaning up")
# print(uasyncio.core._event_loop.objmap, uasyncio.core._event_loop.poller)
# uasyncio.core._event_loop.poller.dump()
yield core.IOReadDone(s)
# print(uasyncio.core._event_loop.objmap)
# uasyncio.core._event_loop.poller.dump()
raise
def recvfrom(s, n):
try:
yield core.IORead(s)
return s.recvfrom(n)
except:
# print("recv: exc, cleaning up")
# print(uasyncio.core._event_loop.objmap, uasyncio.core._event_loop.poller)
# uasyncio.core._event_loop.poller.dump()
yield core.IOReadDone(s)
# print(uasyncio.core._event_loop.objmap)
# uasyncio.core._event_loop.poller.dump()
raise
def sendto(s, buf, addr=None):
while 1:
res = s.sendto(buf, addr)
# print("send res:", res)
if res == len(buf):
return
print("sendto: IOWrite")
yield core.IOWrite(s)
def close(s):
yield core.IOReadDone(s)
s.close()

Wyświetl plik

@ -1,28 +0,0 @@
import uasyncio
from uasyncio.websocket.server import WSReader, WSWriter
def echo(reader, writer):
# Consume GET line
yield from reader.readline()
reader = yield from WSReader(reader, writer)
writer = WSWriter(reader, writer)
while 1:
l = yield from reader.read(256)
print(l)
if l == b"\r":
await writer.awrite(b"\r\n")
else:
await writer.awrite(l)
import logging
# logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.DEBUG)
loop = uasyncio.get_event_loop()
loop.create_task(uasyncio.start_server(echo, "127.0.0.1", 8081))
loop.run_forever()
loop.close()

Wyświetl plik

@ -1,5 +0,0 @@
srctype = micropython-lib
type = package
version = 0.1
author = Paul Sokolovsky
depends = uasyncio

Wyświetl plik

@ -1,25 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio.websocket.server",
version="0.1",
description="uasyncio.websocket.server module for MicroPython",
long_description="This is a module reimplemented specifically for MicroPython standard library,\nwith efficient and lean design in mind. Note that this module is likely work\nin progress and likely supports just a subset of CPython's corresponding\nmodule. Please help with the development if you are interested in this\nmodule.",
url="https://github.com/micropython/micropython-lib",
author="Paul Sokolovsky",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio.websocket"],
install_requires=["micropython-uasyncio"],
)

Wyświetl plik

@ -1,64 +0,0 @@
import uasyncio
import uhashlib, ubinascii
import websocket
def make_respkey(webkey):
d = uhashlib.sha1(webkey)
d.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
respkey = d.digest()
respkey = ubinascii.b2a_base64(respkey) # [:-1]
# Return with trailing "\n".
return respkey
class WSWriter:
def __init__(self, reader, writer):
# Reader is passed for symmetry with WSReader() and ignored.
self.s = writer
async def awrite(self, data):
assert len(data) < 126
await self.s.awrite(b"\x81")
await self.s.awrite(bytes([len(data)]))
await self.s.awrite(data)
def WSReader(reader, writer):
webkey = None
while 1:
l = yield from reader.readline()
print(l)
if not l:
raise ValueError()
if l == b"\r\n":
break
if l.startswith(b"Sec-WebSocket-Key"):
webkey = l.split(b":", 1)[1]
webkey = webkey.strip()
if not webkey:
raise ValueError("Not a websocker request")
respkey = make_respkey(webkey)
await writer.awrite(
b"""\
HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: """
)
await writer.awrite(respkey)
# This will lead to "<key>\n\r\n" being written. Not exactly
# "\r\n\r\n", but browsers seem to eat it.
await writer.awrite("\r\n")
# await writer.awrite("\r\n\r\n")
print("Finished webrepl handshake")
ws = websocket.websocket(reader.ios)
rws = uasyncio.StreamReader(reader.ios, ws)
return rws

Wyświetl plik

@ -1,43 +0,0 @@
uasyncio
========
uasyncio is MicroPython's asynchronous sheduling library, roughly
modeled after CPython's asyncio.
uasyncio doesn't use naive always-iterating scheduling algorithm,
but performs a real time-based scheduling, which allows it (and
thus the whole system) to sleep when there is nothing to do (actual
implementation of that depends on I/O scheduling algorithm which
actually performs the wait operation).
Major conceptual differences to asyncio:
* Avoids defining a notion of Future, and especially wrapping coroutines
in Futures, like CPython asyncio does. uasyncio works directly with
coroutines (and callbacks).
* Methods provided are more consistently coroutines.
* uasyncio uses wrap-around millisecond timebase (as native to all
MicroPython ports.)
* Instead of single large package, number of subpackages are provided
(each installable separately).
Specific differences:
* For millisecond scheduling, ``loop.call_later_ms()`` and
``uasyncio.sleep_ms()`` are provided.
* As there's no monotonic time, ``loop.call_at()`` is not provided.
Instead, there's ``loop.call_at_()`` which is considered an internal
function and has slightly different signature.
* ``call_*`` funcions don't return Handle and callbacks scheduled by
them aren't cancellable. If they need to be cancellable, they should
accept an object as an argument, and a "cancel" flag should be set
in the object, for a callback to test.
* ``Future`` object is not available.
* ``ensure_future()`` and ``Task()`` perform just scheduling operations
and return a native coroutine, not Future/Task objects.
* Some other functions are not (yet) implemented.
* StreamWriter method(s) are coroutines. While in CPython asyncio,
StreamWriter.write() is a normal function (which potentially buffers
unlimited amount of data), uasyncio offers coroutine StreamWriter.awrite()
instead. Also, both StreamReader and StreamWriter have .aclose()
coroutine method.

Wyświetl plik

@ -1,45 +0,0 @@
Testing and Validating
----------------------
To test uasyncio correctness and performance, HTTP server samples can be
used. The simplest test is with test_http_server.py and Apache Benchmark
(ab). In one window, run:
micropython -O test_http_server.py
(-O is needed to short-circuit debug logging calls.)
In another:
ab -n10000 -c10 http://localhost:8081/
ab tests that all responses have the same length, but doesn't check
content. test_http_server.py also serves very short, static reply.
For more heavy testing, test_http_server_heavy.py is provided. It serves
large response split among several async writes. It is also dynamic -
includes incrementing counter, so each response will be different. The
response size generates is more 4Mb, because under Linux, socket writes
can buffer up to 4Mb of content (this appear to be controlled by
/proc/sys/net/ipv4/tcp_wmem and not /proc/sys/net/core/wmem_default).
test_http_server_heavy.py also includes (trivial) handling of
client-induced errors like EPIPE and ECONNRESET. To validate content
served, a post-hook script for "boom" tool
(https://github.com/tarekziade/boom) is provided.
Before start, you may want to bump .listen() value in uasyncio/__init__.py
from default 10 to at least 30.
Start:
micropython -X heapsize=300000000 -O test_http_server_heavy.py
(Yes, that's 300Mb of heap - we'll be serving 4+Mb of content with 30
concurrent connections).
And:
PYTHONPATH=. boom -n1000 -c30 http://localhost:8081 --post-hook=boom_uasyncio.validate
There should be no Python exceptions in the output.

Wyświetl plik

@ -1,39 +0,0 @@
#
# This is validation script for "boom" tool https://github.com/tarekziade/boom
# To use it:
#
# boom -n1000 --post-hook=boom_uasyncio.validate <rest of boom args>
#
# Note that if you'll use other -n value, you should update NUM_REQS below
# to match.
#
NUM_REQS = 1000
seen = []
cnt = 0
def validate(resp):
global cnt
t = resp.text
l = t.split("\r\n", 1)[0]
no = int(l.split()[1])
seen.append(no)
c = t.count(l + "\r\n")
assert c == 400101, str(c)
assert t.endswith("=== END ===")
cnt += 1
if cnt == NUM_REQS:
seen.sort()
print
print seen
print
el = None
for i in seen:
if el is None:
el = i
else:
el += 1
assert i == el
return resp

Wyświetl plik

@ -1,17 +0,0 @@
#!/bin/sh
#
# This in one-shot scripts to test "light load" uasyncio HTTP server using
# Apache Bench (ab).
#
#python3.4.2 test_http_server_light.py &
#micropython -O test_http_server_light.py &
#python3.4.2 test_http_server_medium.py &
micropython -O -X heapsize=200wK test_http_server_medium.py &
sleep 1
ab -n10000 -c100 http://127.0.0.1:8081/
kill %1

Wyświetl plik

@ -1,28 +0,0 @@
#!/bin/sh
#
# This in one-shot scripts to test "heavy load" uasyncio HTTP server using
# Boom tool https://github.com/tarekziade/boom .
#
# Note that this script doesn't test performance, but rather test functional
# correctness of uasyncio server implementation, while serving large amounts
# of data (guaranteedly more than a socket buffer). Thus, this script should
# not be used for benchmarking.
#
if [ ! -d .venv-boom ]; then
virtualenv .venv-boom
. .venv-boom/bin/activate
# PyPI currently has 0.8 which is too old
#pip install boom
pip install git+https://github.com/tarekziade/boom
else
. .venv-boom/bin/activate
fi
micropython -X heapsize=300000000 -O test_http_server_heavy.py &
sleep 1
PYTHONPATH=. boom -n1000 -c30 http://localhost:8081 --post-hook=boom_uasyncio.validate
kill %1

Wyświetl plik

@ -1,42 +0,0 @@
import uasyncio as asyncio
import signal
import errno
cnt = 0
@asyncio.coroutine
def serve(reader, writer):
global cnt
# s = "Hello.\r\n"
s = "Hello. %07d\r\n" % cnt
cnt += 1
yield from reader.read()
yield from writer.awrite("HTTP/1.0 200 OK\r\n\r\n")
try:
yield from writer.awrite(s)
yield from writer.awrite(s * 100)
yield from writer.awrite(s * 400000)
yield from writer.awrite("=== END ===")
except OSError as e:
if e.args[0] == errno.EPIPE:
print("EPIPE")
elif e.args[0] == errno.ECONNRESET:
print("ECONNRESET")
else:
raise
finally:
yield from writer.aclose()
import logging
logging.basicConfig(level=logging.INFO)
# logging.basicConfig(level=logging.DEBUG)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
loop = asyncio.get_event_loop()
# mem_info()
loop.call_soon(asyncio.start_server(serve, "0.0.0.0", 8081, backlog=100))
loop.run_forever()
loop.close()

Wyświetl plik

@ -1,22 +0,0 @@
import uasyncio as asyncio
@asyncio.coroutine
def serve(reader, writer):
# print(reader, writer)
# print("================")
yield from reader.read(512)
yield from writer.awrite("HTTP/1.0 200 OK\r\n\r\nHello.\r\n")
yield from writer.aclose()
# print("Finished processing request")
import logging
# logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
# mem_info()
loop.create_task(asyncio.start_server(serve, "127.0.0.1", 8081, backlog=100))
loop.run_forever()
loop.close()

Wyświetl plik

@ -1,24 +0,0 @@
import uasyncio as asyncio
resp = "HTTP/1.0 200 OK\r\n\r\n" + "Hello.\r\n" * 1500
@asyncio.coroutine
def serve(reader, writer):
# print(reader, writer)
# print("================")
yield from reader.read(512)
yield from writer.awrite(resp)
yield from writer.aclose()
# print("Finished processing request")
import logging
# logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop(80)
# mem_info()
loop.create_task(asyncio.start_server(serve, "127.0.0.1", 8081, backlog=100))
loop.run_forever()
loop.close()

Wyświetl plik

@ -1,27 +0,0 @@
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.run_until_complete(print_http_headers(url))
loop.close()

Wyświetl plik

@ -1,22 +0,0 @@
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.aclose()
print("Finished processing request")
import logging
# logging.basicConfig(level=logging.INFO)
logging.basicConfig(level=logging.DEBUG)
loop = asyncio.get_event_loop()
loop.call_soon(asyncio.start_server(serve, "127.0.0.1", 8081))
loop.run_forever()
loop.close()

Wyświetl plik

@ -1,7 +0,0 @@
srctype = micropython-lib
type = package
version = 2.0
author = Paul Sokolovsky
desc = Lightweight asyncio-like library for MicroPython, built around native Python coroutines.
long_desc = README.rst
depends = uasyncio.core

Wyświetl plik

@ -1,25 +0,0 @@
import sys
# Remove current dir from sys.path, otherwise setuptools will peek up our
# module instead of system's.
sys.path.pop(0)
from setuptools import setup
sys.path.append("..")
import sdist_upip
setup(
name="micropython-uasyncio",
version="2.0",
description="Lightweight asyncio-like library for MicroPython, built around native Python coroutines.",
long_description=open("README.rst").read(),
url="https://github.com/micropython/micropython-lib",
author="Paul Sokolovsky",
author_email="micro-python@googlegroups.com",
maintainer="micropython-lib Developers",
maintainer_email="micro-python@googlegroups.com",
license="MIT",
cmdclass={"sdist": sdist_upip.sdist},
packages=["uasyncio"],
install_requires=["micropython-uasyncio.core"],
)

Wyświetl plik

@ -1,32 +0,0 @@
from uasyncio import get_event_loop, open_connection, start_server, sleep_ms
from unittest import main, TestCase
class EchoTestCase(TestCase):
def test_client_server(self):
"""Simple client-server echo test"""
sockaddr = ("127.0.0.1", 8080)
l = get_event_loop()
async def echo_server(reader, writer):
data = await reader.readline()
await writer.awrite(data)
await writer.aclose()
async def echo_client(line, result):
await sleep_ms(10) # Allow server to get up
reader, writer = await open_connection(*sockaddr)
await writer.awrite(line)
data = await reader.readline()
await writer.aclose()
result.append(data) # capture response
result = []
l.create_task(start_server(echo_server, *sockaddr))
l.run_until_complete(echo_client(b"Hello\r\n", result))
self.assertEqual(result[0], b"Hello\r\n")
if __name__ == "__main__":
main()

Wyświetl plik

@ -1,37 +0,0 @@
try:
import uasyncio as asyncio
except:
import asyncio
try:
import utime as time
except:
import time
done = False
async def receiver():
global done
with open("test_io_starve.py", "rb") as f:
sreader = asyncio.StreamReader(f)
while True:
await asyncio.sleep(0.1)
res = await sreader.readline()
# Didn't get there with the original problem this test shows
done = True
async def foo():
start = time.time()
while time.time() - start < 1:
await asyncio.sleep(0)
loop.stop()
loop = asyncio.get_event_loop()
loop.create_task(foo())
loop.create_task(receiver())
loop.run_forever()
assert done
print("OK")

Wyświetl plik

@ -1,39 +0,0 @@
from uasyncio import StreamReader
class MockSock:
def __init__(self, data_list):
self.data = data_list
def read(self, sz):
try:
return self.data.pop(0)
except IndexError:
return b""
mock = MockSock(
[
b"123",
b"234",
b"5",
b"a",
b"b",
b"c",
b"d",
b"e",
]
)
def func():
sr = StreamReader(mock)
assert await sr.readexactly(3) == b"123"
assert await sr.readexactly(4) == b"2345"
assert await sr.readexactly(5) == b"abcde"
# This isn't how it should be, but the current behavior
assert await sr.readexactly(10) == b""
for i in func():
pass

Wyświetl plik

@ -1,35 +0,0 @@
from uasyncio import StreamReader
class MockSock:
def __init__(self, data_list):
self.data = data_list
def readline(self):
try:
return self.data.pop(0)
except IndexError:
return b""
mock = MockSock(
[
b"line1\n",
b"parts ",
b"of ",
b"line2\n",
b"unterminated",
]
)
def func():
sr = StreamReader(mock)
assert await sr.readline() == b"line1\n"
assert await sr.readline() == b"parts of line2\n"
assert await sr.readline() == b"unterminated"
assert await sr.readline() == b""
for i in func():
pass

Wyświetl plik

@ -1,259 +0,0 @@
import uerrno
import uselect as select
import usocket as _socket
from uasyncio.core import *
DEBUG = 0
log = None
def set_debug(val):
global DEBUG, log
DEBUG = val
if val:
import logging
log = logging.getLogger("uasyncio")
class PollEventLoop(EventLoop):
def __init__(self, runq_len=16, waitq_len=16):
EventLoop.__init__(self, runq_len, waitq_len)
self.poller = select.poll()
self.objmap = {}
def add_reader(self, sock, cb, *args):
if DEBUG and __debug__:
log.debug("add_reader%s", (sock, cb, args))
if args:
self.poller.register(sock, select.POLLIN)
self.objmap[id(sock)] = (cb, args)
else:
self.poller.register(sock, select.POLLIN)
self.objmap[id(sock)] = cb
def remove_reader(self, sock):
if DEBUG and __debug__:
log.debug("remove_reader(%s)", sock)
self.poller.unregister(sock)
del self.objmap[id(sock)]
def add_writer(self, sock, cb, *args):
if DEBUG and __debug__:
log.debug("add_writer%s", (sock, cb, args))
if args:
self.poller.register(sock, select.POLLOUT)
self.objmap[id(sock)] = (cb, args)
else:
self.poller.register(sock, select.POLLOUT)
self.objmap[id(sock)] = cb
def remove_writer(self, sock):
if DEBUG and __debug__:
log.debug("remove_writer(%s)", sock)
try:
self.poller.unregister(sock)
self.objmap.pop(id(sock), None)
except OSError as e:
# StreamWriter.awrite() first tries to write to a socket,
# and if that succeeds, yield IOWrite may never be called
# for that socket, and it will never be added to poller. So,
# ignore such error.
if e.args[0] != uerrno.ENOENT:
raise
def wait(self, delay):
if DEBUG and __debug__:
log.debug("poll.wait(%d)", delay)
# We need one-shot behavior (second arg of 1 to .poll())
res = self.poller.ipoll(delay, 1)
# log.debug("poll result: %s", res)
# Remove "if res" workaround after
# https://github.com/micropython/micropython/issues/2716 fixed.
if res:
for sock, ev in res:
cb = self.objmap[id(sock)]
if ev & (select.POLLHUP | select.POLLERR):
# These events are returned even if not requested, and
# are sticky, i.e. will be returned again and again.
# If the caller doesn't do proper error handling and
# unregister this sock, we'll busy-loop on it, so we
# as well can unregister it now "just in case".
self.remove_reader(sock)
if DEBUG and __debug__:
log.debug("Calling IO callback: %r", cb)
if isinstance(cb, tuple):
cb[0](*cb[1])
else:
cb.pend_throw(None)
self.call_soon(cb)
class StreamReader:
def __init__(self, polls, ios=None):
if ios is None:
ios = polls
self.polls = polls
self.ios = ios
def read(self, n=-1):
while True:
yield IORead(self.polls)
res = self.ios.read(n)
if res is not None:
break
# This should not happen for real sockets, but can easily
# happen for stream wrappers (ssl, websockets, etc.)
# log.warn("Empty read")
if not res:
yield IOReadDone(self.polls)
return res
def readexactly(self, n):
buf = b""
while n:
yield IORead(self.polls)
res = self.ios.read(n)
assert res is not None
if not res:
yield IOReadDone(self.polls)
break
buf += res
n -= len(res)
return buf
def readline(self):
if DEBUG and __debug__:
log.debug("StreamReader.readline()")
buf = b""
while True:
yield IORead(self.polls)
res = self.ios.readline()
assert res is not None
if not res:
yield IOReadDone(self.polls)
break
buf += res
if buf[-1] == 0x0A:
break
if DEBUG and __debug__:
log.debug("StreamReader.readline(): %s", buf)
return buf
def aclose(self):
yield IOReadDone(self.polls)
self.ios.close()
def __repr__(self):
return "<StreamReader %r %r>" % (self.polls, self.ios)
class StreamWriter:
def __init__(self, s, extra):
self.s = s
self.extra = extra
def awrite(self, buf, off=0, sz=-1):
# 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.
if sz == -1:
sz = len(buf) - off
if DEBUG and __debug__:
log.debug("StreamWriter.awrite(): spooling %d bytes", sz)
while True:
res = self.s.write(buf, off, sz)
# If we spooled everything, return immediately
if res == sz:
if DEBUG and __debug__:
log.debug("StreamWriter.awrite(): completed spooling %d bytes", res)
return
if res is None:
res = 0
if DEBUG and __debug__:
log.debug("StreamWriter.awrite(): spooled partial %d bytes", res)
assert res < sz
off += res
sz -= res
yield IOWrite(self.s)
# assert s2.fileno() == self.s.fileno()
if DEBUG and __debug__:
log.debug("StreamWriter.awrite(): can write more")
# Write piecewise content from iterable (usually, a generator)
def awriteiter(self, iterable):
for buf in iterable:
yield from self.awrite(buf)
def aclose(self):
yield IOWriteDone(self.s)
self.s.close()
def get_extra_info(self, name, default=None):
return self.extra.get(name, default)
def __repr__(self):
return "<StreamWriter %r>" % self.s
def open_connection(host, port, ssl=False):
if DEBUG and __debug__:
log.debug("open_connection(%s, %s)", host, port)
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
ai = ai[0]
s = _socket.socket(ai[0], ai[1], ai[2])
s.setblocking(False)
try:
s.connect(ai[-1])
except OSError as e:
if e.args[0] != uerrno.EINPROGRESS:
raise
if DEBUG and __debug__:
log.debug("open_connection: After connect")
yield IOWrite(s)
# if __debug__:
# assert s2.fileno() == s.fileno()
if DEBUG and __debug__:
log.debug("open_connection: After iowait: %s", s)
if ssl:
print("Warning: uasyncio SSL support is alpha")
import ussl
s.setblocking(True)
s2 = ussl.wrap_socket(s)
s.setblocking(False)
return StreamReader(s, s2), StreamWriter(s2, {})
return StreamReader(s), StreamWriter(s, {})
def start_server(client_coro, host, port, backlog=10):
if DEBUG and __debug__:
log.debug("start_server(%s, %s)", host, port)
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
ai = ai[0]
s = _socket.socket(ai[0], ai[1], ai[2])
s.setblocking(False)
s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
s.bind(ai[-1])
s.listen(backlog)
while True:
if DEBUG and __debug__:
log.debug("start_server: Before accept")
yield IORead(s)
if DEBUG and __debug__:
log.debug("start_server: After iowait")
s2, client_addr = s.accept()
s2.setblocking(False)
if DEBUG and __debug__:
log.debug("start_server: After accept: %s", s2)
extra = {"peername": client_addr}
yield client_coro(StreamReader(s2), StreamWriter(s2, extra))
import uasyncio.core
uasyncio.core._event_loop_class = PollEventLoop