Current section

Files

Jump to
temporal_sdk src worker temporal_sdk_worker.erl
Raw

src/worker/temporal_sdk_worker.erl

-module(temporal_sdk_worker).
% elp:ignore W0012 W0040 E1599
-moduledoc {file, "../../docs/worker/-module.md"}.
-export([
count/2,
is_started/3,
list/2,
options/3,
stats/3,
get_limiter_config/3,
set_limiter_config/4,
set_limiter_config/5,
start/3,
start/4,
terminate/3,
terminate/4
]).
-include("proto.hrl").
-type worker_type() :: activity | nexus | session | workflow.
-export_type([worker_type/0]).
-type id() :: atom() | unicode:chardata().
-export_type([id/0]).
-type worker_id() :: atom() | unicode:chardata().
-export_type([worker_id/0]).
-type opts() ::
#{
worker_id => worker_id(),
namespace => unicode:chardata(),
task_queue := unicode:chardata() | session_task_queue_name_fun(),
task_settings => task_settings(),
worker_version => worker_version(),
allowed_temporal_names => allowed_temporal_names(),
allowed_erlang_modules => allowed_erlang_modules(),
temporal_name_to_erlang => temporal_name_to_erlang(),
task_poller_pool_size => pos_integer(),
task_poller_limiter => task_poller_limiter(),
limits => temporal_sdk_limiter:levels_limits(),
limiter_check_frequency => pos_integer(),
limiter_time_windows => limiter_time_windows(),
telemetry_poll_interval => temporal_sdk:time(),
disable_telemetry => boolean()
}.
-export_type([opts/0]).
-type opts_as_list() ::
[
{worker_id, worker_id()}
| {namespace, unicode:chardata()}
| {task_queue, unicode:chardata() | session_task_queue_name_fun()}
| {task_settings, task_settings()}
| {worker_version, worker_version()}
| {allowed_temporal_names, allowed_temporal_names()}
| {allowed_erlang_modules, allowed_erlang_modules()}
| {temporal_name_to_erlang, temporal_name_to_erlang()}
| {task_poller_pool_size, pos_integer()}
| {task_poller_limiter, task_poller_limiter()}
| {limits,
temporal_sdk_limiter:levels_limits() | temporal_sdk_limiter:levels_limits_as_list()}
| {limiter_check_frequency, pos_integer()}
| {limiter_time_windows, limiter_time_windows()}
| {telemetry_poll_interval, temporal_sdk:time()}
| {disable_telemetry, boolean()}
| disable_telemetry
].
-export_type([opts_as_list/0]).
-type session_task_queue_name_fun() ::
fun(
(
Cluster :: temporal_sdk_cluster:cluster_name(),
Namespace :: unicode:chardata(),
ParentTaskQueueName :: unicode:chardata()
) -> unicode:chardata()
).
-export_type([session_task_queue_name_fun/0]).
-type task_settings() ::
activity_settings()
| activity_settings_as_list()
| nexus_settings()
| nexus_settings_as_list()
| workflow_settings()
| workflow_settings_as_list().
-export_type([task_settings/0]).
-type activity_settings() :: #{
data => temporal_sdk_activity:data(),
last_heartbeat => temporal_sdk_activity:heartbeat(),
heartbeat_timeout_ratio => float(),
schedule_to_close_timeout_ratio => float(),
start_to_close_timeout_ratio => float()
}.
-export_type([activity_settings/0]).
-type activity_settings_as_list() :: [
{data, temporal_sdk_activity:data()}
| {last_heartbeat, temporal_sdk_activity:heartbeat()}
| {heartbeat_timeout_ratio, float()}
| {schedule_to_close_timeout_ratio, float()}
| {start_to_close_timeout_ratio, float()}
].
-export_type([activity_settings_as_list/0]).
-type nexus_settings() :: #{
data => temporal_sdk_nexus:data(),
task_timeout_ratio => float(),
error_type => unicode:chardata()
}.
-export_type([nexus_settings/0]).
-type nexus_settings_as_list() :: [
{data, temporal_sdk_nexus:data()}
| {task_timeout_ratio, float()}
| {error_type, unicode:chardata()}
].
-export_type([nexus_settings_as_list/0]).
-type workflow_settings() :: #{
execution_id => temporal_sdk_workflow:execution_id(),
deterministic_check_mod => module(),
run_timeout_ratio => float(),
task_timeout_ratio => float(),
sticky_execution => sticky_execution() | sticky_execution_as_list(),
maximum_page_size => pos_integer(),
await_open_before_close => boolean(),
otp_messages_limits => [
{received, pos_integer() | infinity}
| {recorded, pos_integer() | infinity}
| {ignored, pos_integer() | infinity}
],
eager_execution_settings => activity_settings(),
session_worker => opts() | boolean()
}.
-export_type([workflow_settings/0]).
-type workflow_settings_as_list() :: [
{execution_id, temporal_sdk_workflow:execution_id()}
| {deterministic_check_mod, module()}
| {run_timeout_ratio, float()}
| {task_timeout_ratio, float()}
| {sticky_execution, sticky_execution() | sticky_execution_as_list()}
| {maximum_page_size, pos_integer()}
| {await_open_before_close, boolean()}
| {otp_messages_limits, [
{received, pos_integer() | infinity}
| {recorded, pos_integer() | infinity}
| {ignored, pos_integer() | infinity}
]}
| {eager_execution_settings, activity_settings() | activity_settings_as_list()}
| {session_worker, opts() | opts_as_list() | boolean()}
].
-export_type([workflow_settings_as_list/0]).
-type sticky_execution() ::
#{
type := local | pool | disabled,
schedule_to_start_timeout => temporal_sdk:time(),
pool_size => pos_integer(),
queue_name => unicode:chardata(),
task_poller_limiter => task_poller_limiter(),
limits => temporal_sdk_limiter:levels_limits()
}.
-export_type([sticky_execution/0]).
-type sticky_execution_as_list() ::
[
{type, local | pool | disabled}
| {schedule_to_start_timeout, temporal_sdk:time()}
| {pool_size, pos_integer()}
| {queue_name, unicode:chardata()}
| {task_poller_limiter, task_poller_limiter()}
| {limits, temporal_sdk_limiter:levels_limits()}
].
-export_type([sticky_execution_as_list/0]).
-type worker_version() :: ?TEMPORAL_SPEC:'temporal.api.common.v1.WorkerVersionStamp'().
-export_type([worker_version/0]).
-type allowed_temporal_names() :: all | [unicode:chardata()].
-export_type([allowed_temporal_names/0]).
-type allowed_erlang_modules() :: all | [module()].
-export_type([allowed_erlang_modules/0]).
-type temporal_name_to_erlang() :: fun(
(Cluster :: temporal_sdk_cluster:cluster_name(), TemporalTypeName :: unicode:chardata()) ->
{ok, module()} | {error, Reason :: term()}
).
-export_type([temporal_name_to_erlang/0]).
-type task_poller_limiter() ::
#{limit := pos_integer() | infinity, time_window := temporal_sdk:time() | undefined}.
-export_type([task_poller_limiter/0]).
-type limiter_time_windows() ::
limiter_time_windows_activity()
| limiter_time_windows_activity_as_list()
| limiter_time_windows_nexus()
| limiter_time_windows_nexus_as_list()
| limiter_time_windows_session()
| limiter_time_windows_session_as_list()
| limiter_time_windows_workflow()
| limiter_time_windows_workflow_as_list().
-type limiter_time_windows_activity() :: #{
activity_regular => temporal_sdk_limiter:time_window()
}.
-export_type([limiter_time_windows_activity/0]).
-type limiter_time_windows_activity_as_list() :: [
{activity_regular, temporal_sdk_limiter:time_window()}
].
-export_type([limiter_time_windows_activity_as_list/0]).
-type limiter_time_windows_workflow() :: #{
activity_direct => temporal_sdk_limiter:time_window(),
activity_eager => temporal_sdk_limiter:time_window(),
workflow => temporal_sdk_limiter:time_window()
}.
-export_type([limiter_time_windows_workflow/0]).
-type limiter_time_windows_workflow_as_list() :: [
{activity_eager, temporal_sdk_limiter:time_window()}
| {activity_regular, temporal_sdk_limiter:time_window()}
| {workflow, temporal_sdk_limiter:time_window()}
].
-export_type([limiter_time_windows_workflow_as_list/0]).
-type limiter_time_windows_session() :: #{
activity_session => temporal_sdk_limiter:time_window()
}.
-export_type([limiter_time_windows_session/0]).
-type limiter_time_windows_session_as_list() :: [
{activity_session, temporal_sdk_limiter:time_window()}
].
-export_type([limiter_time_windows_session_as_list/0]).
-type limiter_time_windows_nexus() :: #{
nexus => temporal_sdk_limiter:time_window()
}.
-export_type([limiter_time_windows_nexus/0]).
-type limiter_time_windows_nexus_as_list() :: [
{nexus, temporal_sdk_limiter:time_window()}
].
-export_type([limiter_time_windows_nexus_as_list/0]).
-doc """
Dynamic configuration of the rate limiter.
Use `get_limiter_config/3` to retrieve and `set_limiter_config/4` to update the dynamic configuration
of rate limiters.
See `start/3` for descriptions of the configuration options.
""".
-type limiter_config() :: #{
task_poller_limiter => task_poller_limiter(),
limits => temporal_sdk_limiter:levels_limits(),
limiter_check_frequency => pos_integer()
}.
-export_type([limiter_config/0]).
-type limiter_config_as_list() :: [
{task_poller_limiter, task_poller_limiter()}
| {limits, temporal_sdk_limiter:levels_limits()}
| {limiter_check_frequency, pos_integer()}
].
-export_type([limiter_config_as_list/0]).
-type invalid_error() :: {error, invalid_cluster | invalid_worker}.
-export_type([invalid_error/0]).
-type set_limiter_config_ret() ::
ok
| {error, {invalid_opts, map()}}
| invalid_worker
| invalid_state.
-export_type([set_limiter_config_ret/0]).
%% -------------------------------------------------------------------------------------------------
%% public
-spec count(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow
) -> {ok, non_neg_integer()} | invalid_error().
count(Cluster, WorkerType) ->
case temporal_sdk_worker_registry:count_names(Cluster, WorkerType) of
{error, _} = Err -> Err;
V -> {ok, V}
end.
-spec is_started(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id()
) -> boolean().
is_started(Cluster, session, WorkerId) ->
case temporal_sdk_worker_registry:whereis_name({Cluster, workflow, WorkerId}) of
undefined ->
false;
Pid ->
Chi = supervisor:which_children(Pid),
case lists:keyfind({temporal_sdk_poller_sup, Cluster, session, WorkerId}, 1, Chi) of
{{temporal_sdk_poller_sup, Cluster, session, WorkerId}, _Pid, supervisor, [
temporal_sdk_poller_sup
]} ->
true;
_ ->
false
end
end;
is_started(Cluster, WorkerType, WorkerId) ->
case temporal_sdk_worker_registry:whereis_name({Cluster, WorkerType, WorkerId}) of
undefined -> false;
_Pid -> true
end.
-spec list(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow
) -> {ok, [worker_id()]} | invalid_error().
list(Cluster, WorkerType) ->
case temporal_sdk_worker_registry:registered_names(Cluster, WorkerType) of
{error, _} = Err -> Err;
V -> {ok, V}
end.
-spec options(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id()
) -> {ok, opts()} | invalid_error().
options(Cluster, WorkerType, WorkerId) ->
temporal_sdk_worker_opts:get_opts(Cluster, WorkerType, WorkerId).
-spec stats(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id()
) -> {ok, temporal_sdk_limiter:stats()} | invalid_error().
stats(Cluster, WorkerType, WorkerId) ->
temporal_sdk_worker_opts:stats(Cluster, WorkerType, WorkerId).
-doc {file, "../../docs/worker/get_limiter_config-3.md"}.
-spec get_limiter_config(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id()
) -> {ok, limiter_config()} | invalid_error().
get_limiter_config(Cluster, session, WorkerId) ->
case options(Cluster, workflow, WorkerId) of
{ok, #{task_settings := #{session_worker := SW}}} when is_map(SW) ->
{ok, maps:with([task_poller_limiter, limits, limiter_check_frequency], SW)};
{ok, #{}} ->
{error, invalid_worker};
{error, _} = Err ->
Err
end;
get_limiter_config(Cluster, WorkerType, WorkerId) ->
case options(Cluster, WorkerType, WorkerId) of
{ok, O} -> {ok, maps:with([task_poller_limiter, limits, limiter_check_frequency], O)};
{error, _} = Err -> Err
end.
-doc {file, "../../docs/worker/set_limiter_config-4.md"}.
-spec set_limiter_config(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id(),
NewLimiterConfig :: limiter_config() | limiter_config_as_list()
) -> set_limiter_config_ret().
set_limiter_config(Cluster, session, WorkerId, NewLimiterConfig) ->
maybe
{ok, Limits} ?= temporal_sdk_worker_opts:build_limiter_config(session, NewLimiterConfig),
Pid = temporal_sdk_worker_registry:whereis_name({Cluster, workflow, WorkerId}),
true ?= is_pid(Pid),
Chi = supervisor:which_children(Pid),
{{temporal_sdk_worker_opts, Cluster, workflow, WorkerId}, OptsPid, worker, [
temporal_sdk_worker_opts
]} ?= lists:keyfind([temporal_sdk_worker_opts], 4, Chi),
{{temporal_sdk_poller_sup, Cluster, session, WorkerId}, PollerPid, supervisor, [
temporal_sdk_poller_sup
]} ?= lists:keyfind({temporal_sdk_poller_sup, Cluster, session, WorkerId}, 1, Chi),
PollerChi = supervisor:which_children(PollerPid),
PollerPids = [P || {_, P, worker, [temporal_sdk_poller]} <- PollerChi],
gen_server:cast(OptsPid, {set_session_limits, Limits}),
lists:foreach(
fun(P) when is_pid(P) -> gen_statem:cast(P, {set_limits, Limits}) end, PollerPids
)
else
false -> invalid_worker;
{error, _} = Err -> Err;
_ -> invalid_state
end;
set_limiter_config(Cluster, WorkerType, WorkerId, NewLimiterConfig) ->
maybe
{ok, Limits} ?= temporal_sdk_worker_opts:build_limiter_config(WorkerType, NewLimiterConfig),
Pid = temporal_sdk_worker_registry:whereis_name({Cluster, WorkerType, WorkerId}),
true ?= is_pid(Pid),
Chi = supervisor:which_children(Pid),
{{temporal_sdk_worker_opts, Cluster, WorkerType, WorkerId}, OptsPid, worker, [
temporal_sdk_worker_opts
]} ?= lists:keyfind([temporal_sdk_worker_opts], 4, Chi),
{{temporal_sdk_poller_sup, Cluster, WorkerType, WorkerId}, PollerPid, supervisor, [
temporal_sdk_poller_sup
]} ?= lists:keyfind({temporal_sdk_poller_sup, Cluster, WorkerType, WorkerId}, 1, Chi),
PollerChi = supervisor:which_children(PollerPid),
PollerPids = [P || {_, P, worker, [temporal_sdk_poller]} <- PollerChi],
gen_server:cast(OptsPid, {set_limits, Limits}),
lists:foreach(
fun(P) when is_pid(P) -> gen_statem:cast(P, {set_limits, Limits}) end, PollerPids
)
else
false -> invalid_worker;
{error, _} = Err -> Err;
_ -> invalid_state
end.
-doc {file, "../../docs/worker/set_limiter_config-5.md"}.
-spec set_limiter_config(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: worker_type(),
WorkerId :: worker_id(),
NewLimiterConfig :: limiter_config() | limiter_config_as_list(),
Nodes :: [node()]
) -> ok | [{ok, set_limiter_config_ret()} | {error, {erpc, Reason :: term()}} | term()].
set_limiter_config(Cluster, WorkerType, WorkerId, NewLimiterConfig, Nodes) ->
ErpcResult = erpc:multicall(Nodes, ?MODULE, set_limiter_config, [
Cluster, WorkerType, WorkerId, NewLimiterConfig
]),
case lists:uniq(ErpcResult) of
[{ok, ok}] -> ok;
_ -> ErpcResult
end.
-spec start(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow,
WorkerOpts :: opts() | opts_as_list()
) ->
{ok, opts()}
| {invalid_opts, map()}
| invalid_error()
| supervisor:startchild_ret().
start(Cluster, WorkerType, WorkerOpts) ->
temporal_sdk_worker_manager_sup:start_worker(Cluster, WorkerType, WorkerOpts).
-spec start(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow,
WorkerOpts :: opts() | opts_as_list(),
Nodes :: [node()]
) -> ok.
start(Cluster, WorkerType, WorkerOpts, Nodes) ->
erpc:multicast(Nodes, ?MODULE, start, [Cluster, WorkerType, WorkerOpts]).
-spec terminate(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow,
WorkerId :: worker_id()
) -> ok | {error, invalid_cluster | not_found | simple_one_for_one}.
terminate(Cluster, WorkerType, WorkerId) ->
temporal_sdk_worker_manager_sup:terminate_worker(Cluster, WorkerType, WorkerId).
-spec terminate(
Cluster :: temporal_sdk_cluster:cluster_name(),
WorkerType :: activity | nexus | workflow,
WorkerId :: worker_id(),
Nodes :: [node()]
) -> ok.
terminate(Cluster, WorkerType, WorkerId, Nodes) ->
erpc:multicast(Nodes, ?MODULE, terminate, [Cluster, WorkerType, WorkerId]).