Using libgee from C to access libfolks

If you’re interfacing with Vala code from C, it’s possible that you’ll have to make calls to libgee, a Vala data structure library. libgee is written in Vala, but its C API is perfectly usable once you get your head around the idioms.

A common idiom is that of the GeeIterator, which is the recommended way to iterate through sets, maps and lists (which all implement the GeeIterable interface). For a loop, you create a new GeeIterator, then call its get and next methods to retrieve the object for the current iteration and advance to the next iteration, respectively.

For example, to list all the phone numbers in a FolksIndividual:

GeeSet *phone_numbers;
GeeIterator *iter;
FolksPhoneDetails *details;

details = FOLKS_PHONE_DETAILS (my_individual);
phone_numbers = folks_phone_details_get_phone_numbers (details);
iter = gee_iterable_iterator (GEE_ITERABLE (phone_numbers));

while (gee_iterator_next (iter)) {
	FolksPhoneFieldDetails *details;
	gchar *number;

	/* Get the current iteration’s element.
	 * Note that ownership is transferred. */
	details = gee_iterator_get (iter);
	number = folks_phone_field_details_get_normalised (details);

	g_message ("Phone number: %s", number);

	g_free (number);
	g_object_unref (details);  /* important! */
}

g_object_unref (iter);  /* important! */

One key thing to note here is that the return values from gee_iterator_get() and gee_iterable_iterator() are transfer-full and need to be unreffed. Failing to unref them is a common mistake when using libgee from C, and results in leaked objects — and was the cause of several memory leaks recently fixed in Empathy. When using libgee from Vala this isn’t a problem, since the memory management is handled automatically.

This has been a public service announcement.

Travelling in Honduras

Having just spent 7 weeks in Honduras, working and travelling, I thought it might be useful to document some of the things I’ve learned about the country.

In my experience, Honduras has three distinct areas: the north (Caribbean) side where English is spoken moderately commonly, and many people go to learn to dive (especially on the Bay Islands); the three major cities (Tegucigalpa, San Pedro Sula and La Ceiba) where tourism infrastructure is practically non-existent, pollution is high and crime is high; and the rest of the countryside, which is pleasant, safe (for the most part) and has little tourism infrastructure.

Lightning on the cloudy mountains around Tegucigalpa at night.I spent 5 weeks in the capital (Tegucigalpa) and saw the bad side of Honduras. It is not a safe city to walk around, even in the richer areas, so you have to drive everywhere. I hadn’t previously realised how much I value being able to walk the streets and go where I please without probability of being robbed at gunpoint. Despite its security problems, Tegus was a reasonable-ish place to be. It has all the amenities of a big city — clubbing, for example, there is just as grim as in England, except with more Latin music. There is nothing for tourists to see, and they should only use it as a transport hub (since it has one of the country’s two major airports, and is a major bus terminal) or as a base to explore the much-nicer Valle de Ángeles, La Tigra national park, and Comayagua nearby.

Tegucigalpa demonstrates the two major problems which Honduras suffers from: a huge imbalance in the distribution of wealth, and endemic corruption in government and the police force. These have knock-on effects on everything else. The rich live in enclosed neighbourhoods with high walls, barbed wire, electric fences, and armed private security. They drive luxury 4×4s and have lives which would be considered privileged in Europe. The poor live in shacks which line the roads and are stacked up the hillsides. There is a huge amount of gang crime: it seems that most of the drugs passing from South America to the USA come through Honduras. This gang crime contributes hugely to Honduras’ murder rate, making it officially one of the most dangerous countries in the world — although if you’re not involved in a gang, you’re as safe as in other Central American countries. A lot of this crime goes unpunished, both because the police are unable to tackle the gangs, and also because they’re corrupt. It is the army which enforces the law here.

One of the many building-piers on Utila.The Bay Islands are completely different. I spent a week on Utila, and it was a safe, friendly place, quite different from Tegucigalpa. Its summer climate is hot (30?) and humid (up to 80% relative humidity, with very little wind), which I found unbearable at times. Still, it is easy to get to, has excellent diving opportunities, and caters well for someone who wants to party every night. It’s quite drug-friendly.

The rest of the country is mostly small villages and farmland. There isn’t much tourism infrastructure, but travel is easy and cheap on ‘collectivo’ and chicken buses (old, reappropriated US school buses; less gaudily decorated than in Guatemala). Most villages don’t have much to offer other than colonial architecture and old churches (and a lot of auto repair shops). A few are more popular: Gracias, Lempira; and Copán Ruinas.

Gracias, Lempira in the sun.

Gracias has more quaint architecture than most places, and is next to Celaque national park, home of Honduras’ tallest mountain (Cerro Las Minas). Worth a visit if you like cloud forests, bird watching or hiking in general. Summitting Cerro Las Minas normally takes two days, but an experienced (and fit — it’s a steep mountain) hiker can do it in 7 hours.

A stone face in the ruins at Copán.Copán Ruinas is the gateway to the country’s Mayan ruins, which are definitely worth a visit. Very well preserved hieroglyphics, and lots of wildlife (including a Macaw breeding programme). If you visit Copán Ruinas, seeing the ruins will take half a day, and there is little else to do in the town unless you plan in advance with a local tour company to do a trek or horse ride. I didn’t, so I don’t know what they’re like.

Infrastructure is what Honduras lacks. Its roads are full of potholes (leading to its infamous crazy driving), there are frequent (roughly once a week) power outages and internet outages. None of the tap water is potable and drainage often runs straight onto the street (which in turn expedites damage to the roads). Building and maintaining infrastructure in such a mountainous country is tricky, but it’s necessary.

With the benefit of hindsight, there are a few things I would do differently if I came to Honduras again. Quite a few of them are the standard recommendations for travelling in Central/South America anyway:

  • If you can afford it, hire a car or motorbike (the latter are incredibly popular here). Having your own transport gives you a lot of freedom and safety. Get a vehicle suited to off-road driving: even if you don’t end up on a dirt track (which is quite possible if one of the few major roads is closed due to landslides), there are nasty speed bumps throughout the country which chew up cars with low ground clearance.
  • If you can’t, use collectivo buses, which are safe and, while the routes aren’t listed anywhere online or on paper, generally follow major roads. Any local will know about them. They run very frequently.
  • Find out which areas of a city are considered safe, and which aren’t. If you stick to the safe areas and use common sense, everything will be fine. If you go to an unsafe area, you’re likely to be robbed or kidnapped. For example, even the locals in Tegus don’t venture to Comayaguela. The hotels aren’t always in the safe areas, especially in Tegucigalpa — definitely avoid anything in the Comayaguela area.
  • Don’t plan to do much hiking. It’s not a particularly popular pastime here, so while there are many ‘national parks’, they mostly exist to protect the cloud forests. There are some nice trails in some (for example, La Tigra and Celaque), but don’t expect too much. If you’re interested in birds, however, Honduras is supposed to be pretty good; there are hotels dedicated to bird watching around La Ceiba.
  • If you go to the Bay Islands to dive, have a contingency plan in case of illness (which would prevent you from diving). When I was there, several people I knew got colds and ended up doing absolutely nothing for a week until they got better.
  • Don’t try and plan a tight itinerary in advance, as unexpected transport problems might throw it off. Internet access is easy to come by in Honduras (especially 3G), so make things up as you go along. Factor in plenty of time for making travel connections, as few of the bus times are synchronised.
  • Aside from touristy places (like Copán Ruinas), Honduras doesn’t do European-style hostels. You’re going to have to look at hotels, but there are cheap ones.
  • Similarly, don’t expect to send any postcards. In my experience, they’re hard to find, and harder to send. Post offices are like gold dust.
  • Don’t trust the websites describing most bus routes in Honduras (apart from the huge companies like Hedman Alas). Even medium-sized Honduran companies don’t do websites well, or keep them updated. Phone the bus company up instead, or ask a local.

A summer with XOs in Honduras

One of the main streets in Valle de Ángeles, a village near Tegucigalpa, the capital.

One of the main streets in Valle de Ángeles, a village near Tegucigalpa, the capital.

I’ve been lucky this summer to spend 7 weeks in Honduras, working on the OLPC deployment for primary school kids here. I’ve been training the local team (part of Educatrachos, a government- and IADB-funded education project in Honduras): teaching them Python, how to create activities for Sugar, and some Unix server administration magic.

The deployment itself is impressive. Over the past two years, they’ve delivered 40?000 laptops to 400 schools. A lot of the work has been in providing infrastructure (power and internet): this is tricky given Honduras’ hugely irregular terrain. A lot of the schools are using satellite internet, which is inherently affected by the weather. Despite these obstacles, the infrastructure has been in place for a while and is working nicely.

Showing a hint about which cells to sum in Pascal’s triangle.

Showing a hint about which cells to sum in Pascal’s triangle.

The focus is now moving towards producing and updating educational resources for the laptops. That means creating new Sugar activities and refreshing and redeploying existing resource collections. This is what the bulk of my time has been spent on: training the team here in how to create activities and collections, starting from basic Python and working upwards. It’s been tricky (because of my poor Spanish if nothing else) but the team have tackled the learning with enthusiasm. I hope to see new Sugar activities on the Educatrachos gitorious page soon! So far, we’ve produced one new Sugar activity: a Pascal’s triangle game. We’ve also published the training materials I used for teaching Python and Sugar. They’re available in English and Spanish, although the Spanish translation is pretty patchy (my fault!).

What remains to be seen is how the project will evolve after the change of government from the upcoming Honduran elections.

Tomorrow is my final day in Tegucigalpa: on Friday I leave for two weeks of time off, travelling around the country to end a fun summer of travelling to celebrate graduating from university after four years.

Thanks to Raúl Segales, Walter Bender, Daniel Drake and Martin Abente for answering my silly questions as I dived into Sugar!

(const gchar*) vs. (gchar*) and other memory management stories

Memory management in C is often seen as a minefield, and while it’s not as simple as in more modern languages, if a few conventions are followed it doesn’t have to be hard at all. Here are some pointers about heap memory management in C using GObject and the usual GNOME types and coding conventions, including GObject introspection. A basic knowledge of C is assumed, but no more. Stack memory allocation and other obscure patterns aren’t covered, since they’re much less commonly used in a typical GObject application.

The most important rule when thinking about memory management is: which code has ownership of this memory?

Ownership can be defined as the responsibility to free or deallocate a piece of memory. If code which does own some memory doesn’t deallocate that memory, that’s a memory leak. If code which doesn’t own some memory deallocates it (in addition to the owner doing so), that’s a double-free. Both are bad.

For example, if a function returns a newly allocated string and doesn’t retain a pointer to it, ownership of the string is transferred to the caller. Note that when thinking about ownership transfer, malloc()/free() and reference counting are treated the same: in the former case, a newly allocated piece of heap memory is being transferred; in the latter, a newly incremented reference.

gchar *transfer_full_function (void) {
	return g_strdup ("Newly allocated string.");
}

/* Ownership is transferred here. */
gchar *my_string = transfer_full_function ();

Conversely, if the function does retain a pointer (e.g. inside an object), ownership is not transferred to the caller.

const gchar *transfer_none_function (void) {
	return "Static string.";
}

const gchar *transfer_none_method (MyObject *self) {
	gchar *new_string = g_strdup ("Newly allocated string.");
	/* Ownership is retained by the object here. */
	self->priv->saved_string = new_string;
	return new_string;
}

/* In both of these cases, ownership is not transferred. */
const gchar *my_string1 = transfer_none_function ();
const gchar *my_string2 = transfer_none_method (some_object);

In all of these examples, you may notice the return type of the functions reflects whether they transfer ownership of their return values. const return types indicate no transfer, and non-const return types indicate some transfer.

While this is useful, it’s by no means completely clear. For example, what if a function returns a non-const GList*? Is ownership of the list elements transferred, or just the list? What if the function’s author forgot to make the return type const, and actually there’s no transfer? This is where documentation comments are useful.

There’s a convention in GNOME documentation comments to specify the function which should be used to free a returned value. If such a function is mentioned, ownership of the returned memory is transferred. If no function is mentioned, ownership probably isn’t transferred, but it’s hard to be sure. That’s why it’s good to always be explicit when writing documentation comments.

/**
 * my_object_get_some_string:
 * @self: a #MyObject
 *
 * Gets the value of the #MyObject:some-string property.
 *
 * Return value: (transfer none): some string, owned by the
 * object and must not be freed
 */
const gchar *my_object_get_some_string (MyObject *self) {
	return self->priv->some_string;
}

/**
 * my_object_build_result:
 * @self: a #MyObject
 *
 * Builds a result string which probably represents something
 * meaningful.
 *
 * Return value: (transfer full): a newly allocated result
 * string; free with g_free()
 */
gchar *my_object_build_result (MyObject *self) {
	return g_strdup_printf ("%s %s",
	                        self->priv->some_string,
	                        self->priv->some_other_string);
}

/**
 * my_object_dup_controller:
 * @self: a #MyObject
 *
 * Gets the value of the #MyObject:controller property,
 * incrementing the controller's reference count.
 *
 * Return value: (transfer full): the object's
 * controller; unref with g_object_unref()
 */
MyController *my_object_dup_controller (MyObject *self) {
	return g_object_ref (self->priv->controller);
}

When GObject introspection was introduced, these kinds of documentation comments were formalised as introspection annotations: (transfer full), (transfer container) or (transfer none), as documented on wiki.gnome.org. These allow the runtimes of language bindings to manage memory correctly. Since a C programmer is essentially doing the same job as a language runtime when writing C code, the information provided by transfer annotations is sufficient for perfect memory management. Unfortunately, not all parameters and return types of all functions have these annotations added. The examples above do, however.

Finally, a few libraries use a function naming convention. Functions named *_get_* do not transfer ownership, whereas functions named *_dup_* (for ‘duplicate’) do transfer ownership. This can be seen in the examples above, or with json_node_get_array() vs. json_node_dup_array(). Be aware, though, that only a few libraries use this convention. Other libraries use *_get_* for both functions which do and do not transfer ownership of their results. Other code, such as that generated by gdbus-codegen, uses a different and incompatible convention: *_get_* methods signify full transfer, and *_peek_* methods signify no transfer. For example, goa_object_get_manager() vs. goa_object_peek_manager(). For this reason, going by function naming conventions only works within libraries, not between different libraries.

Memory management of parameters is analogous to return values: look at whether the parameter is const and whether there are any introspection annotations for it.

In summary, here are a set of guidelines one can follow to determine whether ownership of a return value is transferred, and hence whether the caller needs to free it:

  1. If the type has an introspection (transfer) annotation, look at that.
  2. Otherwise, if the type is const, there is no transfer.
  3. Otherwise, if the function documentation explicitly specifies the return value must be freed, there is full or container transfer.
  4. Otherwise, if the function is named *_dup_*, there is full or container transfer.
  5. Otherwise, if the function is named *_peek_*, there is no transfer.
  6. Otherwise, you need to look at the function’s code to determine whether it intends ownership to be transferred. Then file a bug against the documentation for that function, and ask for an introspection annotation to be added.

Some common pitfalls:

  • If you’re using an explicit typecast (e.g. casting a (const gchar*) return value to (gchar*)), it’s likely something’s wrong.
  • Generally, return values which are not transferred (such as (const gchar*)) are freed when the owning object is destroyed — so if you need such a value to persist, you must copy it or increase its reference count. You then have ownership of the copy or new reference, and are responsible for freeing it (not the original).

How can one check for incorrect memory handling? Use Valgrind. It will detect leaks and double-frees, and is simple to use:

valgrind --tool=memcheck --leak-check=full my-program-name

Or, if running your program from the source directory, use the following to avoid running leak checking on the libtool helper scripts:

libtool --mode=execute valgrind --tool=memcheck --leak-check=full ./my-program-name

Valgrind lists each memory problem it detects, along with a short backtrace (if you’ve compiled your program with debug symbols), allowing the cause of the memory error to be pinpointed and fixed!

GUADEC trip report

Another year, another GUADEC. Brno this year was good, and well organised. The intense heat was bearable by eating lots of ice cream (though there was no ice cream deathmatch — what happened to that?) and drinking lots of excellent Czech beer. A number of the usual faces were missing this year for various reasons, which was a great excuse to meet the new contributors and GNOME OPW/GSoC students! They were a big presence at this year’s conference, and it was interesting to see their project lightning talks. I’m looking forward to seeing all the work land in git.

For me, the best talks were Lennart’s talk on sandboxing GNOME applications, the panel discussion on Wayland, and Stef’s talk on security in GNOME (or why we should destroy all security question dialogues). My talk was a little shorter than expected, but a few people seem interested in the idea, which I’ve released as uhttpmock; my slides are available online.

Thanks to the GNOME Foundation for sponsoring my stay in the Taufer dorms. It was reasonable accommodation, and nice to have (almost) all the conferencees together in the same building and able to hack together in the lobby.

I’m now off to Honduras for over a month. See you on the other side!

Testing online services

I’m at GUADEC! The conference has been great so far: a nice location, good organisation, interesting talks, and Czech beer. Thank you to the GNOME Foundation for sponsoring me.

I’ll be giving a talk tomorrow (Saturday, 11:35, room E112) about testing online services. It’ll be a short presentation about some work I’ve started recently on mocking web services so that unit tests for client code can be run offline. This is an area with quite a lot of potential, and what I’ve done so far has only just scratched the surface, so if you’ve got ideas about this kind of thing, please come along and we can have a good discussion.

Update: Thanks to all who attended the talk and gave their input. My slides are available online (and the source files are in the same directory).

A GNOME Foundation sponsorship badge.

How not to write a specification

From the Department of Bad Ways to Document APIs, referring to no service in particular:

  • Don’t refrain from changing and updating the specification after release. Consumers of your service will value API tweaks and improvements over stability.
  • Don't provide a changelog for the specification. Nobody reads them anyway, and they're such a hassle to maintain.
  • Similarly, don't notify consumers of changes to the specification by RSS or Atom, since nobody uses them anymore. They're all such avid fans of your service that they'll take the time to re-read the API specification every few weeks anyway.
  • Move the documentation around every few months and re-brand it. Shiny logos are cool, and maintaining redirects from old documentation locations is too hard.
  • Also don’t worry about moving your issue tracker around every few months, or closing all the old bugs every time. If nobody's touched a bug in months, it's probably been fixed, right?
  • Annotated API call examples are worth 1000 times more than boring, verbose, precise descriptions of constraints, error behaviour and motivations for the API design.
  • Version numbering is hard to get right, and consumers always use the highest-numbered version, so there's no need to annotate APIs with the version or date they were last changed or introduced.

On the other hand, some good ways to document APIs:

  • Allow anchor linking to documentation subsections.
  • Clearly mark deprecations and provide a deprecation schedule.
  • Provide a sandboxed API playground for testing API calls.
  • Provide worked client examples in multiple programming languages.

Turns out, documenting a large API which is expected to be consumed by thousands of people requires a lot of careful work.

Marin Bobcat Trail bike maintenance

I just spent a few days overhauling my aging mountain bike. It was an interesting (and at times frustrating) process, partly due to this being the first time I’ve done anything like it, and partly due to the apparent lack of maintenance manuals for the bike or many of its components. I thought it might be useful to put together a brief Marin Bobcat Trail maintenance manual, in the hope it’s useful to someone else who owns the 2008 or 2009 edition of the bike. I’m new to this, so it’s probably a laughable effort in cycling circles, but it’ll be useful to me next time I overhaul the bike anyway. Feedback welcome at the address given in the document!

Weston frame buffer backend

Just over a week ago everyone’s favourite Xorg replacement, Weston, gained a frame buffer backend I put together as part of some university work. It was remarkably easy to write, since almost all of the code already existed in the DRM and RPI backends; I just needed to hook pixman up to /dev/fb0 and everything worked!

Weston’s code base was a pleasure to work on. Hopefully someone stuck on a frame buffer somewhere finds this work useful. Since I did this work, I also got the frame buffer backend working on FreeBSD (as part of the same university project), but haven’t had time to update, tidy up and submit my patches yet. They’re pretty hacky in (many) places.

Without further ado, a gratuitous flowery screenshot (taken using fbcat):

Weston running on a Linux frame buffer, taken on 2013-01-15.

Weston running on a Linux frame buffer, taken on 2013-01-15.

Single-thread synchronisation in Vala

Update: You might also be interested in the follow-up article on rate limiting of asynchronous calls in Vala.

In libfolks, there are some asynchronous prepare() methods, which may be called multiple times, but must only change program state when called for the first time — subsequent calls must do nothing. As the prepare() method is asynchronous, and yields on other asynchronous methods, control may be yielded to the main loop part-way through running. At this point, another call to prepare() could happen from the same thread; and without adequate synchronisation the second call would proceed to change program state a second time. This is bad. (Throughout this post I’m only considering calls from a single thread; multi-threaded concurrency is completely different, and can rely on locking for safety.)

What are the possible solutions?

  • Locking.
  • Condition variables.
  • Boolean “prepare() in progress” flag.

Locking and condition variables can’t be used because the prepare() calls may come from any thread. Vala uses recursive locks, so calls from the same thread wouldn’t get blocked at all. If non-recursive locks were used, calls from the same thread would cause a deadlock.

A boolean “prepare() in progress” flag is what folks used to use. This was set to true as soon as the first asynchronous call began, so it prevented subsequent calls from changing program state erroneously, but also meant that subsequent calls returned immediately. Returning from a prepare() call typically implies that preparation has completed — but with this implementation that wasn’t correct for anything except the first call to prepare(). How can a call know that it’s not the first call? It can’t. This solution is unsatisfactory.

The solution which has just been implemented in folks is to use a list of waiting continuations for all calls subsequent to the first. The first call will execute all these continuations once it’s finished. This solution feels quite elegant to me, but suggestions from improvement are more than welcome.

A code example and explanation:

public class Logger : GLib.Object
{
  private static Logger _logger;
  private static DelegateWrapper[] _prepare_waiters = null;

  public async void prepare () throws GLib.Error
    {
      if (Logger._logger == null && Logger._prepare_waiters == null)
        {
          /* If this is the first call to prepare(), start some async calls. We
           * then yield to the main thread. Any subsequent calls to prepare()
           * will have their continuations added to the _prepare_waiters list,
           * and will be signalled once the first call returns. */
          Logger._prepare_waiters = new DelegateWrapper[0];

          /* Insert asynchronous initial preparation code here. */
          Logger._logger = yield new Logger ();

          /* Wake up any waiters. */
          foreach (unowned DelegateWrapper wrapper in Logger._prepare_waiters)
            {
              wrapper.cb ();
            }

          Logger._prepare_waiters = null;
        }
      else if (Logger._logger == null && Logger._prepare_waiters != null)
        {
          /* Yield until the first ongoing prepare() call finishes. */
          var wrapper = new DelegateWrapper ();
          wrapper.cb = prepare.callback;
          Logger._prepare_waiters += (owned) wrapper;
          yield;
        }

      /* Put shared code here which is executed on every call to prepare().
       * For example, it could check for failure in the results of the
       * initial call and throw an error as appropriate. */
      if (Logger._logger == null)
        {
          /* Throw an error or something. */
          return;
        }
    }
}

/* See: https://mail.gnome.org/archives/vala-list/2011-June/msg00005.html */
[Compact]
private class DelegateWrapper
{
  public SourceFunc cb;
}

(For the full code to this example, see folks’ source code.) The first call to prepare() is meant to asynchronously set the static Logger._logger variable. Subsequent calls must not re-initialise this variable, but should block until the variable has been set.

How does this work? The first call to prepare() finds Logger._logger is null and Logger._prepare_waiters is also null, so it falls into the initialisation block. It sets Logger._prepare_waiters to be an empty array (which is distinct from null), then proceeds to initialise Logger._logger asynchronously. Since the initialisation is asynchronous, control returns to the main loop, and a second call to prepare() could be made. Assume it is. The second call finds Logger._logger is still null, but finds that Logger._prepare_waiters is non-null, and so falls into the second branch of the if-statement. This saves its continuation into the array of waiters, and then yields control to the main loop again, indefinitely delaying the second asynchronous call from finishing. The first call returns from its asynchronous initialisation code, and proceeds to “Wake up any waiters”. For each of the saved waiters, it calls the waiter’s continuation (in an idle callback), which resumes execution just after the yield statement. Consequently, each waiter executes the shared code at the bottom of prepare(), then its prepare() call finishes and any asynchronous callbacks are executed in normal GLib fashion. The first call then proceeds to the shared code at the bottom of prepare(). Finally, its asynchronous callback is executed, and everything’s finished.

This all hinges on the fact that Vala supports “delegates with targets” (or continuations): the prepare.callback in the code above. This is a copy of the runtime state of the prepare() call, complete enough that it can later be called (using wrapper.cb()) to continue execution as if it never stopped at the yield. In C terms, a delegate is a function pointer; a delegate with a target is a function pointer with a struct full of the values of local variables at the time the delegate was stored. This is similar to the this pointer in C++, but includes more local state.

Why is DelegateWrapper needed? Why can’t the array just contain the continuations directly? Good question. I’d like to know the answer. Vala throws an error message if one tries to define an array of delegates with targets (“Delegates with target are not supported as array element type”). Other methods of encapsulating the continuations result in just the delegate being stored, and the target (local state) being lost, which is not what we want. I got the tip to use a DelegateWrapper class from Jim Nelson in this mailing list post (thanks!).

So that’s it: one way to synchronise multiple concurrent asynchronous method calls in Vala.