1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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.Gun.Conn
11 alias Pleroma.Pool.Connections
14 {:ok, _} = Registry.start_link(keys: :unique, name: API.Mock)
19 name = :test_connections
20 adapter = Application.get_env(:tesla, :adapter)
21 Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
22 on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
25 Connections.start_link({name, [max_connections: 2, receive_connection_timeout: 1_500]})
30 describe "alive?/2" do
31 test "is alive", %{name: name} do
32 assert Connections.alive?(name)
35 test "returns false if not started" do
36 refute Connections.alive?(:some_random_name)
40 test "opens connection and reuse it on next request", %{name: name} do
41 url = "http://some-domain.com"
42 key = "http:some-domain.com:80"
43 refute Connections.checkin(url, name)
44 :ok = Connections.open_conn(url, name)
46 conn = Connections.checkin(url, name)
48 assert Process.alive?(conn)
57 used_by: [{^self, _}],
61 } = Connections.get_state(name)
63 reused_conn = Connections.checkin(url, name)
65 assert conn == reused_conn
72 used_by: [{^self, _}, {^self, _}],
76 } = Connections.get_state(name)
78 :ok = Connections.checkout(conn, self, name)
85 used_by: [{^self, _}],
89 } = Connections.get_state(name)
91 :ok = Connections.checkout(conn, self, name)
102 } = Connections.get_state(name)
105 test "reuse connection for idna domains", %{name: name} do
106 url = "http://ですsome-domain.com"
107 refute Connections.checkin(url, name)
109 :ok = Connections.open_conn(url, name)
111 conn = Connections.checkin(url, name)
113 assert Process.alive?(conn)
119 "http:ですsome-domain.com:80" => %Conn{
122 used_by: [{^self, _}],
126 } = Connections.get_state(name)
128 reused_conn = Connections.checkin(url, name)
130 assert conn == reused_conn
133 test "reuse for ipv4", %{name: name} do
134 url = "http://127.0.0.1"
136 refute Connections.checkin(url, name)
138 :ok = Connections.open_conn(url, name)
140 conn = Connections.checkin(url, name)
142 assert Process.alive?(conn)
148 "http:127.0.0.1:80" => %Conn{
151 used_by: [{^self, _}],
155 } = Connections.get_state(name)
157 reused_conn = Connections.checkin(url, name)
159 assert conn == reused_conn
161 :ok = Connections.checkout(conn, self, name)
162 :ok = Connections.checkout(reused_conn, self, name)
166 "http:127.0.0.1:80" => %Conn{
173 } = Connections.get_state(name)
176 test "reuse for ipv6", %{name: name} do
177 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
179 refute Connections.checkin(url, name)
181 :ok = Connections.open_conn(url, name)
183 conn = Connections.checkin(url, name)
185 assert Process.alive?(conn)
191 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
194 used_by: [{^self, _}],
198 } = Connections.get_state(name)
200 reused_conn = Connections.checkin(url, name)
202 assert conn == reused_conn
205 test "up and down ipv4", %{name: name} do
207 url = "http://127.0.0.1"
208 :ok = Connections.open_conn(url, name)
209 conn = Connections.checkin(url, name)
210 send(name, {:gun_down, conn, nil, nil, nil})
211 send(name, {:gun_up, conn, nil})
215 "http:127.0.0.1:80" => %Conn{
218 used_by: [{^self, _}],
222 } = Connections.get_state(name)
225 test "up and down ipv6", %{name: name} do
227 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
228 :ok = Connections.open_conn(url, name)
229 conn = Connections.checkin(url, name)
230 send(name, {:gun_down, conn, nil, nil, nil})
231 send(name, {:gun_up, conn, nil})
235 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
238 used_by: [{^self, _}],
242 } = Connections.get_state(name)
245 test "reuses connection based on protocol", %{name: name} do
246 http_url = "http://some-domain.com"
247 http_key = "http:some-domain.com:80"
248 https_url = "https://some-domain.com"
249 https_key = "https:some-domain.com:443"
251 refute Connections.checkin(http_url, name)
252 :ok = Connections.open_conn(http_url, name)
253 conn = Connections.checkin(http_url, name)
255 assert Process.alive?(conn)
257 refute Connections.checkin(https_url, name)
258 :ok = Connections.open_conn(https_url, name)
259 https_conn = Connections.checkin(https_url, name)
261 refute conn == https_conn
263 reused_https = Connections.checkin(https_url, name)
265 refute conn == reused_https
267 assert reused_https == https_conn
280 } = Connections.get_state(name)
283 test "connection can't get up", %{name: name} do
284 url = "http://gun-not-up.com"
286 assert capture_log(fn ->
287 :ok = Connections.open_conn(url, name)
288 refute Connections.checkin(url, name)
290 "Received error on opening connection http://gun-not-up.com: {:error, :timeout}"
293 test "process gun_down message and then gun_up", %{name: name} do
295 url = "http://gun-down-and-up.com"
296 key = "http:gun-down-and-up.com:80"
297 :ok = Connections.open_conn(url, name)
298 conn = Connections.checkin(url, name)
301 assert Process.alive?(conn)
308 used_by: [{^self, _}]
311 } = Connections.get_state(name)
313 send(name, {:gun_down, conn, :http, nil, nil})
320 used_by: [{^self, _}]
323 } = Connections.get_state(name)
325 send(name, {:gun_up, conn, :http})
327 conn2 = Connections.checkin(url, name)
331 assert Process.alive?(conn2)
338 used_by: [{^self, _}, {^self, _}]
341 } = Connections.get_state(name)
344 test "async processes get same conn for same domain", %{name: name} do
345 url = "http://some-domain.com"
346 :ok = Connections.open_conn(url, name)
351 Connections.checkin(url, name)
355 tasks_with_results = Task.yield_many(tasks)
358 Enum.map(tasks_with_results, fn {task, res} ->
359 res || Task.shutdown(task, :brutal_kill)
362 conns = for {:ok, value} <- results, do: value
366 "http:some-domain.com:80" => %Conn{
371 } = Connections.get_state(name)
373 assert Enum.all?(conns, fn res -> res == conn end)
376 test "remove frequently used and idle", %{name: name} do
378 http_url = "http://some-domain.com"
379 https_url = "https://some-domain.com"
380 :ok = Connections.open_conn(https_url, name)
381 :ok = Connections.open_conn(http_url, name)
383 conn1 = Connections.checkin(https_url, name)
387 Connections.checkin(http_url, name)
390 http_key = "http:some-domain.com:80"
398 used_by: [{^self, _}, {^self, _}, {^self, _}, {^self, _}]
400 "https:some-domain.com:443" => %Conn{
404 used_by: [{^self, _}]
407 } = Connections.get_state(name)
409 :ok = Connections.checkout(conn1, self, name)
411 another_url = "http://another-domain.com"
412 :ok = Connections.open_conn(another_url, name)
413 conn = Connections.checkin(another_url, name)
417 "http:another-domain.com:80" => %Conn{
426 } = Connections.get_state(name)
429 describe "integration test" do
430 @describetag :integration
432 clear_config([API]) do
433 Pleroma.Config.put([API], Pleroma.Gun)
436 test "opens connection and reuse it on next request", %{name: name} do
437 url = "http://httpbin.org"
438 :ok = Connections.open_conn(url, name)
440 conn = Connections.checkin(url, name)
443 assert Process.alive?(conn)
445 reused_conn = Connections.checkin(url, name)
447 assert conn == reused_conn
451 "http:httpbin.org:80" => %Conn{
456 } = Connections.get_state(name)
459 test "opens ssl connection and reuse it on next request", %{name: name} do
460 url = "https://httpbin.org"
461 :ok = Connections.open_conn(url, name)
463 conn = Connections.checkin(url, name)
466 assert Process.alive?(conn)
468 reused_conn = Connections.checkin(url, name)
470 assert conn == reused_conn
474 "https:httpbin.org:443" => %Conn{
479 } = Connections.get_state(name)
482 test "remove frequently used and idle", %{name: name} do
484 https1 = "https://www.google.com"
485 https2 = "https://httpbin.org"
487 :ok = Connections.open_conn(https1, name)
488 :ok = Connections.open_conn(https2, name)
490 conn = Connections.checkin(https1, name)
493 Connections.checkin(https2, name)
498 "https:httpbin.org:443" => %Conn{
502 "https:www.google.com:443" => %Conn{
507 } = Connections.get_state(name)
509 :ok = Connections.checkout(conn, self, name)
510 http = "http://httpbin.org"
512 :ok = Connections.open_conn(http, name)
513 conn = Connections.checkin(http, name)
517 "http:httpbin.org:80" => %Conn{
521 "https:httpbin.org:443" => %Conn{
526 } = Connections.get_state(name)
529 test "remove earlier used and idle", %{name: name} do
532 https1 = "https://www.google.com"
533 https2 = "https://httpbin.org"
534 :ok = Connections.open_conn(https1, name)
535 :ok = Connections.open_conn(https2, name)
538 Connections.checkin(https1, name)
539 conn = Connections.checkin(https1, name)
542 Connections.checkin(https2, name)
543 Connections.checkin(https2, name)
547 "https:httpbin.org:443" => %Conn{
551 "https:www.google.com:443" => %Conn{
556 } = Connections.get_state(name)
558 :ok = Connections.checkout(conn, self, name)
559 :ok = Connections.checkout(conn, self, name)
561 http = "http://httpbin.org"
562 :ok = Connections.open_conn(http, name)
565 conn = Connections.checkin(http, name)
569 "http:httpbin.org:80" => %Conn{
573 "https:httpbin.org:443" => %Conn{
578 } = Connections.get_state(name)
581 test "doesn't open new conn on pool overflow", %{name: name} do
584 https1 = "https://www.google.com"
585 https2 = "https://httpbin.org"
586 :ok = Connections.open_conn(https1, name)
587 :ok = Connections.open_conn(https2, name)
589 Connections.checkin(https1, name)
590 conn1 = Connections.checkin(https1, name)
591 conn2 = Connections.checkin(https2, name)
595 "https:httpbin.org:443" => %Conn{
599 used_by: [{^self, _}]
601 "https:www.google.com:443" => %Conn{
605 used_by: [{^self, _}, {^self, _}]
608 } = Connections.get_state(name)
610 refute Connections.checkin("http://httpbin.org", name)
614 "https:httpbin.org:443" => %Conn{
618 used_by: [{^self, _}]
620 "https:www.google.com:443" => %Conn{
624 used_by: [{^self, _}, {^self, _}]
627 } = Connections.get_state(name)
630 test "get idle connection with the smallest crf", %{
635 https1 = "https://www.google.com"
636 https2 = "https://httpbin.org"
638 :ok = Connections.open_conn(https1, name)
639 :ok = Connections.open_conn(https2, name)
641 Connections.checkin(https1, name)
642 Connections.checkin(https2, name)
643 Connections.checkin(https1, name)
644 conn1 = Connections.checkin(https1, name)
645 conn2 = Connections.checkin(https2, name)
649 "https:httpbin.org:443" => %Conn{
653 used_by: [{^self, _}, {^self, _}],
656 "https:www.google.com:443" => %Conn{
660 used_by: [{^self, _}, {^self, _}, {^self, _}],
664 } = Connections.get_state(name)
668 :ok = Connections.checkout(conn1, self, name)
669 :ok = Connections.checkout(conn1, self, name)
670 :ok = Connections.checkout(conn1, self, name)
672 :ok = Connections.checkout(conn2, self, name)
673 :ok = Connections.checkout(conn2, self, name)
677 "https:httpbin.org:443" => %Conn{
683 "https:www.google.com:443" => %Conn{
690 } = Connections.get_state(name)
692 http = "http://httpbin.org"
693 :ok = Connections.open_conn(http, name)
695 conn = Connections.checkin(http, name)
699 "https:www.google.com:443" => %Conn{
706 "http:httpbin.org:80" => %Conn{
710 used_by: [{^self, _}],
714 } = Connections.get_state(name)
720 describe "with proxy" do
721 test "as ip", %{name: name} do
722 url = "http://proxy-string.com"
723 key = "http:proxy-string.com:80"
724 :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
726 conn = Connections.checkin(url, name)
735 } = Connections.get_state(name)
737 reused_conn = Connections.checkin(url, name)
739 assert reused_conn == conn
742 test "as host", %{name: name} do
743 url = "http://proxy-tuple-atom.com"
744 :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
745 conn = Connections.checkin(url, name)
749 "http:proxy-tuple-atom.com:80" => %Conn{
754 } = Connections.get_state(name)
756 reused_conn = Connections.checkin(url, name)
758 assert reused_conn == conn
761 test "as ip and ssl", %{name: name} do
762 url = "https://proxy-string.com"
764 :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
765 conn = Connections.checkin(url, name)
769 "https:proxy-string.com:443" => %Conn{
774 } = Connections.get_state(name)
776 reused_conn = Connections.checkin(url, name)
778 assert reused_conn == conn
781 test "as host and ssl", %{name: name} do
782 url = "https://proxy-tuple-atom.com"
783 :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
784 conn = Connections.checkin(url, name)
788 "https:proxy-tuple-atom.com:443" => %Conn{
793 } = Connections.get_state(name)
795 reused_conn = Connections.checkin(url, name)
797 assert reused_conn == conn
800 test "with socks type", %{name: name} do
801 url = "http://proxy-socks.com"
803 :ok = Connections.open_conn(url, name, proxy: {:socks5, 'localhost', 1234})
805 conn = Connections.checkin(url, name)
809 "http:proxy-socks.com:80" => %Conn{
814 } = Connections.get_state(name)
816 reused_conn = Connections.checkin(url, name)
818 assert reused_conn == conn
821 test "with socks4 type and ssl", %{name: name} do
822 url = "https://proxy-socks.com"
824 :ok = Connections.open_conn(url, name, proxy: {:socks4, 'localhost', 1234})
826 conn = Connections.checkin(url, name)
830 "https:proxy-socks.com:443" => %Conn{
835 } = Connections.get_state(name)
837 reused_conn = Connections.checkin(url, name)
839 assert reused_conn == conn
845 crf = Connections.crf(1, 10, 1)
849 test "more used will have crf higher", %{crf: crf} do
851 crf1 = Connections.crf(1, 10, crf)
852 crf1 = Connections.crf(1, 10, crf1)
855 crf2 = Connections.crf(1, 10, crf)
860 test "recently used will have crf higher on equal references", %{crf: crf} do
862 crf1 = Connections.crf(3, 10, crf)
865 crf2 = Connections.crf(4, 10, crf)
870 test "equal crf on equal reference and time", %{crf: crf} do
872 crf1 = Connections.crf(1, 10, crf)
875 crf2 = Connections.crf(1, 10, crf)
880 test "recently used will have higher crf", %{crf: crf} do
881 crf1 = Connections.crf(2, 10, crf)
882 crf1 = Connections.crf(1, 10, crf1)
884 crf2 = Connections.crf(3, 10, crf)
885 crf2 = Connections.crf(4, 10, crf2)
890 describe "get_unused_conns/1" do
891 test "crf is equalent, sorting by reference" do
895 last_reference: now() - 1
899 last_reference: now()
903 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
906 test "reference is equalent, sorting by crf" do
918 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
921 test "higher crf and lower reference" do
926 last_reference: now() - 1
931 last_reference: now()
935 assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(conns)
938 test "lower crf and lower reference" do
943 last_reference: now() - 1
948 last_reference: now()
952 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
957 :os.system_time(:second)