Skip to content

Commit ca6f5d7

Browse files
authored
Add support for automatic shutdown to Supervisor (#12077)
* Prevent auto_shutdown from being used with DynamicSupervisor * Prevent auto_shutdown from being used with PartitionSupervisor
1 parent a60f7c1 commit ca6f5d7

File tree

6 files changed

+108
-14
lines changed

6 files changed

+108
-14
lines changed

lib/elixir/lib/dynamic_supervisor.ex

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -411,30 +411,32 @@ defmodule DynamicSupervisor do
411411
restart = Map.get(child, :restart, :permanent)
412412
type = Map.get(child, :type, :worker)
413413
modules = Map.get(child, :modules, [mod])
414+
significant = Map.get(child, :significant, false)
414415

415416
shutdown =
416417
case type do
417418
:worker -> Map.get(child, :shutdown, 5_000)
418419
:supervisor -> Map.get(child, :shutdown, :infinity)
419420
end
420421

421-
validate_child(start, restart, shutdown, type, modules)
422+
validate_child(start, restart, shutdown, type, modules, significant)
422423
end
423424

424425
defp validate_child({_, start, restart, shutdown, type, modules}) do
425-
validate_child(start, restart, shutdown, type, modules)
426+
validate_child(start, restart, shutdown, type, modules, false)
426427
end
427428

428429
defp validate_child(other) do
429430
{:invalid_child_spec, other}
430431
end
431432

432-
defp validate_child(start, restart, shutdown, type, modules) do
433+
defp validate_child(start, restart, shutdown, type, modules, significant) do
433434
with :ok <- validate_start(start),
434435
:ok <- validate_restart(restart),
435436
:ok <- validate_shutdown(shutdown),
436437
:ok <- validate_type(type),
437-
:ok <- validate_modules(modules) do
438+
:ok <- validate_modules(modules),
439+
:ok <- validate_significant(significant) do
438440
{:ok, {start, restart, shutdown, type, modules}}
439441
end
440442
end
@@ -452,6 +454,9 @@ defmodule DynamicSupervisor do
452454
defp validate_shutdown(shutdown) when shutdown in [:infinity, :brutal_kill], do: :ok
453455
defp validate_shutdown(shutdown), do: {:invalid_shutdown, shutdown}
454456

457+
defp validate_significant(false), do: :ok
458+
defp validate_significant(significant), do: {:invalid_significant, significant}
459+
455460
defp validate_modules(:dynamic), do: :ok
456461

457462
defp validate_modules(mods) do
@@ -621,12 +626,14 @@ defmodule DynamicSupervisor do
621626
max_restarts = Map.get(flags, :intensity, 1)
622627
max_seconds = Map.get(flags, :period, 5)
623628
strategy = Map.get(flags, :strategy, :one_for_one)
629+
auto_shutdown = Map.get(flags, :auto_shutdown, :never)
624630

625631
with :ok <- validate_strategy(strategy),
626632
:ok <- validate_restarts(max_restarts),
627633
:ok <- validate_seconds(max_seconds),
628634
:ok <- validate_dynamic(max_children),
629-
:ok <- validate_extra_arguments(extra_arguments) do
635+
:ok <- validate_extra_arguments(extra_arguments),
636+
:ok <- validate_auto_shutdown(auto_shutdown) do
630637
{:ok,
631638
%{
632639
state
@@ -655,6 +662,11 @@ defmodule DynamicSupervisor do
655662
defp validate_extra_arguments(list) when is_list(list), do: :ok
656663
defp validate_extra_arguments(extra), do: {:error, {:invalid_extra_arguments, extra}}
657664

665+
defp validate_auto_shutdown(auto_shutdown) when auto_shutdown in [:never], do: :ok
666+
667+
defp validate_auto_shutdown(auto_shutdown),
668+
do: {:error, {:invalid_auto_shutdown, auto_shutdown}}
669+
658670
@impl true
659671
def handle_call(:which_children, _from, state) do
660672
%{children: children} = state

lib/elixir/lib/partition_supervisor.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,16 @@ defmodule PartitionSupervisor do
200200
Map.merge(map, %{id: partition, start: start, modules: modules})
201201
end
202202

203-
{init_opts, start_opts} = Keyword.split(opts, [:strategy, :max_seconds, :max_restarts])
203+
auto_shutdown = Keyword.get(opts, :auto_shutdown, :never)
204+
205+
unless auto_shutdown == :never do
206+
raise ArgumentError,
207+
"the :auto_shutdown option must be :never, got: #{inspect(auto_shutdown)}"
208+
end
209+
210+
{init_opts, start_opts} =
211+
Keyword.split(opts, [:strategy, :max_seconds, :max_restarts, :auto_shutdown])
212+
204213
Supervisor.start_link(__MODULE__, {name, partitions, children, init_opts}, start_opts)
205214
end
206215

lib/elixir/lib/supervisor.ex

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ defmodule Supervisor do
145145
and such. It is set automatically based on the `:start` value and it is rarely
146146
changed in practice.
147147
148+
* `:significant` - a boolean indicating if the child process should be
149+
considered significant with regard to automatic shutdown. Only `:transient`
150+
and `:temporary` child processes can be marked as significant. This key is
151+
optional and defaults to `false`. See section "Automatic shutdown" below
152+
for more details.
153+
148154
Let's understand what the `:shutdown` and `:restart` options control.
149155
150156
### Shutdown values (:shutdown)
@@ -302,6 +308,10 @@ defmodule Supervisor do
302308
* `:max_seconds` - the time frame in which `:max_restarts` applies.
303309
Defaults to `5`.
304310
311+
* `:auto_shutdown` - the automatic shutdown option. It can be
312+
`:never`, `:any_significant`, or `:all_significant`. Optional.
313+
See the "Automatic shutdown" section.
314+
305315
* `:name` - a name to register the supervisor process. Supported values are
306316
explained in the "Name registration" section in the documentation for
307317
`GenServer`. Optional.
@@ -327,6 +337,27 @@ defmodule Supervisor do
327337
328338
To efficiently supervise children started dynamically, see `DynamicSupervisor`.
329339
340+
### Automatic shutdown
341+
342+
Supervisors have the ability to automatically shut themselves down when child
343+
processes marked as `:significant` exit.
344+
345+
Supervisors support different automatic shutdown options (through
346+
the `:auto_shutdown` option, as seen above):
347+
348+
* `:never` - this is the default, automatic shutdown is disabled.
349+
350+
* `:any_significant` - if any significant child process exits, the supervisor
351+
will automatically shut down its children, then itself.
352+
353+
* `:all_significant` - when all significant child processes have exited,
354+
the supervisor will automatically shut down its children, then itself.
355+
356+
Only `:transient` and `:temporary` child processes can be marked as significant,
357+
and this configuration affects the behavior. Significant `:transient` child
358+
processes must exit normally for automatic shutdown to be considered, where
359+
`:temporary` child processes may exit for any reason.
360+
330361
### Name registration
331362
332363
A supervisor is bound to the same name registration rules as a `GenServer`.
@@ -521,7 +552,8 @@ defmodule Supervisor do
521552
@type sup_flags() :: %{
522553
strategy: strategy(),
523554
intensity: non_neg_integer(),
524-
period: pos_integer()
555+
period: pos_integer(),
556+
auto_shutdown: auto_shutdown()
525557
}
526558

527559
@typedoc "The supervisor reference"
@@ -532,6 +564,7 @@ defmodule Supervisor do
532564
{:strategy, strategy}
533565
| {:max_restarts, non_neg_integer}
534566
| {:max_seconds, pos_integer}
567+
| {:auto_shutdown, auto_shutdown}
535568

536569
@typedoc "Supported restart options"
537570
@type restart :: :permanent | :transient | :temporary
@@ -542,6 +575,9 @@ defmodule Supervisor do
542575
@typedoc "Supported strategies"
543576
@type strategy :: :one_for_one | :one_for_all | :rest_for_one
544577

578+
@typedoc "Supported automatic shutdown options"
579+
@type auto_shutdown :: :never | :any_significant | :all_significant
580+
545581
@typedoc """
546582
Supervisor type.
547583
@@ -561,7 +597,8 @@ defmodule Supervisor do
561597
optional(:restart) => restart(),
562598
optional(:shutdown) => shutdown(),
563599
optional(:type) => type(),
564-
optional(:modules) => [module()] | :dynamic
600+
optional(:modules) => [module()] | :dynamic,
601+
optional(:significant) => boolean()
565602
}
566603

567604
@doc """
@@ -611,7 +648,9 @@ defmodule Supervisor do
611648
[option | init_option]
612649
) :: {:ok, pid} | {:error, {:already_started, pid} | {:shutdown, term} | term}
613650
def start_link(children, options) when is_list(children) do
614-
{sup_opts, start_opts} = Keyword.split(options, [:strategy, :max_seconds, :max_restarts])
651+
{sup_opts, start_opts} =
652+
Keyword.split(options, [:strategy, :max_seconds, :max_restarts, :auto_shutdown])
653+
615654
start_link(Supervisor.Default, init(children, sup_opts), start_opts)
616655
end
617656

@@ -646,6 +685,9 @@ defmodule Supervisor do
646685
* `:max_seconds` - the time frame in seconds in which `:max_restarts`
647686
applies. Defaults to `5`.
648687
688+
* `:auto_shutdown` - the automatic shutdown option. It can be either
689+
`:never`, `:any_significant`, or `:all_significant`
690+
649691
The `:strategy` option is required and by default a maximum of 3 restarts
650692
is allowed within 5 seconds. Check the `Supervisor` module for a detailed
651693
description of the available strategies.
@@ -681,7 +723,15 @@ defmodule Supervisor do
681723

682724
intensity = Keyword.get(options, :max_restarts, 3)
683725
period = Keyword.get(options, :max_seconds, 5)
684-
flags = %{strategy: strategy, intensity: intensity, period: period}
726+
auto_shutdown = Keyword.get(options, :auto_shutdown, :never)
727+
728+
flags = %{
729+
strategy: strategy,
730+
intensity: intensity,
731+
period: period,
732+
auto_shutdown: auto_shutdown
733+
}
734+
685735
{:ok, {flags, Enum.map(children, &init_child/1)}}
686736
end
687737

@@ -805,7 +855,8 @@ defmodule Supervisor do
805855

806856
def child_spec(module_or_map, overrides) do
807857
Enum.reduce(overrides, init_child(module_or_map), fn
808-
{key, value}, acc when key in [:id, :start, :restart, :shutdown, :type, :modules] ->
858+
{key, value}, acc
859+
when key in [:id, :start, :restart, :shutdown, :type, :modules, :significant] ->
809860
Map.put(acc, key, value)
810861

811862
{key, _value}, _acc ->

lib/elixir/test/elixir/dynamic_supervisor_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ defmodule DynamicSupervisorTest do
9999
assert DynamicSupervisor.start_link(Simple, {:ok, %{extra_arguments: -1}}) ==
100100
{:error, {:supervisor_data, {:invalid_extra_arguments, -1}}}
101101

102+
assert DynamicSupervisor.start_link(Simple, {:ok, %{auto_shutdown: :any_significant}}) ==
103+
{:error, {:supervisor_data, {:invalid_auto_shutdown, :any_significant}}}
104+
102105
assert DynamicSupervisor.start_link(Simple, :unknown) ==
103106
{:error, {:bad_return, {Simple, :init, :unknown}}}
104107

@@ -236,6 +239,13 @@ defmodule DynamicSupervisorTest do
236239
shutdown: -1
237240
}) ==
238241
{:error, {:invalid_shutdown, -1}}
242+
243+
assert DynamicSupervisor.start_child(:not_used, %{
244+
id: 1,
245+
start: {Task, :foo, [:bar]},
246+
significant: true
247+
}) ==
248+
{:error, {:invalid_significant, true}}
239249
end
240250

241251
test "with different returns" do

lib/elixir/test/elixir/partition_supervisor_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,18 @@ defmodule PartitionSupervisorTest do
119119
)
120120
end
121121
end
122+
123+
test "raises with bad auto_shutdown" do
124+
assert_raise ArgumentError,
125+
"the :auto_shutdown option must be :never, got: :any_significant",
126+
fn ->
127+
PartitionSupervisor.start_link(
128+
child_spec: DynamicSupervisor,
129+
name: Foo,
130+
auto_shutdown: :any_significant
131+
)
132+
end
133+
end
122134
end
123135

124136
describe "stop/1" do

lib/elixir/test/elixir/supervisor_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,11 @@ defmodule SupervisorTest do
106106
end
107107

108108
test "init/2" do
109-
flags = %{intensity: 3, period: 5, strategy: :one_for_one}
109+
flags = %{intensity: 3, period: 5, strategy: :one_for_one, auto_shutdown: :never}
110110
children = [%{id: Task, restart: :temporary, start: {Task, :start_link, [[]]}}]
111111
assert Supervisor.init([Task], strategy: :one_for_one) == {:ok, {flags, children}}
112112

113-
flags = %{intensity: 1, period: 2, strategy: :one_for_all}
113+
flags = %{intensity: 1, period: 2, strategy: :one_for_all, auto_shutdown: :never}
114114
children = [%{id: Task, restart: :temporary, start: {Task, :start_link, [:foo]}}]
115115

116116
assert Supervisor.init(
@@ -126,7 +126,7 @@ defmodule SupervisorTest do
126126
end
127127

128128
test "init/2 with old and new child specs" do
129-
flags = %{intensity: 3, period: 5, strategy: :one_for_one}
129+
flags = %{intensity: 3, period: 5, strategy: :one_for_one, auto_shutdown: :never}
130130

131131
children = [
132132
%{id: Task, restart: :temporary, start: {Task, :start_link, [[]]}},

0 commit comments

Comments
 (0)