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 🙏