I'm currently trying to build a Redis clone in Elixir. As part of that effort, I'm using a task to handle a request to get/set and I'm using ETS to store the Keys and Values.
** (ArgumentError) argument error
(stdlib 3.13.2) :ets.insert(:kv, {"doomspork", "Sean", ["Elixir", "Ruby", "Java"]})
Here are the steps I took with the relevant code sections:
def init(_) do
:ets.new(:kv, [:set, :public, :named_table])
end
:ets.insert(:kv, {"doomspork", "Sean", ["Elixir", "Ruby", "Java"]})
IO.inspect(:ets.lookup(:kv, "doomspork"))
[error] Task #PID<0.159.0> started from #PID<0.156.0> terminating
** (ArgumentError) argument error
(stdlib 3.13.2) :ets.insert(:kv, {"doomspork", "Sean", ["Elixir", "Ruby", "Java"]})
(redis 1.0.0) lib/server.ex:61: Server.write_line/2
(redis 1.0.0) lib/server.ex:41: Server.serve/1
(elixir 1.11.0) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
(stdlib 3.13.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Function: #Function<0.28410304/0 in Server.loop_acceptor/1>
Args: []
I was hoping to understand why the error is occurring or how to get suggestions on how I might go about debugging this problem. Any help would be deeply appreciated.
I've listed the entire .ex file for reference. General feedback about the code as a whole is welcome too -- I'm relatively new to Elixir and would love to hear your suggestions so that I can improve.
defmodule Server do
@moduledoc """
Your implementation of a Redis server
"""
use Application
require Logger
def start(_type, _args) do
Supervisor.start_link(
[{Task.Supervisor, name: Server.TaskSupervisor}, {Task, fn -> Server.listen() end}],
strategy: :one_for_one
)
end
def init(_) do
:ets.new(:kv, [:set, :public, :named_table])
end
@doc """
Listen for incoming connections
"""
def listen() do
{:ok, socket} = :gen_tcp.listen(6379, [:binary, active: false, reuseaddr: true])
Logger.info("Accepting connections on 6379")
loop_acceptor(socket)
end
defp loop_acceptor(socket) do
{:ok, client} = :gen_tcp.accept(socket)
{:ok, pid} = Task.Supervisor.start_child(Server.TaskSupervisor, fn -> serve(client) end)
:ok = :gen_tcp.controlling_process(client, pid)
loop_acceptor(socket)
end
defp serve(client) do
client
|> read_line()
|> write_line(client)
serve(client)
end
defp read_line(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
data
end
defp echo(socket, command_arr) do
echo_statement = Enum.at(command_arr, -2)
IO.inspect(echo_statement)
:gen_tcp.send(socket, "+#{echo_statement}\r\n")
end
defp write_line(line, socket) do
command_arr = String.split(line, "\\r\\n")
command = Enum.at(command_arr, 2) |>String.downcase
IO.inspect(command_arr)
IO.inspect(command)
:ets.insert(:kv, {"doomspork", "Sean", ["Elixir", "Ruby", "Java"]})
IO.inspect(:ets.lookup(:kv, "doomspork"))
case command do
"ping" -> :gen_tcp.send(socket, "+PONG\r\n")
"set"-> :ets.insert(:kv, {"samplekey", "sampleresp"})
"get"-> :ets.lookup(:kv, "samplekey")
"echo"-> echo(socket,command_arr)
_ -> :gen_tcp.send(socket, "Nah")
end
end
end
You are trying to access ETS before it was created. init/1
callback is called within the process after it’s started, but the first access attempt happens from the task run by start
which might happen earlier. In your case, this is an Application
which does not have init/1
callback at all.
Simply move :ets.new(:kv, [:set, :public, :named_table])
to start/2
before anything else.