mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-03-10 06:05:47 +03:00
Remove all Thread's except from UpdateState
This commit is contained in:
parent
a17def8026
commit
9716d1d543
|
@ -3,14 +3,12 @@ import errno
|
|||
import socket
|
||||
from datetime import timedelta
|
||||
from io import BytesIO, BufferedWriter
|
||||
from threading import Lock
|
||||
|
||||
|
||||
class TcpClient:
|
||||
def __init__(self, proxy=None, timeout=timedelta(seconds=5)):
|
||||
self.proxy = proxy
|
||||
self._socket = None
|
||||
self._closing_lock = Lock()
|
||||
|
||||
if isinstance(timeout, timedelta):
|
||||
self.timeout = timeout.seconds
|
||||
|
@ -65,11 +63,6 @@ class TcpClient:
|
|||
|
||||
def close(self):
|
||||
"""Closes the connection"""
|
||||
if self._closing_lock.locked():
|
||||
# Already closing, no need to close again (avoid None.close())
|
||||
return
|
||||
|
||||
with self._closing_lock:
|
||||
try:
|
||||
if self._socket is not None:
|
||||
self._socket.shutdown(socket.SHUT_RDWR)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import logging
|
||||
import os
|
||||
import threading
|
||||
from datetime import timedelta, datetime
|
||||
from hashlib import md5
|
||||
from io import BytesIO
|
||||
from threading import Lock
|
||||
from time import sleep
|
||||
|
||||
from . import helpers as utils
|
||||
|
@ -18,7 +16,7 @@ from .network import authenticator, MtProtoSender, Connection, ConnectionMode
|
|||
from .tl import TLObject, Session
|
||||
from .tl.all_tlobjects import LAYER
|
||||
from .tl.functions import (
|
||||
InitConnectionRequest, InvokeWithLayerRequest, PingRequest
|
||||
InitConnectionRequest, InvokeWithLayerRequest
|
||||
)
|
||||
from .tl.functions.auth import (
|
||||
ImportAuthorizationRequest, ExportAuthorizationRequest
|
||||
|
@ -67,8 +65,6 @@ class TelegramBareClient:
|
|||
def __init__(self, session, api_id, api_hash,
|
||||
connection_mode=ConnectionMode.TCP_FULL,
|
||||
proxy=None,
|
||||
update_workers=None,
|
||||
spawn_read_thread=False,
|
||||
timeout=timedelta(seconds=5),
|
||||
**kwargs):
|
||||
"""Refer to TelegramClient.__init__ for docs on this method"""
|
||||
|
@ -101,10 +97,6 @@ class TelegramBareClient:
|
|||
|
||||
self._logger = logging.getLogger(__name__)
|
||||
|
||||
# Two threads may be calling reconnect() when the connection is lost,
|
||||
# we only want one to actually perform the reconnection.
|
||||
self._reconnect_lock = Lock()
|
||||
|
||||
# Cache "exported" sessions as 'dc_id: Session' not to recreate
|
||||
# them all the time since generating a new key is a relatively
|
||||
# expensive operation.
|
||||
|
@ -112,7 +104,7 @@ class TelegramBareClient:
|
|||
|
||||
# This member will process updates if enabled.
|
||||
# One may change self.updates.enabled at any later point.
|
||||
self.updates = UpdateState(workers=update_workers)
|
||||
self.updates = UpdateState(workers=None)
|
||||
|
||||
# Used on connection - the user may modify these and reconnect
|
||||
kwargs['app_version'] = kwargs.get('app_version', self.__version__)
|
||||
|
@ -136,24 +128,10 @@ class TelegramBareClient:
|
|||
# Uploaded files cache so subsequent calls are instant
|
||||
self._upload_cache = {}
|
||||
|
||||
# Constantly read for results and updates from within the main client,
|
||||
# if the user has left enabled such option.
|
||||
self._spawn_read_thread = spawn_read_thread
|
||||
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
|
||||
self._last_ping = datetime.now()
|
||||
self._ping_delay = timedelta(minutes=1)
|
||||
|
||||
# Some errors are known but there's nothing we can do from the
|
||||
# background thread. If any of these happens, call .disconnect(),
|
||||
# and raise them next time .invoke() is tried to be called.
|
||||
self._background_error = None
|
||||
|
||||
# endregion
|
||||
|
||||
|
@ -179,9 +157,6 @@ class TelegramBareClient:
|
|||
If '_cdn' is False, methods that are not allowed on such data
|
||||
centers won't be invoked.
|
||||
"""
|
||||
self._main_thread_ident = threading.get_ident()
|
||||
self._background_error = None # Clear previous errors
|
||||
|
||||
try:
|
||||
self._sender.connect()
|
||||
if not self.session.auth_key:
|
||||
|
@ -268,20 +243,9 @@ class TelegramBareClient:
|
|||
return result
|
||||
|
||||
def disconnect(self):
|
||||
"""Disconnects from the Telegram server
|
||||
and stops all the spawned threads"""
|
||||
"""Disconnects from the Telegram server"""
|
||||
self._user_connected = False
|
||||
self._recv_thread = None
|
||||
|
||||
# Stop the workers from the background thread
|
||||
self.updates.stop_workers()
|
||||
|
||||
# This will trigger a "ConnectionResetError", for subsequent calls
|
||||
# to read or send (from another thread) and usually, the background
|
||||
# thread would try restarting the connection but since the
|
||||
# ._recv_thread = None, it knows it doesn't have to.
|
||||
self._sender.disconnect()
|
||||
|
||||
# TODO Shall we clear the _exported_sessions, or may be reused?
|
||||
pass
|
||||
|
||||
|
@ -296,12 +260,7 @@ class TelegramBareClient:
|
|||
"""
|
||||
if new_dc is None:
|
||||
# Assume we are disconnected due to some error, so connect again
|
||||
with self._reconnect_lock:
|
||||
# Another thread may have connected again, so check that first
|
||||
if not self.is_connected():
|
||||
return self.connect()
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
self.disconnect()
|
||||
self.session.auth_key = None # Force creating new auth_key
|
||||
|
@ -316,10 +275,6 @@ class TelegramBareClient:
|
|||
|
||||
# region Working with different connections/Data Centers
|
||||
|
||||
def _on_read_thread(self):
|
||||
return self._recv_thread is not None and \
|
||||
threading.get_ident() == self._recv_thread.ident
|
||||
|
||||
def _get_dc(self, dc_id, ipv6=False, cdn=False):
|
||||
"""Gets the Data Center (DC) associated to 'dc_id'"""
|
||||
if TelegramBareClient._dc_options is None:
|
||||
|
@ -424,26 +379,12 @@ class TelegramBareClient:
|
|||
x.content_related for x in requests):
|
||||
raise ValueError('You can only invoke requests, not types!')
|
||||
|
||||
# Determine the sender to be used (main or a new connection)
|
||||
on_main_thread = threading.get_ident() == self._main_thread_ident
|
||||
if on_main_thread or self._on_read_thread():
|
||||
sender = self._sender
|
||||
else:
|
||||
sender = self._sender.clone()
|
||||
sender.connect()
|
||||
# TODO Determine the sender to be used (main or a new connection)
|
||||
sender = self._sender # .clone(), .connect()
|
||||
|
||||
# We should call receive from this thread if there's no background
|
||||
# thread reading or if the server disconnected us and we're trying
|
||||
# to reconnect. This is because the read thread may either be
|
||||
# locked also trying to reconnect or we may be said thread already.
|
||||
call_receive = not on_main_thread or self._recv_thread is None \
|
||||
or self._reconnect_lock.locked()
|
||||
try:
|
||||
for _ in range(retries):
|
||||
if self._background_error and on_main_thread:
|
||||
raise self._background_error
|
||||
|
||||
result = self._invoke(sender, call_receive, *requests)
|
||||
result = self._invoke(sender, *requests)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
@ -455,7 +396,7 @@ class TelegramBareClient:
|
|||
# Let people use client.invoke(SomeRequest()) instead client(...)
|
||||
invoke = __call__
|
||||
|
||||
def _invoke(self, sender, call_receive, *requests):
|
||||
def _invoke(self, sender, *requests):
|
||||
try:
|
||||
# Ensure that we start with no previous errors (i.e. resending)
|
||||
for x in requests:
|
||||
|
@ -463,17 +404,6 @@ class TelegramBareClient:
|
|||
x.rpc_error = None
|
||||
|
||||
sender.send(*requests)
|
||||
|
||||
if not call_receive:
|
||||
# TODO This will be slightly troublesome if we allow
|
||||
# switching between constant read or not on the fly.
|
||||
# Must also watch out for calling .read() from two places,
|
||||
# in which case a Lock would be required for .receive().
|
||||
for x in requests:
|
||||
x.confirm_received.wait(
|
||||
sender.connection.get_timeout()
|
||||
)
|
||||
else:
|
||||
while not all(x.confirm_received.is_set() for x in requests):
|
||||
sender.receive(update_state=self.updates)
|
||||
|
||||
|
@ -481,9 +411,8 @@ class TelegramBareClient:
|
|||
pass # We will just retry
|
||||
|
||||
except ConnectionResetError:
|
||||
if not self._authorized or self._reconnect_lock.locked():
|
||||
# Only attempt reconnecting if we're authorized and not
|
||||
# reconnecting already.
|
||||
if not self._authorized:
|
||||
# Only attempt reconnecting if we're authorized
|
||||
raise
|
||||
|
||||
self._logger.debug('Server disconnected us. Reconnecting and '
|
||||
|
@ -520,12 +449,8 @@ class TelegramBareClient:
|
|||
'attempting to reconnect at DC {}'.format(e.new_dc)
|
||||
)
|
||||
|
||||
# TODO What happens with the background thread here?
|
||||
# For normal use cases, this won't happen, because this will only
|
||||
# be on the very first connection (not authorized, not running),
|
||||
# but may be an issue for people who actually travel?
|
||||
self._reconnect(new_dc=e.new_dc)
|
||||
return self._invoke(sender, call_receive, *requests)
|
||||
return self._invoke(sender, *requests)
|
||||
|
||||
except ServerError as e:
|
||||
# Telegram is having some issues, just retry
|
||||
|
@ -759,64 +684,6 @@ class TelegramBareClient:
|
|||
|
||||
def _set_connected_and_authorized(self):
|
||||
self._authorized = True
|
||||
self.updates.setup_workers()
|
||||
if self._spawn_read_thread and self._recv_thread is None:
|
||||
self._recv_thread = threading.Thread(
|
||||
name='ReadThread', daemon=True,
|
||||
target=self._recv_thread_impl
|
||||
)
|
||||
self._recv_thread.start()
|
||||
|
||||
# By using this approach, another thread will be
|
||||
# created and started upon connection to constantly read
|
||||
# from the other end. Otherwise, manual calls to .receive()
|
||||
# must be performed. The MtProtoSender cannot be connected,
|
||||
# or an error will be thrown.
|
||||
#
|
||||
# This way, sending and receiving will be completely independent.
|
||||
def _recv_thread_impl(self):
|
||||
while self._user_connected:
|
||||
try:
|
||||
if datetime.now() > self._last_ping + self._ping_delay:
|
||||
self._sender.send(PingRequest(
|
||||
int.from_bytes(os.urandom(8), 'big', signed=True)
|
||||
))
|
||||
self._last_ping = datetime.now()
|
||||
|
||||
self._sender.receive(update_state=self.updates)
|
||||
except TimeoutError:
|
||||
# No problem.
|
||||
pass
|
||||
except ConnectionResetError:
|
||||
self._logger.debug('Server disconnected us. Reconnecting...')
|
||||
while self._user_connected and not self._reconnect():
|
||||
sleep(0.1) # Retry forever, this is instant messaging
|
||||
|
||||
except Exception as error:
|
||||
# Unknown exception, pass it to the main thread
|
||||
self._logger.debug(
|
||||
'[ERROR] Unknown error on the read thread, please report',
|
||||
error
|
||||
)
|
||||
|
||||
try:
|
||||
import socks
|
||||
if isinstance(error, socks.GeneralProxyError):
|
||||
# This is a known error, and it's not related to
|
||||
# Telegram but rather to the proxy. Disconnect and
|
||||
# hand it over to the main thread.
|
||||
self._background_error = error
|
||||
self.disconnect()
|
||||
break
|
||||
except ImportError:
|
||||
"Not using PySocks, so it can't be a socket error"
|
||||
|
||||
# If something strange happens we don't want to enter an
|
||||
# infinite loop where all we do is raise an exception, so
|
||||
# add a little sleep to avoid the CPU usage going mad.
|
||||
sleep(0.1)
|
||||
break
|
||||
|
||||
self._recv_thread = None
|
||||
# TODO self.updates.setup_workers()
|
||||
|
||||
# endregion
|
||||
|
|
|
@ -52,21 +52,14 @@ from .tl.types.messages import DialogsSlice
|
|||
|
||||
|
||||
class TelegramClient(TelegramBareClient):
|
||||
"""Full featured TelegramClient meant to extend the basic functionality -
|
||||
|
||||
As opposed to the TelegramBareClient, this one features downloading
|
||||
media from different data centers, starting a second thread to
|
||||
handle updates, and some very common functionality.
|
||||
"""
|
||||
"""Full featured TelegramClient meant to extend the basic functionality"""
|
||||
|
||||
# region Initialization
|
||||
|
||||
def __init__(self, session, api_id, api_hash,
|
||||
connection_mode=ConnectionMode.TCP_FULL,
|
||||
proxy=None,
|
||||
update_workers=None,
|
||||
timeout=timedelta(seconds=5),
|
||||
spawn_read_thread=True,
|
||||
**kwargs):
|
||||
"""Initializes the Telegram client with the specified API ID and Hash.
|
||||
|
||||
|
@ -79,22 +72,6 @@ class TelegramClient(TelegramBareClient):
|
|||
This will only affect how messages are sent over the network
|
||||
and how much processing is required before sending them.
|
||||
|
||||
The integer 'update_workers' represents depending on its value:
|
||||
is None: Updates will *not* be stored in memory.
|
||||
= 0: Another thread is responsible for calling self.updates.poll()
|
||||
> 0: 'update_workers' background threads will be spawned, any
|
||||
any of them will invoke all the self.updates.handlers.
|
||||
|
||||
If 'spawn_read_thread', a background thread will be started once
|
||||
an authorized user has been logged in to Telegram to read items
|
||||
(such as updates and responses) from the network as soon as they
|
||||
occur, which will speed things up.
|
||||
|
||||
If you don't want to spawn any additional threads, pending updates
|
||||
will be read and processed accordingly after invoking a request
|
||||
and not immediately. This is useful if you don't care about updates
|
||||
at all and have set 'update_workers=None'.
|
||||
|
||||
If more named arguments are provided as **kwargs, they will be
|
||||
used to update the Session instance. Most common settings are:
|
||||
device_model = platform.node()
|
||||
|
@ -108,8 +85,6 @@ class TelegramClient(TelegramBareClient):
|
|||
session, api_id, api_hash,
|
||||
connection_mode=connection_mode,
|
||||
proxy=proxy,
|
||||
update_workers=update_workers,
|
||||
spawn_read_thread=spawn_read_thread,
|
||||
timeout=timeout,
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from threading import Lock
|
||||
|
||||
import re
|
||||
|
||||
from .. import utils
|
||||
|
@ -20,8 +18,6 @@ class EntityDatabase:
|
|||
"""
|
||||
self.enabled = enabled
|
||||
self.enabled_full = enabled_full
|
||||
|
||||
self._lock = Lock()
|
||||
self._entities = {} # marked_id: user|chat|channel
|
||||
|
||||
if input_list:
|
||||
|
@ -81,7 +77,6 @@ class EntityDatabase:
|
|||
except ValueError:
|
||||
pass
|
||||
|
||||
with self._lock:
|
||||
before = len(self._input_entities)
|
||||
self._input_entities.update(new_input)
|
||||
for e in new:
|
||||
|
|
|
@ -4,7 +4,6 @@ import platform
|
|||
import time
|
||||
from base64 import b64encode, b64decode
|
||||
from os.path import isfile as file_exists
|
||||
from threading import Lock
|
||||
|
||||
from .entity_database import EntityDatabase
|
||||
from .. import helpers
|
||||
|
@ -51,11 +50,6 @@ class Session:
|
|||
self.report_errors = True
|
||||
self.save_entities = True
|
||||
|
||||
# Cross-thread safety
|
||||
self._seq_no_lock = Lock()
|
||||
self._msg_id_lock = Lock()
|
||||
self._save_lock = Lock()
|
||||
|
||||
self.id = helpers.generate_random_long(signed=False)
|
||||
self._sequence = 0
|
||||
self.time_offset = 0
|
||||
|
@ -71,10 +65,9 @@ class Session:
|
|||
|
||||
def save(self):
|
||||
"""Saves the current session object as session_user_id.session"""
|
||||
if not self.session_user_id or self._save_lock.locked():
|
||||
if not self.session_user_id:
|
||||
return
|
||||
|
||||
with self._save_lock:
|
||||
with open('{}.session'.format(self.session_user_id), 'w') as file:
|
||||
out_dict = {
|
||||
'port': self.port,
|
||||
|
@ -149,7 +142,6 @@ class Session:
|
|||
Note that if confirmed=True, the sequence number
|
||||
will be increased by one too
|
||||
"""
|
||||
with self._seq_no_lock:
|
||||
if content_related:
|
||||
result = self._sequence * 2 + 1
|
||||
self._sequence += 1
|
||||
|
@ -166,7 +158,6 @@ class Session:
|
|||
# "message identifiers are divisible by 4"
|
||||
new_msg_id = (int(now) << 32) | (nanoseconds << 2)
|
||||
|
||||
with self._msg_id_lock:
|
||||
if self._last_msg_id >= new_msg_id:
|
||||
new_msg_id = self._last_msg_id + 4
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user