HTTPoison in asynchronous function

Hello.
I’d like to send a request in Task.start_link/1, but my code does not work.

  def create(conn, params) do
    file_path = unless params["image"] == "" do
      uuid = SecureRandom.uuid()
      File.cp(params["image"].path, "./static/image/tmp/#{uuid}.jpg")
      "./static/image/tmp/"<>uuid<>".jpg"
    else
      "./static/image/stones.png"
    end

    map =
      @db_domain_url <> @api_url <> @tournament_url
      |> send_tournament_multipart(params, file_path)

    # first task
    Task.start_link(fn -> 
      map["data"]["followers"]
      |> Enum.each(fn follower -> 
        follower["id"]
        |> Accounts.get_devices_by_user_id()
        |> Enum.each(fn device -> 
          Notifications.push(follower["name"]<>"さんが大会を予定しました。", device.device_id)
        end)
      end)
    end)
    
    # second task
    Task.start_link(fn -> 
      event_time =
        map["data"]["event_date"]
        |> Timex.parse!("{ISO:Extended}")
        |> DateTime.to_unix()

      now = 
        DateTime.utc_now()
        |> DateTime.to_unix()
      IO.inspect(event_time - now)
      Process.sleep((event_time - now)*1000)
      
      response =
        @db_domain_url <> @api_url <> @get_tournament_info_url
        |> IO.inspect(label: :url)
        |> send_json(%{"tournament_id" => map["data"]["id"]})

      response["data"]["entrants"]
      |> Enum.each(fn entrant -> 
        entrant["id"]
        |> Accounts.get_devices_by_user_id()
        |> Enum.each(fn device -> 
          Notifications.push(response["data"]["name"]<>"がスタートしました!", device.device_id)
        end)
      end)

      IO.inspect({"finished sending notifications"})
    end)
    
    unless params["image"] == "" do
      File.rm(file_path)
    end

    json(conn, map)
  end

In this function, it sends a multipart/form-data request to another server. The response should be json and the first task works properly.
But the second task throws an error when it runs send_json/1.

This is the source code of send_json/1 below.

defmodule Common.Tools do
  use Timex

  defmacro __using__(_opts) do
    quote do
      def send_json(url, params) do
        content_type = [{"Content-Type", "application/json"}]

        IO.inspect(params, label: :params)

        p = if is_binary(params) do
          params
        else
          Poison.encode!(params)
        end
        |> IO.inspect(label: :encode)

        with {:ok, attrs} <- Poison.decode(p) |> IO.inspect,
          {:ok, response} <- HTTPoison.post(url, attrs, content_type),
          {:ok, body} <- Poison.decode(response.body) do
            body
        else
          {:error, {reason, _, _}} ->
            %{
              "result" => false,
              "reason" => reason
            }
          {:error, reason} ->
            %{
              "result" => false,
              "reason" => reason
            }
          _ ->
            %{
              "result" => false,
              "reason" => "Unexpected error"
            }
        end
      end
    ...
  end

And the error is this;

url: "http://localhost:4000/api/tournament/get"
params: %{"tournament_id" => 17}
encode: "{\"tournament_id\":17}"
{:ok, %{"tournament_id" => 17}}
[error] Task #PID<0.685.0> started from #PID<0.675.0> terminating
** (stop) {{:case_clause, %{"tournament_id" => 17}}, []}
Function: #Function<2.33228726/0 in PappapWeb.TournamentController.create/2>
    Args: []

I’m wondering that HTTPoison.post/3 cannot be executed in asynchronous function… (though I don’t know what the truth is)

If you know something of this, please let me know :sob:

I don’t think it is related to asynchronous function. Try to run second task outside of Task.async function. I assume you’ll see better error reporting :slight_smile:

task2 = fn -> ... end
# Task.start_link(task2)
task2.()
2 Likes

Thank you for answering my question!

I tried it with this code;

task2 = fn -> 
      event_time =
        map["data"]["event_date"]
        |> Timex.parse!("{ISO:Extended}")
        |> DateTime.to_unix()

      now = 
        DateTime.utc_now()
        |> DateTime.to_unix()
      IO.inspect(event_time - now)
      Process.sleep((event_time - now)*1000)
      
      response =
        @db_domain_url <> @api_url <> @get_tournament_info_url
        |> IO.inspect(label: :url)
        |> send_json(%{"tournament_id" => map["data"]["id"]})

      response["data"]["entrants"]
      |> Enum.each(fn entrant -> 
        entrant["id"]
        |> Accounts.get_devices_by_user_id()
        |> Enum.each(fn device -> 
          Notifications.push(response["data"]["name"]<>"がスタートしました!", device.device_id)
        end)
      end)

      IO.inspect({"finished sending notifications"})
    end

    task2.()

Although I wait for a long time, my terminal shows nothing…

[info] POST /api/tournament
[debug] Processing with PappapWeb.TournamentController.create/2
  Parameters: %{"image" => "", "tournament" => %{"capacity" => "7", "deadline" => "2020-10-27T00:19:00.000+0900", "description" => "", "event_date" => "2020-10-27T00:19:00.000+0900", "game_name" => "at", "join" => "false", "live" => "false", "master_id" => "1", "name" => "taikai", "password" => "[FILTERED]", "type" => "2", "url" => ""}}
  Pipelines: [:api]
129
[debug] QUERY OK source="devices" db=2.2ms idle=1079.0ms
SELECT d0."id", d0."user_id", d0."device_id", d0."inserted_at", d0."updated_at" FROM "devices" AS d0 WHERE (d0."user_id" = $1) [2]
[debug] QUERY OK source="devices" db=0.9ms idle=104.3ms
SELECT d0."id", d0."user_id", d0."device_id", d0."inserted_at", d0."updated_at" FROM "devices" AS d0 WHERE (d0."user_id" = $1) [1]

What should I do next?

Sorry, it was a data format problem.
The issue was solved.

defmodule PappapWeb.TournamentController do
  use PappapWeb, :controller
  use Common.Tools
  use Timex
  alias Pappap.Notifications
  alias Pappap.Accounts

  @db_domain_url "http://localhost:4000"
  @api_url "/api"
  @tournament_url "/tournament"
  @tournament_log_url "/tournament_log"
  @get_participating_tournaments_url "/tournament/get_participating_tournaments"
  @get_tournament_topics_url "/tournament/get_tabs"
  @get_tournament_info_url "/tournament/get"
  @match_start_url "/start"
  @get_url "/get"
  @add_url "/add"
  @delete_loser_url "/deleteloser"

  def create(conn, params) do
    file_path = unless params["image"] == "" do
      uuid = SecureRandom.uuid()
      File.cp(params["image"].path, "./static/image/tmp/#{uuid}.jpg")
      "./static/image/tmp/"<>uuid<>".jpg"
    else
      "./static/image/stones.png"
    end

    map =
      @db_domain_url <> @api_url <> @tournament_url
      |> send_tournament_multipart(params, file_path)

    Task.start_link(fn -> 
      map["data"]["followers"]
      |> Enum.each(fn follower -> 
        follower["id"]
        |> Accounts.get_devices_by_user_id()
        |> Enum.each(fn device -> 
          Notifications.push(follower["name"]<>"さんが大会を予定しました。", device.device_id)
        end)
      end)
    end)
    
    Task.start_link(fn -> 
      event_time =
        map["data"]["event_date"]
        |> Timex.parse!("{ISO:Extended}")
        |> DateTime.to_unix()

      now = 
        DateTime.utc_now()
        |> DateTime.to_unix()
      IO.inspect(event_time - now)
      Process.sleep((event_time - now)*1000)
      
      url = @db_domain_url <> @api_url <> @get_tournament_info_url
      content_type = [{"Content-Type", "application/json"}]

      p = Poison.encode!(%{"tournament_id" => map["data"]["id"]})

      case HTTPoison.post(url, p, content_type) do
        {:ok, response} ->
          IO.inspect(response, label: :ok)
        {:error, reason} ->
          IO.inspect(reason, label: :reason)
      end

      # response["data"]["entrants"]
      # |> Enum.each(fn entrant -> 
      #   entrant["id"]
      #   |> Accounts.get_devices_by_user_id()
      #   |> Enum.each(fn device -> 
      #     Notifications.push(response["data"]["name"]<>"がスタートしました!", device.device_id)
      #   end)
      # end)

      # IO.inspect({"finished sending notifications"})
    end)
    
    unless params["image"] == "" do
      File.rm(file_path)
    end

    json(conn, map)
  end

  def get_participating(conn, params) do
    map =
      @db_domain_url <> @api_url <> @get_participating_tournaments_url
      |> send_json(params)
      |> IO.inspect(label: :partici)

    json(conn, map)
  end

  def get_tournament_topics(conn, params) do
    map =
      @db_domain_url <> @api_url <> @get_tournament_topics_url
      |> send_json(params)

    json(conn, map)
  end
  
  def start(conn, params) do
    log = Task.async(PappapWeb.TournamentController, :add_log, [params])
    map =
      @db_domain_url <> @api_url <> @tournament_url <> @match_start_url
      |> send_json(params)
    Task.await(log)

    json(conn, map)
  end

  def add_log(params) do
    IO.inspect(params)
    tournament_data =
      @db_domain_url <> @api_url <> @tournament_url <> @get_url
      |> send_json(params["tournament"])
      |> IO.inspect(label: :add_log)
    @db_domain_url <> @api_url <> @tournament_log_url <> @add_url
    |> send_json(tournament_data)
  end

  def delete_loser(conn, params) do
    map =
      @db_domain_url <> @api_url <> @tournament_url <> @delete_loser_url
      |> send_json(params)

    json(conn, map)
  end
end
1 Like