elixir-plug / plug_cowboy

Plug adapter for the Cowboy web server
Other
243 stars 48 forks source link

Got error when we trying to start two or more Plug.Cowboy dynamically #20

Closed Shakarim closed 4 years ago

Shakarim commented 4 years ago

Description I'm not sure it's bug or not, but I observe a strange behavior of server. I face with the task of starting two or more supervised http server with different ports, and I did the following:

  1. I've created simple mix application with supervised DynamicSupervisor

    defmodule Master do
    @moduledoc false
    use Application
    require Logger
    
    @doc false
    def start(_type, _args) do
    children = [
      {
        DynamicSupervisor,
        strategy: :one_for_one,
        name: Master.DynamicSupervisor
      }
    ]
    Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
    end
    
    def start_server(port) do
    DynamicSupervisor.start_child(Master.DynamicSupervisor,{
      Plug.Cowboy,
      scheme: :http,
      plug: Router,
      options: [
        port: port
      ]
    })
    end
    end

    This module contain just one custom function, it's Master.start_server/1, it receive the port as argument, and trying to start PlugCowboy with this port as child of DynamicSupervisors.

  2. I've added a simple router with "/echo"

    defmodule Router do
    use Plug.Router
    require Logger
    
    plug Plug.Parsers, parsers: [{:multipart, length: 20_000_000}]
    plug(:match)
    plug(:dispatch)
    
    # Creates basic params for Plug.Conn
    defp get_base_connection(conn), do: conn |> put_resp_content_type("application/json")
    
    get "/echo", do: conn |> get_base_connection |> send_resp(200, Jason.encode!(%{result: "Success!"}))
    
    # 404 page error
    match _, do: send_resp(conn, 404, Jason.encode!(%{code: 404, message: "Page not found"}))
    end
  3. And have tried to start it via iex -S mix

What I've got I've got

Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

So far so good. And now, I'm trying to start my first server:

iex(1)> Master.start_server 7000
{:ok, #PID<0.545.0>}

Still okay, but, if I try to run a second server on another port:

iex(2)> Master.start_server 7001

I got this:

03:49:12.712 [error] Failed to start Ranch listener Router.HTTP in :ranch_tcp:listen([cacerts: :..., key: :..., cert: :..., port: 7000]) for reason :eaddrinuse (address already in use)

{:error,
 {:shutdown,
  {:failed_to_start_child, :ranch_acceptors_sup,
   {:listen_error, Router.HTTP, :eaddrinuse}}}}

What's strange?

  1. I've started my second server with port=7001, but error message contain port=7000 (my first server's port).
  2. If we try to call this function again (with same args) - we will got:
    iex(3)> Master.start_server 7001
    {:ok, #PID<0.668.0>}

**All log of my console***

Interactive Elixir (1.9.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Master.start_server 7000
{:ok, #PID<0.558.0>}
iex(2)> Master.start_server 7001

03:49:12.712 [error] Failed to start Ranch listener Router.HTTP in :ranch_tcp:listen([cacerts: :..., key: :..., cert: :..., port: 7000]) for reason :eaddrinuse (address already in use)

{:error,
 {:shutdown,
  {:failed_to_start_child, :ranch_acceptors_sup,
   {:listen_error, Router.HTTP, :eaddrinuse}}}}
iex(3)> Master.start_server 7001
{:ok, #PID<0.668.0>}
iex(4)> 
josevalim commented 4 years ago

When you start it in port 7001 and it works, is it still running on port 7000? --

José Valim www.plataformatec.com.br Founder and Director of R&D

Shakarim commented 4 years ago

Yeap. Both of em works correctly. Moreover, when I run third, fourth and etc. It's still show me errors, but all of em works.

josevalim commented 4 years ago

So the error is a bit misleading but it is common from Cowboy side, so we will do a report upstream. But in a nutshell, the issue is that you are using the same name for every adapter.

This should fix it:


  def start_server(port) do
    DynamicSupervisor.start_child(Master.DynamicSupervisor,{
      Plug.Cowboy,
      scheme: :http,
      plug: Router,
      options: [
        port: port,
        ref: make_ref()
      ]
    })
  end
Shakarim commented 4 years ago

Problem solved, thank you very much