Skip to content

Commit

Permalink
Merge pull request #2 from gfurtadoalmeida/transport-reconnect
Browse files Browse the repository at this point in the history
Add transport reconnection on write and read
  • Loading branch information
gfurtadoalmeida authored Jan 4, 2025
2 parents 385131c + e46c953 commit 2a32b68
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 32 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ jobs:
with:
allowUpdates: true
artifacts: "${{env.ZIP_NAME}}"
generateReleaseNotes: true
makeLatest: true
name: ESP32 IoT Azure ${{github.ref_name}}
removeArtifacts: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extern "C"
#endif

/**
* @typedef parsed_file_url_t
* @brief URL of a parsed @ref AzureIoTADUUpdateManifestFileUrl_t.
*/
typedef struct
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ extern "C"
* @brief Establish a connection to a server.
* @note The connection must be closed by @ref transport_disconnect.
* @param[in] transport Transport context.
* @param[in] hostname Server address. Must be null-terminated.
* @param[in] hostname Server address. Must be null-terminated and remain in memory as long as the connection is active.
* @param[in] port Server port.
* @param[in] timeout_ms Connection timeout in milliseconds.
* @return @ref transport_status_t with the result of the operation.
Expand Down
124 changes: 93 additions & 31 deletions components/esp32_iot_azure/src/infrastructure/transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@

static const char TAG_TRANSPORT[] = "AZ_TRANSPORT";

static transport_status_t transport_reconnect(transport_t *transport);
static bool should_try_reconnection(int error_num);

struct transport_t
{
esp_transport_handle_t handle; /** @brief ESP transport handle. */
const char *hostname; /** @brief Server address. Must be null-terminated. */
uint16_t port; /** @brief Server port. */
uint16_t timeout_ms; /** @brief Connection timeout in milliseconds. */
};

transport_t *transport_create_tcp()
Expand Down Expand Up @@ -96,37 +102,11 @@ transport_status_t transport_connect(transport_t *transport,
uint16_t port,
uint16_t timeout_ms)
{
uint16_t next_backoff_ms = 0U;
transport_status_t transport_status = TRANSPORT_STATUS_FAILURE;
backoff_algorithm_status_t backoff_status = BACKOFF_ALGORITHM_SUCCESS;
backoff_algorithm_context_t backoff_context;

backoff_algorithm_initialize(&backoff_context,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_BASE_MS,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_MAX_DELAY_MS,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_RETRY_MAX_ATTEMPTS);
do
{
if (esp_transport_connect(transport->handle, hostname, port, timeout_ms) != 0)
{
CMP_LOGW(TAG_TRANSPORT, "failure connecting to %s on %d: %d", hostname, port, esp_transport_get_errno(transport->handle));

backoff_status = backoff_algorithm_get_next(&backoff_context, &next_backoff_ms);
transport->hostname = hostname;
transport->port = port;
transport->timeout_ms = timeout_ms;

if (backoff_status == BACKOFF_ALGORITHM_SUCCESS)
{
CMP_LOGW(TAG_TRANSPORT, "will retry in %d ms", next_backoff_ms);

vTaskDelay(pdMS_TO_TICKS(next_backoff_ms));
}
}
else
{
transport_status = TRANSPORT_STATUS_SUCCESS;
}
} while (transport_status != TRANSPORT_STATUS_SUCCESS && backoff_status == BACKOFF_ALGORITHM_SUCCESS);

return transport_status;
return transport_reconnect(transport);
}

int32_t transport_write(transport_t *transport,
Expand All @@ -136,6 +116,18 @@ int32_t transport_write(transport_t *transport,
{
int result = esp_transport_write(transport->handle, (const char *)buffer, length, timeout_ms);

if (result > -1)
{
return result;
}

if (should_try_reconnection(esp_transport_get_errno(transport->handle)))
{
transport_reconnect(transport);

result = esp_transport_write(transport->handle, (const char *)buffer, length, timeout_ms);
}

if (result < 0)
{
CMP_LOGE(TAG_TRANSPORT, "failure writing: %d", esp_transport_get_errno(transport->handle));
Expand All @@ -151,6 +143,18 @@ int32_t transport_read(transport_t *transport,
{
int result = esp_transport_read(transport->handle, (char *)buffer, expected_length, timeout_ms);

if (result > -1)
{
return result;
}

if (should_try_reconnection(esp_transport_get_errno(transport->handle)))
{
transport_reconnect(transport);

result = esp_transport_read(transport->handle, (char *)buffer, expected_length, timeout_ms);
}

if (result < 0)
{
CMP_LOGE(TAG_TRANSPORT, "failure reading: %d", esp_transport_get_errno(transport->handle));
Expand All @@ -172,4 +176,62 @@ void transport_free(transport_t *transport)
esp_transport_destroy(transport->handle);

free(transport);
}
}

static transport_status_t transport_reconnect(transport_t *transport)
{
uint16_t next_backoff_ms = 0U;
transport_status_t transport_status = TRANSPORT_STATUS_FAILURE;
backoff_algorithm_status_t backoff_status = BACKOFF_ALGORITHM_SUCCESS;
backoff_algorithm_context_t backoff_context;

transport_disconnect(transport);

backoff_algorithm_initialize(&backoff_context,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_BASE_MS,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_MAX_DELAY_MS,
CONFIG_ESP32_IOT_AZURE_TRANSPORT_BACKOFF_RETRY_MAX_ATTEMPTS);
do
{
CMP_LOGI(TAG_TRANSPORT, "connecting to '%s' on %d", transport->hostname, transport->port);

if (esp_transport_connect(transport->handle, transport->hostname, transport->port, transport->timeout_ms) != 0)
{
CMP_LOGW(TAG_TRANSPORT, "failure connecting: %d", esp_transport_get_errno(transport->handle));

backoff_status = backoff_algorithm_get_next(&backoff_context, &next_backoff_ms);

if (backoff_status == BACKOFF_ALGORITHM_SUCCESS)
{
CMP_LOGW(TAG_TRANSPORT, "will retry in %d ms", next_backoff_ms);

vTaskDelay(pdMS_TO_TICKS(next_backoff_ms));
}
}
else
{
CMP_LOGI(TAG_TRANSPORT, "connected");

transport_status = TRANSPORT_STATUS_SUCCESS;
}
} while (transport_status != TRANSPORT_STATUS_SUCCESS && backoff_status == BACKOFF_ALGORITHM_SUCCESS);

return transport_status;
}

static bool should_try_reconnection(int error_num)
{
switch (error_num)
{
case EPIPE: // Writing to a closed connection.
case ECONNRESET: // The connection was closed by the remote peer.
case EADDRINUSE: // The local address is in use. Might occur if trying to reconnect too quickly. Wait before retrying.
case ENETUNREACH: // The local network interface is unavailable. Reconnection might not succeed until the network is restored.
case ETIMEDOUT: // The connection attempt or existing connection timed out.
case EHOSTUNREACH: // The remote host is unreachable. Reconnect after a delay or check network availability.
case ENOTCONN: // The transport is no longer connected.
return true;
default:
return false;
}
}

0 comments on commit 2a32b68

Please sign in to comment.