Compare commits

...
8 Commits
13 changed files with 147 additions and 33 deletions
+17
View File
@@ -63,6 +63,23 @@ When a task is spawned directly from your code, without a supervisor, then the p
This small feature is very powerful. It allows instrumentation and monitoring tools to better track and relate the events happening in your system. This feature can also be used by tools like the "Ecto Sandbox". The "Ecto Sandbox" allows developers to run tests concurrently against the database, by using transactions and an ownership mechanism where each process explicitly gets a connection assigned to it. Without `$callers`, every time you spawned a task that queries the database, the task would not know its caller, and therefore it would be unable to know which connection was assigned to it. This often meant features that relies on tasks could not be tested concurrently. With `$callers`, figuring out this relationship is trivial and you have more tests using the full power of your machine.
## v1.8.2 (2019-05-11)
### 1. Bug fixes
#### EEx
* [EEx] Raise readable error message on bad EEx state
#### Elixir
* [Protocol] Ensure `:debug_info` is kept in protocols
#### Logger
* [Logger] Make sure Logger v1.8 does not get stuck in discard mode
* [Logger.Translator] Translate remote process crash in Logger
## v1.8.1 (2019-01-30)
### 1. Bug fixes
+1 -1
View File
@@ -1 +1 @@
1.8.1
1.8.2
+9
View File
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
@doc false
def handle_begin(state) do
check_state!(state)
%{state | binary: [], dynamic: []}
end
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
@doc false
def handle_body(state) do
check_state!(state)
%{binary: binary, dynamic: dynamic} = state
binary = {:<<>>, [], Enum.reverse(binary)}
dynamic = [binary | dynamic]
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
raise EEx.SyntaxError,
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
end
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
defp check_state!(state) do
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
"This typically means a bug or an outdated EEx.Engine or tool"
end
end
+1 -1
View File
@@ -135,7 +135,7 @@ defmodule GenServer do
Supervisor.start_link(children, strategy: :one_for_all)
In both cases, `Stack.start_link/1` is alwaus invoked.
In both cases, `Stack.start_link/1` is always invoked.
`use GenServer` also accepts a list of options which configures the
child specification and therefore how it runs under a supervisor.
@@ -49,8 +49,8 @@ Elixir version | Supported Erlang/OTP versions
1.4 | 18 - 19 (and Erlang/OTP 20 from v1.4.5)
1.5 | 18 - 20
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
1.7 | 19 - 21
1.8 | 20 - 21
1.7 | 19 - 22
1.8 | 20 - 22
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
+4 -4
View File
@@ -446,10 +446,10 @@ debug_opts(Map, Specs, Opts) ->
end.
take_debug_opts(Opts) ->
case lists:keytake(debug_info, 1, Opts) of
{value, {debug_info, true}, Rest} -> {true, Rest};
{value, {debug_info, false}, Rest} -> {false, Rest};
false -> {elixir_compiler:get_opt(debug_info), Opts}
case proplists:get_value(debug_info, Opts) of
true -> {true, proplists:delete(debug_info, Opts)};
false -> {false, proplists:delete(debug_info, Opts)};
undefined -> {elixir_compiler:get_opt(debug_info), Opts}
end.
extra_chunks_opts([], Opts) -> Opts;
+19
View File
@@ -260,6 +260,25 @@ defmodule ProtocolTest do
end
end
defmodule Protocol.DebugInfoTest do
use ExUnit.Case
test "protocols always keep debug_info" do
Code.compiler_options(debug_info: false)
{:module, _, binary, _} =
defprotocol DebugInfoProto do
end
assert {:ok, {DebugInfoProto, [debug_info: debug_info]}} =
:beam_lib.chunks(binary, [:debug_info])
assert {:debug_info_v1, :elixir_erl, {:elixir_v1, _, _}} = debug_info
after
Code.compiler_options(debug_info: true)
end
end
path = Path.expand("../ebin", __DIR__)
File.mkdir_p!(path)
+1
View File
@@ -555,6 +555,7 @@ defmodule Logger do
:level,
:utc_log,
:discard_threshold,
:discard_threshold_periodic_check,
:translator_inspect_opts
]
@spec configure(keyword) :: :ok
+40 -24
View File
@@ -4,6 +4,7 @@ defmodule Logger.Config do
@behaviour :gen_event
@name __MODULE__
@table __MODULE__
@check_discard {__MODULE__, :check_discard}
def start_link do
GenServer.start_link(__MODULE__, :ok, name: @name)
@@ -76,29 +77,7 @@ defmodule Logger.Config do
end
def handle_event(_event, {state, thresholds}) do
%{mode: mode} = state
case compute_mode(mode, thresholds) do
^mode ->
{:ok, {state, thresholds}}
new_mode ->
if new_mode == :discard do
message =
"Logger has #{message_queue_length()} messages in its queue, " <>
"which is above :discard_threshold. Messages will be discarded " <>
"until the message queue goes back to 75% of the threshold size"
log(:warn, message, state)
end
if mode == :discard do
log(:warn, "Logger has stopped discarding messages", state)
end
state = persist(%{state | mode: new_mode})
{:ok, {state, thresholds}}
end
{:ok, {compute_mode_and_persist_state(state, thresholds), thresholds}}
end
def handle_call({:configure, options}, {%{mode: mode}, _}) do
@@ -127,6 +106,16 @@ defmodule Logger.Config do
{:ok, old, state}
end
def handle_info(@check_discard, {%{mode: :discard} = state, thresholds}) do
state = compute_mode_and_persist_state(state, thresholds)
if state.mode == :discard do
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
end
{:ok, {state, thresholds}}
end
def handle_info(_msg, state) do
{:ok, state}
end
@@ -139,6 +128,31 @@ defmodule Logger.Config do
{:ok, state}
end
defp compute_mode_and_persist_state(%{mode: mode} = state, thresholds) do
case compute_mode(mode, thresholds) do
^mode ->
state
new_mode ->
if new_mode == :discard do
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
message =
"Logger has #{message_queue_length()} messages in its queue, " <>
"which is above :discard_threshold. Messages will be discarded " <>
"until the message queue goes back to 75% of the threshold size"
log(:warn, message, state)
end
if mode == :discard do
log(:warn, "Logger has stopped discarding messages", state)
end
persist(%{state | mode: new_mode})
end
end
## Helpers
defp log(level, message, state) do
@@ -163,7 +177,9 @@ defmodule Logger.Config do
level: Application.get_env(:logger, :level),
translators: Application.get_env(:logger, :translators),
truncate: Application.get_env(:logger, :truncate),
utc_log: Application.get_env(:logger, :utc_log)
utc_log: Application.get_env(:logger, :utc_log),
discard_threshold_periodic_check:
Application.get_env(:logger, :discard_threshold_periodic_check)
})
end
+13
View File
@@ -126,6 +126,19 @@ defmodule Logger.Translator do
{:ok, msg, metadata}
{'Error in process ' ++ _, [pid, node, {reason, stack}]} ->
reason = Exception.normalize(:error, reason, stack)
msg = [
"Process ",
inspect(pid),
" on node ",
inspect(node),
" raised an exception" | format(:error, reason, stack)
]
{:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]}
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
reason = Exception.normalize(:error, reason, stack)
msg = ["Process ", inspect(pid), " raised an exception" | format(:error, reason, stack)]
+1
View File
@@ -24,6 +24,7 @@ defmodule Logger.MixProject do
handle_otp_reports: true,
handle_sasl_reports: false,
discard_threshold_for_error_logger: 500,
discard_threshold_periodic_check: 30_000,
compile_time_purge_level: :debug,
compile_time_purge_matching: [],
compile_time_application: nil,
+26 -1
View File
@@ -12,11 +12,29 @@ defmodule Logger.ConfigTest do
end
test "log/2 relies on discard_threshold" do
Logger.remove_backend(:console)
Logger.configure(discard_threshold: 0)
for _ <- 1..1000, do: Logger.log(:info, "some message")
after
Logger.configure(discard_threshold: 10000)
end
test "log/2 recovers from discard_threshold" do
Logger.remove_backend(:console)
Logger.configure(discard_threshold: 1, discard_threshold_periodic_check: 0)
:sys.suspend(Logger)
assert Logger.log(:info, "BEGIN") == :ok
send(Logger, :garbage)
:sys.resume(Logger)
# Simulate a sync operation to make sure we are in :discard mode
Logger.add_translator({Unknown, :translate})
Logger.remove_translator({Unknown, :translate})
# It should eventually heal
wait_for_mode(:async)
after
Logger.configure(discard_threshold: 10000, discard_threshold_periodic_check: 30000)
Logger.add_backend(:console)
end
@@ -37,4 +55,11 @@ defmodule Logger.ConfigTest do
Logger.configure(level: :debug)
end
end
def wait_for_mode(mode) do
unless Logger.Config.__data__().mode == mode do
Process.sleep(10)
wait_for_mode(mode)
end
end
end
@@ -981,6 +981,19 @@ defmodule Logger.TranslatorTest do
assert {:stop, [_ | _]} = process_metadata[:crash_reason]
end
test "translates process crash with erts" do
assert {:ok, msg, meta} =
Logger.Translator.translate(
:error,
:error,
:format,
{'Error in process ~p on node ~p with exit value:~n~p~n',
[self(), :"name@127.0.0.1", {:badarith, [{:erlang, :/, [1, 0], []}]}]}
)
assert Keyword.get(meta, :crash_reason)
end
test "reports :undefined MFA properly" do
defmodule WeirdFunctionNamesGenServer do
use GenServer