parallel http requests are responded in the same order as the cassette
sescobb27 opened this issue · comments
Hi, i'm getting errors when doing parallel http request that are responded from a cassette.
I'm fetching some records from an API and then fetching details for each record from a different API using Tasks, but it seems ExVcr is using the cassette to respond in the same order the requests were first made, but, because requests are run in parallel i'm getting answers mixed up
defmodule PlaceService do
def get_places(city, lat, lon) do
case HTTPoison.get("places_url") do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
%{"results" => results} = Poison.decode!(body)
places = results
|> enrich_places_with_details()
|> Enum.filter(&(&1 != %{}))
{:ok, places}
{:ok, %HTTPoison.Response{status_code: _code, body: _body}} ->
{:error, []}
end
end
defp enrich_places_with_details(places) do
Enum.map(places, fn place ->
Task.Supervisor.async_nolink(Twd.TaskSupervisor, fn ->
fetch_place_details(place)
end)
end)
|> Task.yield_many()
|> Enum.map(fn {task, result} ->
case result do
{:ok, details} -> details
{:exit, _} -> %{}
nil ->
Logger.info("Timeout fetching details")
Task.shutdown(task, :brutal_kill)
%{}
end
end)
end
defp fetch_place_details(place) do
url = "details_for_place_url"
response = HTTPoison.get(url, [])
{:ok, %HTTPoison.Response{status_code: 200, body: body}} = response
case Poison.decode!(body) do
%{"result" => result} -> Map.merge(place, result)
end
end
end
I have a similar problem, in the latest iteration of the code this is shown even with a simple async stream:
Symbol.supported()
|> Task.async_stream(fn symbol ->
HTTPoison.get(url, [], params: %{"indicator" => indicator, "exchange" => "binance", "symbol" => symbol, "interval" => interval, "candlesCount" => candles})
end)
|> Enum.into([], fn {:ok, {:ok, %HTTPoison.Response{body: body, request: %HTTPoison.Request{params: %{"symbol" => symbol}}}}} -> %{symbol => Poison.decode!(body)} end)
the cassette gets registrered properly, however the test doesn't pass:
defmodule Pandamex.IndicatorTest do
use ExUnit.Case, async: true
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney, options: [clear_mock: true]
alias Pandamex.Indicator
setup_all do
HTTPoison.start()
end
describe "Pandamex.Indicator.get/4" do
@tag timeout: :infinity
test "works" do
use_cassette "indicators/get_indicators" do
assert Indicator.get() == [
%{"BTC/USDT" => %{"value" => 45.67296250448912}},
%{"ETH/USDT" => %{"value" => 44.17848150108721}},
%{"XRP/USDT" => %{"value" => 47.02039812884717}},
%{"LTC/USDT" => %{"value" => 52.7422974568094}},
%{"XMR/USDT" => %{"value" => 54.31603149404031}}
]
end
end
end
end
1) test Pandamex.Indicator.get/4 works (Pandamex.IndicatorTest)
test/pandamex/indicator_test.exs:15
Assertion with == failed
code: assert Indicator.get() == [%{"BTC/USDT" => %{"value" => 45.67296250448912}}, %{"ETH/USDT" => %{"value" => 44.17848150108721}}, %{"XRP/USDT" => %{"value" => 47.02039812884717}}, %{"LTC/USDT" => %{"value" => 52.7422974568094}}, %{"XMR/USDT" => %{"value" => 54.31603149404031}}]
left: [%{"BTC/USDT" => %{"value" => 54.31603149404031}}, %{"ETH/USDT" => %{"value" => 54.31603149404031}}, %{"XRP/USDT" => %{"value" => 54.31603149404031}}, %{"LTC/USDT" => %{"value" => 54.31603149404031}}, %{"XMR/USDT" => %{"value" => 54.31603149404031}}]
right: [%{"BTC/USDT" => %{"value" => 45.67296250448912}}, %{"ETH/USDT" => %{"value" => 44.17848150108721}}, %{"XRP/USDT" => %{"value" => 47.02039812884717}}, %{"LTC/USDT" => %{"value" => 52.7422974568094}}, %{"XMR/USDT" => %{"value" => 54.31603149404031}}]
stacktrace:
test/pandamex/indicator_test.exs:17: (test)
as you can see it returns the value of the first entry in the cassette 54.31603149404031
for each symbol
removing exvcr from the spec and doing the actual http call works.