-
Notifications
You must be signed in to change notification settings - Fork 26
Future Design and Implementation
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
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.
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 of current implementation:
- Calling
get
on self-fulfilling future causes deadlock. -
Passing future to another actor is not supported. CallYou could pass futures around now.get
, then pass the value instead. - Lambda used in future chaining is run by the producer, which would cause data race.