2016-05-25 18:37:40 +03:00
|
|
|
# File descriptor and timer event helper
|
|
|
|
#
|
|
|
|
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
|
|
|
|
#
|
|
|
|
# This file may be distributed under the terms of the GNU GPLv3 license.
|
|
|
|
import select, time, math
|
2016-11-16 03:56:27 +03:00
|
|
|
import greenlet
|
2016-05-25 18:37:40 +03:00
|
|
|
|
|
|
|
class ReactorTimer:
|
|
|
|
def __init__(self, callback, waketime):
|
|
|
|
self.callback = callback
|
|
|
|
self.waketime = waketime
|
|
|
|
|
|
|
|
class ReactorFileHandler:
|
|
|
|
def __init__(self, fd, callback):
|
|
|
|
self.fd = fd
|
|
|
|
self.callback = callback
|
|
|
|
def fileno(self):
|
|
|
|
return self.fd
|
|
|
|
|
2016-11-16 03:56:27 +03:00
|
|
|
class ReactorGreenlet(greenlet.greenlet):
|
|
|
|
def __init__(self, run):
|
|
|
|
greenlet.greenlet.__init__(self, run=run)
|
|
|
|
self.timer = None
|
|
|
|
|
2016-05-25 18:37:40 +03:00
|
|
|
class SelectReactor:
|
|
|
|
NOW = 0.
|
|
|
|
NEVER = 9999999999999999.
|
|
|
|
def __init__(self):
|
|
|
|
self._fds = []
|
|
|
|
self._timers = []
|
|
|
|
self._next_timer = self.NEVER
|
2016-11-16 03:56:27 +03:00
|
|
|
self._process = False
|
|
|
|
self._g_dispatch = None
|
|
|
|
self._greenlets = []
|
2016-05-25 18:37:40 +03:00
|
|
|
# Timers
|
|
|
|
def _note_time(self, t):
|
|
|
|
nexttime = t.waketime
|
|
|
|
if nexttime < self._next_timer:
|
|
|
|
self._next_timer = nexttime
|
|
|
|
def update_timer(self, t, nexttime):
|
|
|
|
t.waketime = nexttime
|
|
|
|
self._note_time(t)
|
|
|
|
def register_timer(self, callback, waketime = NEVER):
|
|
|
|
handler = ReactorTimer(callback, waketime)
|
|
|
|
timers = list(self._timers)
|
|
|
|
timers.append(handler)
|
|
|
|
self._timers = timers
|
|
|
|
self._note_time(handler)
|
|
|
|
return handler
|
|
|
|
def unregister_timer(self, handler):
|
|
|
|
timers = list(self._timers)
|
|
|
|
timers.pop(timers.index(handler))
|
|
|
|
self._timers = timers
|
|
|
|
def _check_timers(self, eventtime):
|
|
|
|
if eventtime < self._next_timer:
|
|
|
|
return min(1., max(.001, self._next_timer - eventtime))
|
|
|
|
self._next_timer = self.NEVER
|
2016-11-16 03:56:27 +03:00
|
|
|
g_dispatch = self._g_dispatch
|
2016-05-25 18:37:40 +03:00
|
|
|
for t in self._timers:
|
|
|
|
if eventtime >= t.waketime:
|
2016-11-16 03:56:27 +03:00
|
|
|
t.waketime = self.NEVER
|
2016-05-25 18:37:40 +03:00
|
|
|
t.waketime = t.callback(eventtime)
|
2016-11-16 03:56:27 +03:00
|
|
|
if g_dispatch is not self._g_dispatch:
|
|
|
|
self._end_greenlet(g_dispatch)
|
|
|
|
return 0.
|
2016-05-25 18:37:40 +03:00
|
|
|
self._note_time(t)
|
|
|
|
if eventtime >= self._next_timer:
|
|
|
|
return 0.
|
|
|
|
return min(1., max(.001, self._next_timer - time.time()))
|
2016-11-16 03:56:27 +03:00
|
|
|
# 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
|
2016-05-25 18:37:40 +03:00
|
|
|
# File descriptors
|
|
|
|
def register_fd(self, fd, callback):
|
|
|
|
handler = ReactorFileHandler(fd, callback)
|
|
|
|
self._fds.append(handler)
|
|
|
|
return handler
|
|
|
|
def unregister_fd(self, handler):
|
|
|
|
self._fds.pop(self._fds.index(handler))
|
|
|
|
# Main loop
|
2016-11-16 03:56:27 +03:00
|
|
|
def _dispatch_loop(self):
|
2016-05-25 18:37:40 +03:00
|
|
|
self._process = True
|
2016-11-16 03:56:27 +03:00
|
|
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
2016-05-25 18:37:40 +03:00
|
|
|
eventtime = time.time()
|
|
|
|
while self._process:
|
|
|
|
timeout = self._check_timers(eventtime)
|
|
|
|
res = select.select(self._fds, [], [], timeout)
|
|
|
|
eventtime = time.time()
|
|
|
|
for fd in res[0]:
|
|
|
|
fd.callback(eventtime)
|
2016-11-16 03:56:27 +03:00
|
|
|
if g_dispatch is not self._g_dispatch:
|
|
|
|
self._end_greenlet(g_dispatch)
|
2017-01-10 18:55:46 +03:00
|
|
|
eventtime = time.time()
|
2016-11-16 03:56:27 +03:00
|
|
|
break
|
|
|
|
self._g_dispatch = None
|
|
|
|
def run(self):
|
|
|
|
g_next = ReactorGreenlet(run=self._dispatch_loop)
|
|
|
|
g_next.switch()
|
2016-05-25 18:37:40 +03:00
|
|
|
def end(self):
|
|
|
|
self._process = False
|
|
|
|
|
|
|
|
class PollReactor(SelectReactor):
|
|
|
|
def __init__(self):
|
|
|
|
SelectReactor.__init__(self)
|
|
|
|
self._poll = select.poll()
|
|
|
|
self._fds = {}
|
|
|
|
# File descriptors
|
|
|
|
def register_fd(self, fd, callback):
|
|
|
|
handler = ReactorFileHandler(fd, callback)
|
|
|
|
fds = self._fds.copy()
|
|
|
|
fds[fd] = callback
|
|
|
|
self._fds = fds
|
|
|
|
self._poll.register(handler, select.POLLIN | select.POLLHUP)
|
|
|
|
return handler
|
|
|
|
def unregister_fd(self, handler):
|
|
|
|
self._poll.unregister(handler)
|
|
|
|
fds = self._fds.copy()
|
|
|
|
del fds[handler.fd]
|
|
|
|
self._fds = fds
|
|
|
|
# Main loop
|
2016-11-16 03:56:27 +03:00
|
|
|
def _dispatch_loop(self):
|
2016-05-25 18:37:40 +03:00
|
|
|
self._process = True
|
2016-11-16 03:56:27 +03:00
|
|
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
2016-05-25 18:37:40 +03:00
|
|
|
eventtime = time.time()
|
|
|
|
while self._process:
|
2017-01-10 18:55:46 +03:00
|
|
|
timeout = self._check_timers(eventtime)
|
|
|
|
res = self._poll.poll(int(math.ceil(timeout * 1000.)))
|
2016-05-25 18:37:40 +03:00
|
|
|
eventtime = time.time()
|
|
|
|
for fd, event in res:
|
|
|
|
self._fds[fd](eventtime)
|
2016-11-16 03:56:27 +03:00
|
|
|
if g_dispatch is not self._g_dispatch:
|
|
|
|
self._end_greenlet(g_dispatch)
|
2017-01-10 18:55:46 +03:00
|
|
|
eventtime = time.time()
|
2016-11-16 03:56:27 +03:00
|
|
|
break
|
|
|
|
self._g_dispatch = None
|
2016-05-25 18:37:40 +03:00
|
|
|
|
|
|
|
class EPollReactor(SelectReactor):
|
|
|
|
def __init__(self):
|
|
|
|
SelectReactor.__init__(self)
|
|
|
|
self._epoll = select.epoll()
|
|
|
|
self._fds = {}
|
|
|
|
# File descriptors
|
|
|
|
def register_fd(self, fd, callback):
|
|
|
|
handler = ReactorFileHandler(fd, callback)
|
|
|
|
fds = self._fds.copy()
|
|
|
|
fds[fd] = callback
|
|
|
|
self._fds = fds
|
|
|
|
self._epoll.register(fd, select.EPOLLIN | select.EPOLLHUP)
|
|
|
|
return handler
|
|
|
|
def unregister_fd(self, handler):
|
|
|
|
self._epoll.unregister(handler.fd)
|
|
|
|
fds = self._fds.copy()
|
|
|
|
del fds[handler.fd]
|
|
|
|
self._fds = fds
|
|
|
|
# Main loop
|
2016-11-16 03:56:27 +03:00
|
|
|
def _dispatch_loop(self):
|
2016-05-25 18:37:40 +03:00
|
|
|
self._process = True
|
2016-11-16 03:56:27 +03:00
|
|
|
self._g_dispatch = g_dispatch = greenlet.getcurrent()
|
2016-05-25 18:37:40 +03:00
|
|
|
eventtime = time.time()
|
|
|
|
while self._process:
|
|
|
|
timeout = self._check_timers(eventtime)
|
|
|
|
res = self._epoll.poll(timeout)
|
|
|
|
eventtime = time.time()
|
|
|
|
for fd, event in res:
|
|
|
|
self._fds[fd](eventtime)
|
2016-11-16 03:56:27 +03:00
|
|
|
if g_dispatch is not self._g_dispatch:
|
|
|
|
self._end_greenlet(g_dispatch)
|
2017-01-10 18:55:46 +03:00
|
|
|
eventtime = time.time()
|
2016-11-16 03:56:27 +03:00
|
|
|
break
|
|
|
|
self._g_dispatch = None
|
2016-05-25 18:37:40 +03:00
|
|
|
|
|
|
|
# Use the poll based reactor if it is available
|
|
|
|
try:
|
|
|
|
select.poll
|
|
|
|
Reactor = PollReactor
|
|
|
|
except:
|
|
|
|
Reactor = SelectReactor
|