mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-01-24 08:14:14 +03:00
Remove temporary connections and use a lock again
These seem to be the reason for missing some updates (#237)
This commit is contained in:
parent
7745b8e7ee
commit
d81dd055e6
|
@ -5,6 +5,7 @@ encrypting every packet, and relies on a valid AuthKey in the used Session.
|
||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
from .. import helpers as utils
|
from .. import helpers as utils
|
||||||
from ..crypto import AES
|
from ..crypto import AES
|
||||||
|
@ -53,6 +54,9 @@ class MtProtoSender:
|
||||||
# Requests (as msg_id: Message) sent waiting to be received
|
# Requests (as msg_id: Message) sent waiting to be received
|
||||||
self._pending_receive = {}
|
self._pending_receive = {}
|
||||||
|
|
||||||
|
# Multithreading
|
||||||
|
self._send_lock = Lock()
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connects to the server."""
|
"""Connects to the server."""
|
||||||
self.connection.connect(self.session.server_address, self.session.port)
|
self.connection.connect(self.session.server_address, self.session.port)
|
||||||
|
@ -71,10 +75,6 @@ class MtProtoSender:
|
||||||
self._need_confirmation.clear()
|
self._need_confirmation.clear()
|
||||||
self._clear_all_pending()
|
self._clear_all_pending()
|
||||||
|
|
||||||
def clone(self):
|
|
||||||
"""Creates a copy of this MtProtoSender as a new connection."""
|
|
||||||
return MtProtoSender(self.session, self.connection.clone())
|
|
||||||
|
|
||||||
# region Send and receive
|
# region Send and receive
|
||||||
|
|
||||||
def send(self, *requests):
|
def send(self, *requests):
|
||||||
|
@ -156,7 +156,8 @@ class MtProtoSender:
|
||||||
|
|
||||||
:param message: the TLMessage to be sent.
|
:param message: the TLMessage to be sent.
|
||||||
"""
|
"""
|
||||||
self.connection.send(utils.pack_message(self.session, message))
|
with self._send_lock:
|
||||||
|
self.connection.send(utils.pack_message(self.session, message))
|
||||||
|
|
||||||
def _decode_msg(self, body):
|
def _decode_msg(self, body):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -163,11 +163,6 @@ class TelegramBareClient:
|
||||||
self._spawn_read_thread = spawn_read_thread
|
self._spawn_read_thread = spawn_read_thread
|
||||||
self._recv_thread = None
|
self._recv_thread = None
|
||||||
|
|
||||||
# Identifier of the main thread (the one that called .connect()).
|
|
||||||
# This will be used to create new connections from any other thread,
|
|
||||||
# so that requests can be sent in parallel.
|
|
||||||
self._main_thread_ident = None
|
|
||||||
|
|
||||||
# Default PingRequest delay
|
# Default PingRequest delay
|
||||||
self._last_ping = datetime.now()
|
self._last_ping = datetime.now()
|
||||||
self._ping_delay = timedelta(minutes=1)
|
self._ping_delay = timedelta(minutes=1)
|
||||||
|
@ -198,7 +193,6 @@ class TelegramBareClient:
|
||||||
__log__.info('Connecting to %s:%d...',
|
__log__.info('Connecting to %s:%d...',
|
||||||
self.session.server_address, self.session.port)
|
self.session.server_address, self.session.port)
|
||||||
|
|
||||||
self._main_thread_ident = threading.get_ident()
|
|
||||||
self._background_error = None # Clear previous errors
|
self._background_error = None # Clear previous errors
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -431,6 +425,9 @@ class TelegramBareClient:
|
||||||
x.content_related for x in requests):
|
x.content_related for x in requests):
|
||||||
raise TypeError('You can only invoke requests, not types!')
|
raise TypeError('You can only invoke requests, not types!')
|
||||||
|
|
||||||
|
if self._background_error:
|
||||||
|
raise self._background_error
|
||||||
|
|
||||||
# For logging purposes
|
# For logging purposes
|
||||||
if len(requests) == 1:
|
if len(requests) == 1:
|
||||||
which = type(requests[0]).__name__
|
which = type(requests[0]).__name__
|
||||||
|
@ -439,66 +436,31 @@ class TelegramBareClient:
|
||||||
len(requests), [type(x).__name__ for x in requests])
|
len(requests), [type(x).__name__ for x in requests])
|
||||||
|
|
||||||
# Determine the sender to be used (main or a new connection)
|
# Determine the sender to be used (main or a new connection)
|
||||||
on_main_thread = threading.get_ident() == self._main_thread_ident
|
__log__.debug('Invoking %s', which)
|
||||||
if on_main_thread or self._on_read_thread():
|
|
||||||
__log__.debug('Invoking %s from main thread', which)
|
|
||||||
sender = self._sender
|
|
||||||
update_state = self.updates
|
|
||||||
else:
|
|
||||||
__log__.debug('Invoking %s from background thread. '
|
|
||||||
'Creating temporary connection', which)
|
|
||||||
|
|
||||||
sender = self._sender.clone()
|
call_receive = self._recv_thread is None or self._reconnect_lock.locked()
|
||||||
sender.connect()
|
for retry in range(retries):
|
||||||
# We're on another connection, Telegram will resend all the
|
result = self._invoke(call_receive, *requests)
|
||||||
# updates that we haven't acknowledged (potentially entering
|
if result is not None:
|
||||||
# an infinite loop if we're calling this in response to an
|
return result
|
||||||
# update event, as it would be received again and again). So
|
|
||||||
# to avoid this we will simply not process updates on these
|
|
||||||
# new temporary connections, as they will be sent and later
|
|
||||||
# acknowledged over the main connection.
|
|
||||||
update_state = None
|
|
||||||
|
|
||||||
# We should call receive from this thread if there's no background
|
__log__.warning('Invoking %s failed %d times, '
|
||||||
# thread reading or if the server disconnected us and we're trying
|
'reconnecting and retrying',
|
||||||
# to reconnect. This is because the read thread may either be
|
[str(x) for x in requests], retry + 1)
|
||||||
# locked also trying to reconnect or we may be said thread already.
|
sleep(1)
|
||||||
call_receive = not on_main_thread or self._recv_thread is None \
|
# The ReadThread has priority when attempting reconnection,
|
||||||
or self._reconnect_lock.locked()
|
# since this thread is constantly running while __call__ is
|
||||||
try:
|
# only done sometimes. Here try connecting only once/retry.
|
||||||
for attempt in range(retries):
|
if not self._reconnect_lock.locked():
|
||||||
if self._background_error and on_main_thread:
|
with self._reconnect_lock:
|
||||||
raise self._background_error
|
self._reconnect()
|
||||||
|
|
||||||
result = self._invoke(
|
raise RuntimeError('Number of retries reached 0.')
|
||||||
sender, call_receive, update_state, *requests
|
|
||||||
)
|
|
||||||
if result is not None:
|
|
||||||
return result
|
|
||||||
|
|
||||||
__log__.warning('Invoking %s failed %d times, '
|
|
||||||
'reconnecting and retrying',
|
|
||||||
[str(x) for x in requests], attempt + 1)
|
|
||||||
sleep(1)
|
|
||||||
# The ReadThread has priority when attempting reconnection,
|
|
||||||
# since this thread is constantly running while __call__ is
|
|
||||||
# only done sometimes. Here try connecting only once/retry.
|
|
||||||
if sender == self._sender:
|
|
||||||
if not self._reconnect_lock.locked():
|
|
||||||
with self._reconnect_lock:
|
|
||||||
self._reconnect()
|
|
||||||
else:
|
|
||||||
sender.connect()
|
|
||||||
|
|
||||||
raise RuntimeError('Number of retries reached 0.')
|
|
||||||
finally:
|
|
||||||
if sender != self._sender:
|
|
||||||
sender.disconnect() # Close temporary connections
|
|
||||||
|
|
||||||
# Let people use client.invoke(SomeRequest()) instead client(...)
|
# Let people use client.invoke(SomeRequest()) instead client(...)
|
||||||
invoke = __call__
|
invoke = __call__
|
||||||
|
|
||||||
def _invoke(self, sender, call_receive, update_state, *requests):
|
def _invoke(self, call_receive, *requests):
|
||||||
try:
|
try:
|
||||||
# Ensure that we start with no previous errors (i.e. resending)
|
# Ensure that we start with no previous errors (i.e. resending)
|
||||||
for x in requests:
|
for x in requests:
|
||||||
|
@ -523,7 +485,7 @@ class TelegramBareClient:
|
||||||
self._wrap_init_connection(GetConfigRequest())
|
self._wrap_init_connection(GetConfigRequest())
|
||||||
)
|
)
|
||||||
|
|
||||||
sender.send(*requests)
|
self._sender.send(*requests)
|
||||||
|
|
||||||
if not call_receive:
|
if not call_receive:
|
||||||
# TODO This will be slightly troublesome if we allow
|
# TODO This will be slightly troublesome if we allow
|
||||||
|
@ -532,11 +494,11 @@ class TelegramBareClient:
|
||||||
# in which case a Lock would be required for .receive().
|
# in which case a Lock would be required for .receive().
|
||||||
for x in requests:
|
for x in requests:
|
||||||
x.confirm_received.wait(
|
x.confirm_received.wait(
|
||||||
sender.connection.get_timeout()
|
self._sender.connection.get_timeout()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
while not all(x.confirm_received.is_set() for x in requests):
|
while not all(x.confirm_received.is_set() for x in requests):
|
||||||
sender.receive(update_state=update_state)
|
self._sender.receive(update_state=self.updates)
|
||||||
|
|
||||||
except BrokenAuthKeyError:
|
except BrokenAuthKeyError:
|
||||||
__log__.error('Authorization key seems broken and was invalid!')
|
__log__.error('Authorization key seems broken and was invalid!')
|
||||||
|
@ -578,7 +540,7 @@ class TelegramBareClient:
|
||||||
# be on the very first connection (not authorized, not running),
|
# be on the very first connection (not authorized, not running),
|
||||||
# but may be an issue for people who actually travel?
|
# but may be an issue for people who actually travel?
|
||||||
self._reconnect(new_dc=e.new_dc)
|
self._reconnect(new_dc=e.new_dc)
|
||||||
return self._invoke(sender, call_receive, update_state, *requests)
|
return self._invoke(call_receive, *requests)
|
||||||
|
|
||||||
except ServerError as e:
|
except ServerError as e:
|
||||||
# Telegram is having some issues, just retry
|
# Telegram is having some issues, just retry
|
||||||
|
|
Loading…
Reference in New Issue
Block a user