I have no access to iex atm, but it could be easily validated with 10 LoCs.
Good call! It seems like exceeding :max_restarts restarts in a :max_period window does lead to the restart of the supervisor:
defmodule TestSup.GoodWorker do
use GenServer
require Logger
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
def init(opts) do
Logger.info("#{inspect(__MODULE__)}::#{inspect(__ENV__.function)}")
{:ok, opts}
end
end
defmodule TestSup.BadWorker do
use GenServer, restart: :transient
require Logger
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
def init(opts) do
Logger.info("#{inspect(__MODULE__)}::#{inspect(__ENV__.function)}")
Process.send_after(self(), :do_work, 500)
{:ok, opts}
end
def handle_info(:do_work, state) do
Logger.warning("#{inspect(__MODULE__)}::#{inspect(__ENV__.function)}: about to die")
raise "error"
{:noreply, state}
end
end
defmodule TestSup.Supervisor do
use Supervisor
require Logger
def start_link(opts), do: Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
def init(_init_arg) do
Logger.info("#{inspect(__MODULE__)}::#{inspect(__ENV__.function)}")
children = [
TestSup.GoodWorker,
TestSup.BadWorker
]
Supervisor.init(children, strategy: :one_for_one, max_restarts: 3, max_seconds: 5)
end
end
defmodule TestSup.Application do
use Application
require Logger
def start(_type, _args) do
Logger.info("#{inspect(__MODULE__)}::#{inspect(__ENV__.function)}")
children = [
TestSup.Supervisor
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: TestSup.AppSupervisor]
Supervisor.start_link(children, opts)
end
end
Running this code will lead to an endless stream of errors and log messages of GenServers and Supervisor restarts:
$ mix run --no-halt
Compiling 1 file (.ex)
Generated test_sup app
20:29:51.683 [info] TestSup.Application::{:start, 2}
20:29:51.685 [info] TestSup.Supervisor::{:init, 1}
20:29:51.685 [info] TestSup.GoodWorker::{:init, 1}
20:29:51.685 [info] TestSup.BadWorker::{:init, 1}
20:29:52.186 [warning] TestSup.BadWorker::{:handle_info, 2}: about to die
20:29:52.188 [error] GenServer TestSup.BadWorker terminating
** (RuntimeError) error
(test_sup 0.1.0) lib/test_sup/application.ex:32: TestSup.BadWorker.handle_info/2
(stdlib 6.2.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: []
20:29:52.192 [info] TestSup.BadWorker::{:init, 1}
20:29:52.693 [warning] TestSup.BadWorker::{:handle_info, 2}: about to die
20:29:52.693 [error] GenServer TestSup.BadWorker terminating
** (RuntimeError) error
(test_sup 0.1.0) lib/test_sup/application.ex:32: TestSup.BadWorker.handle_info/2
(stdlib 6.2.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: []
20:29:52.693 [info] TestSup.BadWorker::{:init, 1}
20:29:53.194 [warning] TestSup.BadWorker::{:handle_info, 2}: about to die
20:29:53.194 [error] GenServer TestSup.BadWorker terminating
** (RuntimeError) error
(test_sup 0.1.0) lib/test_sup/application.ex:32: TestSup.BadWorker.handle_info/2
(stdlib 6.2.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: []
20:29:53.194 [info] TestSup.BadWorker::{:init, 1}
20:29:53.695 [warning] TestSup.BadWorker::{:handle_info, 2}: about to die
20:29:53.695 [error] GenServer TestSup.BadWorker terminating
** (RuntimeError) error
(test_sup 0.1.0) lib/test_sup/application.ex:32: TestSup.BadWorker.handle_info/2
(stdlib 6.2.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: []
20:29:53.695 [info] TestSup.Supervisor::{:init, 1}
20:29:53.695 [info] TestSup.GoodWorker::{:init, 1}
20:29:53.695 [info] TestSup.BadWorker::{:init, 1}
20:29:54.196 [warning] TestSup.BadWorker::{:handle_info, 2}: about to die
20:29:54.196 [error] GenServer TestSup.BadWorker terminating
** (RuntimeError) error
(test_sup 0.1.0) lib/test_sup/application.ex:32: TestSup.BadWorker.handle_info/2
(stdlib 6.2.2.2) gen_server.erl:2345: :gen_server.try_handle_info/3
(stdlib 6.2.2.2) gen_server.erl:2433: :gen_server.handle_msg/6
(stdlib 6.2.2.2) proc_lib.erl:329: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: []
[...repeated...]
You can see that TestSup.Supervisor has been restarted.
If you add max_restarts: 1 to opts in TestSup.Application, you’ll see it will eventually lead to the shutdown of the application:
20:34:07.870 [notice] Application test_sup exited: shutdown
I just wanted to mention it, because in my experience if you don’t really expect an infinite loop there, it’d be safer to increase :max_restarts parameter and forget about this potential issue.
Yeah, it’s a solid advice which I will implement (combined with changing the :restart type of MyWorker to :permanent), I just wanted to get to the bottom of the issue, if possible.