Thursday, March 18, 2010

Why you should be using libnih allocation

After reading Stephen Gallagher's planet post on talloc it occurred to me that it might be worth pimping Upstart's own malloc wrapper, and the nifty little toolbox it comes with.

libnih started life as a sort of growth on the side of Upstart; not really a standalone project, not really part of Upstart itself. Recently its come into its own as a stable library, and its available in Fedora 12 and up.

Central to libnih is nih_alloc, which began life as a talloc-like API. Since then, it has grown one important distinction: multiple parentage.


#include <nih/alloc.h>
#include <nih/list.h>

NihList *queue_a;
NihList *queue_b;

int
send_message (char *msg_text,
int send_to_a,
int send_to_b)
{
nih_local char *message = nih_strdup (NULL, msg_text);

if (! message)
return -1;

if (send_to_a)
send_to_queue (queue_a, message);

if (send_to_b)
send_to_queue (queue_b, message);

return 0;
}

int
send_to_queue (NihList *queue, char *message)
{
NihListEntry *entry = nih_list_entry_new (queue);

if (! entry)
return -1;

entry->str = message;
nih_ref (message, entry);
nih_list_add (queue, entry);

return 0;
}


Let's look at the libnih calls one by one:
  • nih_strdup duplicates the given string into an libnih-allocated object. The first argument specifies an initial parent object for the allocation. All objects must have parents, and when an object has no more parents it is freed. NULL can be a parent, as it is here.

  • nih_list_entry_new creates a new NihList object. The first argument, again, specifies a parent.

  • nih_ref adds a parent to a libnih-allocated object.

  • nih_list_add is part of libnih's NihList linked list API, and does what it sounds like.


We skipped the nih_local keyword, but I'll come back to it.

In our example, the new message string we create is created with one parent, NULL. Each time it gets passed to send_to_queue, it gets one additional parent, in the form of a new list entry in the appropriate queue. Assuming we disposed of the list entries as we processed the queue, the object would loose one parent each time a queue finished with it. Also, since the list entries have the queue they are in as their only parent, they would be freed if we freed an entire queue, and our message string would lose them as parents.

But what about that NULL parent? How does it go away allowing the object to be freed? That's where nih_local comes in. nih_local uses a glibc extension to specify a destructor for the variable it is used with. That destructor will call nih_discard on whatever is in message. nih_discard in turn is equivalent to nih_unref (.., NULL), i.e. it removes one NULL parent from an object. In other words the NULL parent goes away as soon as send_message returns. If we called with both of the send_to arguments as 0, then message will be freed immediately. Otherwise message will be freed when both queues have dispensed with it.

In addition to other allocation bells and whistles, like destructors, there's a lot more to libnih besides an allocator. It has everything from a hash table to a dbus API wrapper. It's also available for experimentation in F12 right now. Go check it out.

2 comments:

slashdotaccount said...

Absolutely love the name!

Karan said...

I was just beginning to build upstart when my system complained for libnih. I googled and stumbled across your post, and would have to admit that your post was particularly helpful in fostering interest. How often have a desired such garbage collection in C :)

I would study more of its API next. Thanks again.