pylakey/aiotdlib

Crash when trying to run 2 clients, multi-threading issue

truenicoco opened this issue · 3 comments

When trying to instantiate a second aiotdlib.Client while a first one is running, I run into this:

INFO:CredentialsValidation:Starting client
2023-11-27T10:33:28.616461274Z INFO:CredentialsValidation:Session workdir: /var/lib/slidge/slidge.localhost/tdlib/.aiotdlib/xxx
2023-11-27T10:33:28.616519104Z INFO:CredentialsValidation:Authorization process has been started with phone
DEBUG:aiotdlib.tdjson:[me >>>] Sending b'{"@extra":{"request_id":"updateAuthorizationState"},"@type":"getAuthorizationState"}'
2023-11-27T10:33:28.617194587Z [ 0][t 0][1701081208.617133140][Client.cpp:284]	Receive must not be called simultaneously from two different threads, but this has just happened. Call it from a fixed thread, dedicated for updates and response processing.

I think this is due to Client.receive awaiting self.loop.run_in_executor(None, self.__tdjson.receive, timeout).

I tried to fix that by wrapping Client.receive with a global asyncio.Lock:

class MultiAccountClient(Client):
	global_receive_lock = asyncio.Lock()

	async def receive(self, timeout=1.0) -> Optional[BaseObject]:
		async with self.global_receive_lock:
			return await super().receive(timeout)

This prevents the crash, but it makes it impossible to authorize the second client:

DEBUG:aiotdlib.tdjson:[me >>>] Sending b'{"@extra":{"request_id":"updateAuthorizationState"},"@type":"getAuthorizationState"}'
2023-11-27T12:40:26.667542049Z DEBUG:aiotdlib.tdjson:[me <<<] Received b'{"@type":"updateOption","name":"version","value":{"@type":"optionValueString","value":"1.8.18"},"@client_id":2}'
2023-11-27T12:40:26.667902010Z DEBUG:aiotdlib.tdjson:[me <<<] Received b'{"@type":"updateOption","name":"commit_hash","value":{"@type":"optionValueString","value":"ceee1ad79a9191411b3ea488aeecfb134ab72add"},"@client_id":2}'
2023-11-27T12:40:26.668405902Z DEBUG:test@localhost:No handler for updateOption, ignoring
2023-11-27T12:40:26.668510902Z DEBUG:aiotdlib.tdjson:[me <<<] Received b'{"@type":"updateAuthorizationState","authorization_state":{"@type":"authorizationStateWaitTdlibParameters"},"@client_id":2}'
2023-11-27T12:40:26.668730783Z DEBUG:aiotdlib.tdjson:[me <<<] Received b'{"@type":"authorizationStateWaitTdlibParameters","@extra":{"request_id":"updateAuthorizationState"},"@client_id":2}'
2023-11-27T12:40:26.669027274Z DEBUG:test@localhost:No handler for authorizationStateWaitTdlibParameters, ignoring
2023-11-27T12:40:26.769470313Z DEBUG:aiotdlib.tdjson:[me >>>] Sending b'{"@extra":{"request_id":"updateAuthorizationState"},"@type":"getAuthorizationState"}'
2023-11-27T12:40:26.769727763Z DEBUG:aiotdlib.tdjson:[me <<<] Received b'{"@type":"authorizationStateWaitTdlibParameters","@extra":{"request_id":"updateAuthorizationState"},"@client_id":2}'

Traceback (most recent call last):
2023-11-27T12:40:36.778980401Z   File "/usr/local/lib/python3.11/asyncio/tasks.py", line 500, in wait_for
2023-11-27T12:40:36.778985131Z     return fut.result()
2023-11-27T12:40:36.778989431Z            ^^^^^^^^^^^^
2023-11-27T12:40:36.778993871Z   File "/usr/local/lib/python3.11/asyncio/locks.py", line 213, in wait
2023-11-27T12:40:36.778998231Z     await fut
2023-11-27T12:40:36.779003011Z asyncio.exceptions.CancelledError
2023-11-27T12:40:36.779007441Z 
2023-11-27T12:40:36.779011511Z The above exception was the direct cause of the following exception:
2023-11-27T12:40:36.779015871Z 
2023-11-27T12:40:36.779019921Z Traceback (most recent call last):
2023-11-27T12:40:36.779024101Z   File "/venv/lib/python3.11/site-packages/legacy_module/gateway.py", line 94, in validate_two_factor_code
2023-11-27T12:40:36.779046121Z     await asyncio.wait_for(auth_task, config.REGISTRATION_AUTH_CODE_TIMEOUT)
2023-11-27T12:40:36.779050611Z   File "/usr/local/lib/python3.11/asyncio/tasks.py", line 489, in wait_for
2023-11-27T12:40:36.779054981Z     return fut.result()
2023-11-27T12:40:36.779059001Z            ^^^^^^^^^^^^
2023-11-27T12:40:36.779062991Z   File "/venv/lib/python3.11/site-packages/aiotdlib/client.py", line 1098, in start
2023-11-27T12:40:36.779067381Z     await self.authorize()
2023-11-27T12:40:36.779071391Z   File "/venv/lib/python3.11/site-packages/aiotdlib/client.py", line 1067, in authorize
2023-11-27T12:40:36.779075661Z     result = await next_action()
2023-11-27T12:40:36.779079761Z              ^^^^^^^^^^^^^^^^^^^
2023-11-27T12:40:36.779083891Z   File "/venv/lib/python3.11/site-packages/aiotdlib/client.py", line 798, in _auth_start
2023-11-27T12:40:36.779088111Z     return await self.api.get_authorization_state(request_id=AUTHORIZATION_REQUEST_ID)
2023-11-27T12:40:36.779092281Z            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2023-11-27T12:40:36.779096451Z   File "/venv/lib/python3.11/site-packages/aiotdlib/api/api.py", line 7556, in get_authorization_state
2023-11-27T12:40:36.779100801Z     return await self.client.request(
2023-11-27T12:40:36.779104951Z            ^^^^^^^^^^^^^^^^^^^^^^^^^^
2023-11-27T12:40:36.779109041Z   File "/venv/lib/python3.11/site-packages/aiotdlib/client.py", line 995, in request
2023-11-27T12:40:36.779113411Z     await pending_request.wait(raise_exc=True, timeout=request_timeout)
2023-11-27T12:40:36.779117561Z   File "/venv/lib/python3.11/site-packages/aiotdlib/utils.py", line 159, in wait
2023-11-27T12:40:36.779121751Z     result = await asyncio.wait_for(self.__ready_event.wait(), timeout=timeout)
2023-11-27T12:40:36.779125962Z              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2023-11-27T12:40:36.779131042Z   File "/usr/local/lib/python3.11/asyncio/tasks.py", line 502, in wait_for
2023-11-27T12:40:36.779135282Z     raise exceptions.TimeoutError() from exc
2023-11-27T12:40:36.779139372Z TimeoutError
2023-11-27T12:40:36.779143382Z 
2023-11-27T12:40:36.779147362Z During handling of the above exception, another exception occurred:
2023-11-27T12:40:36.779151552Z 
2023-11-27T12:40:36.779155762Z Traceback (most recent call last):
2023-11-27T12:40:36.779159872Z   File "/venv/lib/python3.11/site-packages/slidge/command/adhoc.py", line 84, in __wrap_handler
2023-11-27T12:40:36.779164172Z     return await f(*a, **k)
2023-11-27T12:40:36.779168262Z            ^^^^^^^^^^^^^^^^
2023-11-27T12:40:36.779172242Z   File "/venv/lib/python3.11/site-packages/slidge/command/register.py", line 160, in two_fa
2023-11-27T12:40:36.779176572Z     await self.xmpp.validate_two_factor_code(user, form_values["code"])
2023-11-27T12:40:36.779186892Z   File "/venv/lib/python3.11/site-packages/legacy_module/gateway.py", line 96, in validate_two_factor_code
2023-11-27T12:40:36.779191542Z     raise XMPPError(
2023-11-27T12:40:36.779195602Z slixmpp.exceptions.XMPPError: not-authorized

This traceback is a bit dense because it's my full thing and not a MWE, but the interesting part is that await self.api.get_authorization_state(request_id=AUTHORIZATION_REQUEST_ID) times out, despite Received b'{"@type":"authorizationStateWaitTdlibParameters","@extra":{"request_id":"updateAuthorizationState"},"@client_id":2}'.
I did not manage to get anything more fruitful from my investigations.

Any idea how to enable multi-account support?

Aiotdlib doesn't support running multiple clients at the moment.

Although tdlib supports it via its tdjson interface. You can slightly reimplement TDJson class for your use case.

Create multiple clients instead of one and handle "@client_id" field thoroughly instead of dropping those updates like it's implemented now in tdjson.py.

You can read more about this design here

I'll probably add multi-client support in the future, along with Pydantic v2.

Oh, I missed this line. Besides making sure we only call receive() from a single thread at once, and not dropping this client_id field, do you foresee any trouble supporting multiple clients? I'll give it a try, soonish.

I wouldn't name it a trouble.

But we need to make TDJson instance kind of singleton, handling all updates in it in separate single thread.

It might be good to rewrite Client class using pub/sub pattern instead of creating TDJson instance.

It's not a problem I think, but I have no time for now, therefore any pull requests are welcome 🙏