If you have more than one Context on an Evaluator, it is instructive to think of them as a stack:
Task |
ActiveContext |
... |
RootContext |
In this stack, the root context forms the bottom. The top is called the active context. The stack of contexts is formed by calls to submitContext() to the event types that allow this (AllocatedEvaluator and ActiveContext). Contexts are the main way for an Evaluator to be exposed and accessed. For instance, Tasks are submitted to an ActiveContext which represents the top Context on the Evaluator.
Beyond this, a Driver can submit a Context to the root, or in fact any, Context, as long as the resulting structure is that of a stack: The root Context forms the bottom of the stack, the top-most Context is called active, hence theActiveContext event. The two can be one and the same, and often are: The root Context is the subject of the first ActiveContext event on an Evaluator.
Nomenclature: When Context B is submitted to an already existing Context A, we say that Context A is the parent Context of Context B. Also, Context B is the child of Context A.
It is only the ActiveContext that allows the submission of Tasks or child Contexts. This ensures the stack property of contexts.
It is convenient to think of a Context as a Configuration that gets merged with the Configuration supplied for Tasks and child Contexts. While not entirely true (see below), this view allows us to show just why Contexts are a convenient construct.
It is often the case that subsequent tasks that get executed on an Evaluator want access to the same Configuration variables and / or the same objects. Consider a simple LinkedList bound to a named parameter. If that linked list is part of the subsequent Task Configurations submited, each Task will get its very own LinkedList. If the named parameter is bound in the Context Configuration, all Tasks subsequently submitted to the Context will get the very same LinkedListinstance.
Contexts also have their own life cycle: They generate events like ContextStart and ContextStop.This allows us to group the life cycle of subsequent, related tasks. Consider the group communications library as an example: It establishes network connections between all the participating Evaluators. Having these managed in the Context (instead of a Task) allows us to amortize the cost of setting these up across Tasks.
This mechanism is implemented by using Tang's Injectors. On the Evaluator, a Task is launched by first forking the Context's Injector with the TaskConfiguration and then requesting an instance of the Task interface from that forked Injector. By this mechanism and the fact that objects are singletons with respect to an Injector in Tang, object sharing can be implemented. All objects already instantiated on the Context Injector will also be referenced by the TaskInjector. Hence, the LinkedList in the example above would be shared amongst subsequent Task Injectors in the construction of the Task instance.
To a first degree, you can think of this structure as the following stack:
| Level | Injector |
Task | taskInjector = activeContextInjector.fork(taskConfiguration) task = taskInjector.getInstance(Task.class) |
ActiveContext | activeContextInjector = rootContextInjector.fork(contextConfiguration) |
RootContext | rootContextInjector = Tang.newInjector(rootContextConfiguration) |