2017-05-01 13:09:34 +00:00
|
|
|
import utime as time
|
2016-12-22 10:04:09 +00:00
|
|
|
import utimeq
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
import ucollections
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
|
2017-05-01 13:04:28 +00:00
|
|
|
type_gen = type((lambda: (yield))())
|
|
|
|
|
2016-11-13 11:52:31 +00:00
|
|
|
DEBUG = 0
|
2017-05-01 13:04:28 +00:00
|
|
|
log = None
|
2016-11-13 11:52:31 +00:00
|
|
|
|
2017-05-01 13:04:28 +00:00
|
|
|
def set_debug(val):
|
|
|
|
global DEBUG, log
|
|
|
|
DEBUG = val
|
|
|
|
if val:
|
|
|
|
import logging
|
2017-06-07 23:44:28 +00:00
|
|
|
log = logging.getLogger("uasyncio.core")
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
|
2018-01-07 08:41:51 +00:00
|
|
|
class CancelledError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TimeoutError(CancelledError):
|
2017-12-03 00:09:10 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
class EventLoop:
|
|
|
|
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
def __init__(self, runq_len=16, waitq_len=16):
|
|
|
|
self.runq = ucollections.deque((), runq_len, True)
|
|
|
|
self.waitq = utimeq.utimeq(waitq_len)
|
2017-12-02 22:22:17 +00:00
|
|
|
# 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
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
def time(self):
|
2016-11-12 22:32:32 +00:00
|
|
|
return time.ticks_ms()
|
2014-10-23 21:24:13 +00:00
|
|
|
|
2014-11-04 00:49:44 +00:00
|
|
|
def create_task(self, coro):
|
|
|
|
# CPython 3.4.2
|
2017-05-16 18:47:02 +00:00
|
|
|
self.call_later_ms(0, coro)
|
2014-11-04 00:49:44 +00:00
|
|
|
# CPython asyncio incompatibility: we don't return Task object
|
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
def call_soon(self, callback, *args):
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
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)
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
def call_later(self, delay, callback, *args):
|
2017-05-22 10:21:54 +00:00
|
|
|
self.call_at_(time.ticks_add(self.time(), int(delay * 1000)), callback, args)
|
2016-11-12 22:32:32 +00:00
|
|
|
|
2017-05-22 10:21:54 +00:00
|
|
|
def call_later_ms(self, delay, callback, *args):
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
if not delay:
|
|
|
|
return self.call_soon(callback, *args)
|
2016-11-13 11:51:04 +00:00
|
|
|
self.call_at_(time.ticks_add(self.time(), delay), callback, args)
|
2014-10-23 21:24:13 +00:00
|
|
|
|
2016-11-13 11:51:04 +00:00
|
|
|
def call_at_(self, time, callback, args=()):
|
|
|
|
if __debug__ and DEBUG:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
log.debug("Scheduling in waitq: %s", (time, callback, args))
|
|
|
|
self.waitq.push(time, callback, args)
|
2016-11-13 11:51:04 +00:00
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
def wait(self, delay):
|
|
|
|
# Default wait implementation, to be overriden in subclasses
|
|
|
|
# with IO scheduling
|
2016-11-13 11:52:31 +00:00
|
|
|
if __debug__ and DEBUG:
|
2016-07-04 09:58:40 +00:00
|
|
|
log.debug("Sleeping for: %s", delay)
|
2016-11-12 22:32:32 +00:00
|
|
|
time.sleep_ms(delay)
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
def run_forever(self):
|
2016-12-22 10:04:09 +00:00
|
|
|
cur_task = [0, 0, 0]
|
2014-10-23 21:24:13 +00:00
|
|
|
while True:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
# 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
|
|
|
|
|
2016-11-13 11:52:31 +00:00
|
|
|
if __debug__ and DEBUG:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
log.info("Next coroutine to run: %s", (cb, args))
|
2017-12-02 22:22:17 +00:00
|
|
|
self.cur_task = cb
|
2014-10-23 21:24:13 +00:00
|
|
|
delay = 0
|
|
|
|
try:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
if args is ():
|
2016-07-04 09:57:08 +00:00
|
|
|
ret = next(cb)
|
|
|
|
else:
|
|
|
|
ret = cb.send(*args)
|
2016-11-13 11:52:31 +00:00
|
|
|
if __debug__ and DEBUG:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
log.info("Coroutine %s yield result: %s", cb, ret)
|
2015-12-05 20:23:10 +00:00
|
|
|
if isinstance(ret, SysCall1):
|
|
|
|
arg = ret.arg
|
2016-12-26 16:51:11 +00:00
|
|
|
if isinstance(ret, SleepMs):
|
|
|
|
delay = arg
|
2014-10-23 21:24:13 +00:00
|
|
|
elif isinstance(ret, IORead):
|
2017-12-14 17:05:23 +00:00
|
|
|
cb.pend_throw(False)
|
2016-12-26 01:22:55 +00:00
|
|
|
self.add_reader(arg, cb)
|
2014-10-23 21:24:13 +00:00
|
|
|
continue
|
|
|
|
elif isinstance(ret, IOWrite):
|
2017-12-14 17:05:23 +00:00
|
|
|
cb.pend_throw(False)
|
2016-12-26 01:22:55 +00:00
|
|
|
self.add_writer(arg, cb)
|
2014-10-23 21:24:13 +00:00
|
|
|
continue
|
|
|
|
elif isinstance(ret, IOReadDone):
|
2016-12-26 01:22:55 +00:00
|
|
|
self.remove_reader(arg)
|
2014-10-23 21:24:13 +00:00
|
|
|
elif isinstance(ret, IOWriteDone):
|
2016-12-26 01:22:55 +00:00
|
|
|
self.remove_writer(arg)
|
2014-10-23 21:24:13 +00:00
|
|
|
elif isinstance(ret, StopLoop):
|
|
|
|
return arg
|
2016-12-26 16:11:38 +00:00
|
|
|
else:
|
|
|
|
assert False, "Unknown syscall yielded: %r (of type %r)" % (ret, type(ret))
|
2014-10-23 21:24:13 +00:00
|
|
|
elif isinstance(ret, type_gen):
|
|
|
|
self.call_soon(ret)
|
2016-11-12 22:32:32 +00:00
|
|
|
elif isinstance(ret, int):
|
|
|
|
# Delay
|
|
|
|
delay = ret
|
2014-10-23 21:24:13 +00:00
|
|
|
elif ret is None:
|
|
|
|
# Just reschedule
|
|
|
|
pass
|
2017-10-27 23:13:50 +00:00
|
|
|
elif ret is False:
|
|
|
|
# Don't reschedule
|
|
|
|
continue
|
2014-10-23 21:24:13 +00:00
|
|
|
else:
|
|
|
|
assert False, "Unsupported coroutine yield value: %r (of type %r)" % (ret, type(ret))
|
|
|
|
except StopIteration as e:
|
2016-11-13 11:52:31 +00:00
|
|
|
if __debug__ and DEBUG:
|
2014-10-25 21:20:24 +00:00
|
|
|
log.debug("Coroutine finished: %s", cb)
|
2014-10-23 21:24:13 +00:00
|
|
|
continue
|
2018-01-07 08:41:51 +00:00
|
|
|
except CancelledError as e:
|
|
|
|
if __debug__ and DEBUG:
|
|
|
|
log.debug("Coroutine cancelled: %s", cb)
|
|
|
|
continue
|
2017-05-22 22:26:08 +00:00
|
|
|
# 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.
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
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)
|
2014-10-23 21:24:13 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2017-05-22 10:49:27 +00:00
|
|
|
def stop(self):
|
|
|
|
self.call_soon((lambda: (yield StopLoop(0)))())
|
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
def close(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class SysCall:
|
|
|
|
|
|
|
|
def __init__(self, *args):
|
|
|
|
self.args = args
|
|
|
|
|
|
|
|
def handle(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-12-05 20:23:10 +00:00
|
|
|
# Optimized syscall with 1 arg
|
|
|
|
class SysCall1(SysCall):
|
|
|
|
|
|
|
|
def __init__(self, arg):
|
|
|
|
self.arg = arg
|
|
|
|
|
|
|
|
class StopLoop(SysCall1):
|
2014-10-23 21:24:13 +00:00
|
|
|
pass
|
|
|
|
|
2015-12-05 20:23:10 +00:00
|
|
|
class IORead(SysCall1):
|
2014-10-23 21:24:13 +00:00
|
|
|
pass
|
|
|
|
|
2015-12-05 20:23:10 +00:00
|
|
|
class IOWrite(SysCall1):
|
2014-10-23 21:24:13 +00:00
|
|
|
pass
|
|
|
|
|
2015-12-05 20:23:10 +00:00
|
|
|
class IOReadDone(SysCall1):
|
2014-10-23 21:24:13 +00:00
|
|
|
pass
|
|
|
|
|
2015-12-05 20:23:10 +00:00
|
|
|
class IOWriteDone(SysCall1):
|
2014-10-23 21:24:13 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-11-03 04:44:05 +00:00
|
|
|
_event_loop = None
|
|
|
|
_event_loop_class = EventLoop
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
def get_event_loop(runq_len=16, waitq_len=16):
|
2014-11-03 04:44:05 +00:00
|
|
|
global _event_loop
|
|
|
|
if _event_loop is None:
|
uasyncio.core: Switch to separate run and wait queues.
Instead of using single priority queue for all tasks, split into using
"run queue", which represents tasks not waiting until specific time,
which should be run on every (well, next) loop iteration, and wait queue,
still a priority queue. Run queue is a simple FIFO, implemented by
ucollections.deque, recently introduced in pfalcon/micropython. Thus,
there's minimal storage overhead and intrinsic scheduling fairness.
Generally, run queue should hold both a callback/coro and its arguments,
but as we don't feed any send args into coros still, it's optimized to
hold just 1 items for coros, while 2 for callbacks.
Introducing run queue will also allow to get rid of tie-breaking counter
in utimeq implementation, which was introduced to enforce fair scheduling.
It's no longer needed, as all tasks which should be run at given time
are batch-removed from wait queue and batch-inserted into run queue. So,
they may be executed not in the order scheduled (due to non-stable order
of heap), but the whole batch will be executed "atomically", and any new
schedulings from will be processed no earlier than next loop iteration.
2018-02-06 22:06:10 +00:00
|
|
|
_event_loop = _event_loop_class(runq_len, waitq_len)
|
2014-11-03 04:44:05 +00:00
|
|
|
return _event_loop
|
|
|
|
|
|
|
|
def sleep(secs):
|
2016-11-12 22:32:32 +00:00
|
|
|
yield int(secs * 1000)
|
|
|
|
|
2016-12-26 16:51:11 +00:00
|
|
|
# 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()
|
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
|
2018-01-07 08:41:51 +00:00
|
|
|
def cancel(coro):
|
|
|
|
prev = coro.pend_throw(CancelledError())
|
|
|
|
if prev is False:
|
|
|
|
_event_loop.call_soon(coro)
|
|
|
|
|
|
|
|
|
2017-12-03 00:09:10 +00:00
|
|
|
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)
|
2017-12-14 17:05:23 +00:00
|
|
|
prev = timeout_obj.coro.pend_throw(TimeoutError())
|
|
|
|
#print("prev pend", prev)
|
|
|
|
if prev is False:
|
|
|
|
_event_loop.call_soon(timeout_obj.coro)
|
2017-12-03 00:09:10 +00:00
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
2014-10-23 21:24:13 +00:00
|
|
|
def coroutine(f):
|
|
|
|
return f
|
|
|
|
|
2014-11-03 04:44:05 +00:00
|
|
|
#
|
|
|
|
# The functions below are deprecated in uasyncio, and provided only
|
|
|
|
# for compatibility with CPython asyncio
|
|
|
|
#
|
|
|
|
|
2016-05-27 23:33:09 +00:00
|
|
|
def ensure_future(coro, loop=_event_loop):
|
2014-11-03 04:44:05 +00:00
|
|
|
_event_loop.call_soon(coro)
|
|
|
|
# CPython asyncio incompatibility: we don't return Task object
|
2014-10-23 21:24:13 +00:00
|
|
|
return coro
|
|
|
|
|
2014-11-03 04:44:05 +00:00
|
|
|
|
|
|
|
# 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)
|