2014-04-21 23:51:04 +00:00
|
|
|
import errno
|
2014-10-23 21:32:32 +00:00
|
|
|
import select
|
|
|
|
import usocket as _socket
|
2014-10-23 21:24:13 +00:00
|
|
|
from uasyncio.core import *
|
2014-04-18 18:57:16 +00:00
|
|
|
|
|
|
|
|
2014-04-19 21:01:57 +00:00
|
|
|
class EpollEventLoop(EventLoop):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
EventLoop.__init__(self)
|
|
|
|
self.poller = select.epoll(1)
|
|
|
|
|
|
|
|
def add_reader(self, fd, cb, *args):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("add_reader%s", (fd, cb, args))
|
uasyncio: Use EPOLLONESHOT flag for add_reader/writer().
When we issue IORead/IOWrite syscall, we want get back notification just for
that call. But if we add fd to epoll, it will trigger continuously when the
condition is met. For example, a socket with empty write buffer will always
signal EPOLLOUT, regardless if we want to write to it now or not. This will
lead to situation when our coro will be woken up on such socket on *any*
syscall, and this syscall will get completely different socket as result (
or if syscall doesn't return socket - completely different result value).
So, to get semantics right, we need to make sure that for each IORead/IOWrite,
we get notified only once, and further events on socket are ignored until
we ask for them again. This is exactly what EPOLLONESHOT flag does.
The other alternative is to remove fd from epoll after each IORead/IOWrite,
but apparently EPOLLONESHOT is more performant way.
Yet another alternarnative would be to use edge-triggered mode of epoll,
but it has own peculiarities, like, after each event, client must make sure
that it is handled completely and reset, otherwise it may not trigger again,
even if there's unprocessed data. For example, if EPOLLIN|EPOLLET is used,
client must make sure that it reads all data available, until read() returns
EAGAIN. If it reads say just 10 bytes, then next time event simply won't
trigger (because it's edge event, which triggers on change like "no data" -
"data"; if we didn't read all data, the situation is "data" - "data", there's
no change in condition, and event is not triggered). Surely, that's not what
we want (at least not without restructuring how StreamReader works).
So, EPOLLONESHOT is the most obvious, and easiest to reason way to get needed
semantics.
2014-11-02 22:37:16 +00:00
|
|
|
self.poller.register(fd, select.EPOLLIN | select.EPOLLONESHOT, (cb, args))
|
2014-04-19 21:01:57 +00:00
|
|
|
|
2014-04-23 22:18:51 +00:00
|
|
|
def remove_reader(self, fd):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("remove_reader(%s)", fd)
|
2014-04-23 22:18:51 +00:00
|
|
|
self.poller.unregister(fd)
|
|
|
|
|
2014-04-19 21:01:57 +00:00
|
|
|
def add_writer(self, fd, cb, *args):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("add_writer%s", (fd, cb, args))
|
uasyncio: Use EPOLLONESHOT flag for add_reader/writer().
When we issue IORead/IOWrite syscall, we want get back notification just for
that call. But if we add fd to epoll, it will trigger continuously when the
condition is met. For example, a socket with empty write buffer will always
signal EPOLLOUT, regardless if we want to write to it now or not. This will
lead to situation when our coro will be woken up on such socket on *any*
syscall, and this syscall will get completely different socket as result (
or if syscall doesn't return socket - completely different result value).
So, to get semantics right, we need to make sure that for each IORead/IOWrite,
we get notified only once, and further events on socket are ignored until
we ask for them again. This is exactly what EPOLLONESHOT flag does.
The other alternative is to remove fd from epoll after each IORead/IOWrite,
but apparently EPOLLONESHOT is more performant way.
Yet another alternarnative would be to use edge-triggered mode of epoll,
but it has own peculiarities, like, after each event, client must make sure
that it is handled completely and reset, otherwise it may not trigger again,
even if there's unprocessed data. For example, if EPOLLIN|EPOLLET is used,
client must make sure that it reads all data available, until read() returns
EAGAIN. If it reads say just 10 bytes, then next time event simply won't
trigger (because it's edge event, which triggers on change like "no data" -
"data"; if we didn't read all data, the situation is "data" - "data", there's
no change in condition, and event is not triggered). Surely, that's not what
we want (at least not without restructuring how StreamReader works).
So, EPOLLONESHOT is the most obvious, and easiest to reason way to get needed
semantics.
2014-11-02 22:37:16 +00:00
|
|
|
self.poller.register(fd, select.EPOLLOUT | select.EPOLLONESHOT, (cb, args))
|
2014-04-19 21:01:57 +00:00
|
|
|
|
2014-04-23 22:18:51 +00:00
|
|
|
def remove_writer(self, fd):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("remove_writer(%s)", fd)
|
2014-10-26 22:30:28 +00:00
|
|
|
try:
|
|
|
|
self.poller.unregister(fd)
|
|
|
|
except OSError as e:
|
|
|
|
# StreamWriter.awrite() first tries to write to an fd,
|
|
|
|
# and if that succeeds, yield IOWrite may never be called
|
|
|
|
# for that fd, and it will never be added to poller. So,
|
|
|
|
# ignore such error.
|
|
|
|
if e.args[0] != errno.ENOENT:
|
|
|
|
raise
|
2014-04-23 22:18:51 +00:00
|
|
|
|
2014-04-19 21:01:57 +00:00
|
|
|
def wait(self, delay):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("epoll.wait(%d)", delay)
|
2014-04-20 03:01:04 +00:00
|
|
|
if delay == -1:
|
|
|
|
res = self.poller.poll(-1)
|
|
|
|
else:
|
|
|
|
res = self.poller.poll(int(delay * 1000))
|
2014-10-25 21:20:24 +00:00
|
|
|
#log.debug("epoll result: %s", res)
|
2014-04-19 21:01:57 +00:00
|
|
|
for cb, ev in res:
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("Calling IO callback: %s%s", cb[0], cb[1])
|
2014-04-19 21:01:57 +00:00
|
|
|
cb[0](*cb[1])
|
|
|
|
|
2014-04-18 18:57:16 +00:00
|
|
|
|
2014-04-20 03:08:49 +00:00
|
|
|
class StreamReader:
|
|
|
|
|
|
|
|
def __init__(self, s):
|
|
|
|
self.s = s
|
|
|
|
|
2014-05-06 22:57:38 +00:00
|
|
|
def read(self, n=-1):
|
2014-05-03 20:13:35 +00:00
|
|
|
s = yield IORead(self.s)
|
2014-05-06 22:57:38 +00:00
|
|
|
while True:
|
|
|
|
res = self.s.read(n)
|
|
|
|
if res is not None:
|
|
|
|
break
|
|
|
|
log.warn("Empty read")
|
2014-05-03 20:13:35 +00:00
|
|
|
if not res:
|
2014-06-01 22:23:16 +00:00
|
|
|
yield IOReadDone(self.s)
|
2014-05-03 20:13:35 +00:00
|
|
|
return res
|
|
|
|
|
2014-04-20 03:08:49 +00:00
|
|
|
def readline(self):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamReader.readline()")
|
2014-04-20 03:08:49 +00:00
|
|
|
s = yield IORead(self.s)
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamReader.readline(): after IORead: %s", s)
|
2014-05-06 22:57:38 +00:00
|
|
|
while True:
|
|
|
|
res = self.s.readline()
|
|
|
|
if res is not None:
|
|
|
|
break
|
|
|
|
log.warn("Empty read")
|
2014-04-23 23:13:21 +00:00
|
|
|
if not res:
|
2014-06-01 22:23:16 +00:00
|
|
|
yield IOReadDone(self.s)
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamReader.readline(): res: %s", res)
|
2014-04-20 03:08:49 +00:00
|
|
|
return res
|
|
|
|
|
2015-01-04 19:20:37 +00:00
|
|
|
def aclose(self):
|
2014-10-28 22:54:49 +00:00
|
|
|
yield IOReadDone(self.s)
|
|
|
|
self.s.close()
|
|
|
|
|
2014-10-18 01:57:51 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return "<StreamReader %r>" % self.s
|
|
|
|
|
2014-04-20 03:08:49 +00:00
|
|
|
|
|
|
|
class StreamWriter:
|
|
|
|
|
2015-06-03 13:25:27 +00:00
|
|
|
def __init__(self, s, extra):
|
2014-04-20 03:08:49 +00:00
|
|
|
self.s = s
|
2015-06-03 13:25:27 +00:00
|
|
|
self.extra = extra
|
2014-04-20 03:08:49 +00:00
|
|
|
|
2014-05-31 21:41:45 +00:00
|
|
|
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.
|
2014-05-06 23:12:29 +00:00
|
|
|
sz = len(buf)
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamWriter.awrite(): spooling %d bytes", sz)
|
2014-05-06 23:12:29 +00:00
|
|
|
while True:
|
|
|
|
res = self.s.write(buf)
|
2014-05-31 21:41:45 +00:00
|
|
|
# If we spooled everything, return immediately
|
2014-05-06 23:12:29 +00:00
|
|
|
if res == sz:
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamWriter.awrite(): completed spooling %d bytes", res)
|
2014-05-06 23:12:29 +00:00
|
|
|
return
|
|
|
|
if res is None:
|
|
|
|
res = 0
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamWriter.awrite(): spooled partial %d bytes", res)
|
2014-07-24 15:47:16 +00:00
|
|
|
assert res < sz
|
2014-05-06 23:12:29 +00:00
|
|
|
buf = buf[res:]
|
|
|
|
sz -= res
|
2014-11-02 22:37:15 +00:00
|
|
|
s2 = yield IOWrite(self.s)
|
|
|
|
#assert s2.fileno() == self.s.fileno()
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("StreamWriter.awrite(): can write more")
|
2014-04-20 03:08:49 +00:00
|
|
|
|
2015-01-01 23:23:16 +00:00
|
|
|
def aclose(self):
|
2014-06-01 22:23:16 +00:00
|
|
|
yield IOWriteDone(self.s)
|
2014-05-03 20:13:35 +00:00
|
|
|
self.s.close()
|
|
|
|
|
2015-06-03 13:25:27 +00:00
|
|
|
def get_extra_info(self, name, default=None):
|
|
|
|
return self.extra.get(name, default)
|
|
|
|
|
2014-10-18 01:57:51 +00:00
|
|
|
def __repr__(self):
|
|
|
|
return "<StreamWriter %r>" % self.s
|
|
|
|
|
2014-04-20 03:08:49 +00:00
|
|
|
|
|
|
|
def open_connection(host, port):
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("open_connection(%s, %s)", host, port)
|
2014-04-20 03:08:49 +00:00
|
|
|
s = _socket.socket()
|
|
|
|
s.setblocking(False)
|
|
|
|
ai = _socket.getaddrinfo(host, port)
|
|
|
|
addr = ai[0][4]
|
|
|
|
try:
|
|
|
|
s.connect(addr)
|
|
|
|
except OSError as e:
|
2014-04-21 23:51:04 +00:00
|
|
|
if e.args[0] != errno.EINPROGRESS:
|
|
|
|
raise
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("open_connection: After connect")
|
2014-11-02 22:37:15 +00:00
|
|
|
s2 = yield IOWrite(s)
|
|
|
|
if __debug__:
|
|
|
|
assert s2.fileno() == s.fileno()
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("open_connection: After iowait: %s", s)
|
2015-06-03 13:25:27 +00:00
|
|
|
return StreamReader(s), StreamWriter(s, {})
|
2014-05-03 20:15:38 +00:00
|
|
|
|
|
|
|
|
2014-11-06 15:14:21 +00:00
|
|
|
def start_server(client_coro, host, port, backlog=10):
|
2014-05-03 20:15:38 +00:00
|
|
|
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)
|
2014-11-06 15:14:21 +00:00
|
|
|
s.listen(backlog)
|
2014-05-03 20:15:38 +00:00
|
|
|
while True:
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("start_server: Before accept")
|
2014-05-03 20:15:38 +00:00
|
|
|
yield IORead(s)
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("start_server: After iowait")
|
2014-05-03 20:15:38 +00:00
|
|
|
s2, client_addr = s.accept()
|
|
|
|
s2.setblocking(False)
|
2014-10-25 21:20:24 +00:00
|
|
|
if __debug__:
|
|
|
|
log.debug("start_server: After accept: %s", s2)
|
2015-06-03 13:25:27 +00:00
|
|
|
extra = {"peername": client_addr}
|
|
|
|
yield client_coro(StreamReader(s2), StreamWriter(s2, extra))
|
2014-11-04 00:48:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
import uasyncio.core
|
|
|
|
uasyncio.core._event_loop_class = EpollEventLoop
|