Error handling

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) {
  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) {
  ...
  if ((ev == MG_EV_ERROR) || (ev == MG_EV_CLOSE)) {
    *(bool *) c->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) {
  if (ev == MG_EV_OPEN) {
    // Connection created. Store connect expiration time in c->data
    *(uint64_t *) c->data = mg_millis() + s_timeout_ms;
  } else if (ev == MG_EV_POLL) {
    if (mg_millis() > *(uint64_t *) c->data &&
        (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:

  • Declare a variable for that client connection on a file scope, for example:

    struct mg_connection *s_conn = NULL;
    
  • Create a periodic timer that checks that variable. If it is NULL (means disconnected), timer function reconnects:

    static void timer_fn(void *arg) {
      struct mg_mgr *mgr = (struct mg_mgr *) arg; // Pass event manager to us!
      ...
      if (s_conn == NULL) {
        s_conn = mg_mqtt_connect(mgr, "mqtt://...", 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 to the timer_fn();

  • In the client connection event handler function, reset the connection variable to NULL when a connection closes:

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

Example code

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

Handling memory usage

Mongoose does not impose limits by itself; send and receive buffers, and the number of accepted connections, can grow indefinitely. If you need to keep a bound on them, you can do that in your respective event handlers.

Too many connections

To control the number of accepted connections, check it in your server event handler and close new connections:

static void fn(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_ACCEPT) {
    if (mg_mgr_numconns(c->mgr) > LIMIT) {
      MG_ERROR(("Too many connections"));
      c->is_closing = 1; // close this new connection
    }
  }
  ...
}

Receive buffer growth

To limit memory usage on receive buffers, check for growth in your event handler:

static void fn(struct mg_connection *c, int ev, void *ev_data) {
  if (ev == MG_EV_READ) {
    if (c->recv.len > LIMIT) {
      MG_ERROR(("Msg too large"));
      c->is_draining = 1; // close this connection
    }
  }
  ...
}

Transmit buffer growth

Mongoose internally shapes traffic when serving large files, you don't need to worry about this unless you are dynamically sending data; for example from a timer event handler or some other situation when you just send unsolicited data.

To limit transmit buffer growth, do it in your respective event handler:

static void timer_fn(void *arg) {
  struct mg_connection *c = (struct mg_connection *) arg;
  ...
  if (c->send.len > LIMIT) {
    MG_ERROR(("Stalled"));
  } else {
    // mg_send, mg_ws_send ... (c, ...)
  }
  ...
}