MONGOC_CURSORS(3)

πŸ“¦
version 1.26.0 Β· 3 versions
, in ubuntu noble

πŸ’– MONGOC_CURSORS πŸ’–

okay bestie, welcome to MONGOC_CURSORS(3) from the libmongoc girlies ✨ this one's all about cursors and how to not get emotionally wrecked by them πŸŽ€


🌸 HANDLING CURSOR FAILURES 🌸

so like, cursors literally live on a MongoDB server, right? but the mongoc_cursor_t structure is the little handle your local process gets to hold onto that cursor 🫢 and here's the thing β€” errors can pop off on the server while you're iterating that cursor on the client. and yeah, a whole network partition could even happen. no cap. so your app needs to be robust about cursor failures, like genuinely πŸ’…

while you're iterating cursors, you gotta check whether an error happened. peep this example for how to robustly check for errors:

static void
print_all_documents (mongoc_collection_t *collection)
{
   mongoc_cursor_t *cursor;
   const bson_t *doc;
   bson_error_t error;
   bson_t query = BSON_INITIALIZER;
   char *str;

   cursor = mongoc_collection_find_with_opts (collection, query, NULL, NULL);

   while (mongoc_cursor_next (cursor, &doc)) {
      str = bson_as_canonical_extended_json (doc, NULL);
      printf ("%s\n", str);
      bson_free (str);
   }

   if (mongoc_cursor_error (cursor, &error)) {
      fprintf (stderr, "Failed to iterate all documents: %s\n", error.message);
   }

   mongoc_cursor_destroy (cursor);
}

πŸ¦‹ DESTROYING SERVER-SIDE CURSORS πŸ¦‹

the MongoDB C driver will automatically destroy a server-side cursor when mongoc_cursor_destroy() gets called ✨ but listen β€” if you don't call this function when you're done with a cursor, it's gonna leak memory client side and consume extra memory server side 😭 and if the cursor was configured to never timeout? it becomes a full-on memory leak on the server. not the vibe. πŸ’€


🌈 TAILABLE CURSORS 🌈

tailable cursors are the main characters that stay open even after they've returned a final result πŸ‘‘ so if more documents get added to a collection (i.e., to the cursor's result set), you can keep calling mongoc_cursor_next() to retrieve those extra results. obsessed.

here's a complete test case that demonstrates the use of tailable cursors 🧚

NOTE: Tailable cursors are for capped collections only. πŸŽ€

an example to tail the oplog from a replica set:

mongoc-tail.c

#include <bson/bson.h>
#include <mongoc/mongoc.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#define sleep(_n) Sleep ((_n) *1000)
#endif

static void
print_bson (const bson_t *b)
{
   char *str;

   str = bson_as_canonical_extended_json (b, NULL);
   fprintf (stdout, "%s\n", str);
   bson_free (str);
}

static mongoc_cursor_t *
query_collection (mongoc_collection_t *collection, uint32_t last_time)
{
   mongoc_cursor_t *cursor;
   bson_t query;
   bson_t gt;
   bson_t opts;

   BSON_ASSERT (collection);

   bson_init (&query);
   BSON_APPEND_DOCUMENT_BEGIN (&query, "ts", &gt);
   BSON_APPEND_TIMESTAMP (&gt, "$gt", last_time, 0);
   bson_append_document_end (&query, &gt);

   bson_init (&opts);
   BSON_APPEND_BOOL (&opts, "tailable", true);
   BSON_APPEND_BOOL (&opts, "awaitData", true);

   cursor = mongoc_collection_find_with_opts (collection, &query, &opts, NULL);

   bson_destroy (&query);
   bson_destroy (&opts);

   return cursor;
}

static void
tail_collection (mongoc_collection_t *collection)
{
   mongoc_cursor_t *cursor;
   uint32_t last_time;
   const bson_t *doc;
   bson_error_t error;
   bson_iter_t iter;

   BSON_ASSERT (collection);

   last_time = (uint32_t) time (NULL);

   while (true) {
      cursor = query_collection (collection, last_time);
      while (!mongoc_cursor_error (cursor, &error) &&
             mongoc_cursor_more (cursor)) {
         if (mongoc_cursor_next (cursor, &doc)) {
            if (bson_iter_init_find (&iter, doc, "ts") &&
                BSON_ITER_HOLDS_TIMESTAMP (&iter)) {
               bson_iter_timestamp (&iter, &last_time, NULL);
            }
            print_bson (doc);
         }
      }
      if (mongoc_cursor_error (cursor, &error)) {
         if (error.domain == MONGOC_ERROR_SERVER) {
            fprintf (stderr, "%s\n", error.message);
            exit (1);
         }
      }

      mongoc_cursor_destroy (cursor);
      sleep (1);
   }
}

int
main (int argc, char *argv[])
{
   mongoc_collection_t *collection;
   mongoc_client_t *client;
   mongoc_uri_t *uri;
   bson_error_t error;

   if (argc != 2) {
      fprintf (stderr, "usage: %s MONGO_URI\n", argv[0]);
      return EXIT_FAILURE;
   }

   mongoc_init ();

   uri = mongoc_uri_new_with_error (argv[1], &error);
   if (!uri) {
      fprintf (stderr,
               "failed to parse URI: %s\n"
               "error message:       %s\n",
               argv[1],
               error.message);
      return EXIT_FAILURE;
   }

   client = mongoc_client_new_from_uri (uri);
   if (!client) {
      return EXIT_FAILURE;
   }

   mongoc_client_set_error_api (client, 2);

   collection = mongoc_client_get_collection (client, "local", "oplog.rs");

   tail_collection (collection);

   mongoc_collection_destroy (collection);
   mongoc_uri_destroy (uri);
   mongoc_client_destroy (client);

   return EXIT_SUCCESS;
}

let's compile and run this example against a replica set to see updates as they get made πŸ”₯

$ gcc -Wall -o mongoc-tail mongoc-tail.c $(pkg-config --cflags --libs libmongoc-1.0)
$ ./mongoc-tail mongodb://example.com/?replicaSet=myReplSet
{
    "h" : -8458503739429355503,
    "ns" : "test.test",
    "o" : {
        "_id" : {
            "$oid" : "5372ab0a25164be923d10d50"
        }
    },
    "op" : "i",
    "ts" : {
        "$timestamp" : {
            "i" : 1,
            "t" : 1400023818
        }
    },
    "v" : 2
}

that line of output is a sample from performing db.test.insert({}) from the mongo shell on the replica set πŸ’…

SEE ALSO:

  • mongoc_cursor_set_max_await_time_ms().

✨ AUTHOR ✨

MongoDB, Inc πŸ‘‘

2017-present, MongoDB, Inc


and that's the whole page bestie!! this was libmongoc version 1.26.0, dated Mar 31, 2024 β€” ily, go destroy your cursors, periodt πŸ’…βœ¨πŸ«Ά

πŸ“¦ where this page comes from