GenServer: Stop and Terminate after work is complete

Hello,

I’m uploading a file from Phoenix LiveView that gets processed, I then start a GenServer to keep checking on the status of the given file and then when the work is done, I want to terminate the GenServer. The trouble I’m encountering is that the GenServer init function keeps on being called so I think I am doing it wrong. Additionally, the call to self to send my updated status via PubSub doesn’t seem to be being called? I’ve stripped it all back so hopefully this helps:

File upload and PubSub listener in live view:

  def handle_event("do_processing", _params, socket) do
    consume_uploaded_entries(socket, :file, fn %{path: _path}, _entry ->
      # {:ok, result} = HelloPhoenix.FileProcessor.begin_processing(path, entry)
      # request_id = result.request_id

      request_id = for _ <- 1..10, into: "", do: <<Enum.random('0123456789abcdef')>>

      {:ok, _pid} =
        DynamicSupervisor.start_child(
          HelloPhoenix.FormProcessorSupervisor,
          {HelloPhoenix.FormProcessorApp, name: via_tuple(request_id), request_id: request_id}
        )

      {:ok, request_id}
    end)

    {:noreply, socket |> assign(:status, "running")}
  end

  def handle_info({"status", status}, socket) do
    {:noreply, assign(socket, status: status)}
  end

  def mount(_params, _session, socket) do
    if connected?(socket) do
      HelloPhoenixWeb.Endpoint.subscribe(@file_update_topic)
    end

    {:ok,
     socket
     |> assign(:status, nil)
     |> allow_upload(:file, accept: ~w(.jpg .jpeg .png .pdf), max_entries: 1)}
  end

GenServer:

defmodule HelloPhoenix.FormProcessorApp do
  use GenServer

  alias Phoenix.PubSub
  @file_update_topic "file_update"

  def start_link(opts), do: GenServer.start_link(__MODULE__, Keyword.get(opts, :request_id), opts)

  def init(request_id) do
    IO.inspect("init") # I see this 4 times in the terminal
    schedule_work()
    {:ok, request_id}
  end

  def handle_info(:work, state) do
    # do_check = HelloPhoenix.FileProcessor.do_check(state)

    # case do_check do
    #   "succeeded" ->
    # Do Work here...
    #     IO.inspect("Work is done") # I see this 4 times in the terminal
    #     {:stop, :normal, state}

    #   _ ->
    #     schedule_work()
    #     {:noreply, state}
    # end

    send(self(), {:status, "genserver status"})

    IO.inspect("Work is done") # I see this 4 times in the terminal
    {:stop, :normal, state}
  end

  def handle_info({:status, status}, state) do
    IO.inspect("pubsub") # this is never called?

    PubSub.broadcast(HelloPhoenix.PubSub, @file_update_topic, {"status", status})

    {:noreply, state}
  end

  defp schedule_work do
    Process.send_after(self(), :work, :timer.seconds(1))
  end
end

Terminal output when I begin uploading:

"init"
[debug] Replied in 684µs
"Work is done"
"init"
"Work is done"
"init"
"Work is done"
"init"
"Work is done"

Any help would be appreciated thanks

First, handle_info({:status, _}, _) is never called, because the process is stopped before the message gets processed. To handle status and stop right after, you should return {:noreply, state} in handle_info(:work, _) and return {:stop, :normal, state} in handle_info({:status, _}, _)

Second, you need to specify the correct restart option in child spec, because the default one is permanent which means that the child is always restarted. I’d suggest using transient to restart in case of an error.

Read more about child_specs and restart values here and here


Please don’t forget to mark the solution