diff --git a/lib/elixir/lib/dynamic_supervisor.ex b/lib/elixir/lib/dynamic_supervisor.ex index 92ef5b6c9ec..92161c1f861 100644 --- a/lib/elixir/lib/dynamic_supervisor.ex +++ b/lib/elixir/lib/dynamic_supervisor.ex @@ -411,6 +411,7 @@ defmodule DynamicSupervisor do restart = Map.get(child, :restart, :permanent) type = Map.get(child, :type, :worker) modules = Map.get(child, :modules, [mod]) + significant = Map.get(child, :significant, false) shutdown = case type do @@ -418,23 +419,24 @@ defmodule DynamicSupervisor do :supervisor -> Map.get(child, :shutdown, :infinity) end - validate_child(start, restart, shutdown, type, modules) + validate_child(start, restart, shutdown, type, modules, significant) end defp validate_child({_, start, restart, shutdown, type, modules}) do - validate_child(start, restart, shutdown, type, modules) + validate_child(start, restart, shutdown, type, modules, false) end defp validate_child(other) do {:invalid_child_spec, other} end - defp validate_child(start, restart, shutdown, type, modules) do + defp validate_child(start, restart, shutdown, type, modules, significant) do with :ok <- validate_start(start), :ok <- validate_restart(restart), :ok <- validate_shutdown(shutdown), :ok <- validate_type(type), - :ok <- validate_modules(modules) do + :ok <- validate_modules(modules), + :ok <- validate_significant(significant) do {:ok, {start, restart, shutdown, type, modules}} end end @@ -452,6 +454,9 @@ defmodule DynamicSupervisor do defp validate_shutdown(shutdown) when shutdown in [:infinity, :brutal_kill], do: :ok defp validate_shutdown(shutdown), do: {:invalid_shutdown, shutdown} + defp validate_significant(false), do: :ok + defp validate_significant(significant), do: {:invalid_significant, significant} + defp validate_modules(:dynamic), do: :ok defp validate_modules(mods) do @@ -621,12 +626,14 @@ defmodule DynamicSupervisor do max_restarts = Map.get(flags, :intensity, 1) max_seconds = Map.get(flags, :period, 5) strategy = Map.get(flags, :strategy, :one_for_one) + auto_shutdown = Map.get(flags, :auto_shutdown, :never) with :ok <- validate_strategy(strategy), :ok <- validate_restarts(max_restarts), :ok <- validate_seconds(max_seconds), :ok <- validate_dynamic(max_children), - :ok <- validate_extra_arguments(extra_arguments) do + :ok <- validate_extra_arguments(extra_arguments), + :ok <- validate_auto_shutdown(auto_shutdown) do {:ok, %{ state @@ -655,6 +662,11 @@ defmodule DynamicSupervisor do defp validate_extra_arguments(list) when is_list(list), do: :ok defp validate_extra_arguments(extra), do: {:error, {:invalid_extra_arguments, extra}} + defp validate_auto_shutdown(auto_shutdown) when auto_shutdown in [:never], do: :ok + + defp validate_auto_shutdown(auto_shutdown), + do: {:error, {:invalid_auto_shutdown, auto_shutdown}} + @impl true def handle_call(:which_children, _from, state) do %{children: children} = state diff --git a/lib/elixir/lib/partition_supervisor.ex b/lib/elixir/lib/partition_supervisor.ex index 32a9f40a754..add408d8399 100644 --- a/lib/elixir/lib/partition_supervisor.ex +++ b/lib/elixir/lib/partition_supervisor.ex @@ -200,7 +200,16 @@ defmodule PartitionSupervisor do Map.merge(map, %{id: partition, start: start, modules: modules}) end - {init_opts, start_opts} = Keyword.split(opts, [:strategy, :max_seconds, :max_restarts]) + auto_shutdown = Keyword.get(opts, :auto_shutdown, :never) + + unless auto_shutdown == :never do + raise ArgumentError, + "the :auto_shutdown option must be :never, got: #{inspect(auto_shutdown)}" + end + + {init_opts, start_opts} = + Keyword.split(opts, [:strategy, :max_seconds, :max_restarts, :auto_shutdown]) + Supervisor.start_link(__MODULE__, {name, partitions, children, init_opts}, start_opts) end diff --git a/lib/elixir/lib/supervisor.ex b/lib/elixir/lib/supervisor.ex index a6d75bfe33b..95f59f10b16 100644 --- a/lib/elixir/lib/supervisor.ex +++ b/lib/elixir/lib/supervisor.ex @@ -145,6 +145,12 @@ defmodule Supervisor do and such. It is set automatically based on the `:start` value and it is rarely changed in practice. + * `:significant` - a boolean indicating if the child process should be + considered significant with regard to automatic shutdown. Only `:transient` + and `:temporary` child processes can be marked as significant. This key is + optional and defaults to `false`. See section "Automatic shutdown" below + for more details. + Let's understand what the `:shutdown` and `:restart` options control. ### Shutdown values (:shutdown) @@ -302,6 +308,10 @@ defmodule Supervisor do * `:max_seconds` - the time frame in which `:max_restarts` applies. Defaults to `5`. + * `:auto_shutdown` - the automatic shutdown option. It can be + `:never`, `:any_significant`, or `:all_significant`. Optional. + See the "Automatic shutdown" section. + * `:name` - a name to register the supervisor process. Supported values are explained in the "Name registration" section in the documentation for `GenServer`. Optional. @@ -327,6 +337,27 @@ defmodule Supervisor do To efficiently supervise children started dynamically, see `DynamicSupervisor`. + ### Automatic shutdown + + Supervisors have the ability to automatically shut themselves down when child + processes marked as `:significant` exit. + + Supervisors support different automatic shutdown options (through + the `:auto_shutdown` option, as seen above): + + * `:never` - this is the default, automatic shutdown is disabled. + + * `:any_significant` - if any significant child process exits, the supervisor + will automatically shut down its children, then itself. + + * `:all_significant` - when all significant child processes have exited, + the supervisor will automatically shut down its children, then itself. + + Only `:transient` and `:temporary` child processes can be marked as significant, + and this configuration affects the behavior. Significant `:transient` child + processes must exit normally for automatic shutdown to be considered, where + `:temporary` child processes may exit for any reason. + ### Name registration A supervisor is bound to the same name registration rules as a `GenServer`. @@ -521,7 +552,8 @@ defmodule Supervisor do @type sup_flags() :: %{ strategy: strategy(), intensity: non_neg_integer(), - period: pos_integer() + period: pos_integer(), + auto_shutdown: auto_shutdown() } @typedoc "The supervisor reference" @@ -532,6 +564,7 @@ defmodule Supervisor do {:strategy, strategy} | {:max_restarts, non_neg_integer} | {:max_seconds, pos_integer} + | {:auto_shutdown, auto_shutdown} @typedoc "Supported restart options" @type restart :: :permanent | :transient | :temporary @@ -542,6 +575,9 @@ defmodule Supervisor do @typedoc "Supported strategies" @type strategy :: :one_for_one | :one_for_all | :rest_for_one + @typedoc "Supported automatic shutdown options" + @type auto_shutdown :: :never | :any_significant | :all_significant + @typedoc """ Supervisor type. @@ -561,7 +597,8 @@ defmodule Supervisor do optional(:restart) => restart(), optional(:shutdown) => shutdown(), optional(:type) => type(), - optional(:modules) => [module()] | :dynamic + optional(:modules) => [module()] | :dynamic, + optional(:significant) => boolean() } @doc """ @@ -611,7 +648,9 @@ defmodule Supervisor do [option | init_option] ) :: {:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term} def start_link(children, options) when is_list(children) do - {sup_opts, start_opts} = Keyword.split(options, [:strategy, :max_seconds, :max_restarts]) + {sup_opts, start_opts} = + Keyword.split(options, [:strategy, :max_seconds, :max_restarts, :auto_shutdown]) + start_link(Supervisor.Default, init(children, sup_opts), start_opts) end @@ -646,6 +685,9 @@ defmodule Supervisor do * `:max_seconds` - the time frame in seconds in which `:max_restarts` applies. Defaults to `5`. + * `:auto_shutdown` - the automatic shutdown option. It can be either + `:never`, `:any_significant`, or `:all_significant` + The `:strategy` option is required and by default a maximum of 3 restarts is allowed within 5 seconds. Check the `Supervisor` module for a detailed description of the available strategies. @@ -681,7 +723,15 @@ defmodule Supervisor do intensity = Keyword.get(options, :max_restarts, 3) period = Keyword.get(options, :max_seconds, 5) - flags = %{strategy: strategy, intensity: intensity, period: period} + auto_shutdown = Keyword.get(options, :auto_shutdown, :never) + + flags = %{ + strategy: strategy, + intensity: intensity, + period: period, + auto_shutdown: auto_shutdown + } + {:ok, {flags, Enum.map(children, &init_child/1)}} end @@ -805,7 +855,8 @@ defmodule Supervisor do def child_spec(module_or_map, overrides) do Enum.reduce(overrides, init_child(module_or_map), fn - {key, value}, acc when key in [:id, :start, :restart, :shutdown, :type, :modules] -> + {key, value}, acc + when key in [:id, :start, :restart, :shutdown, :type, :modules, :significant] -> Map.put(acc, key, value) {key, _value}, _acc -> diff --git a/lib/elixir/test/elixir/dynamic_supervisor_test.exs b/lib/elixir/test/elixir/dynamic_supervisor_test.exs index 6ff44ed2dfd..f222b368877 100644 --- a/lib/elixir/test/elixir/dynamic_supervisor_test.exs +++ b/lib/elixir/test/elixir/dynamic_supervisor_test.exs @@ -99,6 +99,9 @@ defmodule DynamicSupervisorTest do assert DynamicSupervisor.start_link(Simple, {:ok, %{extra_arguments: -1}}) == {:error, {:supervisor_data, {:invalid_extra_arguments, -1}}} + assert DynamicSupervisor.start_link(Simple, {:ok, %{auto_shutdown: :any_significant}}) == + {:error, {:supervisor_data, {:invalid_auto_shutdown, :any_significant}}} + assert DynamicSupervisor.start_link(Simple, :unknown) == {:error, {:bad_return, {Simple, :init, :unknown}}} @@ -236,6 +239,13 @@ defmodule DynamicSupervisorTest do shutdown: -1 }) == {:error, {:invalid_shutdown, -1}} + + assert DynamicSupervisor.start_child(:not_used, %{ + id: 1, + start: {Task, :foo, [:bar]}, + significant: true + }) == + {:error, {:invalid_significant, true}} end test "with different returns" do diff --git a/lib/elixir/test/elixir/partition_supervisor_test.exs b/lib/elixir/test/elixir/partition_supervisor_test.exs index f9ec0c0c156..3295cac1832 100644 --- a/lib/elixir/test/elixir/partition_supervisor_test.exs +++ b/lib/elixir/test/elixir/partition_supervisor_test.exs @@ -119,6 +119,18 @@ defmodule PartitionSupervisorTest do ) end end + + test "raises with bad auto_shutdown" do + assert_raise ArgumentError, + "the :auto_shutdown option must be :never, got: :any_significant", + fn -> + PartitionSupervisor.start_link( + child_spec: DynamicSupervisor, + name: Foo, + auto_shutdown: :any_significant + ) + end + end end describe "stop/1" do diff --git a/lib/elixir/test/elixir/supervisor_test.exs b/lib/elixir/test/elixir/supervisor_test.exs index e51faa88503..6b7ea82c402 100644 --- a/lib/elixir/test/elixir/supervisor_test.exs +++ b/lib/elixir/test/elixir/supervisor_test.exs @@ -106,11 +106,11 @@ defmodule SupervisorTest do end test "init/2" do - flags = %{intensity: 3, period: 5, strategy: :one_for_one} + flags = %{intensity: 3, period: 5, strategy: :one_for_one, auto_shutdown: :never} children = [%{id: Task, restart: :temporary, start: {Task, :start_link, [[]]}}] assert Supervisor.init([Task], strategy: :one_for_one) == {:ok, {flags, children}} - flags = %{intensity: 1, period: 2, strategy: :one_for_all} + flags = %{intensity: 1, period: 2, strategy: :one_for_all, auto_shutdown: :never} children = [%{id: Task, restart: :temporary, start: {Task, :start_link, [:foo]}}] assert Supervisor.init( @@ -126,7 +126,7 @@ defmodule SupervisorTest do end test "init/2 with old and new child specs" do - flags = %{intensity: 3, period: 5, strategy: :one_for_one} + flags = %{intensity: 3, period: 5, strategy: :one_for_one, auto_shutdown: :never} children = [ %{id: Task, restart: :temporary, start: {Task, :start_link, [[]]}},