-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Splitting this out from #9.
I haven't done a lot of testing, but 0045680 seems to have intermittently but significantly reduced response time, presumably by eliminating both the TCP handshake (to the Netherlands) and TLS negotiation for some requests.
Right now, each thread gets its own requests.Session, so each bot's long-poll Session stays active pretty much constantly but the main thread's Session (and any connections it maintains) can go idle potentially for hours at a time (and hence time out and need to be reestablished before a response can be delivered).
Yes. HTTPX is intended to be thread-safe, and yes, a single client-instance across all threads will do better in terms of connection pooling, than using an instance-per-thread.
However, changing requests.py to:
_SESSION = httpx.Client(http2=True)
⋮
def post(*args, **kwargs):
return _SESSION.post(*args, **kwargs)consistently gives:
2024-08-23 14:59:28,013 INFO Thread-1 (_poll_bot) _client.py:1026] HTTP Request: POST https://api.telegram.org/bot44…/getupdates "HTTP/2 200 OK"
2024-08-23 15:00:01,741 INFO MainThread reminders.py:42] Running periodic.
2024-08-23 15:00:08,887 INFO MainThread reminders.py:192] Editing reminder -40…/8197.
2024-08-23 15:00:18,220 INFO Thread-1 (_poll_bot) _client.py:1026] HTTP Request: POST https://api.telegram.org/bot44…/getupdates "HTTP/2 200 OK"
2024-08-23 15:00:18,221 INFO MainThread _client.py:1026] HTTP Request: POST https://api.telegram.org/bot44…/editmessagecaption "HTTP/2 200 OK"
which suggests that requests to the same host are serialized (the editMessageCaption didn't complete until 1 ms after a previous getUpdates completed). A quick test seems to confirm this is an actual effect, not just a discrepancy in the logging:
>>> import ntelebot
>>> import threading
>>> import time
>>> bot = ntelebot.bot.Bot('18…')
>>> print(time.time(), bot.getme(), time.time())
1724451017.3645363 {'id': 18…, 'is_bot': True, 'first_name': 'ntelebot', 'username': 'ntelebot', 'can_join_groups': False, 'can_read_all_group_messages': False, 'supports_inline_queries': False, 'can_connect_to_business': False, 'has_main_web_app': False} 1724451017.9219635
>>> t = threading.Thread(target=lambda: print(time.time(), bot.getupdates(timeout=20), time.time())); t.daemon = True; t.start()
>>> print(time.time(), bot.getme(), time.time())
1724451058.9599593 [] 1724451079.5572999
1724451061.5596561 {'id': 18…, 'is_bot': True, 'first_name': 'ntelebot', 'username': 'ntelebot', 'can_join_groups': False, 'can_read_all_group_messages': False, 'supports_inline_queries': False, 'can_connect_to_business': False, 'has_main_web_app': False} 1724451079.5575902
The exact timing (getUpdates returning at 1724451079.5572999 and getMe returning at 1724451079.5575902, 290 µs — not ms — later) suggests the requests did actually go out as requested, but something caused the responses to stack up. I haven't dug deeply enough to figure out if this is actually happening in the HTTP/2 stream or if this is happening somewhere inside httpx.
Even more worryingly:
>>> t = threading.Thread(target=lambda: print(bot.getupdates())); t.daemon = True; t.start(); print(bot.getme())
Exception in thread Thread-1 (<lambda>):
Traceback (most recent call last):
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 69, in map_httpcore_exceptions
yield
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 233, in handle_request
resp = self._pool.handle_request(req)
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 216, in handle_request
raise exc from None
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 196, in handle_request
response = connection.handle_request(
File "lib/python3.10/site-packages/httpcore/_sync/connection.py", line 101, in handle_request
return self._connection.handle_request(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 185, in handle_request
raise exc
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 148, in handle_request
status, headers = self._receive_response(
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 292, in _receive_response
event = self._receive_stream_event(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 333, in _receive_stream_event
self._receive_events(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 361, in _receive_events
events = self._read_incoming_data(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 452, in _read_incoming_data
raise exc
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 440, in _read_incoming_data
raise RemoteProtocolError("Server disconnected")
httpcore.RemoteProtocolError: Server disconnected
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner
self.run()
File "/usr/lib/python3.10/threading.py", line 953, in run
self._target(*self._args, **self._kwargs)
File "<stdin>", line 1, in <lambda>
Traceback (most recent call last):
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 69, in map_httpcore_exceptions
yield
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 233, in handle_request
resp = self._pool.handle_request(req)
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 216, in handle_request
raise exc from None
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 196, in handle_request
response = connection.handle_request(
File "lib/python3.10/site-packages/httpcore/_sync/connection.py", line 101, in handle_request
return self._connection.handle_request(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 185, in handle_request
raise exc
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 148, in handle_request
status, headers = self._receive_response(
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 292, in _receive_response
event = self._receive_stream_event(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 333, in _receive_stream_event
self._receive_events(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 361, in _receive_events
events = self._read_incoming_data(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 435, in _read_incoming_data
raise self._read_exception # pragma: nocover
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 69, in map_httpcore_exceptions
yield
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 233, in handle_request
resp = self._pool.handle_request(req)
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 216, in handle_request
raise exc from None
File "lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 196, in handle_request
response = connection.handle_request(
File "lib/python3.10/site-packages/httpcore/_sync/connection.py", line 101, in handle_request
return self._connection.handle_request(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 185, in handle_request
raise exc
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 148, in handle_request
status, headers = self._receive_response(
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 292, in _receive_response
event = self._receive_stream_event(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 333, in _receive_stream_event
self._receive_events(request, stream_id)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 361, in _receive_events
events = self._read_incoming_data(request)
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 452, in _read_incoming_data
raise exc
File "lib/python3.10/site-packages/httpcore/_sync/http2.py", line 440, in _read_incoming_data
raise RemoteProtocolError("Server disconnected")
httpcore.RemoteProtocolError: Server disconnected
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "ntelebot/ntelebot/bot.py", line 57, in __call__
data = ntelebot.requests.post(self.url, timeout=self.timeout, **_prepare(params)).json()
File "ntelebot/ntelebot/requests.py", line 29, in post
return _SESSION.post(*args, **kwargs)
File "lib/python3.10/site-packages/httpx/_client.py", line 1145, in post
return self.request(
File "lib/python3.10/site-packages/httpx/_client.py", line 827, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
File "lib/python3.10/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
File "lib/python3.10/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
File "lib/python3.10/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "lib/python3.10/site-packages/httpx/_client.py", line 1015, in _send_single_request
response = transport.handle_request(request)
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 232, in handle_request
with map_httpcore_exceptions():
File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__
self.gen.throw(typ, value, traceback)
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 86, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.RemoteProtocolError: Server disconnected
File "ntelebot/ntelebot/bot.py", line 57, in __call__
data = ntelebot.requests.post(self.url, timeout=self.timeout, **_prepare(params)).json()
>>> File "ntelebot/ntelebot/requests.py", line 29, in post
return _SESSION.post(*args, **kwargs)
File "lib/python3.10/site-packages/httpx/_client.py", line 1145, in post
return self.request(
File "lib/python3.10/site-packages/httpx/_client.py", line 827, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
File "lib/python3.10/site-packages/httpx/_client.py", line 914, in send
response = self._send_handling_auth(
File "lib/python3.10/site-packages/httpx/_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
File "lib/python3.10/site-packages/httpx/_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
File "lib/python3.10/site-packages/httpx/_client.py", line 1015, in _send_single_request
response = transport.handle_request(request)
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 232, in handle_request
with map_httpcore_exceptions():
File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__
self.gen.throw(typ, value, traceback)
File "lib/python3.10/site-packages/httpx/_transports/default.py", line 86, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.RemoteProtocolError: Server disconnected
but I have not been able to reliably reproduce this, so I don't know if this is something like a really tight race in httpx or if TBA just decided to dump a connection at the perfect wrong time. Either way, though, I think this is showing that both requests were canceled, i.e. if TBA dumped the connection during getUpdates it caused the subsequent getMe to fail too, rather than trigger a reconnect 🙁.