View Source Hooks
Hooks are used to trigger events on specific actions. Hooks can be registered by passing the
handlers to the :hooks
option of the ProcessHub.t/0
configuration struct or by inserting them
dynamically using the ProcessHub.Service.HookManager
module.
ProcessHub heavily uses hooks internally in the integration tests and strategy implementations.
Hooks have to be in the format of an mfa
tuple. Basically, they are functions that will be called
when the hook is triggered.
It is possible to register a hook handler with a wildcard argument :_
, which will be replaced with the hook data when the hook is dispatched.
Hook registration examples
Example dynamic hook registration using the ProcessHub.t/0
configuration struct:
# Register a hook handler for the `:pre_cluster_join_hook` event with a wildcard argument.
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
ProcessHub.child_spec(%ProcessHub{
hub_id: :my_hub,
hooks: %{
ProcessHub.Constant.Hook.pre_cluster_join() => [
{MyModule, :my_function, [:some_data, :_]},
{MyModule2, :my_function2, [:some_data]}
],
ProcessHub.Constant.Hook.post_cluster_join() => [
{MyModule, :my_function, [:some_data, :_]}
]
}
})
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Example hook registration using the ProcessHub.Service.HookManager
module:
# Register a hook handler for the `:cluster_join` event with a wildcard argument.
ProcessHub.Service.HookManager.register_handler(
:my_hub,
ProcessHub.Constant.Hook.post_cluster_join(),
%HookManager{
id: :hook_id_1,
m: MyModule,
f: :my_function,
a: [:some_data, :_]
}
)
Hook handler examples
# The hook handler should be in the following format:
defmodule MyModule do
def my_function(:some_data, dynamic_hook_data) do
# Do something with the data.
end
end
Hooks in custom strategy implementations
Hooks can be used in custom strategy implementations to trigger events on specific actions and extend the functionality of the strategy.
Here's an example of ProcessHub using hooks internally in the
ProcessHub.Strategy.Distribution.ConsistentHashing
strategy implementation:
alias ProcessHub.Constant.StorageKey
alias ProcessHub.Service.Storage
alias ProcessHub.Service.HookManager
# Register hook handlers to update the hash ring when node joins or leaves the cluster.
def init(_strategy, hub_id) do
hub_nodes = Storage.get(hub_id, :hub_nodes)
Storage.insert(hub_id, StorageKey.hr, Ring.create_ring(hub_nodes))
join_handler = %HookManager{
id: :ch_join,
m: ProcessHub.Strategy.Distribution.ConsistentHashing,
f: :handle_node_join,
a: [hub_id, :_]
}
leave_handler = %HookManager{
id: :ch_join,
m: ProcessHub.Strategy.Distribution.ConsistentHashing,
f: :handle_node_leave,
a: [hub_id, :_]
}
HookManager.register_handlers(hub_id, Hook.pre_cluster_join(), join_handler)
HookManager.register_handlers(hub_id, Hook.pre_cluster_leave(), leave_handler)
end
# Handler functions that will be called when the hooks are triggered.
def handle_node_join(hub_id, node) do
hash_ring = Ring.get_ring(hub_id) |> Ring.add_node(node)
Storage.insert(hub_id, StorageKey.hr, hash_ring)
end
def handle_node_leave(hub_id, node) do
hash_ring = Ring.get_ring(hub_id) |> Ring.remove_node(node)
Storage.insert(hub_id, StorageKey.hr, hash_ring)
end
Using hooks to react and handle asynchronous events
Sometimes when starting a child process, it is necessary to know when the child process has been
started and is ready to receive messages. We sure could use the :async_wait
option but that would
block the process that is starting the child process. Instead, we can use hooks to react to the
:pre_children_start
event asynchronously.
Example:
# Register hook handler
alias ProcessHub.Service.HookManager
alias ProcessHub.Constant.Hook
...
hook_handler = %HookManager{
id: :handler_id_1,
m: MyModule,
f: :handle_child_start,
a: [hub_id, :_]
}
# We're triggering the hook when child process has been registered under the process registry.
HookManager.register_handler(hub_id, Hook.registry_pid_inserted(), join_handler)
...
# The hook handler
defmodule MyModule do
def handle_child_start(hub_id, {child_id, node_pids}) do
# Send message to all started child processes.
Enum.each(node_pids, fn {_node, pid} ->
Process.send(pid, :hello_world, [])
end)
end
end
Available hooks
Event Key | Trigger | Data |
---|---|---|
:pre_cluster_join_hook | Node joins hub cluster, not handled | node() |
:post_cluster_join_hook | Node joins hub cluster, handled | node() |
:pre_cluster_leave_hook | Node leaves hub cluster, not handled | node() |
:post_cluster_leave_hook | Node leaves hub cluster, handled | node() |
:registry_pid_inserted_hook | Process registered | {child_id(), [{node(), pid()}]} |
:registry_pid_removed_hook | Process unregistered | child_id() |
:children_migrated_hook | Processes migrated | {node(), [child_spec()]} |
:priority_state_updated_hook | Priority state updated | {priority_level(), list()} |
:pre_nodes_redistribution_hook | Nodes redistribution start | {:nodeup or :nodedown, node()} |
:post_nodes_redistribution_hook | Nodes redistribution end | {:nodeup or :nodedown, node()} |
:forwarded_migration_hook | Migration was forwarded and handled | [{node(), [children_start_data()]}] |
:pre_children_start | Before child process is started | ProcessHub.Handler.ChildrenAdd.StartHandle.t() |
:post_children_start | After child process is started | [{child_id(), pid(), node()}] |
:pre_children_redistribution_hook | Before redistribution of children is called | [{child_id(), node(), {:pid, pid()}}] |
:coordinator_shutdown_hook | Coordinator shutdown | any() |
:process_startups_hook | Processes have been startups | [%{cid: child_id, pid: pid()}] |
See ProcessHub.Constant.Hook
module for more information.