py-netty 1.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_netty/__init__.py +5 -0
- py_netty/bootstrap.py +115 -0
- py_netty/bytebuf.py +21 -0
- py_netty/channel.py +532 -0
- py_netty/eventfd.py +148 -0
- py_netty/eventloop.py +406 -0
- py_netty/handler.py +111 -0
- py_netty/utils.py +67 -0
- py_netty-1.0.6.dist-info/LICENSE +21 -0
- py_netty-1.0.6.dist-info/METADATA +255 -0
- py_netty-1.0.6.dist-info/RECORD +13 -0
- py_netty-1.0.6.dist-info/WHEEL +5 -0
- py_netty-1.0.6.dist-info/top_level.txt +1 -0
py_netty/__init__.py
ADDED
py_netty/bootstrap.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
import typing
|
|
3
|
+
import socket
|
|
4
|
+
import logging
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from .eventloop import EventLoopGroup
|
|
7
|
+
from .handler import EchoChannelHandler, ChannelHandlerAdapter
|
|
8
|
+
from .channel import ChannelFuture, ChannelContext, NioSocketChannel, NioServerSocketChannel
|
|
9
|
+
from attrs import define, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _handler_initializer():
|
|
16
|
+
return EchoChannelHandler()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache(maxsize=8)
|
|
20
|
+
def _client_ssl_context(verify=True):
|
|
21
|
+
if verify:
|
|
22
|
+
return ssl.create_default_context()
|
|
23
|
+
else: # no verify
|
|
24
|
+
ssl_context = ssl._create_unverified_context()
|
|
25
|
+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
26
|
+
ssl_context.set_ciphers("ALL")
|
|
27
|
+
return ssl_context
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@lru_cache(maxsize=8)
|
|
31
|
+
def _server_ssl_context(certfile, keyfile):
|
|
32
|
+
s_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
33
|
+
s_context.load_cert_chain(certfile, keyfile)
|
|
34
|
+
return s_context
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@define(slots=True)
|
|
38
|
+
class Bootstrap:
|
|
39
|
+
eventloop_group: EventLoopGroup = field(factory=EventLoopGroup)
|
|
40
|
+
handler_initializer: typing.Callable = field(default=_handler_initializer)
|
|
41
|
+
tls: bool = False
|
|
42
|
+
verify: bool = True
|
|
43
|
+
ssl_context_cb: typing.Callable = None
|
|
44
|
+
|
|
45
|
+
def _create_ssl_context(self):
|
|
46
|
+
ctx = _client_ssl_context(self.verify)
|
|
47
|
+
if self.ssl_context_cb:
|
|
48
|
+
try:
|
|
49
|
+
self.ssl_context_cb(ctx)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error("Error in ssl_context_cb(client): %s", e)
|
|
52
|
+
return ctx
|
|
53
|
+
|
|
54
|
+
def _wrap_ssl_socket(self, sock, server_hostname_or_address):
|
|
55
|
+
return self._create_ssl_context().wrap_socket(sock, server_hostname=server_hostname_or_address)
|
|
56
|
+
|
|
57
|
+
def connect(self, address, port, ensure_connected: bool = False) -> ChannelFuture:
|
|
58
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
59
|
+
# if ensure_connected or self.tls:
|
|
60
|
+
if ensure_connected:
|
|
61
|
+
sock.connect((address, port))
|
|
62
|
+
if self.tls:
|
|
63
|
+
sock = self._wrap_ssl_socket(sock, address)
|
|
64
|
+
sock.setblocking(False)
|
|
65
|
+
else:
|
|
66
|
+
sock.setblocking(False)
|
|
67
|
+
if self.tls:
|
|
68
|
+
sock = self._wrap_ssl_socket(sock, address)
|
|
69
|
+
sock.connect_ex((address, port)) # non blocking
|
|
70
|
+
return NioSocketChannel(
|
|
71
|
+
self.eventloop_group.get_eventloop(),
|
|
72
|
+
sock,
|
|
73
|
+
handler_initializer=self.handler_initializer
|
|
74
|
+
).register()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@define(slots=True)
|
|
78
|
+
class ServerBootstrap:
|
|
79
|
+
parant_group: EventLoopGroup = field(factory=EventLoopGroup)
|
|
80
|
+
child_group: EventLoopGroup = field(factory=EventLoopGroup)
|
|
81
|
+
child_handler_initializer: typing.Callable = field(default=_handler_initializer)
|
|
82
|
+
certfile: str = None
|
|
83
|
+
keyfile: str = None
|
|
84
|
+
ssl_context_cb: typing.Callable = None
|
|
85
|
+
|
|
86
|
+
def bind(self, address='localhost', port=-1) -> ChannelFuture:
|
|
87
|
+
assert port > 0
|
|
88
|
+
assert ((self.certfile is not None) ^ (self.keyfile is not None)) is False, "Both certfile and keyfile must be specified"
|
|
89
|
+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
90
|
+
if self.certfile and self.keyfile:
|
|
91
|
+
ssl_ctx = _server_ssl_context(self.certfile, self.keyfile)
|
|
92
|
+
if self.ssl_context_cb:
|
|
93
|
+
try:
|
|
94
|
+
self.ssl_context_cb(ssl_ctx)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error("Error in ssl_context_cb(server): %s", e)
|
|
97
|
+
server_socket = ssl_ctx.wrap_socket(server_socket, server_side=True)
|
|
98
|
+
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
99
|
+
server_socket.bind((address, port))
|
|
100
|
+
server_socket.listen(128)
|
|
101
|
+
server_socket.setblocking(0)
|
|
102
|
+
eventloop = self.parant_group.get_eventloop()
|
|
103
|
+
|
|
104
|
+
class _ChannelInitializer(ChannelHandlerAdapter):
|
|
105
|
+
def channel_read(this, ctx: ChannelContext, client_socket: socket.socket):
|
|
106
|
+
logger.debug("Initializing client socket: %s", client_socket)
|
|
107
|
+
client_socket.setblocking(0)
|
|
108
|
+
NioSocketChannel(
|
|
109
|
+
self.child_group.get_eventloop(),
|
|
110
|
+
client_socket,
|
|
111
|
+
handler_initializer=self.child_handler_initializer
|
|
112
|
+
).register()
|
|
113
|
+
|
|
114
|
+
return NioServerSocketChannel(eventloop, server_socket, handler_initializer=_ChannelInitializer).register()
|
|
115
|
+
# return eventloop.register(server_socket, is_server=True, handler_initializer=_ChannelInitializer)
|
py_netty/bytebuf.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from attrs import define, field
|
|
2
|
+
from concurrent.futures import Future
|
|
3
|
+
|
|
4
|
+
EMPTY_BUFFER = b''
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@define(slots=True)
|
|
8
|
+
class Chunk:
|
|
9
|
+
|
|
10
|
+
buffer: bytes = field()
|
|
11
|
+
future: Future = field(default=None)
|
|
12
|
+
close: bool = field(default=False)
|
|
13
|
+
|
|
14
|
+
def __attrs_post_init__(self):
|
|
15
|
+
self.future = self.future or Future()
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return f"Chunk(bytes={len(self.buffer)}, future={self.future}, close={self.close})"
|
|
19
|
+
|
|
20
|
+
def __repr__(self):
|
|
21
|
+
return self.__str__()
|
py_netty/channel.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import ssl
|
|
3
|
+
import time
|
|
4
|
+
import socket
|
|
5
|
+
import logging
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from concurrent.futures import Future
|
|
8
|
+
from typing import Callable, List, Union, Tuple, Optional
|
|
9
|
+
from .bytebuf import Chunk, EMPTY_BUFFER
|
|
10
|
+
from .handler import LoggingChannelHandler
|
|
11
|
+
from .utils import sockinfo, log, LoggerAdapter, flag_to_str
|
|
12
|
+
import selectors
|
|
13
|
+
from attrs import define, field
|
|
14
|
+
import errno
|
|
15
|
+
|
|
16
|
+
logger = LoggerAdapter(logging.getLogger(__name__))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_DEFAULT_LOW_WATER_MARK = int(os.getenv('PY_NETTY_LOW_WATER_MARK', 32 * 1024))
|
|
20
|
+
_DEFAULT_HIGH_WATER_MARK = int(os.getenv('PY_NETTY_HIGH_WATER_MARK', 64 * 1024))
|
|
21
|
+
_ROUNDS = int(os.getenv('PY_NETTY_TUNING_ROUNDS', 16))
|
|
22
|
+
_INITIAL_BUFFER_SIZE = int(os.getenv('PY_NETTY_TUNING_INIT_BUFFER_SIZE', 1024))
|
|
23
|
+
_MIN_BUFFER_SIZE = int(os.getenv('PY_NETTY_TUNING_MIN_BUFFER_SIZE', _INITIAL_BUFFER_SIZE >> 1))
|
|
24
|
+
_MAX_BUFFER_SIZE = int(os.getenv('PY_NETTY_TUNING_MAX_BUFFER_SIZE', _INITIAL_BUFFER_SIZE << 4))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@define(slots=True, kw_only=True)
|
|
28
|
+
class ChannelInfo:
|
|
29
|
+
|
|
30
|
+
sock: socket.socket = field()
|
|
31
|
+
id: str = field()
|
|
32
|
+
sockname: Tuple[str, int] = field()
|
|
33
|
+
peername: Tuple[str, int] = field()
|
|
34
|
+
fileno: int = field(default=-1)
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def of(cls, sock: socket.socket):
|
|
38
|
+
return cls(
|
|
39
|
+
sock=sock,
|
|
40
|
+
id=hex(id(sock)),
|
|
41
|
+
sockname=sock.getsockname()[:2],
|
|
42
|
+
peername=sock.getpeername()[:2],
|
|
43
|
+
fileno=sock.fileno()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def adaptive_bufsize(previous_bufsize, data_size):
|
|
48
|
+
if data_size < (previous_bufsize >> 1) and previous_bufsize > _MIN_BUFFER_SIZE:
|
|
49
|
+
return max(previous_bufsize >> 1, _MIN_BUFFER_SIZE)
|
|
50
|
+
elif data_size == previous_bufsize and previous_bufsize < _MAX_BUFFER_SIZE:
|
|
51
|
+
return min(previous_bufsize << 1, _MAX_BUFFER_SIZE)
|
|
52
|
+
else:
|
|
53
|
+
return previous_bufsize
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@define(slots=True)
|
|
57
|
+
class AbstractChannel:
|
|
58
|
+
|
|
59
|
+
_eventloop: 'EventLoop' = field()
|
|
60
|
+
_socket: socket.socket = field()
|
|
61
|
+
_handler_initializer: Callable = field(factory=LoggingChannelHandler)
|
|
62
|
+
|
|
63
|
+
def __attrs_post_init__(self):
|
|
64
|
+
self._fileno = self._socket.fileno()
|
|
65
|
+
assert self._fileno > 0
|
|
66
|
+
self._close_future = ChannelFuture(self)
|
|
67
|
+
self._active = False
|
|
68
|
+
self._handler = None # lazy initialization
|
|
69
|
+
self._flag = 0 # interested events
|
|
70
|
+
self._server_channel = False
|
|
71
|
+
self._ever_active = False
|
|
72
|
+
self._sockinfo = None
|
|
73
|
+
self._channelinfo = None
|
|
74
|
+
self._channel_future = ChannelFuture(self)
|
|
75
|
+
|
|
76
|
+
def channel_future(self) -> 'ChannelFuture':
|
|
77
|
+
return self._channel_future
|
|
78
|
+
|
|
79
|
+
def eventloop(self) -> 'EventLoop':
|
|
80
|
+
return self._eventloop
|
|
81
|
+
|
|
82
|
+
def id(self):
|
|
83
|
+
return str(hex(id(self.socket())))
|
|
84
|
+
|
|
85
|
+
def context(self) -> 'ChannelContext':
|
|
86
|
+
return ChannelContext(self)
|
|
87
|
+
|
|
88
|
+
def set_flag(self, flag):
|
|
89
|
+
self._flag = flag
|
|
90
|
+
|
|
91
|
+
def socket(self) -> socket.socket:
|
|
92
|
+
return self._socket
|
|
93
|
+
|
|
94
|
+
def channelinfo(self) -> Optional[ChannelInfo]:
|
|
95
|
+
"""Include ORIGINAL socket info, even if the sock is closed."""
|
|
96
|
+
if self._channelinfo is None:
|
|
97
|
+
_ = str(self)
|
|
98
|
+
return self._channelinfo
|
|
99
|
+
|
|
100
|
+
def register(self) -> 'ChannelFuture':
|
|
101
|
+
return self.eventloop().register(self)
|
|
102
|
+
|
|
103
|
+
def unregister(self) -> 'ChannelFuture':
|
|
104
|
+
return self.eventloop().unregister(self)
|
|
105
|
+
|
|
106
|
+
def flag(self):
|
|
107
|
+
return self._flag
|
|
108
|
+
|
|
109
|
+
def add_flag(self, flag):
|
|
110
|
+
if not self.in_eventloop():
|
|
111
|
+
self._eventloop.submit_task(lambda: self.add_flag(flag))
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
if self._flag & flag:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
self._flag |= flag
|
|
118
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
119
|
+
logger.debug("add flag %s(%s) to channel %s/%s, current flag: %s(%s)",
|
|
120
|
+
flag, flag_to_str(flag), self.id(), self._fileno, self._flag, flag_to_str(self._flag))
|
|
121
|
+
try:
|
|
122
|
+
self.eventloop().modify_flag(self)
|
|
123
|
+
except Exception:
|
|
124
|
+
logger.exception("add flag %s(%s) to channel %s/%s failed (maybe fileno is closed)", flag, flag_to_str(flag), self.id(), self._fileno)
|
|
125
|
+
|
|
126
|
+
def remove_flag(self, flag):
|
|
127
|
+
if not self.in_eventloop():
|
|
128
|
+
self._eventloop.submit_task(lambda: self.remove_flag(flag))
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if not self._flag & flag:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
self._flag &= ~flag
|
|
135
|
+
|
|
136
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
137
|
+
logger.debug("remove flag %s(%s) from channel %s/%s, current flag: %s(%s)",
|
|
138
|
+
flag, flag_to_str(flag), self.id(), self._fileno, self._flag, flag_to_str(self._flag))
|
|
139
|
+
try:
|
|
140
|
+
self.eventloop().modify_flag(self)
|
|
141
|
+
except Exception: # maybe fileno is closed
|
|
142
|
+
logger.exception("remove flag %s(%s) from channel %s failed", flag, flag_to_str(flag), self.id())
|
|
143
|
+
|
|
144
|
+
def handler(self):
|
|
145
|
+
if self._handler is None:
|
|
146
|
+
self._handler = self._handler_initializer()
|
|
147
|
+
return self._handler
|
|
148
|
+
|
|
149
|
+
def handler_context(self) -> 'ChannelHandlerContext':
|
|
150
|
+
return ChannelHandlerContext(self)
|
|
151
|
+
|
|
152
|
+
def is_server(self):
|
|
153
|
+
"""Returns True if this channel is related to a server listening socket, False otherwise."""
|
|
154
|
+
return self._server_channel
|
|
155
|
+
|
|
156
|
+
def fileno(self):
|
|
157
|
+
return self._socket.fileno()
|
|
158
|
+
|
|
159
|
+
def fileno0(self) -> int:
|
|
160
|
+
"""origin fileno"""
|
|
161
|
+
return self._fileno
|
|
162
|
+
|
|
163
|
+
def close_future(self) -> 'ChannelFuture':
|
|
164
|
+
return self._close_future
|
|
165
|
+
|
|
166
|
+
def is_active(self):
|
|
167
|
+
if self.close_future().done():
|
|
168
|
+
return False
|
|
169
|
+
return self._active
|
|
170
|
+
|
|
171
|
+
def _refresh_sock_info(self) -> str:
|
|
172
|
+
self._sockinfo = None
|
|
173
|
+
self._channelinfo = None
|
|
174
|
+
return str(self)
|
|
175
|
+
|
|
176
|
+
@log(logger)
|
|
177
|
+
def set_active(self, active, reason=''):
|
|
178
|
+
origin = self._active
|
|
179
|
+
if origin != active:
|
|
180
|
+
logger.debug("set channel %s active status: %s, reason: %s", self.id(), active, reason)
|
|
181
|
+
self._active = active
|
|
182
|
+
if origin is True and active is False:
|
|
183
|
+
self.handler_context().fire_channel_inactive()
|
|
184
|
+
if origin is False and active is True:
|
|
185
|
+
self._ever_active = True
|
|
186
|
+
self._refresh_sock_info()
|
|
187
|
+
if isinstance(self.socket(), ssl.SSLSocket):
|
|
188
|
+
try:
|
|
189
|
+
s = time.perf_counter()
|
|
190
|
+
self.socket().do_handshake(True)
|
|
191
|
+
cost = time.perf_counter() - s # seconds
|
|
192
|
+
if cost > 1:
|
|
193
|
+
logger.warning("ssl handshake cost: ~%ss", round(cost, 2))
|
|
194
|
+
except socket.timeout:
|
|
195
|
+
logger.exception("ssl handshake timeout")
|
|
196
|
+
self.close(True)
|
|
197
|
+
except socket.error as socket_err:
|
|
198
|
+
logger.debug("ssl handshake error: %s", str(socket_err))
|
|
199
|
+
# if errno.ENOTCONN is not socket_err.errno:
|
|
200
|
+
# logger.debug("ssl handshake error: %s", str(socket_err))
|
|
201
|
+
else:
|
|
202
|
+
self.handler_context().fire_channel_handshake_complete()
|
|
203
|
+
# finally:
|
|
204
|
+
# with suppress(Exception):
|
|
205
|
+
# self.socket().settimeout(0)
|
|
206
|
+
|
|
207
|
+
self.handler_context().fire_channel_active()
|
|
208
|
+
|
|
209
|
+
def close(self, force=False):
|
|
210
|
+
if force:
|
|
211
|
+
self.close_forcibly()
|
|
212
|
+
else: # gracefully
|
|
213
|
+
self.close_gracefully()
|
|
214
|
+
|
|
215
|
+
def close_forcibly(self) -> 'ChannelFuture':
|
|
216
|
+
if not self.in_eventloop():
|
|
217
|
+
self._eventloop.submit_task(self.close_forcibly)
|
|
218
|
+
return self.close_future()
|
|
219
|
+
logger.debug(f"Closing channel FORCIBLY: {self}")
|
|
220
|
+
self.eventloop()._close_channel_internally(self, 'close channel forcibly')
|
|
221
|
+
return self.close_future()
|
|
222
|
+
|
|
223
|
+
def close_gracefully(self) -> 'ChannelFuture':
|
|
224
|
+
if not self.in_eventloop():
|
|
225
|
+
self._eventloop.submit_task(self.close_gracefully)
|
|
226
|
+
return self.close_future()
|
|
227
|
+
|
|
228
|
+
logger.debug(f"Closing channel (active:{self.is_active()}) GRACEFULLY: {self}")
|
|
229
|
+
if not self.is_active():
|
|
230
|
+
return self.close_future()
|
|
231
|
+
|
|
232
|
+
if self.is_server():
|
|
233
|
+
self.eventloop()._close_channel_internally(self, 'close server channel gracefully')
|
|
234
|
+
else: # client channel
|
|
235
|
+
self.add_pending(Chunk(EMPTY_BUFFER, self.close_future().future, True))
|
|
236
|
+
return self.close_future()
|
|
237
|
+
|
|
238
|
+
def in_eventloop(self):
|
|
239
|
+
return self._eventloop.in_eventloop()
|
|
240
|
+
|
|
241
|
+
def __str__(self):
|
|
242
|
+
if not self._sockinfo:
|
|
243
|
+
self._sockinfo = sockinfo(self._socket)
|
|
244
|
+
if not self._channelinfo:
|
|
245
|
+
try:
|
|
246
|
+
self._channelinfo = ChannelInfo.of(self._socket)
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
if self._channelinfo:
|
|
250
|
+
ci = self._channelinfo
|
|
251
|
+
l_addr = ':'.join(map(str, ci.sockname))
|
|
252
|
+
r_addr = ':'.join(map(str, ci.peername))
|
|
253
|
+
sign = '-'
|
|
254
|
+
if not self._ever_active:
|
|
255
|
+
sign = '?'
|
|
256
|
+
elif not self.is_active():
|
|
257
|
+
sign = '!'
|
|
258
|
+
return f"[id:{ci.id}, fd:{ci.fileno}, L:/{l_addr} {sign} R:/{r_addr}]"
|
|
259
|
+
if not self._ever_active:
|
|
260
|
+
return self._sockinfo.replace('-', '?')
|
|
261
|
+
if not self.is_active():
|
|
262
|
+
return self._sockinfo.replace('-', '!')
|
|
263
|
+
return self._sockinfo
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class NioSocketChannel(AbstractChannel):
|
|
267
|
+
|
|
268
|
+
def __init__(self, eventloop: 'EventLoop', sock: socket.socket, handler_initializer: Callable, connect_timeout_millis: int = 3000):
|
|
269
|
+
super().__init__(eventloop, sock, handler_initializer)
|
|
270
|
+
self._pendings = [] # [Chunk, ...]
|
|
271
|
+
self._pending_bytes = 0
|
|
272
|
+
self._writable = True
|
|
273
|
+
self._auto_read = True
|
|
274
|
+
try:
|
|
275
|
+
self.socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
276
|
+
except Exception:
|
|
277
|
+
logger.exception("setsockopt TCP_NODELAY failed")
|
|
278
|
+
self._connect_timeout_millis = connect_timeout_millis
|
|
279
|
+
|
|
280
|
+
def is_writable(self) -> bool:
|
|
281
|
+
return self._writable
|
|
282
|
+
|
|
283
|
+
def _check_writability(self):
|
|
284
|
+
# assert self.in_eventloop()
|
|
285
|
+
writable = self._writable
|
|
286
|
+
if self._pending_bytes >= _DEFAULT_HIGH_WATER_MARK:
|
|
287
|
+
writable = False
|
|
288
|
+
elif self._pending_bytes <= _DEFAULT_LOW_WATER_MARK:
|
|
289
|
+
writable = True
|
|
290
|
+
if self._writable != writable:
|
|
291
|
+
self._writable = writable
|
|
292
|
+
self.handler_context().fire_channel_writability_changed()
|
|
293
|
+
|
|
294
|
+
def connect_timeout_millis(self) -> int:
|
|
295
|
+
return self._connect_timeout_millis
|
|
296
|
+
|
|
297
|
+
def pendings(self) -> List['Chunk']:
|
|
298
|
+
return self._pendings
|
|
299
|
+
|
|
300
|
+
def set_pendings(self, pendings: List['Chunk']):
|
|
301
|
+
self._pendings = pendings
|
|
302
|
+
|
|
303
|
+
def add_pending(self, chunk: 'Chunk'):
|
|
304
|
+
if chunk is None:
|
|
305
|
+
return
|
|
306
|
+
if chunk.close is False and not chunk.buffer:
|
|
307
|
+
return
|
|
308
|
+
self._pendings.append(chunk)
|
|
309
|
+
self._pending_bytes += len(chunk.buffer)
|
|
310
|
+
self.add_flag(selectors.EVENT_WRITE)
|
|
311
|
+
|
|
312
|
+
def is_auto_read(self) -> bool:
|
|
313
|
+
return self._auto_read
|
|
314
|
+
|
|
315
|
+
# a way of flow control
|
|
316
|
+
def set_auto_read(self, auto_read: bool):
|
|
317
|
+
if self._auto_read == auto_read:
|
|
318
|
+
return
|
|
319
|
+
self._auto_read = auto_read
|
|
320
|
+
if auto_read:
|
|
321
|
+
self.add_flag(selectors.EVENT_READ)
|
|
322
|
+
else:
|
|
323
|
+
self.remove_flag(selectors.EVENT_READ)
|
|
324
|
+
|
|
325
|
+
def has_pendings(self) -> bool:
|
|
326
|
+
return len(self._pendings) > 0
|
|
327
|
+
|
|
328
|
+
def write(self, buffer, channel_future: 'ChannelFuture' = None) -> 'ChannelFuture':
|
|
329
|
+
cf = channel_future or ChannelFuture(self)
|
|
330
|
+
if not self.in_eventloop():
|
|
331
|
+
self._eventloop.submit_task(lambda: self.write(buffer, cf))
|
|
332
|
+
return cf
|
|
333
|
+
self.add_pending(Chunk(buffer, cf.future))
|
|
334
|
+
return cf
|
|
335
|
+
|
|
336
|
+
def try_send(self, bytebuf: bytes, spin=1) -> bytes:
|
|
337
|
+
if not bytebuf:
|
|
338
|
+
return b''
|
|
339
|
+
total_sent = 0
|
|
340
|
+
while total_sent < len(bytebuf):
|
|
341
|
+
try:
|
|
342
|
+
total_sent += self.socket().send(bytebuf[total_sent:])
|
|
343
|
+
except socket.error as socket_err:
|
|
344
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
345
|
+
logger.debug("try_send socket.error: %s, spin: %s", str(socket_err), spin)
|
|
346
|
+
if spin > 0:
|
|
347
|
+
spin -= 1
|
|
348
|
+
continue
|
|
349
|
+
break
|
|
350
|
+
return bytebuf[total_sent:]
|
|
351
|
+
|
|
352
|
+
def recvall(self) -> (bytes, bool):
|
|
353
|
+
# if isinstance(self.socket(), ssl.SSLSocket):
|
|
354
|
+
# return self.recvall_ssl()
|
|
355
|
+
buffer = b''
|
|
356
|
+
bufsize = _INITIAL_BUFFER_SIZE
|
|
357
|
+
rounds = 0
|
|
358
|
+
total_costs = 0
|
|
359
|
+
while True:
|
|
360
|
+
rounds += 1
|
|
361
|
+
try:
|
|
362
|
+
s = time.perf_counter()
|
|
363
|
+
received = self.socket().recv(bufsize)
|
|
364
|
+
cost = time.perf_counter() - s # seconds
|
|
365
|
+
total_costs += cost
|
|
366
|
+
if not received: # EOF
|
|
367
|
+
return buffer, True
|
|
368
|
+
recv_len = len(received)
|
|
369
|
+
bufsize = adaptive_bufsize(bufsize, recv_len)
|
|
370
|
+
buffer += received
|
|
371
|
+
if rounds == _ROUNDS or total_costs > 0.1 or cost > 0.01:
|
|
372
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
373
|
+
cost = round(cost, 3)
|
|
374
|
+
total_costs = round(total_costs, 3)
|
|
375
|
+
sock_info = sockinfo(self.socket())
|
|
376
|
+
logger.debug(f"yield from recvall, rounds:{rounds}, current cost:{cost * 1000}ms, total cost:{total_costs * 1000}ms, bufsize:{bufsize}, buffer: {len(buffer)}, socket:{sock_info}")
|
|
377
|
+
return buffer, False
|
|
378
|
+
# except ssl.SSLWantReadError: # for ssl socket
|
|
379
|
+
# logger.debug("recvall ssl.SSLWantReadError, readable: %s", self.is_readable())
|
|
380
|
+
# if self.is_readable():
|
|
381
|
+
# continue
|
|
382
|
+
# return buffer, False
|
|
383
|
+
except socket.error as socket_err:
|
|
384
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
385
|
+
logger.debug("recvall socket.error: %s, readable: %s", str(socket_err), self.is_readable())
|
|
386
|
+
if self.is_readable():
|
|
387
|
+
continue
|
|
388
|
+
return buffer, socket_err.errno in (errno.ECONNABORTED, errno.ECONNRESET)
|
|
389
|
+
|
|
390
|
+
def is_readable(self) -> bool:
|
|
391
|
+
try:
|
|
392
|
+
return not self.socket().recv(16, socket.MSG_DONTWAIT | socket.MSG_PEEK) == b''
|
|
393
|
+
except Exception:
|
|
394
|
+
return False
|
|
395
|
+
|
|
396
|
+
# def recvall_ssl(self) -> (bytes, bool):
|
|
397
|
+
# buffer = b''
|
|
398
|
+
# while True:
|
|
399
|
+
# try:
|
|
400
|
+
# received = self.socket().recv(1024)
|
|
401
|
+
# buffer += received
|
|
402
|
+
# if not received: # EOF
|
|
403
|
+
# return buffer, True
|
|
404
|
+
# except ssl.SSLWantReadError:
|
|
405
|
+
# if self.is_readable():
|
|
406
|
+
# continue
|
|
407
|
+
# return buffer, False
|
|
408
|
+
# except socket.error:
|
|
409
|
+
# return buffer, False
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class NioServerSocketChannel(AbstractChannel):
|
|
413
|
+
|
|
414
|
+
def __init__(self, eventloop: 'EventLoop', sock: socket.socket, handler_initializer: Callable):
|
|
415
|
+
super().__init__(eventloop, sock, handler_initializer)
|
|
416
|
+
self._server_channel = True
|
|
417
|
+
|
|
418
|
+
def acceptall(self) -> list: # [(socket, address), ...]
|
|
419
|
+
result = []
|
|
420
|
+
while True:
|
|
421
|
+
try:
|
|
422
|
+
result.append(self.socket().accept())
|
|
423
|
+
except socket.error:
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
@define(slots=True)
|
|
428
|
+
class ChannelContext:
|
|
429
|
+
_channel: AbstractChannel = field()
|
|
430
|
+
|
|
431
|
+
def close(self):
|
|
432
|
+
self._channel.close()
|
|
433
|
+
|
|
434
|
+
def write(self, buffer):
|
|
435
|
+
self._channel.write(buffer)
|
|
436
|
+
|
|
437
|
+
def channel(self):
|
|
438
|
+
return self._channel
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# annotation
|
|
442
|
+
def _catch_exception(func):
|
|
443
|
+
@wraps(func)
|
|
444
|
+
def inner(self, *args, **kwargs):
|
|
445
|
+
try:
|
|
446
|
+
func(self, *args, **kwargs)
|
|
447
|
+
except Exception as e:
|
|
448
|
+
self.fire_exception_caught(e)
|
|
449
|
+
return inner
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@define(slots=True)
|
|
453
|
+
class ChannelHandlerContext:
|
|
454
|
+
|
|
455
|
+
_channel: AbstractChannel = field()
|
|
456
|
+
|
|
457
|
+
def close(self):
|
|
458
|
+
self._channel.close()
|
|
459
|
+
|
|
460
|
+
def write(self, bytebuf) -> 'ChannelFuture':
|
|
461
|
+
return self._channel.write(bytebuf)
|
|
462
|
+
|
|
463
|
+
def channel(self):
|
|
464
|
+
return self._channel
|
|
465
|
+
|
|
466
|
+
def handler(self):
|
|
467
|
+
return self._channel.handler()
|
|
468
|
+
|
|
469
|
+
def fire_exception_caught(self, exception):
|
|
470
|
+
try:
|
|
471
|
+
self.handler().exception_caught(self, exception)
|
|
472
|
+
except Exception:
|
|
473
|
+
logger.exception(f"Exception caught while handling exception: {exception}")
|
|
474
|
+
|
|
475
|
+
@_catch_exception
|
|
476
|
+
def fire_channel_registered(self):
|
|
477
|
+
self.handler().channel_registered(self)
|
|
478
|
+
|
|
479
|
+
@_catch_exception
|
|
480
|
+
def fire_channel_unregistered(self):
|
|
481
|
+
self.handler().channel_unregistered(self)
|
|
482
|
+
|
|
483
|
+
@_catch_exception
|
|
484
|
+
def fire_channel_read(self, msg: Union[bytes, socket.socket]):
|
|
485
|
+
self.handler().channel_read(self, msg)
|
|
486
|
+
|
|
487
|
+
@_catch_exception
|
|
488
|
+
def fire_channel_active(self):
|
|
489
|
+
self.handler().channel_active(self)
|
|
490
|
+
|
|
491
|
+
@_catch_exception
|
|
492
|
+
def fire_channel_inactive(self):
|
|
493
|
+
self.handler().channel_inactive(self)
|
|
494
|
+
|
|
495
|
+
@_catch_exception
|
|
496
|
+
def fire_channel_writability_changed(self):
|
|
497
|
+
self.handler().channel_writability_changed(self)
|
|
498
|
+
|
|
499
|
+
@_catch_exception
|
|
500
|
+
def fire_channel_handshake_complete(self):
|
|
501
|
+
self.handler().channel_handshake_complete(self)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
@define(slots=True)
|
|
505
|
+
class ChannelFuture:
|
|
506
|
+
|
|
507
|
+
_channel: AbstractChannel = field()
|
|
508
|
+
future: Future = field(default=None)
|
|
509
|
+
|
|
510
|
+
def __attrs_post_init__(self):
|
|
511
|
+
self.future = self.future or Future()
|
|
512
|
+
|
|
513
|
+
def channel(self) -> AbstractChannel:
|
|
514
|
+
return self._channel
|
|
515
|
+
|
|
516
|
+
def close_future(self) -> 'ChannelFuture':
|
|
517
|
+
return self.channel().close_future()
|
|
518
|
+
|
|
519
|
+
def sync(self) -> 'ChannelFuture':
|
|
520
|
+
self.future.result()
|
|
521
|
+
return self
|
|
522
|
+
|
|
523
|
+
def done(self) -> bool:
|
|
524
|
+
return self.future.done()
|
|
525
|
+
|
|
526
|
+
def set(self, channel: AbstractChannel) -> None:
|
|
527
|
+
if self.future.done():
|
|
528
|
+
return
|
|
529
|
+
self.future.set_result(channel)
|
|
530
|
+
|
|
531
|
+
def add_listener(self, listener: Callable) -> None:
|
|
532
|
+
self.future.add_done_callback(lambda f: listener(self))
|