Skip to content

Pitfalls

Bert Kleewein edited this page Jun 16, 2020 · 1 revision

work in progress

Common pitfalls using this library

Sample code can hide errors because of lack of exception handling.

The usage pattern established in current sample code allows for exceptions to be swallowed and go unnoticed. Using async-hub-scenarios/receive_message.py, for example:

    # define behavior for receiving a message
    async def message_listener(device_client):
        while True:
            message = await device_client.receive_message()  # blocking call
            # NOT SHOWN: code to handle message

    # Schedule task for message listener
    asyncio.create_task(message_listener(device_client))

Any exceptions that heppen inside the message_listener coroutine will be silently ignored. This happens because the code that calls create_task never checks to make sure the returned Task is still running (by using await or by calling the done(), result(), or exception() methods on the Task.) If this happens, it will appear to the user that messages are not being received. In reality, the message_listener() task is no longer running.

The fix is to either add a try/except block around message_listener which notifies the developer, or to update the code to watch the task for errors.

This problem is made more difficult in samples that use the asyncio.gather method because one of the tasks being gathered might raise an exception while the others continue to execute. Detecting and recovering from this condition is left as an exercise to the reader.

Async API cancel method not implemented

The cancel method on tasks that are returned from the async API is not implemented. It will appear to functoin, but no actual functionality will be impacted. This includes implicit cancel operations, such as the one used inside the asyncio.wait_for method.

As an example:

    send_result = asyncio.wait_for(client.send_message(msg, 10))

If client.send_message does not complete within 10 seconds, this code will raise an asyncio.TimeoutError but the sending of the message will not be cancelled. The client object will continue to attempt sending this message until the message is successfully sent or the client shuts down.

It's easy to create multiple client instances and cause instability

This problem is improved by the changes in #576, but there is still the possibility of having multiple clients which fight with each other over resources.

For example:

    client = IoTHubDeviceClient.create_from_connection_string(conn_str)
    client.send_message(msg)

    # some time passes

    client = IoTHubDeviceClient.create_from_connection_string(conn_str)
    client.send_message(msg2)

If you do this, there is a chance that you will have 2 client instances active at one time. This can lead to the 2 instances "fighting" over the network connection. In the example above, the second client will connect, which will cause IoTHub to forcefully close the first client's connection. The first client will set a reconnect timer and attempt to reconnect. When the first client reconnects, IoTHub will force the second client's connection closed, which will cause the second client to set a reconnect timer, and so on.

There are two workarounds:

  1. Don't ever create two client objects.
  2. Call the disconnect method when you are done using a client. This will cause the client to stop reconnecting.

Before #576 was committed, the disconect method wasn't sufficient to prevent the client from reconnecting.