Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elixir Task - Graceful shutdown

I have Elixir Task, which takes some time (10 seconds). When the application is upgrading, this task is killed by Task.Supervisor despite on shutdown: 30000:

=SUPERVISOR REPORT==== 13-Aug-2015::00:03:09 ===
 Supervisor: {local,tasks_sup}
 Context:    child_terminated
 Reason:     killed
 Offender:   [{pid,<0.304.0>},
              {id,'Elixir.Task.Supervised'},
              {mfargs,{'Elixir.Task.Supervised',start_link,undefined}},
              {restart_type,temporary},
              {shutdown,30000},
              {child_type,worker}]

I don't know how to gracefully stop the task (wait until task is complete) on application upgrade. Here is the code describing my problem:

defmodule MyApp do
  use Application

  def start(_, _) do
    MyApp.Supervisor.start_link([])
  end

end

defmodule MyApp.Supervisor do

  use Supervisor

  def start_link(state) do
    Supervisor.start_link(__MODULE__, state, name: __MODULE__)
  end

  def init(state) do
    children = [
      supervisor(Task.Supervisor, [[name: :tasks_sup, shutdown: 30000]]),
      worker(MyApp.Worker, [state], restart: :permanent)
    ]

    supervise(children, strategy: :one_for_one)
  end

end

defmodule MyApp.Worker do

  def start_link(state) do
    GenServer.start_link(__MODULE__, state, [name: MyApp.Worker])
  end

  def init(state) do
    {:ok, state}
  end

  def handle_call(:which_children, _, state) do
    children = [{Task.Supervisor, :tasks_sup, :supervisor, [Task.Supervisor]}]
    {:reply, children, state}
  end

  def handle_info({:task, data}, state) do
    Task.Supervisor.async(:tasks_sup, MyApp.TaskRunner, :perform, [data])
  end

  def handle_info(_, state) do
    {:noreply, state}
  end

end

defmodule MyApp.TaskRunner do

  def perform(data) do
    # some 10 secs job
  end

end

Is there any thoughts or assumptions how to wait until MyApp.TaskRunner.perform finishes and then allow to stop the task?

It doesn't matter for me how to process tasks: using native Elixir's Task or via own some TaskProcessor module.

Task.Supervisor.async links tasks to caller, which might be a problem. However, I tried several times different cases with async and start_link and each time got the same result. My latest test was:

children = [
  supervisor(Task.Supervisor, [[name: :tasks_sup, shutdown: 30000]]),
  worker(MyApp.Worker, [state], restart: :permanent)
]

supervise(children, strategy: :one_for_one)

and

Task.Supervisor.start_child(:tasks_sup, MyApp.TaskRunner, :perform, [data])

The worker was killed after about 2-3 secs.

like image 954
alPacino Avatar asked Aug 12 '15 23:08

alPacino


1 Answers

The links may be the ones bringing your tasks down. Because the worker calls Task.Supervisor.async, it will link the task to your worker. The worker has a timeout of 5000 miliseconds, so it will be shutdown before the supervisor, killing the tasks. You can confirm this by setting the earlier reports.

Btw, you should only call async if you are calling await later by the same process and it doesn't seem to be the case here. You should likely call Task.Supervisor.start_child instead (so the tasks are not linked to the caller).

Another possible reason your functions are getting killed is that the VM only keeps the latest two code module versions. If you upgrade twice in a short-interval, the old versions will be purged and their running processes killed.

like image 181
José Valim Avatar answered Oct 20 '22 10:10

José Valim