lud

lud

Should I call Task.await if Task.yield returns {:ok, value}?

All is in the title, but to clarify:

It seems that if Task.yield returns {:ok, value} it means that the task result message was received, so there is no risk of receiving an unhandled message later, thus no need to call Task.await (which would give the same value anyway).

But I want to be sure.

Thank you

Marked As Solved

lud

lud

I need to send the task result in a 2500ms timeframe. If the result is not ready yet, I have to send a dummy message, and I have another way to send the result back.

So basically

params = some_data()
ref = make_ref()
parent = self()

spawn(fn ->
  t = Task.async(fn -> mod.run(params) end)

  case Task.yield(t, @max_wait_ms) do
    {:ok, result} ->
      send(parent, {:result, ref, result})

    nil ->
      send(parent, {:result, ref, :still_running})
      result = Task.await(t)
      send_delayed_response(params, result)

    {:exit, reason} ->
      exit(reason)
  end
end)

receive do
  {:result, ^ref, result} -> {:ok, result}
end

So I did a simple test in the shell and I have my answer anyway : after calling Task.yield, no more messages from the task are coming (the monitor is also cleaned).

iex(1)> t = Task.async(fn -> :test end)
%Task{
  owner: #PID<0.104.0>,
  pid: #PID<0.106.0>,
  ref: #Reference<0.1716671456.3384016898.195927>
}
iex(2)> Task.yield t
{:ok, :test}
iex(3)> flush
:ok

Also Liked

LostKobrakai

LostKobrakai

I’ve started build a library around oban a few month ago, which does what you’re doing here, but with the persistent queue of oban. I just didn’t finish it properly at the time.

lud

lud

Well as you want details, there is no phoenix channel, the other way is to post the response to another website via HTTP.

I am implementing a Slack command that will do some stuff on the Gitlab API. Heavy stuff, so it can take a while, but sometimes it will take half a second to work. This is for other devs and they do not really care about the first meaningful paint :smiley:

Indeed, as I am coding it, I am leaning towards always acknowledging first and always sending the reply through the other way. It is not what is asked though, but it seems that the Slack timeout is too sensitive about network latency.

The exercise was entertaining though.

peerreynders

peerreynders

Your approach still lacks resilience as the parent may get the still_running message but there never is a follow up because the Task goes zombie or crashes after being late.

I’d look into using Task.Supervisor instead.

Example:

# file: my_app/lib/my_app/application.ex
#
# created with "mix new my_app --sup"
# 
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    name = MyApp.TaskSupervisor

    children = [
      {Task.Supervisor, name: name}
    ]

    opts = [strategy: :rest_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
# file: my_app/lib/demo.ex
#
defmodule Demo do
  @name MyApp.TaskSupervisor
  @max_wait_ms 500
  @timely_timeout div(@max_wait_ms, 2)
  @late_timeout @max_wait_ms + @timely_timeout
  @too_late_timeout 3 * @max_wait_ms

  def launch(arg) do
    task = Task.Supervisor.async_nolink(@name, __MODULE__, :some_work, [arg])

    task
    |> Task.yield(@max_wait_ms)
    |> handle_task_yield(task)
  end

  defp handle_task_yield({:ok, value}, _) do
    IO.puts("result: #{inspect(value)}")
    {:ok, value}
  end

  defp handle_task_yield(nil, task) do
    IO.puts("Timed out")
    await_late_result(task)
  end

  defp handle_task_yield({:exit, reason}, _) do
    IO.puts("Task exit: #{inspect(reason)}")
    {:error, reason}
  end

  defp await_late_result(%Task{ref: mon, pid: pid}) do
    # in GenServer these messages would go through 
    # handle_info
    receive do
      {^mon, value} ->
        Process.demonitor(mon, [:flush])
        IO.puts("Better late than never: #{inspect(value)}")
        {:ok, value}

      {:DOWN, ^mon, _, ^pid, reason} when reason != :normal ->
        Process.demonitor(mon, [:flush])
        IO.puts("Task LATE exit: #{inspect(reason)}")
        {:error, reason}
    after
      @max_wait_ms ->
        IO.puts("I'm not waiting forever!")
        Process.demonitor(mon, [:flush])
        Process.exit(pid, :kill)
        {:error, :far_too_late}
    end
  end

  def some_work(:timely = type) do
    Process.sleep(@timely_timeout)
    type
  end

  def some_work(:late = type) do
    Process.sleep(@late_timeout)
    type
  end

  def some_work(:too_late = type) do
    Process.sleep(@too_late_timeout)
    type
  end

  def some_work(:crash = type) do
    exit(type)
  end

  def some_work(:late_crash = type) do
    Process.sleep(@late_timeout)
    exit(type)
  end

  def some_work(:too_late_crash = type) do
    Process.sleep(@too_late_timeout)
    exit(type)
  end
end
$ iex -S mix
Erlang/OTP 22 [erts-10.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Compiling 1 file (.ex)
Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Demo.launch(:timely)
result: :timely
{:ok, :timely}
iex(2)> Demo.launch(:late)
Timed out
Better late than never: :late
{:ok, :late}
iex(3)> Demo.launch(:too_late)
Timed out
I'm not waiting forever!
{:error, :far_too_late}
iex(4)> Demo.launch(:crash)

11:17:17.617 [error] Task #PID<0.153.0> started from #PID<0.145.0> terminating
** (stop) :crash
    (my_app) lib/demo.ex:71: Demo.some_work/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (elixir) lib/task/supervised.ex:35: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Function: &Demo.some_work/1
    Args: [:crash]
Task exit: :crash
{:error, :crash}
iex(5)> Demo.launch(:late_crash)
Timed out
Task LATE exit: :late_crash

11:17:18.370 [error] Task #PID<0.155.0> started from #PID<0.145.0> terminating
** (stop) :late_crash
    (my_app) lib/demo.ex:76: Demo.some_work/1
    (elixir) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
    (elixir) lib/task/supervised.ex:35: Task.Supervised.reply/5
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Function: &Demo.some_work/1
    Args: [:late_crash]
{:error, :late_crash}
iex(6)> Demo.launch(:too_late_crash)
Timed out
I'm not waiting forever!
{:error, :far_too_late}
iex(7)> flush
:ok
iex(8)> 

Where Next?

Popular in Questions Top

New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
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
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
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
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43622 214
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
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
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
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
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
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

We're in Beta

About us Mission Statement