|
YOrch 1.0.0
|
YOrch currently groups tasks into three main semantics: regular tasks, direct-output tasks, and forward-prev tasks. The biggest difference between them is how they relate to a slot.
A regular task produces data through the callable return value:
T, YOrch moves that value into the current node's slot.void, the task does not write data into a slot.A direct-output task does not produce its main data through the return value. Instead, it receives a write handle to the current node's slot:
A forward-prev task does not create new slot content for the current node. It reuses the existing slot from the direct parent:
Why are there three task types? For every registered task, YOrch needs to know how that task relates to a slot:
For tasks with no output, the related slot can be optimized away.
A callable can be written directly in the task API, or you can define a lambda, free function, or member function first and then register it as a task.
Use task(...) for regular callables:
Free functions can also be registered as regular tasks:
Use task_member(...) for member functions. A member function task must also specify a receiver object. During execution, YOrch obtains that object first and then invokes the member function on it.
The last argument of a direct-output callable must be yorch::direct_out<T>, where T is the current task's output type.
All arguments before the final direct_out<T> are still described with normal argument specs.
Use task_into(...) for regular callables:
Free functions can also be registered as direct-output tasks:
Use task_into_member(...) for member functions. A direct-output member function also needs a receiver, and its last parameter must also be yorch::direct_out<T>.
A forward-prev task requires the callable to use the previous node's output object and keep that object as the current node's logical output.
In the current model, the forwarded payload enters the callable as a mutable reference.
You can initially think of forward-prev as a task form that "does not return a new value; it mutates and forwards an existing payload". Flow-control return values are described later.
Use task_forward_prev(...) for regular callables:
Free functions can also be registered as forward-prev tasks:
Use task_forward_prev_member(...) for member functions. A forward-prev member function also needs a receiver. The forwarded payload can be a member-function argument or the receiver itself, but one forward-prev task should have only one forwarded payload.
The task registration examples above omit .... In complete code, those positions describe where each callable argument comes from. YOrch calls this kind of argument-source description a spec.
A spec does not fetch a value immediately. It records a source relationship. At execution time, YOrch uses these descriptions to pass the corresponding objects to the callable.
Prev arguments can express several access intentions:
The following examples use regular tasks. The number and order of specs must match the callable arguments one by one.
Use yorch::from_ctx<T>() to say that this argument comes from a T object in the execution context.
Use yorch::value(x) to save x into the task and pass it as an argument during execution.
If you want to store a reference to an external object, use std::ref(...):
Use yorch::borrow_prev<T>() to get const T& from the direct parent's output slot.
Use yorch::borrow_prev_mut<T>() to get T& from the direct parent's output slot.
Use yorch::copy_prev<T>() to copy a new T from the direct parent's output slot.
Use yorch::consume_prev<T>() to move a T out of the direct parent's output slot.
The callable may also receive T&& directly:
These examples combine the task types and argument specs. They only show task registration, not task tree construction.
The last argument of emit_result is yorch::direct_out<Result>, so it is registered as a direct-output task. The first two arguments come from context and from a saved value.
Here yorch::from_ctx<Emitter>() describes the member-function receiver. The two following specs correspond to the normal parameters of emit(...). The final direct_out<Result> does not need a spec.
borrow_prev_mut<Payload>() means this task directly mutates the parent's Payload. That same Payload continues as the current task's logical output.
The member-function receiver comes from context, and the forwarded payload comes from the parent output. One forward-prev task should have only one forwarded payload.
std::ref(worker) makes the task store a reference to the external worker. Execution invokes Worker::make on that object.
A task return value can produce data, but it can also express "how this step finished". YOrch interprets task return values as two parts:
If a callable returns void or a normal value T, it has no explicit flow-control intention.
void means the task completed normally and produces no slot data.T means the task completed normally and the return value becomes output data in the current node's slot.So not returning step_result does not mean "unknown success"; it means "treat
as success unless stated otherwise".
step_resultyorch::step_result only expresses flow-control state. It does not carry output data.
success: the task succeeded.failure: the task failed.retry: the task requests retry.abort_branch: stop the current branch.abort_execution: stop the entire execution.Because step_result carries no data, a regular task returning it does not write output into the current node's slot.
task_result<T>yorch::task_result<T> expresses both flow control and output data.
T, and that T enters the current node's slot.This fits tasks that sometimes produce a value and sometimes fail or abort early.
Payload {1} enters the current node's slot, and the flow state is treated as success.
void returnThis task only performs an action and does not produce new slot data. The flow state is treated as success.
step_result returnThis task only decides success or failure and does not produce slot data.
task_result<T> returnOn success, the task produces Payload and writes it into the slot. On failure, it only returns a failure state and does not construct Payload.
Direct-output tasks already write data through direct_out<T>, so their return value only needs to express flow control. If they return void, normal callable completion is treated as success.
Forward-prev tasks get their logical output from the parent's existing slot, so they also do not produce new data through their return value. If they need to express failure or abort, they can return step_result. Returning void means normal completion is success.
A task adapter wraps extra behavior around the original callable without changing the callable itself. The current adapters mainly cover:
retry.In the task(...) registration APIs, one or more adapters can be passed through yorch::adapters(...).
adapt_catch_as_failure() catches exceptions thrown during task execution and converts them into failure results.
YOrch's main execution path is designed as a no-exception surface. If a task may throw, or if the callable does not provide a noexcept call surface, it should first be converted into a non-throwing task through this adapter.
Without a custom policy, the default rule only fits tasks returning void or yorch::step_result, because those task kinds can directly construct a failure state.
You can also provide a custom catch policy. The policy receives the captured std::exception_ptr and must be callable with noexcept.
If different exception types need different handling, the policy can rethrow the exception_ptr internally and catch by type, but exceptions must not escape the policy.
Classify by exception type:
If the task returns a normal value or task_result<T>, the custom policy must return a result convertible to that task return type.
Direct-output tasks can also use catch-as-failure. For direct-output, the policy returns yorch::step_result.
adapt_retry(policy) re-executes a task when it returns retry. It is only meaningful for return types that can express retry, such as yorch::step_result and yorch::task_result<T>.
The currently built-in retry policies are:
retry_fixed_policy: allow a fixed number of retries; when exhausted, convert the final retry into failure.retry_fixed_passthrough_policy: allow a fixed number of retries; when exhausted, keep the final retry state.retry_forever_policy: keep retrying as long as the task keeps returning retry.Custom retry policies are also supported. A policy should provide should_retry(retry_count), where retry_count is the number of retries already approved.
If a custom policy does not provide a rule for exhaustion, the final retry state is preserved. It may additionally provide on_exhausted(...) to decide the exhausted result.
Multiple adapters can be combined. They are applied in written order: earlier adapters are closer to the original task, and later adapters wrap around the outside.
In this example, the original task is first wrapped by the catch adapter, and the outer retry adapter decides whether to retry based on the result.