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 ADDED
@@ -0,0 +1,5 @@
1
+ from .bootstrap import Bootstrap, ServerBootstrap
2
+ from .handler import ChannelHandlerAdapter
3
+ from .eventloop import EventLoopGroup
4
+
5
+ __all__ = ['Bootstrap', 'ServerBootstrap', 'ChannelHandlerAdapter', 'EventLoopGroup']
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))