gmqtt
gmqtt copied to clipboard
Unhandled ConnectionRefusedError
If you run properties example with host that is not running broker (eg localhost), program will crash
Traceback (most recent call last):
File "properties.py", line 98, in <module>
loop.run_until_complete(main('localhost', port, None))
File "/usr/lib64/python3.7/asyncio/base_events.py", line 579, in run_until_complete
return future.result()
File "properties.py", line 51, in main
await sub_client.connect(broker_host, broker_port)
File "/home/d21d3q/workspace/gmqtt/venv/lib/python3.7/site-packages/gmqtt-0.4.0-py3.7.egg/gmqtt/client.py", line 144, in connect
File "/home/d21d3q/workspace/gmqtt/venv/lib/python3.7/site-packages/gmqtt-0.4.0-py3.7.egg/gmqtt/client.py", line 160, in _create_connection
File "/home/d21d3q/workspace/gmqtt/venv/lib/python3.7/site-packages/gmqtt-0.4.0-py3.7.egg/gmqtt/mqtt/connection.py", line 26, in create_connection
File "/usr/lib64/python3.7/asyncio/base_events.py", line 954, in create_connection
raise exceptions[0]
File "/usr/lib64/python3.7/asyncio/base_events.py", line 941, in create_connection
await self.sock_connect(sock, address)
File "/usr/lib64/python3.7/asyncio/selector_events.py", line 464, in sock_connect
return await fut
File "/usr/lib64/python3.7/asyncio/selector_events.py", line 494, in _sock_connect_cb
raise OSError(err, f'Connect call failed {address}')
ConnectionRefusedError: [Errno 111] Connect call failed ('127.0.0.1', 1883)
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Client._resend_qos_messages() running at /home/d21d3q/workspace/gmqtt/venv/lib/python3.7/site-packages/gmqtt-0.4.0-py3.7.egg/gmqtt/client.py:96> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f238afa0f10>()]>>
Expected behavior would be to try to reconnect normally.
workaround:
try:
await pub_client.connect(broker_host, broker_port)
except ConnectionRefusedError:
asyncio.ensure_future(pub_client.reconnect(delay=True))
Actually this is expected behavior. Reconnects are good when you client is working, but connection at the moment becomes broken and after some time your client automatically reconnects. If the first connection attempt was unsuccessful, it probably means that configuration is bad, host, port or authorization method should be fixed.
Hmmm, my use case is to embed mqtt client in IoT device and let it go into the wild, and report some data. In such case I would except from program (device), to warn me about bad configuration, but keep retrying to connect without crashing. I am configuring device once (asserting that it is working), and letting him live its own life (like gps trackers :) ). But there are other factors that might fail and device should not care about them. Imagine that your device reboots during such conditions
- broker goes offline (crash/update) - it is temporary unavailable, but
connect
fails due toConnectionRefusedError
- network goes down - temporarily, but
connect
fails due tosocket.gaierror
- somebody deletes (mqtt) user credentails on server, and later restore them,
So if device reboots and goes through connect
while one of above edge cases occur, then program would crash.
However when all exceptions on connect
are handled and reconnect
is manually rescheduled then it works (as suggesed).
That is why I am suggesting that connect
should reconnect automatically but notify rest of program about connection failure through some callback.
Hey @d21d3q
I added kwarg raise_exc=True
to client.connect
method. Together with unlimitted reconnects this should work in your case. LEt me know if it helps :slightly_smiling_face:
@d21d3q does this resolve your problem? Can this issue be closed?
I will have a look at it on Monday. Thank you.
No, this is not the correct way of handling this. If you take a look at the reconnect
function you will see that it handles OSError
and connect
function does not. This is why the client explodes when broker is not online and you try to connect.
Workaround is something like this:
client = MQTTClient("client_id")
while True:
try:
await client.connect(broker_host)
except OSError:
print(f'Can not connect to the broker at {broker_host}')
await asyncio.wait([asyncio.sleep(5), STOP.wait()], return_when=asyncio.FIRST_COMPLETED)
if STOP.is_set():
sys.exit()
else:
break
await STOP.wait()
await client.disconnect()
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033
If a server sends a CONNACK packet containing a non-zero return code it MUST then close the Network Connection [MQTT-3.2.2-5]
gmqtt doesn't implement the MQTT standard for this particular point.
@NICOLASGON Specification tells server must close the connection after it sends bad CONNACK package. gmqtt is about client relaization and docs do not specify any required actions after receiving bad connack code.
@Lenka42 - Using the current master branch (as of 25 FEB 2022), what is the correct answer?
This works:
try: await lLocalMRClient.connect("localhost", 1884, keepalive=60) except ConnectionRefusedError: asyncio.ensure_future(lLocalMRClient.reconnect(delay=True))
as does:
try: await lLocalMRClient.connect("localhost", 1884, keepalive=60, raise_exc=True) except: asyncio.ensure_future(lLocalMRClient.reconnect(delay=True))
And I see above the suggestion to catch OSError