-1

I'm just starting out in Elixir and wanted to build a very simple API with Plug. I used this guide to get a very simple API up and running.


Basically, the problem I'm facing is that the process that I registered as :qs doesn't seem to be found (and errors out), whenever I use the send/2 function in queue_service.ex. What I'm trying to achieve is a process that sticks around so that I can have state being maintained across requests.


In my router.ex file, I have:

defmodule SecondQueue.Router do
  use Plug.Router

  alias SecondQueue.Plug.VerifyParams
  alias SecondQueue.QueueService

  plug Plug.Parsers, parsers: [:urlencoded, :multipart]
  plug :match
  plug :dispatch


  get "/receive-message" do
    # gather query parameters from connection
    queue = Map.get(conn.params, "queue")
    message = Map.get(conn.params, "message")

    # handle the params
    QueueService.handle_incoming(queue, message)

    send_resp(conn, 201, "Created")
  end
end


Then inside queue_service.ex, I initiate the queues process, register it to an atom of :qs, and then want to be able to get at that process later via a function that a request calls. I have:

defmodule SecondQueue.QueueService do
  alias SecondQueue.QueueStore
  use Agent

  {:ok, agent_pid} = QueueStore.start_queues_link()
  Process.register(agent_pid, :qs)

  def handle_incoming(queue, message) do
    queue_atom = String.to_atom(queue)
    send(:qs, {:put, queue_atom, "success"})
  end
end

And then finally, in queue_store.ex, I actually define the process that I want to store the state, and run a loop so that it stays alive, and ready to receive messages. I have:

defmodule SecondQueue.QueueStore do
  def start_queues_link() do
    Task.start_link(fn -> queues_loop(%{}) end)
  end

  defp queues_loop(map) do
    receive do
      {:get, key, caller} ->
        send caller, Map.get(map, key)
        queues_loop(map)
      {:put, key, value} ->
        IO.puts("i am here")
        queues_loop(Map.put(map, key, value))
    end
  end
end

Update:
Github repo: https://github.com/qarthandgi/test-second-queue

qarthandso
  • 2,100
  • 2
  • 24
  • 40
  • Like Aleksei said, you are starting an naming the process at compile time, so it is not there at run time. Another issue is that you are currently using a Task when you should look into using either a `GenServer` or an `Agent`. Tasks are not really meant to be used in this capacity. `Agent`s and `GenServer`s also have the benefit of not needing to roll your own loops for things, because there are corner cases that you will not think of. – Justin Wood Aug 23 '19 at 14:07
  • @JustinWood I appreciate the additional insight. Will definitely look into those. I love Elixir so far, just a different way of thinking. – qarthandso Aug 23 '19 at 14:08
  • _Sidenote:_ `String.to_atom(queue)` is potentially dangerous if `queue` comes from the outside. It’s vulnerable to atoms DoS attack. – Aleksei Matiushkin Aug 23 '19 at 14:10
  • @AlekseiMatiushkin It does come from the outside. I just saw [this article](https://til.hashrocket.com/posts/gkwwfy9xvw-converting-strings-to-atoms-safely). From this, I'm deducing that you should try to convert it to an existing atom first, and if the exception is thrown, then create a new atom. Is that what needs to be done to protect this kind of attack? – qarthandso Aug 23 '19 at 14:17
  • 1
    No, that won’t protect from atoms DoS. When it comes from outside you should keep it as a string. Map keys could perfectly be strings. – Aleksei Matiushkin Aug 23 '19 at 14:19
  • If I need to register a `PID` though, I can only do it as an atom right? I can't register a `PID` to anything else. – qarthandso Aug 23 '19 at 15:29

1 Answers1

3

Elixir is a compiled language. The code below gets executed during compilation stage; no process is being started in runtime.

defmodule SecondQueue.QueueService do
  ...
  {:ok, agent_pid} = QueueStore.start_queues_link()
  Process.register(agent_pid, :qs)
  ...
end

Instead, you need to put this code into a function and explicitly call this function to start QueueStore (directly or by plugging it into your app’s supervision tree.)

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Wow, thank you for that. Should the `start_queue_store()` function be called right inside the `router.ex` file? Inside `application.ex`? I recognize it's probably preference, but I just wanted to know if there's a best practice way of doing this. I'm also updating the question to include my github repo. – qarthandso Aug 23 '19 at 14:06
  • 1
    If this is something that is meant to be running for the lifetime of your application, you will want to add it to your supervision tree. – Justin Wood Aug 23 '19 at 14:07
  • If you have an application, the common pattern would be to put the process into the supervision tree for it to be gracefully restarted by OTP on crashes/failures. – Aleksei Matiushkin Aug 23 '19 at 14:08
  • @AlekseiMatiushkin Appreciate the insight. Thank you! – qarthandso Aug 23 '19 at 14:09