Skip to content

Module dev_stack.erl

A device that contains a stack of other devices, and manages their execution.

Description

It can run in two modes: fold (the default), and map.

In fold mode, it runs upon input messages in the order of their keys. A stack maintains and passes forward a state (expressed as a message) as it progresses through devices.

For example, a stack of devices as follows:

   Device -> Stack
   Device-Stack/1/Name -> Add-One-Device
   Device-Stack/2/Name -> Add-Two-Device

When called with the message:

   #{ Path = "FuncName", binary => <code><<"0">></code> }

Will produce the output:

   #{ Path = "FuncName", binary => <code><<"3">></code> }
   {ok, #{ bin => <code><<"3">></code> }}

In map mode, the stack will run over all the devices in the stack, and combine their results into a single message. Each of the devices' output values have a key that is the device's name in the Device-Stack (its number if the stack is a list).

You can switch between fold and map modes by setting the Mode key in the Msg2 to either Fold or Map, or set it globally for the stack by setting the Mode key in the Msg1 message. The key in Msg2 takes precedence over the key in Msg1.

The key that is called upon the device stack is the same key that is used upon the devices that are contained within it. For example, in the above scenario we resolve FuncName on the stack, leading FuncName to be called on Add-One-Device and Add-Two-Device.

A device stack responds to special statuses upon responses as follows:

skip: Skips the rest of the device stack for the current pass.

pass: Causes the stack to increment its pass number and re-execute the stack from the first device, maintaining the state accumulated so far. Only available in fold mode.

In all cases, the device stack will return the accumulated state to the caller as the result of the call to the stack.

The dev_stack adds additional metadata to the message in order to track the state of its execution as it progresses through devices. These keys are as follows:

Stack-Pass: The number of times the stack has reset and re-executed from the first device for the current message.

Input-Prefix: The prefix that the device should use for its outputs and inputs.

Output-Prefix: The device that was previously executed.

All counters used by the stack are initialized to 1.

Additionally, as implemented in HyperBEAM, the device stack will honor a number of options that are passed to it as keys in the message. Each of these options is also passed through to the devices contained within the stack during execution. These options include:

Error-Strategy: Determines how the stack handles errors from devices. See maybe_error/5 for more information.

Allow-Multipass: Determines whether the stack is allowed to automatically re-execute from the first device when the pass tag is returned. See maybe_pass/3 for more information.

Under-the-hood, dev_stack uses a default handler to resolve all calls to devices, aside set/2 which it calls itself to mutate the message's device key in order to change which device is currently being executed. This method allows dev_stack to ensure that the message's HashPath is always correct, even as it delegates calls to other devices. An example flow for a dev_stack execution is as follows:

    /Msg1/AlicesExcitingKey ->
        dev_stack:execute ->
            /Msg1/Set?device=/Device-Stack/1 ->
            /Msg2/AlicesExcitingKey ->
            /Msg3/Set?device=/Device-Stack/2 ->
            /Msg4/AlicesExcitingKey
            ... ->
            /MsgN/Set?device=[This-Device] ->
        returns {ok, /MsgN+1} ->
    /MsgN+1

In this example, the device key is mutated a number of times, but the resulting HashPath remains correct and verifiable.

Function Index

benchmark_test/0*
example_device_for_stack_test/0*
generate_append_device/1
generate_append_device/2*
increment_pass/2*Helper to increment the pass number.
info/1
input_and_output_prefixes_test/0*
input_output_prefixes_passthrough_test/0*
input_prefix/3Return the input prefix for the stack.
many_devices_test/0*
maybe_error/5*
no_prefix_test/0*
not_found_test/0*
output_prefix/3Return the output prefix for the stack.
output_prefix_test/0*
pass_test/0*
prefix/3Return the default prefix for the stack.
reinvocation_test/0*
resolve_fold/3*The main device stack execution engine.
resolve_fold/4*
resolve_map/3*Map over the devices in the stack, accumulating the output in a single message of keys and values, where keys are the same as the keys in the original message (typically a number).
router/3*
router/4The device stack key router.
simple_map_test/0*
simple_stack_execute_test/0*
skip_test/0*
test_prefix_msg/0*
transform/3*Return Message1, transformed such that the device named Key from the Device-Stack key in the message takes the place of the original Device key.
transform_external_call_device_test/0*Ensure we can generate a transformer message that can be called to return a version of msg1 with only that device attached.
transform_internal_call_device_test/0*Test that the transform function can be called correctly internally by other functions in the module.
transformer_message/2*Return a message which, when given a key, will transform the message such that the device named Key from the Device-Stack key in the message takes the place of the original Device key.

Function Details

benchmark_test/0 *

benchmark_test() -> any()

example_device_for_stack_test/0 *

example_device_for_stack_test() -> any()

generate_append_device/1

generate_append_device(Separator) -> any()

generate_append_device/2 *

generate_append_device(Separator, Status) -> any()

increment_pass/2 *

increment_pass(Message, Opts) -> any()

Helper to increment the pass number.

info/1

info(Msg) -> any()

input_and_output_prefixes_test/0 *

input_and_output_prefixes_test() -> any()

input_output_prefixes_passthrough_test/0 *

input_output_prefixes_passthrough_test() -> any()

input_prefix/3

input_prefix(Msg1, Msg2, Opts) -> any()

Return the input prefix for the stack.

many_devices_test/0 *

many_devices_test() -> any()

maybe_error/5 *

maybe_error(Message1, Message2, DevNum, Info, Opts) -> any()

no_prefix_test/0 *

no_prefix_test() -> any()

not_found_test/0 *

not_found_test() -> any()

output_prefix/3

output_prefix(Msg1, Msg2, Opts) -> any()

Return the output prefix for the stack.

output_prefix_test/0 *

output_prefix_test() -> any()

pass_test/0 *

pass_test() -> any()

prefix/3

prefix(Msg1, Msg2, Opts) -> any()

Return the default prefix for the stack.

reinvocation_test/0 *

reinvocation_test() -> any()

resolve_fold/3 *

resolve_fold(Message1, Message2, Opts) -> any()

The main device stack execution engine. See the moduledoc for more information.

resolve_fold/4 *

resolve_fold(Message1, Message2, DevNum, Opts) -> any()

resolve_map/3 *

resolve_map(Message1, Message2, Opts) -> any()

Map over the devices in the stack, accumulating the output in a single message of keys and values, where keys are the same as the keys in the original message (typically a number).

router/3 *

router(Message1, Message2, Opts) -> any()

router/4

router(Key, Message1, Message2, Opts) -> any()

The device stack key router. Sends the request to resolve_stack, except for set/2 which is handled by the default implementation in dev_message.

simple_map_test/0 *

simple_map_test() -> any()

simple_stack_execute_test/0 *

simple_stack_execute_test() -> any()

skip_test/0 *

skip_test() -> any()

test_prefix_msg/0 *

test_prefix_msg() -> any()

transform/3 *

transform(Msg1, Key, Opts) -> any()

Return Message1, transformed such that the device named Key from the Device-Stack key in the message takes the place of the original Device key. This transformation allows dev_stack to correctly track the HashPath of the message as it delegates execution to devices contained within it.

transform_external_call_device_test/0 *

transform_external_call_device_test() -> any()

Ensure we can generate a transformer message that can be called to return a version of msg1 with only that device attached.

transform_internal_call_device_test/0 *

transform_internal_call_device_test() -> any()

Test that the transform function can be called correctly internally by other functions in the module.

transformer_message/2 *

transformer_message(Msg1, Opts) -> any()

Return a message which, when given a key, will transform the message such that the device named Key from the Device-Stack key in the message takes the place of the original Device key. This allows users to call a single device from the stack:

/Msg1/Transform/DeviceName/keyInDevice -> keyInDevice executed on DeviceName against Msg1.