Using Task.async properly..?

I am trying to implement several queries against Cassandra DB, here is my attempt:

def last_locations(conn, params) do

  {user_ids, _}= Code.eval_string(params["user_ids"])
  
  locations= user_ids
  |> Enum.map(&Task.async(fn -> last_location(&1, params) end))
  |> Enum.map(&Task.await/1)
  
  json(conn, locations)
end

defp last_location(user_id, params) do

  client = CQEx.Client.new!

  organization_id= params["organization_id"]
  user_id= params["user_id"]

  query= "SELECT * from myapp.locations WHERE organization_id=#{organization_id} and user_id=#{user_id}  LIMIT 1;"

  result= for [organization_id: _, user_id: _, date: _, unix_time: _, lat: lat, long: long] <- Q.call!(client, query) 
  |> Enum.to_list,
  do: [lat, long]
  
    result

end

So, basically, I get user ids, and pass them to last_location function through Task.async, but when I call last_locations I get error:

Is there something I am missing?

EDIT

I have tested the function last_locations to return user_ids via json(conn, user_ids) without calling Enum.map and I got them back, so the function last_locations is being called for sure.

Only when executing:

locations= user_ids
  |> Enum.map(&Task.async(fn -> last_location(&1, params) end))
  |> Enum.map(&Task.await/1)

I will never get a response back.

1 Like

I will never get a response back.

Task.await/1 has a timeout of 5 seconds, so if you were not really getting anything back, you would eventually trigger a timeout. You need to investigate a bit more what is happening.

{user_ids, _}= Code.eval_string(params["user_ids"])

Please do not ever evaluate user input. The snippet above will allow anyone to execute any code they want in your server. If you want to convert from strings to integers, use String.to_integer or Integer.parse while traversing the list.

4 Likes

Thanks, but how would I convert "[1, 2, 3, 4]" to [1, 2, 3, 4] without using eval ?

1 Like

By parsing it yourself. (lexx and yeec may help here)

Or at least by prechecking if it really does it match this regex ~r/\[[0-9, ]\]/ before evaluating.

Golden rule of userinput: don’t trust it without serverside validation.

1 Like

Just hint:
"1, 2, 3, 4" |> String.split(",") |> Enum.map(&(String.trim(&1) |> String.to_integer))

results: [1, 2, 3, 4]

1 Like

Cool :blush:

1 Like

Just to make sure you understand the severity of the risk with eval: What if user_ids was code to send all of your application passwords to another website? Or install malware on your server? or delete specific data? You’d have just run it.

1 Like

oh, well why implement own parser when you can:

iex(2)> Poison.decode!("[1, 2, 3, 4]")
[1, 2, 3, 4]

Most likely he uses Poison already.

1 Like

Thats of course is true.

The suggestion to write a parser was more for even more complex stuff one wants to “convert”.

1 Like

Yes, I’ve got the point, very dangerous indeed.

1 Like

Thank you, worked perfectly

1 Like

Or via using ExSpirit:

input = "[1, 2, 3, 4, 5]"
%{error: nil, result: output} = parse(input, seq([lit(\[), uint(), repeat(lit(?,) |> uint()), lit(\])]), skipper: repeat(lit(?\s)))
# output = [1, 2, 3, 4, 5]

And that is if you want the [ and ] there for sure, with , as separators, and optional any amount of spacing.

^.^

1 Like

Which is a parser combinator…

1 Like

I <3 these. ^.^

1 Like