How to turn a :DOWN message into an exception?

I’m monitoring a process and I receive a down message:

Last message: {:DOWN, #Reference<0.2151946553.1195376641.173350>, :process, #PID<0.1251.0>, {{:badmatch, [%{"archive_at" => nil, "created_at" => "2016-01-05T21:21:58.119800Z", "effective_end_at" => nil, "end_at" => nil, "frozen_at" => nil, "start_at" => "2013-02-25", "updated_at" => "2016-01-05T21:21:58.119800Z"}]}, [{Indexer.DocumentValidationJob, :new_fields, 3, [file: 'lib/indexer/jobs/document_validation_job.ex', line: 78]}, {Indexer.DocumentValidationJob, :perform, 4, [file: 'lib/indexer/jobs/document_validation_job.ex', line: 59]}, {Task.Supervised, :invoke_mfa, 2, [file: 'lib/task/supervised.ex', line: 90]}, {Task.Supervised, :reply, 5, [file: 'lib/task/supervised.ex', line: 35]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}}

How can I take that down message and turn it to something that Exception.normalize/3 likes?

I’m using Elixir 1.12.0 and Erlang 24.0.1.

Thanks for the help.

Hi @cjbottaro, maybe this link can help you Start normalizing exits in Exception · Issue #2231 · elixir-lang/elixir · GitHub

I think this works…

case reason do
  {reason, trace} -> Exception.normalize(:error, reason, trace)
  reason -> %ProcessExit{message: to_string(reason)}
end

Where ProcessExit is an exception module I made myself.

1 Like

That actually doesn’t work. My monitored process created a :DOWN message due to Task.async_stream timing out. The message looks like this:

{
  :DOWN,
  #Reference<0.3929462963.2848456705.228691>,
  :process,
  #PID<0.2162.0>,
  {:timeout, {Task.Supervised, :stream, [5000]}}
}

So in my snippet:

case reason do
  {reason, trace} -> Exception.normalize(:error, reason, trace)
  reason -> %ProcessExit{message: to_string(reason)}
end

It hits the 2nd cause clause, and bombs due to trying to run to_string/1 on a tuple.

Sure I can add another case clause for this one specific example, but I worry how many other error shapes exist in the wild that I won’t know until I see them.

So what is the best way to try to turn a :DOWN message into an exception? It seems that some down messages are exceptions, and some aren’t, and the shape isn’t consistent at all.

1 Like

That’s something you cannot fix. You can try and make a clever module that checks if the result is a tuple, tries to find a string inside it (or the same for a list) etc. but in the end it’s a losing battle. You have to know your potential inputs and handle only those.

Unless I am misunderstanding you of course? But shouldn’t you have a complete grasp on all possible return values when a process exits in your app?

2 Likes

Actually, you can use Exception.normalize/3 for any data in the 4th element of the tuple.

iex(33)> Exception.format(:error, Exception.normalize(:error, {:timeout, {Task.Supervised, :stream, [5000]}})) |> IO.puts
** (ErlangError) Erlang error: {:timeout, {Task.Supervised, :stream, [5000]}}
iex(34)> Exception.format(:error, Exception.normalize(:error, :shit)) |> IO.puts                                         
** (ErlangError) Erlang error: :shit
1 Like

Does Phoenix know every possible error shape that can happen in a controller/action? The use case for this is writing a background job processor. Each job is run in a monitored process (a Task). As a library, you have absolutely no control over what code is executed by the library user.

Further, the background job server that I’m using requires you report failed jobs with three pieces of metadata:

  • error type
  • error message
  • stack trace (optional)

Hence the need to convert any kind of down message to those three things.

I feel like there is something missing between Exception.normalize and Exception.format. But you’re right, I can just use Exception.format and parse out the error type, error message, and backtrace using regular expressions. But that feels dirty.