debajit

debajit

How do I gracefully trap an exit with Task.async_stream?

I’m trying to convert a directory of Markdown files into HTML, and I’m trying to do it concurrently with Task.async_stream. Here is the code that I have:

defmodule SomeModule do
  defp write_articles_html do
    Process.flag(:trap_exit, true)

    html_conversion_results =
      Article.files
        |> Task.async_stream(fn markdown_file ->
             write_article_html(markdown_file)
           end,
           max_concurrency: System.schedulers_online * 2)
        |> Enum.to_list

    IO.inspect html_conversion_results
  end

The function called in Task.async_stream i.e. write_article_html looks like this:

  defp write_article_html(markdown_file_path) do
    html_dir = article_html_dir(markdown_file_path)
    full_html_dir = Path.join(@build_directory, html_dir)

    File.mkdir!(full_html_dir)
    # ...
  end
end # defmodule SomeModule

When the File.mkdir! fails, I would like the parent process (that made the Task.async_stream) not to crash.

I understand I can do this by trapping exits, and I’ve added the code to do it (Process.flag(:trap_exit, true)

The main process still crashes though (crash output shown below). What am I missing? (Do I need to add a receive somewhere for the :exit message? Both these function are in the same module, for reference)

How could I make sure the caller process can handle the mkdir failing? (If this is explained in any documentation, please let me know).


ERROR

Here is the output when the process crashes:

[ok: :ok, ok: :ok, ok: :ok, ok: :ok, ok: :ok, ok: :ok,
 exit: {%File.Error{action: "make directory", path: "_build/my-new-article",
   reason: :eexist},
  [{File, :mkdir!, 1, [file: 'lib/file.ex', line: 183]},
   {Task.Supervised, :do_apply, 2, [file: 'lib/task/supervised.ex', line: 85]},
   {Task.Supervised, :reply, 5, [file: 'lib/task/supervised.ex', line: 36]},
   {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]},
 exit: {%File.Error{action: "make directory", path: "_build/my-new-article",
   reason: :eexist},
  [{File, :mkdir!, 1, [file: 'lib/file.ex', line: 183]},
   {Task.Supervised, :do_apply, 2, [file: 'lib/task/supervised.ex', line: 85]},
   {Task.Supervised, :reply, 5, [file: 'lib/task/supervised.ex', line: 36]},
   {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}]

20:58:50.968 [error] Task #PID<0.127.0> started from #PID<0.120.0> terminating
** (File.Error) could not make directory "_build/my-new-article": file already exists
    (elixir) lib/file.ex:183: File.mkdir!/1
    (elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:36: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: &:erlang.apply/2
    Args: [#Function<0.35336826/1 in Lydian.Builder.Impl.write_articles_html/1>, ["source/2017-08-23-my-new-article.md"]]

20:58:50.976 [error] Task #PID<0.128.0> started from #PID<0.120.0> terminating
** (File.Error) could not make directory "_build/my-new-article": file already exists
    (elixir) lib/file.ex:183: File.mkdir!/1
    (elixir) lib/task/supervised.ex:85: Task.Supervised.do_apply/2
    (elixir) lib/task/supervised.ex:36: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Function: &:erlang.apply/2
    Args: [#Function<0.35336826/1 in Lydian.Builder.Impl.write_articles_html/1>, ["source/2017-11-12-my-new-article.md"]]

Most Liked Responses

dom

dom

The main process did not crash in your example. The first line in the log is the output of IO.inspect html_conversion_results which wouldn’t have happened if the process had crashed.

What’s wrong with something like {:ok, {:error, :directory_exists}}? The first :ok just means that the task did not crash. You can still use the return value to report success or error.

svilen

svilen

Author of Concurrent Data Processing in Elixir

I would personally go with @dom’s solution, but for the sake of argument: you can catch the exception and log it without re-throwing:

Task.async_stream(fn markdown_file ->
  try do
    write_article_html(markdown_file)
  rescue
    e -> Logger.error(Exception.message(e))
  end
end,

Where Next?

Popular in Questions Top

New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; "XXX...
New

We're in Beta

About us Mission Statement