Skip to content

MQTTAsync_reconnect() doesn't reset serverUri index to 0. The client can be stuck on the last server uri of the configuration list. #1661

Description

@tnielens

Describe the bug

I expect MQTTAsync_reconnect() to retry connections to all serverUris configured for my client.
In practice only the last server uri in the list is tried with MQTTAsync_reconnect when the client already failed over to the last available.
Same remark for the autoreconnect client option. I expect autoreconnect to start cycling over the server uris from the start and not stay stuck on the last uri in the list when the client failed over to the last available.

To Reproduce
Here is a program to illustrate the behavior. Requires docker/podman to run mosquitto and toxiproxy.
It's AI-generated. It ran fine on my side.

/*
 * Reproducer: MQTTAsync_reconnect does not restart the server URI cycling from the beginning.
 *
 * Expected behavior: when a client is configured with serverURIs=[A, B] and
 * MQTTAsync_reconnect is called, it should iterate over all configured URIs starting
 * from the first one, retrying until one succeeds.
 *
 * Actual behavior: MQTTAsync_reconnect doesnt modify the serverUri index, 
 * and reconnecting is only attempted again the last serverUri in the list. 
 *
 * Workaround: MQTTAsync_disconnect followed by MQTTAsync_connect resets the URI
 * index to 0 and iterates from the beginning.
 *
 * Dependencies: paho-mqtt-c (async), mosquitto broker, toxiproxy 2.x
 *
 * Setup (docker/podman):
 *   docker run -d --name mosquitto -p 1883:1883 eclipse-mosquitto:1.6
 *   docker run -d --name toxiproxy -p 8474:8474 -p 1884:1884 -p 1885:1885 \
 *     ghcr.io/shopify/toxiproxy:2.9.0
 *   curl -s -X POST http://localhost:8474/populate -H 'Content-Type: application/json' \
 *     -d '[{"name":"mqtt","listen":"0.0.0.0:1884","upstream":"mosquitto:1883","enabled":true},
 *          {"name":"mqtt2","listen":"0.0.0.0:1885","upstream":"mosquitto:1883","enabled":true}]'
 */

#include <MQTTAsync.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>

/* ---- assert ---- */

#define ASSERT(cond, msg) do { \
    if (!(cond)) { fprintf(stderr, "FAIL [%s:%d]: %s\n", __FILE__, __LINE__, msg); exit(1); } \
    else { printf("PASS: %s\n", msg); } \
} while(0)

/* ---- toxiproxy control via curl(1) ---- */

static void toxi(const char *method, const char *path, const char *body) {
    char cmd[512];
    if (body)
        snprintf(cmd, sizeof(cmd),
            "curl -sf -X %s http://localhost:8474%s "
            "-H 'Content-Type: application/json' -d '%s' > /dev/null", method, path, body);
    else
        snprintf(cmd, sizeof(cmd),
            "curl -sf -X %s http://localhost:8474%s > /dev/null", method, path);
    if (system(cmd) != 0) { fprintf(stderr, "toxiproxy request failed: %s\n", cmd); exit(1); }
}

/* ---- callbacks ---- */

typedef struct {
    volatile bool connected;
    volatile bool connect_failed;
} conn_ctx_t;

static void on_connect(void *ctx, MQTTAsync_successData *r) {
    (void)r; ((conn_ctx_t *)ctx)->connected = true;
}
static void on_connect_fail(void *ctx, MQTTAsync_failureData *r) {
    (void)r; ((conn_ctx_t *)ctx)->connect_failed = true;
}
static void on_conn_lost(void *ctx, char *cause) {
    (void)ctx; (void)cause;
}
static void on_disconnect(void *ctx, MQTTAsync_successData *r) {
    (void)r; *(int *)ctx = 1;
}

/* ---- wait helpers ---- */

#define WAIT_TRUE(expr, ms) do { \
    for (int _i = 0; _i < (ms)/10 && !(expr); _i++) usleep(10000); \
} while(0)

/* ---- main ---- */

int main(void) {
    const char *uris[] = {"tcp://localhost:1884", "tcp://localhost:1885"};
    conn_ctx_t conn = {0};

    MQTTAsync client;
    MQTTAsync_create(&client, uris[0], "repro_client", MQTTCLIENT_PERSISTENCE_NONE, NULL);
    MQTTAsync_setCallbacks(client, &conn, on_conn_lost, NULL, NULL);

    MQTTAsync_connectOptions co = MQTTAsync_connectOptions_initializer;
    co.cleansession     = 1;
    co.automaticReconnect = 0;
    co.serverURIs       = (char **)uris;
    co.serverURIcount   = 2;
    co.onSuccess        = on_connect;
    co.onFailure        = on_connect_fail;
    co.context          = &conn;

    /* start with only 1884 up */
    toxi("POST", "/proxies/mqtt",  "{\"enabled\":true}");
    toxi("POST", "/proxies/mqtt2", "{\"enabled\":false}");

    MQTTAsync_connect(client, &co);
    WAIT_TRUE(conn.connected || conn.connect_failed, 3000);
    ASSERT(MQTTAsync_isConnected(client), "initial connect on 1884");

    /* take 1884 down */
    toxi("POST", "/proxies/mqtt", "{\"enabled\":false}");
    WAIT_TRUE(!MQTTAsync_isConnected(client), 3000);
    ASSERT(!MQTTAsync_isConnected(client), "disconnected after 1884 went down");

    /* bring 1885 up, reconnect — paho tries next URI (1885), succeeds */
    toxi("POST", "/proxies/mqtt2", "{\"enabled\":true}");
    conn.connected = false; conn.connect_failed = false;
    MQTTAsync_reconnect(client);
    WAIT_TRUE(conn.connected || conn.connect_failed, 3000);
    ASSERT(MQTTAsync_isConnected(client), "reconnected via 1885");

    /* take 1885 down, bring 1884 up */
    toxi("POST", "/proxies/mqtt2", "{\"enabled\":false}");
    toxi("POST", "/proxies/mqtt",  "{\"enabled\":true}");
    WAIT_TRUE(!MQTTAsync_isConnected(client), 3000);
    ASSERT(!MQTTAsync_isConnected(client), "disconnected after 1885 went down");

    /* reconnect — expected to restart cycle from 1884, but URI index is not reset:
       paho tries 1885 again (stuck at last index), fails, stays disconnected */
    conn.connected = false; conn.connect_failed = false;
    MQTTAsync_reconnect(client);
    WAIT_TRUE(conn.connected || conn.connect_failed, 3000);
    ASSERT(!MQTTAsync_isConnected(client),
        "BUG: reconnect did not restart cycle from 1884 — stayed stuck on 1885");

    /* bring 1885 back, reconnect — succeeds on 1885 */
    toxi("POST", "/proxies/mqtt2", "{\"enabled\":true}");
    conn.connected = false; conn.connect_failed = false;
    MQTTAsync_reconnect(client);
    WAIT_TRUE(conn.connected || conn.connect_failed, 3000);
    ASSERT(MQTTAsync_isConnected(client), "reconnected via 1885 after it came back");

    /* disconnect + connect (not reconnect) — resets URI index to 0, connects to 1884 */
    int disc = 0;
    MQTTAsync_disconnectOptions dco = MQTTAsync_disconnectOptions_initializer;
    dco.onSuccess = on_disconnect; dco.context = &disc;
    MQTTAsync_disconnect(client, &dco);
    WAIT_TRUE(disc, 3000);

    toxi("POST", "/proxies/mqtt2", "{\"enabled\":false}"); /* only 1884 available */
    toxi("POST", "/proxies/mqtt",  "{\"enabled\":true}");
    conn.connected = false; conn.connect_failed = false;
    MQTTAsync_connect(client, &co);
    WAIT_TRUE(conn.connected || conn.connect_failed, 3000);
    ASSERT(MQTTAsync_isConnected(client),
        "WORKAROUND: disconnect+connect resets URI cycle, reconnects to 1884");

    MQTTAsync_destroy(&client);
    printf("done.\n");
    return 0;
}

Expected behavior
When MQTTAsync_reconnect() is called, the serverUri index should be reset to 0.

Environment (please complete the following information):

  • OS: fedora 43

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions