xHasKx / luamqtt

luamqtt - Pure-lua MQTT v3.1.1 and v5.0 client
https://xhaskx.github.io/luamqtt/
MIT License
154 stars 41 forks source link

Connect is blocking #23

Open Python1320 opened 4 years ago

Python1320 commented 4 years ago

Our broker was recently DDoSed, which caused a disconnect on our luamqtt end, and a reconnect attempt where the connecting routine took over a minute after which our service was automatically killed for being frozen by our watchdog. The stacktrace showed us that in fact the ioloop is not used for connecting in mqtt: https://github.com/xHasKx/luamqtt/blob/929209a2b9d037a77d9f9e28fc8c72429b5c7dfd/mqtt/client.lua#L730 (settimeout is only applied later)

Is this an oversight or by design? Would a rearchitecturing be out of the question so that connect would be asynchronous also? (Connect will always block if there is DNS resolving to be done so this may also not be the perfect solution for everyone). Another option is we could establish a connection ourselves and just provide the connected socket to luamqtt.

xHasKx commented 4 years ago

@Python1320 , good point. I'll think about how to make connector.connect call non-blocking...

xHasKx commented 4 years ago

@Python1320 , I found a way how to use settimeout() on almost all stages of the connection lifetime (except DNS resolving). But this will be a breaking change and actually it will use socket.select() technique, which may suffer if you plan to handle hundreds or thousands of simultaneous connections. If you plan to handle such highload - you should consider using another mqtt framework with ioloop builtin into its runtime, like gmqtt for Pyhton or mqtt.js for nodejs.

On the other hand, luamqtt library is built to be small and simple, that's why it does still not have ioloop on all connection stages.

Do you still plan to use it in your project?

rafale77 commented 4 years ago

I have been struggling with a similar problem, I can't seem to be able to keep a client based on this library connected and listening to the broker for messages without blocking the lua socket. It makes it unusable even in my very simple use case of one publishing client, one listening client with one broker. Am I missing something?

xHasKx commented 4 years ago

@rafale77 , currently only TCP Connection opening process is blocking and all next stages is async thanks to ioloop, allowing other MQTT clients to work in the same lua script. Did you used mqtt.run_ioloop(client)?

rafale77 commented 4 years ago

Yes I did and I observed that it opens a listener and waits for a message. While waiting, it does block my main program's socket activities.

xHasKx commented 4 years ago

Yes I did and I observed that it opens a listener and waits for a message. While waiting, it does block my main program's socket activities.

That's because mqtt.run_ioloop(client) is running an infinity loop to call your mqtt messages handlers when new MQTT messages arrived over TCP connection. Which runtime is running your code?

If you want to run some parallel calculations or network communications in the same script with luamqtt - you have several options:

Python1320 commented 4 years ago

I am slowly trying out https://github.com/flukso/lua-mosquitto but we are still using luamqtt, but with a 2 second timeout on connect and exponential backoff. A broken MQTT connection is better in my case than a fully frozen process :)

Keeping things simple and flexible is indeed difficult.

In my case I need many things to cooperate at the same time and MQTT freezing the other parts is not acceptable. Imagine a light switch: I want to be able to toggle the light on and off by polling the button status, but I also want to maintain a MQTT connection to tell about the switch status changes. Creating a thread for mqtt sounds like an overkill in this case, but indeed, MQTT must not block toggling the light switch on network troubles.

xHasKx commented 4 years ago

Did you try OpenResty - https://openresty.org/ ? It's Lua engine has async sockets and supported in luamqtt with appropriate connector

xHasKx commented 4 years ago

Anyway, there is a way to provide a new method in luamqtt to start connecting async like described in https://github.com/xHasKx/luamqtt/issues/23#issuecomment-653740404 I'll try to implement it on the next week

Tieske commented 2 years ago

@Python1320 The main "connect" problem is here; https://github.com/xHasKx/luamqtt/blob/master/mqtt/luasocket.lua#L12

It uses socket.connect shortcut, which is blocking without a timeout. Can be circumvented easily by using code like this;

local sock = socket.tcp()
sock:settimeout(timeout)
local ok, err = sock:connect(host, port)   --> calling `connect` on the socket, NOT the library!

The call will still be blocking, but at least it will timeout at some point and not just hang. If you use an IP instead of a name, you can take out the DNS resolution call as well.

@xHasKx As for using a select based implementation, that is pretty hard. Essentially you would rewrite Copas inside this library. So imo the better option is to keep this simple, and for non-blocking sockets refer people to Copas.