<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="228959" data-post-id="228959">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="andrzej-mag" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/andrzej-mag/120/22749_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  andrzej-mag
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<blockquote>
<p>…clients wanting to send message and wait response from this socket.<br>
…For example to have hundred of parallel requests in the same time.</p>
</blockquote>
<p><a href="https://github.com/ninenines/ranch" rel="noopener nofollow ugc">ranch</a> + <a href="http://erlang.org/doc/man/gen_statem.html" rel="nofollow">gen_statem</a>?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="228959" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/ets-for-tcp-connections/43113/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-228959" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="228959"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="228960" data-post-id="228960">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="smanza" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  smanza
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Could you elaborate please ?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="228960" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/ets-for-tcp-connections/43113/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-228960" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="228960"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="228978" data-post-id="228978">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="smanza" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  smanza
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Here some code example I ran with livebook:</p>
<p>For the server side:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Listener do
  use GenServer

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg)
  end

  def init(arg) do
    port = Keyword.get(arg, :port)

    {:ok, listen_socket} =
      :gen_tcp.listen(port, [
        :binary,
        {:packet, 4},
        {:active, :once},
        {:reuseaddr, true},
        {:backlog, 500}
      ])

    Enum.each(1..100, fn _ -&gt;
      spawn(fn -&gt; accept_loop(listen_socket) end)
    end)

    {:ok, %{listen_socket: listen_socket}}
  end

  def accept_loop(listen_socket) do
    case :gen_tcp.accept(listen_socket) do
      {:ok, socket} -&gt;
        {:ok, pid} = AcceptedConnection.start_link(socket: socket)
        :gen_tcp.controlling_process(socket, pid)
        accept_loop(listen_socket)

      {:error, _} = e -&gt;
        e
    end
  end
end

defmodule AcceptedConnection do
  use GenServer

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg)
  end

  def init(arg) do
    socket = Keyword.get(arg, :socket)
    {:ok, %{socket: socket}}
  end

  def handle_info({:tcp, socket, data}, state) do
    :inet.setopts(socket, active: :once)
    # Reply back the message
    :gen_tcp.send(socket, data)
    {:noreply, state}
  end

  def handle_info({:tcp_closed, _}, state) do
    {:stop, :normal, state}
  end
end
</code></pre>
<p>For the client:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule Connection do
  use GenServer

  def start_link(arg) do
    GenServer.start_link(__MODULE__, arg)
  end

  def send_message(pid, msg) do
    GenServer.call(pid, {:send_message, msg})
  end

  def init(arg) do
    ip = Keyword.get(arg, :ip)
    port = Keyword.get(arg, :port)
    Registry.register(ConnectionRegistry, :connections, _value = nil)

    {:ok, socket} = :gen_tcp.connect(ip, port, [:binary, {:packet, 4}, {:active, :once}])

    {:ok, %{socket: socket, requests: %{}, next_id: 1}}
  end

  def handle_call({:send_message, data}, from, state = %{socket: socket, next_id: next_id}) do
    :gen_tcp.send(socket, &lt;&lt;next_id::32, data::binary&gt;&gt;)

    new_state =
      state
      |&gt; Map.update!(:next_id, &amp;(&amp;1 + 1))
      |&gt; Map.update!(:requests, &amp;Map.put(&amp;1, next_id, from))

    {:noreply, new_state}
  end

  def handle_info(
        {:tcp, _, &lt;&lt;id::32, data::binary&gt;&gt;},
        state = %{socket: socket, requests: requests}
      ) do
    :inet.setopts(socket, active: :once)
    {from, requests} = Map.pop(requests, id)
    GenServer.reply(from, {:ok, data})
    {:noreply, %{state | requests: requests}}
  end

  def handle_info({:tcp_closed, _}, state) do
    Registry.unregister(ConnectionRegistry, :connections)
    {:stop, :normal, state}
  end
end
</code></pre>
<p>Benchmark suite:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">port = 10_000

{:ok, pid} = Listener.start_link(port: port)
%{listen_socket: listen_socket} = :sys.get_state(pid)

Registry.start_link(name: ConnectionRegistry, keys: :duplicate)

{:ok, conn1} = Connection.start_link(ip: {127, 0, 0, 1}, port: port)
{:ok, conn2} = Connection.start_link(ip: {127, 0, 0, 1}, port: port)
{:ok, conn2} = Connection.start_link(ip: {127, 0, 0, 1}, port: port)

{:ok, socket1} = :gen_tcp.connect({127, 0, 0, 1}, port, [:binary, {:packet, 4}, {:active, false}])
{:ok, socket2} = :gen_tcp.connect({127, 0, 0, 1}, port, [:binary, {:packet, 4}, {:active, false}])
{:ok, socket3} = :gen_tcp.connect({127, 0, 0, 1}, port, [:binary, {:packet, 4}, {:active, false}])

if :ets.info(:connections) == :undefined do
  :ets.new(:connections, [:named_table, :public, read_concurrency: true])
end

:ets.insert(:connections, {{{127, 0, 0, 1}, port}, socket1})

Benchee.run(
  %{
    "single genserver" =&gt; fn -&gt;
      Connection.send_message(conn1, "hello")
    end,
    "pool of genserver" =&gt; fn -&gt;
      connections = Registry.lookup(ConnectionRegistry, :connections)
      {pid, _} = Enum.random(connections)
      Connection.send_message(pid, "hello")
    end,
    "ets" =&gt; fn -&gt;
      {_, socket_ets} = :ets.lookup(:connections, {{127, 0, 0, 1}, port}) |&gt; Enum.random()
      :gen_tcp.send(socket_ets, "hello")
      :gen_tcp.recv(socket_ets, 0)
    end
  },
  parallel: 4
)

:ets.delete(:connections)
:gen_tcp.close(listen_socket)
</code></pre>
<p>Benchmarks results</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Operating System: macOS
CPU Information: Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz
Number of Available Cores: 4
Available memory: 16 GB
Elixir 1.12.3
Erlang 24.1.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
parallel: 4
inputs: none specified
Estimated total run time: 21 s

Benchmarking ets...
Benchmarking pool of genserver...
Benchmarking single genserver...

Name                        ips        average  deviation         median         99th %
ets                     22.47 K       44.50 μs   ±311.73%          29 μs         211 μs
single genserver         9.67 K      103.43 μs    ±58.76%          93 μs         268 μs
pool of genserver        8.43 K      118.64 μs   ±130.49%          98 μs         323 μs

Comparison: 
ets                     22.47 K
single genserver         9.67 K - 2.32x slower +58.93 μs
pool of genserver        8.43 K - 2.67x slower +74.14 μs
</code></pre>
<p>However, if I’m increasing the parallel execution number, the latency between ETS and pool of GenServer is reducing, but it requires few connections instead of a single one.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="228978" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/ets-for-tcp-connections/43113/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-228978" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="228978"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="228980" data-post-id="228980">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="smanza" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  smanza
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Also if I increase the latency for example:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def handle_info({:tcp, socket, data}, state) do
    :inet.setopts(socket, active: :once)
    spawn(fn -&gt;
       # Reply back the message
       Process.sleep(200)
      :gen_tcp.send(socket, data)
    end)
   
    {:noreply, state}
  end
</code></pre>
<p>The ets solution shines and the pool vs single genserver remains the same:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Operating System: macOS
CPU Information: Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz
Number of Available Cores: 4
Available memory: 16 GB
Elixir 1.12.3
Erlang 24.1.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
parallel: 100
inputs: none specified
Estimated total run time: 21 s

Benchmarking ets...
Benchmarking pool of genserver...
Benchmarking single genserver...

Name                        ips        average  deviation         median         99th %
ets                     1173.44        0.85 ms   ±578.99%        0.42 ms        7.64 ms
single genserver           4.97      201.02 ms     ±0.10%      201.01 ms      201.63 ms
pool of genserver          4.97      201.20 ms     ±0.37%      201.02 ms      205.29 ms

Comparison: 
ets                     1173.44
single genserver           4.97 - 235.89x slower +200.17 ms
pool of genserver          4.97 - 236.09x slower +200.34 ms
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="228980" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/ets-for-tcp-connections/43113/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-228980" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="228980"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="229347" data-post-id="229347">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="smanza" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  smanza
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Finally I was able to reach pretty good performance with GenServer by implementing two things:</p>
<ul>
<li>Backlog management : like a semaphore for GenServer , limiting the message sending from the client (ETS table and atomic counter)</li>
<li>Using asynchronous calls (GenServer.cast) and queuing requests (ETS table) , avoiding timeout and limitation from the GenServer.call</li>
<li>Using active once to have a reactive way with message handling while having a backpressure from the TCP backlog.</li>
</ul>
<p>Even with a single GenServer I got pretty good perf.</p>
<p>I got inspired by Shackle</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="229347" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/ets-for-tcp-connections/43113/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-229347" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="229347"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>