Skip to content

Future Design and Implementation

Albert Netymk edited this page Jun 5, 2015 · 2 revisions

GC

GC for non-primitive type inside future:

the producer of f calls future_gc_send_value, which calls gc_send, and inside get we call acquire_future_value, which sends ACQUIRE messages to the producer. gc_recv is called in the finalizer of the future, so that #gc_recv == #gc_send

Eager VS Lazy

There are two strategies for using futures, which could be toggled using LAZY_IMPL in encore.h.

In eager strategy, we prepare each actor with one context right before calling its dispatch function, in handle_message of actor.c.

If no block happens, the context would finish successfully, and uc_link assigned before dispatch would make sure the execution returns to handle_message. If a block happens, we would save the current context, and manually jump back to where we left in handle_message, by calling swapcontext. In both cases, the control would returns to the handle_ message, like a normal function call; while in the former case, the context is discarded, in the latter case, the context is saved for later resuming.

One optimization implemented currently is to have a pool of stacks, which could be cached and reused, instead of calling malloc all the time.

In lazy strategy, we delay the creation of context until the block actual happens, so handle_message is kept the same in lazy strategy. Instead, we save current context for lat er resuming when the actor is blocked in actor_block of encore.c, and create a new context, using pop_context (A pool is used for the same reason discussed above), for othe r actors in the queue. Correspondingly, there's push_context, which is used to collected unused contexts. A context becomes unused only when we are above to resume a blocked ac tor, because the blocked actor has a saved context to use. Therefore, push_context is called in actor_resume. One caveat here is that we can just call push_context blindly each time we resume a blocked actor, for the first context we save contains the stack created by OS when the main thread is constructed. Therefore, we need to check whether the c urrent context is created us or by OS before we put it into the pool.

It's possible that a actor is blocked on pthread A but resumed by pthread B, so before the system terminates, we need to make sure each thread returns to its original place befor e calling pthread_exit. That's where jump_origin comes in. However, calling swapcontext directly would cause all threads jumping at the same time, which could result into p otential data raec. Therefore, a rendezvous point (like a buffer) is created for all threads to assemble before jumping to their origin.

Future Chaining

f' = chain f lambda

The chained closure would always run by the producer of the chained future. In the above example, lambda is run by the producer of f.

Calling get on f' would block the current actor until f' becomes fulfillled due to the running of lambda.

Limitation

Limitation of current implementation:

  1. Calling get on self-fulfilling future causes deadlock.
  2. Passing future to another actor is not supported. Call get, then pass the value instead. You could pass futures around now.
  3. Lambda used in future chaining is run by the producer, which would cause data race.