1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Pool.ConnectionsTest do
7 use Pleroma.Tests.Helpers
8 import ExUnit.CaptureLog
10 alias Pleroma.Pool.Connections
13 {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.GunMock)
17 clear_config([:connections_pool, :retry]) do
18 Pleroma.Config.put([:connections_pool, :retry], 5)
22 name = :test_connections
23 adapter = Application.get_env(:tesla, :adapter)
24 Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
26 {:ok, pid} = Connections.start_link({name, [max_connections: 2, checkin_timeout: 1_500]})
29 Application.put_env(:tesla, :adapter, adapter)
31 if Process.alive?(pid) do
39 describe "alive?/2" do
40 test "is alive", %{name: name} do
41 assert Connections.alive?(name)
44 test "returns false if not started" do
45 refute Connections.alive?(:some_random_name)
49 test "opens connection and reuse it on next request", %{name: name} do
50 url = "http://some-domain.com"
51 key = "http:some-domain.com:80"
52 refute Connections.checkin(url, name)
53 :ok = Conn.open(url, name)
55 conn = Connections.checkin(url, name)
57 assert Process.alive?(conn)
66 used_by: [{^self, _}],
70 } = Connections.get_state(name)
72 reused_conn = Connections.checkin(url, name)
74 assert conn == reused_conn
81 used_by: [{^self, _}, {^self, _}],
85 } = Connections.get_state(name)
87 :ok = Connections.checkout(conn, self, name)
94 used_by: [{^self, _}],
98 } = Connections.get_state(name)
100 :ok = Connections.checkout(conn, self, name)
111 } = Connections.get_state(name)
114 test "reuse connection for idna domains", %{name: name} do
115 url = "http://ですsome-domain.com"
116 refute Connections.checkin(url, name)
118 :ok = Conn.open(url, name)
120 conn = Connections.checkin(url, name)
122 assert Process.alive?(conn)
128 "http:ですsome-domain.com:80" => %Conn{
131 used_by: [{^self, _}],
135 } = Connections.get_state(name)
137 reused_conn = Connections.checkin(url, name)
139 assert conn == reused_conn
142 test "reuse for ipv4", %{name: name} do
143 url = "http://127.0.0.1"
145 refute Connections.checkin(url, name)
147 :ok = Conn.open(url, name)
149 conn = Connections.checkin(url, name)
151 assert Process.alive?(conn)
157 "http:127.0.0.1:80" => %Conn{
160 used_by: [{^self, _}],
164 } = Connections.get_state(name)
166 reused_conn = Connections.checkin(url, name)
168 assert conn == reused_conn
170 :ok = Connections.checkout(conn, self, name)
171 :ok = Connections.checkout(reused_conn, self, name)
175 "http:127.0.0.1:80" => %Conn{
182 } = Connections.get_state(name)
185 test "reuse for ipv6", %{name: name} do
186 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
188 refute Connections.checkin(url, name)
190 :ok = Conn.open(url, name)
192 conn = Connections.checkin(url, name)
194 assert Process.alive?(conn)
200 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
203 used_by: [{^self, _}],
207 } = Connections.get_state(name)
209 reused_conn = Connections.checkin(url, name)
211 assert conn == reused_conn
214 test "up and down ipv4", %{name: name} do
216 url = "http://127.0.0.1"
217 :ok = Conn.open(url, name)
218 conn = Connections.checkin(url, name)
219 send(name, {:gun_down, conn, nil, nil, nil})
220 send(name, {:gun_up, conn, nil})
224 "http:127.0.0.1:80" => %Conn{
227 used_by: [{^self, _}],
231 } = Connections.get_state(name)
234 test "up and down ipv6", %{name: name} do
236 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
237 :ok = Conn.open(url, name)
238 conn = Connections.checkin(url, name)
239 send(name, {:gun_down, conn, nil, nil, nil})
240 send(name, {:gun_up, conn, nil})
244 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
247 used_by: [{^self, _}],
251 } = Connections.get_state(name)
254 test "reuses connection based on protocol", %{name: name} do
255 http_url = "http://some-domain.com"
256 http_key = "http:some-domain.com:80"
257 https_url = "https://some-domain.com"
258 https_key = "https:some-domain.com:443"
260 refute Connections.checkin(http_url, name)
261 :ok = Conn.open(http_url, name)
262 conn = Connections.checkin(http_url, name)
264 assert Process.alive?(conn)
266 refute Connections.checkin(https_url, name)
267 :ok = Conn.open(https_url, name)
268 https_conn = Connections.checkin(https_url, name)
270 refute conn == https_conn
272 reused_https = Connections.checkin(https_url, name)
274 refute conn == reused_https
276 assert reused_https == https_conn
289 } = Connections.get_state(name)
292 test "connection can't get up", %{name: name} do
293 url = "http://gun-not-up.com"
295 assert capture_log(fn ->
296 refute Conn.open(url, name)
297 refute Connections.checkin(url, name)
299 "Received error on opening connection http://gun-not-up.com {:error, :timeout}"
302 test "process gun_down message and then gun_up", %{name: name} do
304 url = "http://gun-down-and-up.com"
305 key = "http:gun-down-and-up.com:80"
306 :ok = Conn.open(url, name)
307 conn = Connections.checkin(url, name)
310 assert Process.alive?(conn)
317 used_by: [{^self, _}]
320 } = Connections.get_state(name)
322 send(name, {:gun_down, conn, :http, nil, nil})
329 used_by: [{^self, _}]
332 } = Connections.get_state(name)
334 send(name, {:gun_up, conn, :http})
336 conn2 = Connections.checkin(url, name)
340 assert Process.alive?(conn2)
347 used_by: [{^self, _}, {^self, _}]
350 } = Connections.get_state(name)
353 test "async processes get same conn for same domain", %{name: name} do
354 url = "http://some-domain.com"
355 :ok = Conn.open(url, name)
360 Connections.checkin(url, name)
364 tasks_with_results = Task.yield_many(tasks)
367 Enum.map(tasks_with_results, fn {task, res} ->
368 res || Task.shutdown(task, :brutal_kill)
371 conns = for {:ok, value} <- results, do: value
375 "http:some-domain.com:80" => %Conn{
380 } = Connections.get_state(name)
382 assert Enum.all?(conns, fn res -> res == conn end)
385 test "remove frequently used and idle", %{name: name} do
387 http_url = "http://some-domain.com"
388 https_url = "https://some-domain.com"
389 :ok = Conn.open(https_url, name)
390 :ok = Conn.open(http_url, name)
392 conn1 = Connections.checkin(https_url, name)
396 Connections.checkin(http_url, name)
399 http_key = "http:some-domain.com:80"
407 used_by: [{^self, _}, {^self, _}, {^self, _}, {^self, _}]
409 "https:some-domain.com:443" => %Conn{
413 used_by: [{^self, _}]
416 } = Connections.get_state(name)
418 :ok = Connections.checkout(conn1, self, name)
420 another_url = "http://another-domain.com"
421 :ok = Conn.open(another_url, name)
422 conn = Connections.checkin(another_url, name)
426 "http:another-domain.com:80" => %Conn{
435 } = Connections.get_state(name)
438 describe "with proxy" do
439 test "as ip", %{name: name} do
440 url = "http://proxy-string.com"
441 key = "http:proxy-string.com:80"
442 :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
444 conn = Connections.checkin(url, name)
453 } = Connections.get_state(name)
455 reused_conn = Connections.checkin(url, name)
457 assert reused_conn == conn
460 test "as host", %{name: name} do
461 url = "http://proxy-tuple-atom.com"
462 :ok = Conn.open(url, name, proxy: {'localhost', 9050})
463 conn = Connections.checkin(url, name)
467 "http:proxy-tuple-atom.com:80" => %Conn{
472 } = Connections.get_state(name)
474 reused_conn = Connections.checkin(url, name)
476 assert reused_conn == conn
479 test "as ip and ssl", %{name: name} do
480 url = "https://proxy-string.com"
482 :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
483 conn = Connections.checkin(url, name)
487 "https:proxy-string.com:443" => %Conn{
492 } = Connections.get_state(name)
494 reused_conn = Connections.checkin(url, name)
496 assert reused_conn == conn
499 test "as host and ssl", %{name: name} do
500 url = "https://proxy-tuple-atom.com"
501 :ok = Conn.open(url, name, proxy: {'localhost', 9050})
502 conn = Connections.checkin(url, name)
506 "https:proxy-tuple-atom.com:443" => %Conn{
511 } = Connections.get_state(name)
513 reused_conn = Connections.checkin(url, name)
515 assert reused_conn == conn
518 test "with socks type", %{name: name} do
519 url = "http://proxy-socks.com"
521 :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234})
523 conn = Connections.checkin(url, name)
527 "http:proxy-socks.com:80" => %Conn{
532 } = Connections.get_state(name)
534 reused_conn = Connections.checkin(url, name)
536 assert reused_conn == conn
539 test "with socks4 type and ssl", %{name: name} do
540 url = "https://proxy-socks.com"
542 :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234})
544 conn = Connections.checkin(url, name)
548 "https:proxy-socks.com:443" => %Conn{
553 } = Connections.get_state(name)
555 reused_conn = Connections.checkin(url, name)
557 assert reused_conn == conn
563 crf = Connections.crf(1, 10, 1)
567 test "more used will have crf higher", %{crf: crf} do
569 crf1 = Connections.crf(1, 10, crf)
570 crf1 = Connections.crf(1, 10, crf1)
573 crf2 = Connections.crf(1, 10, crf)
578 test "recently used will have crf higher on equal references", %{crf: crf} do
580 crf1 = Connections.crf(3, 10, crf)
583 crf2 = Connections.crf(4, 10, crf)
588 test "equal crf on equal reference and time", %{crf: crf} do
590 crf1 = Connections.crf(1, 10, crf)
593 crf2 = Connections.crf(1, 10, crf)
598 test "recently used will have higher crf", %{crf: crf} do
599 crf1 = Connections.crf(2, 10, crf)
600 crf1 = Connections.crf(1, 10, crf1)
602 crf2 = Connections.crf(3, 10, crf)
603 crf2 = Connections.crf(4, 10, crf2)
608 describe "get_unused_conns/1" do
609 test "crf is equalent, sorting by reference", %{name: name} do
610 Connections.add_conn(name, "1", %Conn{
612 last_reference: now() - 1
615 Connections.add_conn(name, "2", %Conn{
617 last_reference: now()
620 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
623 test "reference is equalent, sorting by crf", %{name: name} do
624 Connections.add_conn(name, "1", %Conn{
629 Connections.add_conn(name, "2", %Conn{
634 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
637 test "higher crf and lower reference", %{name: name} do
638 Connections.add_conn(name, "1", %Conn{
641 last_reference: now() - 1
644 Connections.add_conn(name, "2", %Conn{
647 last_reference: now()
650 assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name)
653 test "lower crf and lower reference", %{name: name} do
654 Connections.add_conn(name, "1", %Conn{
657 last_reference: now() - 1
660 Connections.add_conn(name, "2", %Conn{
663 last_reference: now()
666 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
670 test "count/1", %{name: name} do
671 assert Connections.count(name) == 0
672 Connections.add_conn(name, "1", %Conn{conn: self()})
673 assert Connections.count(name) == 1
674 Connections.remove_conn(name, "1")
675 assert Connections.count(name) == 0
679 :os.system_time(:second)