Search code examples
socketsudpelixirgraphitegraphite-carbon

gen_udp in Elixir fails to connect to a UDP socket


I'm trying to make Graphitex (a Graphite Carbon API client for Elixir) use gen_udp instead of gen_tcp.

The client is a GenServer that wraps a UDP socket, with a public API which is not related to the question.

The offending bits seem to be connect/1, terminate/2 and handle_cast/2 GenServer callbacks.

Original implementation can be found here, my fork is here.

What I did:

  • replaced :gen_tcp.connect(host, port, opts) (where host and port are those of a remote Graphite Carbon UDP endpoint) with :gen_udp.open(0, opts) (to get an OS-chosen port)
  • replaced :gen_tcp.send(socket, msg) with :gen_udp.send(socket, host, port, msg) to send msg to remote host:port via OS-chosen UDP socket

The error I get when running the app with my fork of graphitex:

12:12:14.160 [info]  Connecting to carbon at localhost:2003
12:12:14.383 [error] GenServer Graphitex.Client terminating
** (FunctionClauseError) no function clause matching in Graphitex.Client.terminate/2
    (graphitex) lib/graphitex/client.ex:78: Graphitex.Client.terminate({{:badmatch, {:error, :econnrefused}}, [{Graphitex.Client, :connect, 1, [file: 'lib/graphitex/client.ex', line: 73]}, {Graphitex.Client, :handle_cast, 2, [file: 'lib/graphitex/client.ex', line: 86]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 637]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 711]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 249]}]}, %{socket: nil})
    (stdlib) gen_server.erl:673: :gen_server.try_terminate/3
    (stdlib) gen_server.erl:858: :gen_server.terminate/10
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:metric, 'graphite_consumer 1000 1570525934\n'}}
State: %{socket: nil}

It seems that the socket (that I clearly put in state in connect/1) is nil. I'm puzzled why can it be; :gen_udp.open(0) in IEx works fine and returns {:ok, socket}.


Solution

  • UDP is connection-less protocol, which mean that you do not need to connect to the remote socket to send data. All you need to do is:

    {:ok, socket} = :gen_udp.open(0)
    
    :ok = :gen_udp.send(socket, host, port, msg)
    

    No need for connecting, that is why there is no documentation for :gen_udp.connect, as it shouldn't be used at all.