-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

6
  • 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. Agents and GenServers 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. Commented Aug 23, 2019 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. Commented Aug 23, 2019 at 14:08
  • Sidenote: String.to_atom(queue) is potentially dangerous if queue comes from the outside. It’s vulnerable to atoms DoS attack. Commented Aug 23, 2019 at 14:10
  • @AlekseiMatiushkin It does come from the outside. I just saw this article. 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? Commented Aug 23, 2019 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. Commented Aug 23, 2019 at 14:19

1 Answer 1

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.)

Sign up to request clarification or add additional context in comments.

4 Comments

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.
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.
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.
@AlekseiMatiushkin Appreciate the insight. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.