reactor: Add support for greenlets

Add support for greenlets - a mechanism for tasks that can pause while
still allowing regular reactor events to occur.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2016-11-15 19:56:27 -05:00
parent ceb60ffcc6
commit bafe796eeb
2 changed files with 54 additions and 6 deletions

View File

@ -34,7 +34,7 @@ regular "pi" user:
``` ```
virtualenv ~/klippy-env virtualenv ~/klippy-env
~/klippy-env/bin/pip install cffi==1.6.0 pyserial==2.7 ~/klippy-env/bin/pip install cffi==1.6.0 pyserial==2.7 greenlet==0.4.10
``` ```
Building Klipper Building Klipper

View File

@ -3,8 +3,8 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import select, time, math import select, time, math
import greenlet
class ReactorTimer: class ReactorTimer:
def __init__(self, callback, waketime): def __init__(self, callback, waketime):
@ -18,6 +18,11 @@ class ReactorFileHandler:
def fileno(self): def fileno(self):
return self.fd return self.fd
class ReactorGreenlet(greenlet.greenlet):
def __init__(self, run):
greenlet.greenlet.__init__(self, run=run)
self.timer = None
class SelectReactor: class SelectReactor:
NOW = 0. NOW = 0.
NEVER = 9999999999999999. NEVER = 9999999999999999.
@ -25,7 +30,9 @@ class SelectReactor:
self._fds = [] self._fds = []
self._timers = [] self._timers = []
self._next_timer = self.NEVER self._next_timer = self.NEVER
self._process = True self._process = False
self._g_dispatch = None
self._greenlets = []
# Timers # Timers
def _note_time(self, t): def _note_time(self, t):
nexttime = t.waketime nexttime = t.waketime
@ -49,13 +56,36 @@ class SelectReactor:
if eventtime < self._next_timer: if eventtime < self._next_timer:
return min(1., max(.001, self._next_timer - eventtime)) return min(1., max(.001, self._next_timer - eventtime))
self._next_timer = self.NEVER self._next_timer = self.NEVER
g_dispatch = self._g_dispatch
for t in self._timers: for t in self._timers:
if eventtime >= t.waketime: if eventtime >= t.waketime:
t.waketime = self.NEVER
t.waketime = t.callback(eventtime) t.waketime = t.callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
return 0.
self._note_time(t) self._note_time(t)
if eventtime >= self._next_timer: if eventtime >= self._next_timer:
return 0. return 0.
return min(1., max(.001, self._next_timer - time.time())) return min(1., max(.001, self._next_timer - time.time()))
# Greenlets
def pause(self, waketime):
g = greenlet.getcurrent()
if g is not self._g_dispatch:
return self._g_dispatch.switch(waketime)
if self._greenlets:
g_next = self._greenlets.pop()
else:
g_next = ReactorGreenlet(run=self._dispatch_loop)
g_next.parent = g.parent
g.timer = self.register_timer(g.switch, waketime)
return g_next.switch()
def _end_greenlet(self, g_old):
self._greenlets.append(g_old)
self.unregister_timer(g_old.timer)
g_old.timer = None
self._g_dispatch.switch(self.NEVER)
self._g_dispatch = g_old
# File descriptors # File descriptors
def register_fd(self, fd, callback): def register_fd(self, fd, callback):
handler = ReactorFileHandler(fd, callback) handler = ReactorFileHandler(fd, callback)
@ -64,8 +94,9 @@ class SelectReactor:
def unregister_fd(self, handler): def unregister_fd(self, handler):
self._fds.pop(self._fds.index(handler)) self._fds.pop(self._fds.index(handler))
# Main loop # Main loop
def run(self): def _dispatch_loop(self):
self._process = True self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time() eventtime = time.time()
while self._process: while self._process:
timeout = self._check_timers(eventtime) timeout = self._check_timers(eventtime)
@ -73,6 +104,13 @@ class SelectReactor:
eventtime = time.time() eventtime = time.time()
for fd in res[0]: for fd in res[0]:
fd.callback(eventtime) fd.callback(eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
break
self._g_dispatch = None
def run(self):
g_next = ReactorGreenlet(run=self._dispatch_loop)
g_next.switch()
def end(self): def end(self):
self._process = False self._process = False
@ -95,8 +133,9 @@ class PollReactor(SelectReactor):
del fds[handler.fd] del fds[handler.fd]
self._fds = fds self._fds = fds
# Main loop # Main loop
def run(self): def _dispatch_loop(self):
self._process = True self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time() eventtime = time.time()
while self._process: while self._process:
timeout = int(math.ceil(self._check_timers(eventtime) * 1000.)) timeout = int(math.ceil(self._check_timers(eventtime) * 1000.))
@ -104,6 +143,10 @@ class PollReactor(SelectReactor):
eventtime = time.time() eventtime = time.time()
for fd, event in res: for fd, event in res:
self._fds[fd](eventtime) self._fds[fd](eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
break
self._g_dispatch = None
class EPollReactor(SelectReactor): class EPollReactor(SelectReactor):
def __init__(self): def __init__(self):
@ -124,8 +167,9 @@ class EPollReactor(SelectReactor):
del fds[handler.fd] del fds[handler.fd]
self._fds = fds self._fds = fds
# Main loop # Main loop
def run(self): def _dispatch_loop(self):
self._process = True self._process = True
self._g_dispatch = g_dispatch = greenlet.getcurrent()
eventtime = time.time() eventtime = time.time()
while self._process: while self._process:
timeout = self._check_timers(eventtime) timeout = self._check_timers(eventtime)
@ -133,6 +177,10 @@ class EPollReactor(SelectReactor):
eventtime = time.time() eventtime = time.time()
for fd, event in res: for fd, event in res:
self._fds[fd](eventtime) self._fds[fd](eventtime)
if g_dispatch is not self._g_dispatch:
self._end_greenlet(g_dispatch)
break
self._g_dispatch = None
# Use the poll based reactor if it is available # Use the poll based reactor if it is available
try: try: