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

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

Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
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
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
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
gazoon
I want to know absolute current module path. In python i could do that: os.path.abspath(__file__) Does elixir have anything similar?
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
wernerlaude
In AR this is so simple @articles = current_user.articles How to do in Ecto? def index(conn, _params) do current_user = conn.assig...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
yurko
Here are few pieces of (common) Linux knowledge that we use for reasonably small one server apps. We use Ubuntu but this should work for ...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement