Attempt at fixing #5 (RPCError) and updated README

Now RPC results can be received from the updates thread, as long
as they are errors. This, however, should not happen!
A recursive lock is now used (and released on every method, rather
than only on the `.receive()` one)
This commit is contained in:
Lonami 2016-10-02 13:30:14 +02:00
parent 77aa37d2ad
commit e035939aa2
2 changed files with 49 additions and 44 deletions

View File

@ -14,7 +14,6 @@ The project's **core only** is based on TLSharp, a C# Telegram client implementa
- [Tips for porting Telethon](#tips-for-porting-telethon) - [Tips for porting Telethon](#tips-for-porting-telethon)
- [Code generator limitations](#code-generator-limitations) - [Code generator limitations](#code-generator-limitations)
- [Updating the `scheme.tl`](#updating-the-schemetl) - [Updating the `scheme.tl`](#updating-the-schemetl)
- [Plans for the future](#plans-for-the-future)
## Why Telethon? ## Why Telethon?
> Why should I bother with Telethon? There are more mature projects already, such as > Why should I bother with Telethon? There are more mature projects already, such as

View File

@ -1,7 +1,7 @@
import gzip import gzip
from telethon.errors import * from telethon.errors import *
from time import sleep from time import sleep
from threading import Thread, Lock from threading import Thread, RLock
import telethon.helpers as utils import telethon.helpers as utils
from telethon.crypto import AES from telethon.crypto import AES
@ -19,8 +19,12 @@ class MtProtoSender:
self.need_confirmation = [] # Message IDs that need confirmation self.need_confirmation = [] # Message IDs that need confirmation
self.on_update_handlers = [] self.on_update_handlers = []
# Store a Lock instance to make this class safely multi-threaded # Store an RLock instance to make this class safely multi-threaded
self.lock = Lock() self.lock = RLock()
# Flag used to determine whether we've received a sent request yet or not
# We need this to avoid using the updates thread if we're waiting to read
self.waiting_receive = False
self.updates_thread = Thread(target=self.updates_thread_method, name='Updates thread') self.updates_thread = Thread(target=self.updates_thread_method, name='Updates thread')
self.updates_thread_running = False self.updates_thread_running = False
@ -68,7 +72,7 @@ class MtProtoSender:
# region Send and receive # region Send and receive
def send(self, request, resend=False): def send(self, request):
"""Sends the specified MTProtoRequest, previously sending any message """Sends the specified MTProtoRequest, previously sending any message
which needed confirmation. This also pauses the updates thread""" which needed confirmation. This also pauses the updates thread"""
@ -78,9 +82,10 @@ class MtProtoSender:
if self.updates_thread_receiving: if self.updates_thread_receiving:
self.transport.cancel_receive() self.transport.cancel_receive()
# Now only us can be using this method if we're not resending # Now only us can be using this method
if not resend: with self.lock:
self.lock.acquire() # Set the flag to true so the updates thread stops trying to receive
self.waiting_receive = True
# If any message needs confirmation send an AckRequest first # If any message needs confirmation send an AckRequest first
if self.need_confirmation: if self.need_confirmation:
@ -98,14 +103,12 @@ class MtProtoSender:
# And update the saved session # And update the saved session
self.session.save() self.session.save()
# Don't resume the updates thread yet,
# since every send() is preceded by a receive()
def receive(self, request): def receive(self, request):
"""Receives the specified MTProtoRequest ("fills in it" """Receives the specified MTProtoRequest ("fills in it"
the received data). This also restores the updates thread""" the received data). This also restores the updates thread"""
try: with self.lock:
# Don't stop trying to receive until we get the request we wanted # Don't stop trying to receive until we get the request we wanted
while not request.confirm_received: while not request.confirm_received:
seq, body = self.transport.receive() seq, body = self.transport.receive()
@ -114,10 +117,8 @@ class MtProtoSender:
with BinaryReader(message) as reader: with BinaryReader(message) as reader:
self.process_msg(remote_msg_id, remote_sequence, reader, request) self.process_msg(remote_msg_id, remote_sequence, reader, request)
finally: # We can now set the flag to False thus resuming the updates thread
# Once we are done trying to get our request, self.waiting_receive = False
# restore the updates thread and release the lock
self.lock.release()
# endregion # endregion
@ -252,7 +253,7 @@ class MtProtoSender:
raise ValueError('Tried to handle a bad server salt with no request specified') raise ValueError('Tried to handle a bad server salt with no request specified')
# Resend # Resend
self.send(request, resend=True) self.send(request)
return True return True
@ -265,19 +266,19 @@ class MtProtoSender:
raise BadMessageError(error_code) raise BadMessageError(error_code)
def handle_rpc_result(self, msg_id, sequence, reader, request): def handle_rpc_result(self, msg_id, sequence, reader, request):
if not request:
raise ValueError('RPC results should only happen after a request was sent')
code = reader.read_int(signed=False) code = reader.read_int(signed=False)
request_id = reader.read_long(signed=False) request_id = reader.read_long(signed=False)
inner_code = reader.read_int(signed=False) inner_code = reader.read_int(signed=False)
if request_id == request.msg_id: if request and request_id == request.msg_id:
request.confirm_received = True request.confirm_received = True
if inner_code == 0x2144ca19: # RPC Error if inner_code == 0x2144ca19: # RPC Error
error = RPCError(code=reader.read_int(), message=reader.tgread_string()) error = RPCError(code=reader.read_int(), message=reader.tgread_string())
if error.must_resend: if error.must_resend:
if not request:
raise ValueError('The previously sent request must be resent. '
'However, no request was previously sent (called from updates thread).')
request.confirm_received = False request.confirm_received = False
if error.message.startswith('FLOOD_WAIT_'): if error.message.startswith('FLOOD_WAIT_'):
@ -290,6 +291,9 @@ class MtProtoSender:
else: else:
raise error raise error
else: else:
if not request:
raise ValueError('Cannot receive a request from inside an RPC result from the updates thread.')
if inner_code == 0x3072cfa1: # GZip packed if inner_code == 0x3072cfa1: # GZip packed
unpacked_data = gzip.decompress(reader.tgread_bytes()) unpacked_data = gzip.decompress(reader.tgread_bytes())
with BinaryReader(unpacked_data) as compressed_reader: with BinaryReader(unpacked_data) as compressed_reader:
@ -323,6 +327,8 @@ class MtProtoSender:
def updates_thread_method(self): def updates_thread_method(self):
"""This method will run until specified and listen for incoming updates""" """This method will run until specified and listen for incoming updates"""
while self.updates_thread_running: while self.updates_thread_running:
# Only try to receive updates if we're not waiting to receive a request
if not self.waiting_receive:
with self.lock: with self.lock:
try: try:
self.updates_thread_receiving = True self.updates_thread_receiving = True