PSPDFKit-labs / bypass

Bypass provides a quick way to create a custom plug that can be put in place instead of an actual HTTP server to return prebaked responses to client requests.
https://hex.pm/packages/bypass
MIT License
964 stars 111 forks source link

bypass randomly fails with eaddrinuse error #121

Open ananthakumaran opened 2 years ago

ananthakumaran commented 2 years ago
{{:badmatch, {:error, :eaddrinuse}},
 [
   {Bypass.Instance, :do_up, 2, [file: 'lib/bypass/instance.ex', line: 322]},
   {Bypass.Instance, :init, 1, [file: 'lib/bypass/instance.ex', line: 34]},
   {:gen_server, :init_it, 2, [file: 'gen_server.erl', line: 374]},
   {:gen_server, :init_it, 6, [file: 'gen_server.erl', line: 342]},
   {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}
 ]}

The race condition could be reproduced with the following script

defmodule BypassReproTest.Case do
  use ExUnit.Case, async: true

  test "reproduce bypass" do
    :ok = :hackney_pool.start_pool(:first_pool, timeout: 15000, max_connections: 100)
    Application.put_env(:bypass, :test_framework, :espec)

    1..10_000
    |> Task.async_stream(
      fn i ->
        bypass = Bypass.open()

        Bypass.stub(bypass, "GET", "/ping", fn conn ->
          Plug.Conn.resp(conn, 200, "pong")
        end)

        response =
          HTTPoison.get!("http://localhost:#{bypass.port}/ping", [], hackney: [pool: :bypass])

        assert response.status_code == 200
        Bypass.verify_expectations!(bypass)
      end,
      ordered: false,
      max_concurrency: 50,
      timeout: 50000
    )
    |> Stream.run()
  end
end

The Code.ensure_loaded should be commented out as well.

  if Code.ensure_loaded?(ESpec) do
    defp verify_expectations!(:espec, bypass) do
      do_verify_expectations(bypass.pid, ESpec.AssertionError)
    end
  end