I was following this thread due to how common this use case is as expressed by @stefanchrobot and and the solution @josevalim gave seemed interesting and simple.
So I went and code it myself as I understood the use case and Task documentation (https://hexdocs.pm/elixir/1.5/Task.html#content).
It took me a while to grasp but this is the code. Please correct me if there is anything I misunderstood:
-
Crete the project
mix new stask --sup cd stask mix deps.get
-
The supervision tree
defmodule Stask.Application do @moduledoc false use Application def start(_type, _args) do children = [ {Task.Supervisor, name: Stask.TaskSupervisor, restart: :temporary } ] opts = [strategy: :one_for_one, name: Stask.Supervisor] Supervisor.start_link(children, opts) end end
-
The basic task
defmodule Stask do def some_task(text) do # Some hard work Process.sleep(15000) # Final result {:ok, "Hello #{text}"} end end
-
The GenServer
defmodule Stask.Server do use GenServer ## Client API def start() do GenServer.start(__MODULE__, nil, name: __MODULE__) end def execute_task(pid, name, task_timeout) do GenServer.cast(pid, {:execute, name, task_timeout}) end ## Server Callbacks def init(_) do Process.flag(:trap_exit, true) {:ok, %{msg: "", timer: "", timeout: ""}} end def handle_cast({:execute, name, task_timeout}, state) do task = Task.Supervisor.async(Stask.TaskSupervisor, Stask, :some_task, [name]) timer_ref = Process.send_after(self(), {:kill, task}, task_timeout) {:noreply, %{state | timer: timer_ref}} end ## Handle info functions # This handle function executes when the task has timed out def handle_info({:kill, task}, state) do # Do some book keeping here IO.puts("Task has been canceled due to time out") # You can specify how much time you can wait a task to exit. If it # does not exit in this treshold time it will be killed # The default time is 5000ms. You can have Task.shutdown(task, 2000). # You can also kill it immediatly. Task.shutdown(task, :brutal_kill) # Check documentation: https://hexdocs.pm/elixir/1.5/Task.html#shutdown/2 case Task.shutdown(task) do {:ok, _reply} -> # Do some book keeping, the task responded while the task was # been shutdown IO.puts("The task responded while it was shutting down") {:exit, _reason} -> # Do seme book keeping, if task dies while it was waiting for # shutdown IO.puts("The task died before just it was shutdown") nil -> # Do some book kepping, the task was shutdown IO.puts("The task was shutdown") end {:noreply, state} end # This handle_info functions receives the message when the task # finished successfully in time def handle_info({_ref, {:ok, msg}}, state) do # Do some book keeping, the task finish successfully IO.puts("Task finished successfully") Process.cancel_timer(state.timer) {:noreply, %{state | msg: msg}} end # Once the task finised successfully, it exits normally. # This handle_info function responds to this message. def handle_info({:EXIT, _pid, :normal}, state) do # Do some book keeping IO.puts("The task exited and finished normally") {:noreply, state} end # Finally, when the task, the caller receives a DOWN message. # In this case the caller was the GenServer. def handle_info({:DOWN, _ref, :process, _pid, :normal}, state) do # Do some book keeping one the task goes down IO.puts(state.msg) {:noreply, state} end end