Thanks to visit codestin.com
Credit goes to github.com

Skip to content

MqttCore.connect: return false instead of raising #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

jimmylomro
Copy link

Issue #, if available: 248

Description of changes: When MqttCore.connect_async successfully returns when it is called from MqttCore.connect, self._event_consumer.stop() is never called so neither is self._internal_async_client.stop_background_network_io(). This means that when MqttCore.connect_async returns and not event.wait(self._connect_disconnect_timeout_sec) == True the call to MqttCore.connect will raise an error, but the system might still connect to the broker as the background_network_io is still running. A side effect of this is, if the user tries to call the connect method when it "fails" like this:

connected = False
while not connected:
  try:
    client.connect()
  except connectTimeoutException:
    pass
  else:
    connected = True

The daemon threads spawned by self._start_workers() will be spawned again and thus duplicated.

With these changes, the user will be able to do something like:

if client.connect():
  print('Client connected!')
else:
  print('Client did not connect, but will retry automatically')

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

self._logger.info("Performing sync connect...")
event = Event()
self.connect_async(keep_alive_sec, self._create_blocking_ack_callback(event))
if not event.wait(self._connect_disconnect_timeout_sec):
self._logger.error("Connect timed out")
raise connectTimeoutException()
self._logger.warning("Connect timed out, will try to reconnect")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not really an error as the background_network_io will still try to reconnect later

Copy link
Contributor

@bretambrose bretambrose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thanks for submitting this. I agree that the error behavior here is an unpleasant surprise and could be more intuitive and less error-prone.

I think your second code sample is missing an "raise_on_timeout=False" from the connect call, right?

I'm wondering if we could make a tweak to this though. Right now the solution involves two levels of calls and I feel like it would be clearer to the user (who at best will look at the code of the first connect()) if the behavioral change was limited to the first call. The two approaches would otherwise be equivalent.

So how about this?

  • Undo the changes to mqtt_core's connect()
  • Add a try block to the client's connect() that swallows connectTimeoutException if raiseOnTimeout is false, otherwise rethrows

In this way if the user says "what does raiseOnTimeout do?" they only need to follow a single call. Either way, the new (non-default though) contract for connect()'s result remains:

  1. True if connected
  2. False if not connected but will retry
  3. Exception if not connected and will not retry

One final thing, do Python doc strings allow for a sample section? If so, adding a sample (on the client connect()) showing this new behavior and how it simplifies usage would be a great addition.

@jimmylomro
Copy link
Author

You are completely right, it should be

if client.connect(raise_on_timeout=False):
  print('Client connected!')
else:
  print('Client did not connect, but will retry automatically')

With regards to catching the exception in the client.connect call instead of in mqtt_core.connect, I think I would disagree as mqtt_core is the one initialising the resources and thus it should be the one to clean them. Also, it would be adding a third place where behaviours change and would become a bit messy. Otherwise I do agree it would be more readable the way you suggest. Can make the changes if you think are necessary though...

@bretambrose
Copy link
Contributor

You are completely right, it should be

if client.connect(raise_on_timeout=False):
  print('Client connected!')
else:
  print('Client did not connect, but will retry automatically')

With regards to catching the exception in the client.connect call instead of in mqtt_core.connect, I think I would disagree as mqtt_core is the one initialising the resources and thus it should be the one to clean them. Also, it would be adding a third place where behaviours change and would become a bit messy. Otherwise I do agree it would be more readable the way you suggest. Can make the changes if you think are necessary though...

Can you explain a bit further about the resource clean up? My impression from reading the code is that no cleanup behavior is modified whether the new parameter is true or false and all we're doing is clarifying the return value contract of connect(). How would the second version make cleanup not the responsibility of mqtt_core?

@jimmylomro
Copy link
Author

That was some poor explanation on my side, apologies: what I mean is, if mqtt_core raisesconnectTimeoutException, then it should do a cleanup similar to what is done in connect_async to finish the daemon threads; if there is no cleanup, and the error is not caught or connect is called again, there will be some hanging threads left behind. And so, if you leave the responsibility of catching connectTimeoutException to client.connect then you should also do the cleanup of the threads in client.connect. Those threads were spawned by mqtt_core.connect and thus, in my opinion, should be cleaned in the same place before the exception is raised.

I did not make those changes part of this PR as that is more of an opinion on the semantics of what object is responsible for what.

@github-actions
Copy link

Greetings! Sorry to say but this is a very old issue that is probably not getting as much attention as it deserves. We encourage you to try V2 and if you find that this is still a problem, please feel free to open a new issue there.

@github-actions github-actions bot added closing-soon This issue will automatically close in 4 days unless further comments are made. closed-for-staleness and removed closing-soon This issue will automatically close in 4 days unless further comments are made. labels Oct 20, 2020
@github-actions github-actions bot closed this Oct 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants