Overview

Sometimes not everything goes right and a connection fails for some reason. The host could be down, the network cable can be cut off during the request, network congestion might have arised, what have you. Mongoose Library will let you catch that condition and act accordingly.

Generic error handling

The right way to catch errors is to handle the MG_EV_ERROR event. An error reason is passed as a char * pointer in parameter ev_data:

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_ERROR) {
    printf("Error: %s", (char *) ev_data);
  }

Note that MG_EV_CLOSE, which will be called on normal connection closures, will also be called after MG_EV_ERROR (unless you signal the event manager to exit, of course).

If you need to exit the event manager after an error or other condition, check a variable that can be passed a pointer to the connection handler, and mark it there:

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  ...
  if ((ev == MG_EV_ERROR) || (ev == MG_EV_CLOSE)) {
    *(bool *) fn_data = true;               // Tell event loop to stop
  }
}

int main(int argc, char *argv[]) {
  bool done = false;                        // Event handler flips it to true
   ...
  mg_mgr_init(&mgr);                        // Initialise event manager
  mg_http_connect(&mgr, s_url, fn, &done);  // Create client connection, pass a pointer to 'done' 
  while (!done) mg_mgr_poll(&mgr, 50);      // Event manager loops until 'done'
  mg_mgr_free(&mgr);                        // Free resources
  return 0;
}

Don't forget to free resources.

Handling connection timeouts

TCP-based protocols will timeout depending on your TCP/IP stack implementation, triggering an MG_EV_ERROR event. You can always retry if time is not long enough for your application.

For other protocols, application control, shorter times, etc., connect timeout should be done manually; but we can take advantage of Mongoose's architecture.

  • When a connection is created, the client callback function will receive an MG_EV_OPEN event.
  • Periodically, the client callback function will receive an MG_EV_POLL event.
    • Check the current timestamp and the is_connecting and is_resolving flags.
    • Close the connection if it takes too long, by calling mg_error(). This function call will request a connection closure and fire the MG_EV_ERROR event.
static const uint64_t s_timeout_ms = 1500;  // Connect timeout in milliseconds

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_OPEN) {
    // Connection created. Store connect expiration time in c->label
    *(uint64_t *) c->label = mg_millis() + s_timeout_ms;
  } else if (ev == MG_EV_POLL) {
    if (mg_millis() > *(uint64_t *) c->label &&
        (c->is_connecting || c->is_resolving)) {
      mg_error(c, "Connect timeout");
    }
  }

It is important to keep the event manager polling timeout smaller than the timeout interval and the desired tolerance; if there are no network events, the event manager would sleep for the whole polling interval and if it is larger than desired, the event firing would be late.

int main(int argc, char *argv[]) {
  ...
  for (;;) mg_mgr_poll(&mgr, 50);           // Infinite loop, poll every 50ms

Connection retries

For any client connection that must be kept alive, follow this pattern:

  • Client connection functions return a pointer to a struct mg_connection, or NULL on failure

  • Check this variable inside a timer event handler function, and start the connection if NULL

    static struct mg_connection *s_conn;
    
    static void timer_fn(void *arg) {
      struct mg_mgr *mgr = (struct mg_mgr *) arg;
      ...
      if (s_conn == NULL) s_conn = mg_mqtt_connect(mgr, s_url, &opts, fn, NULL);
    
  • To start the connection immediately the first time, create the timer with the MG_TIMER_RUN_NOW flag

    int main(int argc, char *argv[]) {
      struct mg_mgr mgr;
      ...
      mg_mgr_init(&mgr);
      mg_timer_add(&mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr);
    

    Note that we pass a pointer to the event manager structure, so it will be passed back to the timer event handler in order for it to be able to pass it to the client connection function

  • On connection closure, set s_conn to NULL. The timer event handler function will see this and retry

    static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
      ...
      if (ev == MG_EV_CLOSE) {
        s_conn = NULL;
    

Example code

The HTTP client and MQTT client examples implement these concepts, check them out.