diff --git a/lib/elixir/lib/task/supervisor.ex b/lib/elixir/lib/task/supervisor.ex index 7dacc2d6ffe..6e20449af41 100644 --- a/lib/elixir/lib/task/supervisor.ex +++ b/lib/elixir/lib/task/supervisor.ex @@ -404,7 +404,7 @@ defmodule Task.Supervisor do build_stream(supervisor, :nolink, enumerable, {module, function, args}, options) end - @doc """ + @doc ~S""" Returns a stream that runs the given `function` concurrently on each element in `enumerable`. @@ -414,6 +414,38 @@ defmodule Task.Supervisor do to `async_nolink/3`. See `async_stream/6` for discussion and examples. + + ## Error handling and cleanup + + Even if tasks are not linked to the caller, there is no risk of leaving dangling tasks + running after the stream halts. + + Consider the following example: + + Task.Supervisor.async_stream_nolink(MySupervisor, collection, fun, on_timeout: :kill_task, ordered: false) + |> Enum.each(fn + {:ok, _} -> :ok + {:exit, reason} -> raise "Task exited: #{Exception.format_exit(reason)}" + end) + + If one task raises or times out: + + 1. the second clause gets called + 2. an exception is raised + 3. the stream halts + 4. all ongoing tasks will be shut down + + Here is another example: + + Task.Supervisor.async_stream_nolink(MySupervisor, collection, fun, on_timeout: :kill_task, ordered: false) + |> Stream.filter(&match?({:ok, _}, &1)) + |> Enum.take(3) + + This will return the three first tasks to succeed, ignoring timeouts and errors, and shut down + every ongoing task. + + Just running the stream with `Stream.run/1` on the other hand would ignore errors and process the whole stream. + """ @doc since: "1.4.0" @spec async_stream_nolink(Supervisor.supervisor(), Enumerable.t(), (term -> term), keyword) ::