How do I write a Exq worker here?

I have to write a worker for whenever I’m setting the key in redis. But the function is maintaining a state here

 def set(pid, key, value) do
    GenServer.call(pid, {:set, key, value})
  end

  # handle call for set function
  def handle_call({:set, key, value}, _from, state) do
    reply = Redix.command(state, ["SET", key, value])
    {:reply, reply, state}
  end

So I have used Exq to process the background jobs. I have defined a module call SetWorker and
used this function to perform the redis set key

  def perform(conn, key, value) do
    Redix.command!(conn, ["SET", key, value])
  end

Now I’ve define a function like this

 def handle_call({:set, key, value}, _from, state) do
    reply =  Exq.enqueue(Exq, "q1", SetWorker, [key, value])
    {:reply, reply, state}
 end

but it’s giving me this error

Elixir.Gorm.SetWorker[baf417d7-14ac-455a-9038-ce4c21190098] start
[error] Process #PID<0.502.0> raised an exception
** (UndefinedFunctionError) function Gorm.SetWorker.perform/2 is undefined or private
    (gorm) Gorm.SetWorker.perform("onetwdassddo", "three")
    (exq) lib/exq/worker/server.ex:186: anonymous fn/4 in Exq.Worker.Server.dispatch_work/3

I don’t know what I’m doing wrong here.

Are these interview questions or something by the way? You’ve asked a series of very specific questions that try to do things in very unusual ways.

I’m not sure what you expect here. Have you used Exq at all? Do you have it set up?

Yes I have setup it in my config file. Are you asking about the dependency? also done that.


  config :exq,
  name: Exq,
  host: "127.0.0.1",
  port: 6379,
  namespace: "exq",
  concurrency: 500,
  queues: ["q1"]

this is my SetWorker function here

>  def perform(conn, key, value) do
>     Redix.command!(conn, ["SET", key, value])
>  end

def handle_call({:set, key, value}, _from, state) do
    reply = Exq.enqueue(Exq, "q1", SetWorker, [key, value])
    {:reply, reply, state}
end

i think I have setup it correctly.

My friend, check out your error. You invoke Gorm.SetWorker.perform/2, but you have defined Gorm.SetWorker.perform/3.

Takes 3 arguments:

def perform(conn, key, value)

Called with two arguments

** (UndefinedFunctionError) function Gorm.SetWorker.perform/2 is undefined or private
(gorm) Gorm.SetWorker.perform("onetwdassddo", "three")
1 Like

I did see that. But after doing some changes I got this

If I just do this in my worker function

def perform(key, value) do
    Redix.command!(["SET", key, value])
end

and then call this


 def handle_call({:set, key, value}, _from, state) do
    reply =  Exq.enqueue(Exq, "q1", SetWorker, [key, value])
    {:reply, reply, state}
 end

Now I’m getting this error

{:ok, pid} =  Database.start_link("redis://127.0.0.1:6379")
[info] connect to url redis://127.0.0.1:6379
{:ok, #PID<0.489.0>}
iex(3)> Database.set(pid, "onetwaso", "three")  
{:ok, "d461dbbc-250b-4037-bfa0-2053d881109f"}
iex(4)> [info] Elixir.Gorm.SetWorker[d461dbbc-250b-4037-bfa0-2053d881109f] start
[error] Process #PID<0.495.0> raised an exception
** (UndefinedFunctionError) function Redix.command!/1 is undefined or private
    (redix) Redix.command!(["SET", "onetwaso", "three"])
    (exq) lib/exq/worker/server.ex:186: anonymous fn/4 in Exq.Worker.Server.dispatch_work/3

The error presented really does a good job of telling you what’s wrong.

2 Likes

I did solve that.

But this error I’m not able to understand

Database.set(pid, "onetwo", "three")
{:ok, "f6a42502-637b-4c7c-82b8-17be837173be"}
iex(7)> [info] Elixir.Gorm.SetWorker[f6a42502-637b-4c7c-82b8-17be837173be] start
[error] Process #PID<0.633.0> raised an exception
** (ArgumentError) expected a list of binaries as each Redis command, got: "onetwo"
    (redix) lib/redix.ex:827: Redix.assert_valid_command/1
    (elixir) lib/enum.ex:783: Enum."-each/2-lists^foreach/1-0-"/2
    (elixir) lib/enum.ex:783: Enum.each/2
    (redix) lib/redix.ex:437: Redix.pipeline/3
    (redix) lib/redix.ex:585: Redix.command/3
    (redix) lib/redix.ex:627: Redix.command!/3
    (exq) lib/exq/worker/server.ex:186: anonymous fn/4 in Exq.Worker.Server.dispatch_work/3

Have a go a inspecting the error and telling us what you think it might be. You have posted three errors in this thread so far, one or two you have resolved yourself, and others in a number of other threads. This forum isn’t for ongoing basic debugging help.

1 Like

Did I ask you to debug it? I said I’m not able to understand it. I’m just posting those errors so that others can know where I am.

It appears to be step-by-step debugging. What part of the error message don’t you understand? We’re trying to help you get better at being self-sufficient and a more effective Elixir developer, but when you repeatedly post a stack trace with no commentary on which bit you don’t understand, it is very hard to know whether you are sincerely trying or best or just trying to crowd-source your debugging effort.

So, asking again, which bit of the error message that explains the issue don’t you understand?

So when I was using this exq function
{:ok, ack} = Exq.enqueue(Exq, "default", MyWorker, ["arg1", "arg2"])

This gave me something like this


{:ok, "99be77c1-ae2b-4f0d-b825-62b5a548bd3a"}
iex(2)> ack
"99be77c1-ae2b-4f0d-b825-62b5a548bd3a"

Now when I have the worker for redis to set the key like this


def handle_call({:set, key, value}, _from, _state) do
    reply =  Exq.enqueue(Exq, "q1", SetWorker, [key, value]
    {:reply, reply, _state}
end

I was expecting return to be {:ok, {:ok, “OK”}} But since it’s giving me after doing some string with random value.
So in reply variable it’s returning that.

iex(6)> Database.set(pid, "onetwo", "three")
{:ok, "f6a42502-637b-4c7c-82b8-17be837173be"}
iex(7)> [info] Elixir.Gorm.SetWorker[f6a42502-637b-4c7c-82b8-17be837173be] start
  1. Now what is this {:ok, “f6a42502-637b-4c7c-82b8-17be837173be”} ?
    I was trying to find an answer for this but in docs they didn’t mention this

The string that gets returned is a job id. Some opaque value to identify the job you just enqueued.

You probably need it to identify it’s result in a lot of other asynchronously enqueued jobs.

1 Like

** (ArgumentError) expected a list of binaries as each Redis command, got: "onetwo"

Is this mean that I have to make a connection with redis in my SetWorker because when I do this


  def perform(key, value) do
    IO.inspect("oenas")
    Redix.command!("SET", key, value)
  end

perform is working here and when I inspect this it does return that “oenas”. So what I mean to say when we call


def(conn, key, value) do
  Redix.command(conn, ["SET", key, value])
end

So I think we can’t really accept a connection here. So do I have to make a connection first before calling this?

I don’t know if I’m making sense.

The error itself has nothing to do with the connection. It says that Redix.command expects a list of binaries (like [“SET”, “foo”, “bar”]) but it received the single binary ”onetwo” instead. Something in your code is calling Redix.command with the wrong arguments.

That said, yes, you need to pass a Redix connection as the first argument, and a list of binaries representing the Redis command as the second argument, like Redix.command(conn, [“GET”, “foo”]).

This is also wrong:

def perform(key, value) do
  IO.inspect("oenas")
  Redix.command!("SET", key, value)
end

The Redix.command! function is called with the wrong arguments (it should be Redix.command!(conn, [“SET”, key, value])). The oenas output you are seeing is just due to the IO.inspect call, not the return value of your function.

As for the last part, sorry, I don’t think I understand… this is definitely invalid code:

def(conn, key, value) do

If you mean to say that perform should be something like:

def perform(conn, key, value) do
  Redix.command!(conn, ["SET", key, value])
end

Then yes, you’re on the right track there.

Sorry It was a typing mistake. I was writing this


 def perform(conn, key, value) do
    IO.inspect("oenas")
    Redix.command!(conn, ["SET", key, value])
 end

IO.inspect is because I wanted to check if the request is coming here or not when I was calling this function

def handle_call({:set, key, value}, _from, state) do
    reply =  Exq.enqueue(Exq, "q1", SetWorker, [key, value])
    {:reply, reply, state}
end

Yes, this version looks on the right track.

But now here handle call has a enqueue for which Exq as first argument and q1 will be the queue I’m starting, SetWorker will be the module where I have defined the perform function. Now the fourth argument takes what we’re passing in the perform function here

def perform(conn, key, value)

So what I tried

  def handle_call({:set, key, value}, _from, state) do
    reply =  Exq.enqueue(Exq, "q1", SetWorker, [key, value])
    {:reply, reply, state}
  end

So many levels of indirection…

As far from what I’ve seen so far, you are using a GenServer, which serialises the requests, then you use a async worker to offload the redis stuff to somewhere else and delay it indefinitely…

You should perhaps rething your design and keep it simple…

1 Like

If your SetWorker contains the 3-arguments version of perform that you wrote above, then you have to pass an argument list of 3 elements:

Exq.enqueue(Exq, "q1", SetWorker, [conn, key, value])