diff options
author | José Valim <jose.valim@plataformatec.com.br> | 2018-06-20 23:53:55 +0200 |
---|---|---|
committer | José Valim <jose.valim@plataformatec.com.br> | 2018-06-20 23:53:55 +0200 |
commit | 12a4ea4c9fb83dd9f370fc9247207c02cf0d5443 (patch) | |
tree | 0743d787b93b199f931cf4eb5a2e11adb5de65d4 | |
parent | 45eb56f40ce6e25ffeb48f67a392cd5a629dd137 (diff) | |
download | elixir-12a4ea4c9fb83dd9f370fc9247207c02cf0d5443.tar.gz |
Add handle_continue/2 and deprecate super in GenServer callbacks
-rw-r--r-- | lib/elixir/lib/agent/server.ex | 8 | ||||
-rw-r--r-- | lib/elixir/lib/gen_server.ex | 99 | ||||
-rw-r--r-- | lib/elixir/lib/registry.ex | 4 | ||||
-rw-r--r-- | lib/elixir/lib/string_io.ex | 8 | ||||
-rw-r--r-- | lib/elixir/src/elixir_expand.erl | 22 | ||||
-rw-r--r-- | lib/elixir/src/elixir_overridable.erl | 12 | ||||
-rw-r--r-- | lib/elixir/test/elixir/gen_server_test.exs | 8 | ||||
-rw-r--r-- | lib/elixir/test/elixir/kernel/warning_test.exs | 16 |
8 files changed, 113 insertions, 64 deletions
diff --git a/lib/elixir/lib/agent/server.ex b/lib/elixir/lib/agent/server.ex index 0f32ba049..03cb763ea 100644 --- a/lib/elixir/lib/agent/server.ex +++ b/lib/elixir/lib/agent/server.ex @@ -23,18 +23,10 @@ defmodule Agent.Server do {:reply, :ok, run(fun, [state])} end - def handle_call(msg, from, state) do - super(msg, from, state) - end - def handle_cast({:cast, fun}, state) do {:noreply, run(fun, [state])} end - def handle_cast(msg, state) do - super(msg, state) - end - def code_change(_old, state, fun) do {:ok, run(fun, [state])} end diff --git a/lib/elixir/lib/gen_server.ex b/lib/elixir/lib/gen_server.ex index 6fa7ed996..9964d2cfe 100644 --- a/lib/elixir/lib/gen_server.ex +++ b/lib/elixir/lib/gen_server.ex @@ -90,7 +90,7 @@ defmodule GenServer do end # Server (callbacks) - + @impl true def init(stack) do {:ok, stack} @@ -350,18 +350,24 @@ defmodule GenServer do except `handle_info(:timeout, state)` will be called after `timeout` milliseconds if no messages are received within the timeout. - Returning `{:ok, state, :hibernate}` is similar to - `{:ok, state}` except the process is hibernated before entering the loop. See + Returning `{:ok, state, :hibernate}` is similar to `{:ok, state}` + except the process is hibernated before entering the loop. See `c:handle_call/3` for more information on hibernation. - Returning `:ignore` will cause `start_link/3` to return `:ignore` and the - process will exit normally without entering the loop or calling `c:terminate/2`. - If used when part of a supervision tree the parent supervisor will not fail - to start nor immediately try to restart the `GenServer`. The remainder of the - supervision tree will be (re)started and so the `GenServer` should not be - required by other processes. It can be started later with - `Supervisor.restart_child/2` as the child specification is saved in the parent - supervisor. The main use cases for this are: + Returning `{:ok, state, {:continue, continue}}` is similar to + `{:ok, state}` except that immediately after entering the loop + the `c:handle_continue/2` callback will be invoked with `Continue` + as first argument. + + Returning `:ignore` will cause `start_link/3` to return `:ignore` and + the process will exit normally without entering the loop or calling + `c:terminate/2`. If used when part of a supervision tree the parent + supervisor will not fail to start nor immediately try to restart the + `GenServer`. The remainder of the supervision tree will be started + and so the `GenServer` should not be required by other processes. + It can be started later with `Supervisor.restart_child/2` as the child + specification is saved in the parent supervisor. The main use cases for + this are: * The `GenServer` is disabled by configuration but might be enabled later. * An error occurred and it will be handled by a different mechanism than the @@ -374,7 +380,7 @@ defmodule GenServer do """ @callback init(args :: term) :: {:ok, state} - | {:ok, state, timeout | :hibernate} + | {:ok, state, timeout | :hibernate | {:continue, term}} | :ignore | {:stop, reason :: any} when state: any @@ -401,6 +407,10 @@ defmodule GenServer do `GenServer` causes garbage collection and leaves a continuous heap that minimises the memory used by the process. + Returning `{:reply, reply, new_state, {:continue, continue}}` is similar to + `{:reply, reply, new_state}` except `c:handle_continue/2` will be invoked + immediately after with `continue` as first argument. + Hibernating should not be used aggressively as too much time could be spent garbage collecting. Normally it should only be used when a message is not expected soon and minimising the memory of the process is shown to be @@ -422,9 +432,9 @@ defmodule GenServer do process exits without replying as the caller will be blocking awaiting a reply. - Returning `{:noreply, new_state, timeout | :hibernate}` is similar to - `{:noreply, new_state}` except a timeout or hibernation occurs as with a - `:reply` tuple. + Returning `{:noreply, new_state, timeout | :hibernate | {:continue, continue}}` + is similar to `{:noreply, new_state}` except a timeout, hibernation or continue + occurs as with a `:reply` tuple. Returning `{:stop, reason, reply, new_state}` stops the loop and `c:terminate/2` is called with reason `reason` and state `new_state`. Then the `reply` is sent @@ -433,15 +443,14 @@ defmodule GenServer do Returning `{:stop, reason, new_state}` is similar to `{:stop, reason, reply, new_state}` except a reply is not sent. - If this callback is not implemented, the default implementation by - `use GenServer` will fail with a `RuntimeError` exception with a message: - attempted to call `GenServer` but no `handle_call/3` clause was provided. + This callback is optional. If one is not implemented, the server will fail + if a call is performed against it. """ @callback handle_call(request :: term, from, state :: term) :: {:reply, reply, new_state} - | {:reply, reply, new_state, timeout | :hibernate} + | {:reply, reply, new_state, timeout | :hibernate | {:continue, term}} | {:noreply, new_state} - | {:noreply, new_state, timeout | :hibernate} + | {:noreply, new_state, timeout | :hibernate, {:continue, term}} | {:stop, reason, reply, new_state} | {:stop, reason, new_state} when reply: term, new_state: term, reason: term @@ -462,17 +471,20 @@ defmodule GenServer do `{:noreply, new_state}` except the process is hibernated before continuing the loop. See `c:handle_call/3` for more information. + Returning `{:noreply, new_state, {:continue, continue}}` is similar to + `{:nreply, new_state}` except `c:handle_continue/2` will be invoked + immediately after with `continue` as first argument. + Returning `{:stop, reason, new_state}` stops the loop and `c:terminate/2` is called with the reason `reason` and state `new_state`. The process exits with reason `reason`. - If this callback is not implemented, the default implementation by - `use GenServer` will fail with a `RuntimeError` exception with a message: - attempted to call `GenServer` but no `handle_cast/2` clause was provided. + This callback is optional. If one is not implemented, the server will fail + if a cast is performed against it. """ @callback handle_cast(request :: term, state :: term) :: {:noreply, new_state} - | {:noreply, new_state, timeout | :hibernate} + | {:noreply, new_state, timeout | :hibernate | {:continue, term}} | {:stop, reason :: term, new_state} when new_state: term @@ -484,12 +496,31 @@ defmodule GenServer do Return values are the same as `c:handle_cast/2`. - If this callback is not implemented, the default implementation by - `use GenServer` will return `{:noreply, state}`. + This callback is optional. If one is not implemented, the received message + will be logged. """ @callback handle_info(msg :: :timeout | term, state :: term) :: {:noreply, new_state} - | {:noreply, new_state, timeout | :hibernate} + | {:noreply, new_state, timeout | :hibernate | {:continue, term}} + | {:stop, reason :: term, new_state} + when new_state: term + + @doc """ + Invoked to handle `continue` instructions. + + It is useful for performing work after initialization or for splitting the work + in a callback in multiple steps, updating the process state along the way. + + Return values are the same as `c:handle_cast/2`. + + This callback is optional. If one is not implemented, the server will fail + if a continue instruction is used. + + This callback is only supported on Erlang/OTP 21+. + """ + @callback handle_continue(continue :: term, state :: term) :: + {:noreply, new_state} + | {:noreply, new_state, timeout | :hibernate | {:continue, term}} | {:stop, reason :: term, new_state} when new_state: term @@ -532,6 +563,8 @@ defmodule GenServer do If `reason` is not `:normal`, `:shutdown`, nor `{:shutdown, term}` an error is logged. + + This callback is optional. """ @callback terminate(reason, state :: term) :: term when reason: :normal | :shutdown | {:shutdown, term} @@ -554,6 +587,8 @@ defmodule GenServer do If `c:code_change/3` raises the code change fails and the loop will continue with its previous state. Therefore this callback does not usually contain side effects. + + This callback is optional. """ @callback code_change(old_vsn, state :: term, extra :: term) :: {:ok, new_state :: term} @@ -581,7 +616,13 @@ defmodule GenServer do @callback format_status(reason, pdict_and_state :: list) :: term when reason: :normal | :terminate - @optional_callbacks format_status: 2 + @optional_callbacks code_change: 3, + terminate: 2, + handle_info: 2, + handle_cast: 2, + handle_call: 3, + format_status: 2, + handle_continue: 2 @typedoc "Return values of `start*` functions" @type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term} @@ -696,7 +737,7 @@ defmodule GenServer do {:ok, state} end - defoverridable GenServer + defoverridable code_change: 3, terminate: 2, handle_info: 2, handle_cast: 2, handle_call: 3 end end diff --git a/lib/elixir/lib/registry.ex b/lib/elixir/lib/registry.ex index 97c6c6b07..431189f0c 100644 --- a/lib/elixir/lib/registry.ex +++ b/lib/elixir/lib/registry.ex @@ -1313,8 +1313,4 @@ defmodule Registry.Partition do {:noreply, ets} end - - def handle_info(msg, state) do - super(msg, state) - end end diff --git a/lib/elixir/lib/string_io.ex b/lib/elixir/lib/string_io.ex index 549e4da26..989685bf8 100644 --- a/lib/elixir/lib/string_io.ex +++ b/lib/elixir/lib/string_io.ex @@ -177,8 +177,8 @@ defmodule StringIO do {:noreply, state} end - def handle_info(message, state) do - super(message, state) + def handle_info(_message, state) do + {:noreply, state} end @impl true @@ -194,10 +194,6 @@ defmodule StringIO do {:stop, :normal, {:ok, {input, output}}, state} end - def handle_call(request, from, state) do - super(request, from, state) - end - defp io_request(from, reply_as, req, state) do {reply, state} = io_request(req, state) io_reply(from, reply_as, to_reply(reply)) diff --git a/lib/elixir/src/elixir_expand.erl b/lib/elixir/src/elixir_expand.erl index 00a61f4fc..01debcfbf 100644 --- a/lib/elixir/src/elixir_expand.erl +++ b/lib/elixir/src/elixir_expand.erl @@ -290,7 +290,7 @@ expand({with, Meta, [_ | _] = Args}, E) -> %% Super -expand({super, Meta, Args}, #{file := File} = E) when is_list(Args) -> +expand({super, Meta, Args}, E) when is_list(Args) -> assert_no_match_or_guard_scope(Meta, "super", E), Module = assert_module_scope(Meta, super, E), Function = assert_function_scope(Meta, super, E), @@ -298,11 +298,27 @@ expand({super, Meta, Args}, #{file := File} = E) when is_list(Args) -> case length(Args) of Arity -> - {Kind, Name} = elixir_overridable:super(Meta, File, Module, Function), + {Kind, Name, SuperMeta} = elixir_overridable:super(Meta, Module, Function, E), + + %% TODO: Remove this on Elixir v2.0 and make all GenServer callbacks optional + case lists:keyfind(context, 1, SuperMeta) of + {context, 'Elixir.GenServer'} -> + Message = + io_lib:format( + "calling super for GenServer callback ~ts/~B is deprecated", + [element(1, Function), Arity] + ), + + elixir_errors:warn(?line(Meta), ?key(E, file), Message); + + _ -> + ok + end, + {EArgs, EA} = expand_args(Args, E), {{super, Meta, [{Kind, Name} | EArgs]}, EA}; _ -> - form_error(Meta, File, ?MODULE, wrong_number_of_args_for_super) + form_error(Meta, ?key(E, file), ?MODULE, wrong_number_of_args_for_super) end; %% Vars diff --git a/lib/elixir/src/elixir_overridable.erl b/lib/elixir/src/elixir_overridable.erl index 618981fbd..e630ddeb8 100644 --- a/lib/elixir/src/elixir_overridable.erl +++ b/lib/elixir/src/elixir_overridable.erl @@ -16,17 +16,17 @@ overridable(Module, Value) -> {Set, _} = elixir_module:data_tables(Module), ets:insert(Set, {?attr, Value}). -super(Meta, File, Module, Function) -> +super(Meta, Module, Function, E) -> case store(Module, Function, true) of - {_, _} = KindName -> - KindName; + {_, _, _} = KindNameMeta -> + KindNameMeta; error -> - elixir_errors:form_error(Meta, File, ?MODULE, {no_super, Module, Function}) + elixir_errors:form_error(Meta, ?key(E, file), ?MODULE, {no_super, Module, Function}) end. store_pending(Module) -> [begin - {_, _} = store(Module, Pair, false), + {_, _, _} = store(Module, Pair, false), Pair end || {Pair, {_, _, _, false}} <- maps:to_list(overridable(Module)), not 'Elixir.Module':'defines?'(Module, Pair)]. @@ -62,7 +62,7 @@ store(Module, Function, Hidden) -> ok end, - {FinalKind, FinalName}; + {FinalKind, FinalName, Meta}; error -> error end. diff --git a/lib/elixir/test/elixir/gen_server_test.exs b/lib/elixir/test/elixir/gen_server_test.exs index a8d00a2da..47db7755e 100644 --- a/lib/elixir/test/elixir/gen_server_test.exs +++ b/lib/elixir/test/elixir/gen_server_test.exs @@ -23,18 +23,10 @@ defmodule GenServerTest do {:reply, reason, state} end - def handle_call(request, from, state) do - super(request, from, state) - end - def handle_cast({:push, item}, state) do {:noreply, [item | state]} end - def handle_cast(request, state) do - super(request, state) - end - def terminate(_reason, _state) do # There is a race condition if the agent is # restarted too fast and it is registered. diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index fbec772b0..1e945fbdd 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -1380,6 +1380,22 @@ defmodule Kernel.WarningTest do end end + test "deprecated GenServer super" do + assert capture_err(fn -> + Code.eval_string(""" + defmodule Sample do + use GenServer + + def handle_call(a, b, c) do + super(a, b, c) + end + end + """) + end) =~ "calling super for GenServer callback handle_call/3 is deprecated" + after + purge(Sample) + end + defp purge(list) when is_list(list) do Enum.each(list, &purge/1) end |