Promises and Futures

Promises and Futures is a programming paradigm that simplifies synchronization when concurrent execution is present.

Since C does not natively support this feature the Eina library provides the Eina_Promise and Eina_Future objects described in this programming guide.

Introduction

In procedural languages like C if you need a value which is not yet available, for instance because it takes a long time to calculate or has to be fetched from a remote server, you typically have to wait.

In other words, the following code completely blocks execution until the function call returns:

int item_price = go_fetch_item_price();
int total_price = item_price * number_of_items;

Other languages however can return a Promise and continue execution immediately. A promise acts as a placeholder for the requested value: the value is not available yet but will be at some point in the future.

With a promise in hand you can attach callbacks to be triggered when the value becomes available (i.e. when the promise is fulfilled), then continue your calculations. You can even pass the promise to other methods which will then be executed as values become available, forming complex chains.

An Eina_Promise can be considered as an object with the sole purpose of emitting the "Promise Resolved" event. Eina_Future are callbacks attached to this object to be called when the event is emitted. The promised value (item_price in the example above) is passed to the callbacks whenever it's available.

The following sections explain how to use Eina_Promise and Eina_Future along with details such as how to handle error conditions and cancel pending promises.

Creating a Promise

The code returning the delayed value (the promise of a value) creates an Eina_Promise using eina_promise_new() like so:

   Efl_Loop *loop;
   Eina_Future_Scheduler *scheduler;
   Eina_Promise *promise;
   void *data;
 
   [...]
   scheduler = efl_loop_future_scheduler_get(loop);
   promise = eina_promise_new(scheduler, _promise_cancel_cb, data);

Most of the time you'll be using the application loop you received in the efl_main() method of your application (see the Main Loop Programming Guide) and its scheduler so you can cache the loop and scheduler variables from the above snippet in your code. There is no need to free them.

NOTE: You can also use efl_loop_promise_new() to simplify your code a bit:

   promise = efl_loop_promise_new(loop, _promise_cancel_cb, data);

You can attach arbitrary data to Eina_Promises which you can later retrieve with eina_promise_data_get().

Finally when creating Eina_Promises you should always provide a _cancel_callback as above, so you're notified if the promise is cancelled. This is chiefly because any pointer you might be keeping to the cancelled promise will be invalid after the callback but also because you have a chance to stop whatever process you had running to obtain the promised value.

See an example in Test 1 of eina_future.c.

Creating a Future

The code receiving an Eina_Promise (i.e. the promise of a value instead of the value itself) must associate it with a callback in order to be notified when the value becomes available. The Eina_Future object contains such a callback amongst others along with associated information (see below).

You can create an Eina_Future using eina_future_new() and assign callbacks to it using one of the eina_future_then*() family of functions:

   Eina_Future *future;
 
   future = eina_future_new(promise);
   eina_future_then_easy(future,
                         .success = _promise_resolved_cb,
                         .error = _promise_error_cb,
                         .free = _cleanup_data_cb,
                         .data = NULL);

The benefit of eina_future_then_easy() is that you can define the callbacks you want without worrying about the others. There's no need to pass a string of NULLs for unneeded callbacks.

The success callback will be triggered when the promise is fulfilled and will receive the promised value as a parameter. The error callback will be triggered if any error prevented the promise from being resolved for instance if it was rejected or cancelled, (see below). You can be certain that either one will be called.

You can pass any data you want when registering callbacks. They will receive it through the data parameter. Use the free callback to free it since it will be called no matter what way the promise ends (successfully resolved or cancelled).

See an example in Test 1 of eina_future.c.

Resolving a Promise

Once the code that delivered an Eina_Promise instead of a value has the actual value, it's time to fulfill the promise. This is called promise resolution and is performed through the eina_promise_resolve() method:

   Eina_Promise *promise;
   Eina_Value *value;
 
   [...]
   eina_promise_resolve(promise, value);

Resolving a promise means delivering the promised value. This is done through an Eina_Value object since it can accommodate any kind of value. Read more about these in the Eina Generic Values Programming Guide.

All the callback methods registered with this promise (through Eina_Future objects as seen above) will be called and the Eina_Value will be delivered to them.

See an example in Test 1 of eina_future.c.

Rejecting a Promise

Sometimes the code that delivers a promise can't fulfill it (think of a server returning an error instead of a file, or a calculation unexpectedly dividing by zero). However, there might be code waiting for the promised value and it must be notified of the failure or it will keep waiting forever. Eina allows promises to be rejected using eina_promise_reject() to resolve issues like these:

   Eina_Promise *promise;
   Eina_Value *value;
 
   [...]
   eina_promise_reject(promise, EINA_ERROR_NOT_IMPLEMENTED);

Instead of the success callback used in normal promise resolution, all Eina_Future objects registered to the rejected Eina_Promise will have their error callback triggered. Instead of the promised Eina_Value they'll receive the Eina_Error provided to eina_promise_reject().

See an example in Test 2 of eina_future.c.

Cancelling a Future

Sometimes, the code no longer requires a value it requested (think of a user requesting a file and then quitting the application before the file arrives). You could simply disregard the delivered value once the callback is triggered but that would be a waste of resources as there may still be code running trying to fulfill the promise.

It's more efficient to cancel the Eina_Future once you know you're no longer interested in the value. This has benefits beyond saving system resources:

  • Once the Eina_Future is cancelled its callbacks won't be triggered. This means you don't have to bother checking whether you are still interested in the delivered value in the callback code.

  • The code trying to fulfill the promise will receive a cancellation callback (registered with eina_promise_new()) so it has a chance to stop whatever process it started to retrieve the requested value. For instance, if this process involves a CPU-intensive calculation the power-saving gains can be significant.

Future cancellation is achieved through efl_future_cancel():

   promise = efl_loop_promise_new(loop, _promise_cancel_cb, data);
   future = eina_future_new(promise);
   eina_future_then_easy(future,
                         .success = _promise_resolved_cb,
                         .error = _promise_error_cb,
                         .free = _cleanup_data_cb);
 
   [...]
   eina_future_cancel(future);

In the above example, _promise_cancel_cb() will be called to notify the promise-fulfilling code that it has been cancelled. In addition to this _promise_error_cb() will be called to notify any code waiting on the promised value that it will never arrive (the reported error will be EINA_ERROR_FUTURE_CANCEL).

See an example in Test 3 of eina_future.c.

Chaining Futures

Typically, values are not needed on their own but as part of a calculation. Therefore when waiting for a promised value to become available, there are probably other parts of the code waiting on us to deliver our value too.

In this scenario, Eina's ability to chain futures is very handy. Using eina_future_chain() you can specify multiple callbacks to be triggered in order when an Eina_Promise is resolved. The value delivered by the promise then trickles down the chain, and each callback has a chance to modify the value before passing it along to the next callback.

Compare this behavior with attaching multiple independent Eina_Future to the same promise: all callbacks will be triggered when the promise is resolved but they will all get the same value, without the chance to build on top of each other's results.

Future chains are built with eina_future_chain(), which works similarly to eina_future_then_easy() explained before:

   eina_future_chain(eina_future_new(promise),
                     {.cb = _step1_cb, .data = NULL},
                     {.cb = _step2_cb, .data = NULL},
                     {.cb = _step3_cb, .data = NULL});

_step1_cb() will be given the Eina_Value delivered by the Eina_Promise and it will return a new Eina_Value which will in turn be passed onto _step2_cb(), and so on.

Note also how each callback can have different data passed to it.

A rejection of the promise or the cancellation of any of the futures will cancel the whole chain.

See an example in Test 4 of eina_future.c.

Callbacks and Easy Callbacks

You might have noticed how eina_future_then_easy() accepted callbacks for success, error and free (all of them optional), whereas eina_future_chain() only accepted one callback, cb. That's because most of the Eina_Promise and Eina_Future methods come in two flavors: regular and easy.

Regular methods only provide one callback (called cb). This single callback will be triggered if the promise is resolved and also if it is rejected (or if the future is cancelled). Your callback code has to distinguish each case by inspecting the type of the Eina_Value that the promise delivers to you.

If you receive an Eina_Value of type EINA_VALUE_TYPE_ERROR the promise has not been resolved, and the value contains information about the problem:

static Eina_Value
_step1_cb(void *data, const Eina_Value value, const Eina_Future *dead_future EINA_UNUSED)
{
   if (value.type == EINA_VALUE_TYPE_ERROR)
    {
      Eina_Error err;
      eina_value_get(&value, &err);
      fprintf(stderr, "Operation failed. Reason: %s\n", eina_error_msg_get(err));
      return value;
    }
}

Note how the error value is also returned so it can be passed along to other callbacks if used inside a chain.

If the data you pass to your callback needs to be freed, you must do it in the callback.

Easy methods require that you provide separate callbacks for success, error and free conditions making it a bit more cumbersome to use. The benefit however is that the actual callback code can be simplified, since you do not need to check whether the promise resolved OK or not.

Due to the particular syntax of these methods (making use of ISO C99's Designated Initializers) you do not need to provide all callbacks. If, for instance, your data does not need to be freed you don't need to provide the free callback to eina_future_then_easy(). Similarly you don't need to provide the data pointer if it's not required.

Summary

  • If you cannot deliver a requested result immediately and do not want to block program execution, return an Eina_Promise and resolve it at a later stage.

  • If you are given an Eina_Promise instead of an actual value, create an Eina_Future containing callbacks to be triggered when the value is available and continue your calculations there.

  • Eina_Promise can be resolved or rejected.

  • Eina_Future can be cancelled if you are no longer interested in the promised value.

  • Always provide cancellation callbacks for your promises.

  • Eina_Future callbacks can be chained to perform calculations in multiple stages in separate parts of the code.

Further Reading

Eina Promise Reference Guide
Reference Guide for the Eina_Promise class.
Eina Future Reference Guide
Reference Guide for the Eina_Future class.
Eina Generic Value Programming Guide
Programming guide for the Eina_value class.
reference/c/eina/src/eina_future.c
Set of examples using Eina_Promise and Eina_Future.