summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@plataformatec.com.br>2018-06-20 23:53:55 +0200
committerJosé Valim <jose.valim@plataformatec.com.br>2018-06-20 23:53:55 +0200
commit12a4ea4c9fb83dd9f370fc9247207c02cf0d5443 (patch)
tree0743d787b93b199f931cf4eb5a2e11adb5de65d4
parent45eb56f40ce6e25ffeb48f67a392cd5a629dd137 (diff)
downloadelixir-12a4ea4c9fb83dd9f370fc9247207c02cf0d5443.tar.gz
Add handle_continue/2 and deprecate super in GenServer callbacks
-rw-r--r--lib/elixir/lib/agent/server.ex8
-rw-r--r--lib/elixir/lib/gen_server.ex99
-rw-r--r--lib/elixir/lib/registry.ex4
-rw-r--r--lib/elixir/lib/string_io.ex8
-rw-r--r--lib/elixir/src/elixir_expand.erl22
-rw-r--r--lib/elixir/src/elixir_overridable.erl12
-rw-r--r--lib/elixir/test/elixir/gen_server_test.exs8
-rw-r--r--lib/elixir/test/elixir/kernel/warning_test.exs16
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