From 42c100605e13eabce36e02617b53e8b6a01e8337 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 26 Jul 2026 10:15:52 +0400 Subject: [PATCH] fix(deps): pin Hackney to the connection-leak fix An unpooled Hackney 4 connection that ends without a reusable socket -- a body cut short by the peer, a bodyless 204/304, or a failed read -- parks in its `closed` state forever, holding every refc binary it read. read_full_body/2 hands back `socket = undefined`, so it never reaches the reuse check; `closed(enter)` arms a stop timer only for pooled connections; and an unpooled connection is owned by hackney_conn_sup, so the owner-DOWN cleanup never fires. Nothing here can work around it. Since 4.0 Hackney ignores `with_body` and always returns a fully read binary, so a synchronous request never yields a handle to close, and the short read still reports {:ok, body}. Media proxy warming hits this whenever an attachment fetch is cut short and the outbound proxy is used as a CONNECT tunnel, since tunnels are never pooled. Pin the fix, a single commit on top of the 4.7.2 tag, until it ships in a release. While it is a git dep Hackney drops out of `mix deps.audit` and `mix hex.outdated`, so this wants moving back to hex promptly. benoitc/hackney#918, benoitc/hackney#919 --- changelog.d/hackney-conn-leak.fix | 1 + mix.exs | 18 +++++++++++++++++- mix.lock | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 changelog.d/hackney-conn-leak.fix diff --git a/changelog.d/hackney-conn-leak.fix b/changelog.d/hackney-conn-leak.fix new file mode 100644 index 000000000..508449eca --- /dev/null +++ b/changelog.d/hackney-conn-leak.fix @@ -0,0 +1 @@ +Stop leaking a Hackney connection process, and the response bytes it read, for every unpooled request that ends without a reusable socket diff --git a/mix.exs b/mix.exs index dfae5c7bc..6d8517636 100644 --- a/mix.exs +++ b/mix.exs @@ -218,7 +218,23 @@ defmodule Pleroma.Mixfile do {:covertool, "~> 2.0", only: :test}, # Hackney 4.7 requires h2 0.11 while WebTransport still declares 0.10.x. {:h2, "~> 0.11.0", override: true}, - {:hackney, "~> 4.7.2", override: true}, + # 4.7.2 plus https://github.com/benoitc/hackney/pull/919, which is a single + # commit on top of the 4.7.2 tag (`git diff 4.7.2..5042f6a` to review it). + # An unpooled connection that ends without a reusable socket -- a body cut + # short by the peer, a bodyless 204/304, or a failed read -- parks forever + # holding what it read, and nothing downstream can release it. Media proxy + # warming hits that whenever an attachment fetch is cut short and the + # outbound proxy is used as a CONNECT tunnel, since tunnels are never + # pooled. See test/pleroma/http/hackney_conn_leak_test.exs and + # https://github.com/benoitc/hackney/issues/918. + # + # Being a git dep, hackney is invisible to `mix deps.audit` and + # `mix hex.outdated` until this moves back to a hex release, which should + # happen as soon as the fix ships in one. + {:hackney, + git: "https://github.com/lambadalambda/hackney.git", + ref: "5042f6a09df526a46de2c8945634f95605d2fc76", + override: true}, {:mox, "~> 1.2", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:benchee, "~> 1.4", only: :benchmark}, diff --git a/mix.lock b/mix.lock index c43183bcb..42af72293 100644 --- a/mix.lock +++ b/mix.lock @@ -59,7 +59,7 @@ "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "gun": {:hex, :gun, "2.4.1", "c3a8bff8e155e1fc8e499216599bb7effbd27bc1985662a8b25016090dc18428", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "1450575b4d393aa0811322727b90ad1dd94b5c10026f54a1641785bcbd250384"}, "h2": {:hex, :h2, "0.11.0", "b8c9f7c86f400582c5cbed59f79c0efd6cf49ad6478c210bede91d2d15019b14", [:rebar3], [], "hexpm", "c61f24361d0dc76375eaf35c2bf0e01235d81b7685538df571db036467fac358"}, - "hackney": {:hex, :hackney, "4.7.2", "8f712c94d90220cc21ea8049172c2e760ffc41737060667603aa1aef52ab9a4d", [:rebar3], [{:certifi, "~> 2.17.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:h2, "~> 0.11.0", [hex: :h2, repo: "hexpm", optional: false]}, {:idna, "~> 7.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.2", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:webtransport, "~> 0.4.3", [hex: :webtransport, repo: "hexpm", optional: false]}], "hexpm", "fad03e527a6b1811ef3362ebe9d6d826d988927c18afc71358c35bf2098110e4"}, + "hackney": {:git, "https://github.com/lambadalambda/hackney.git", "5042f6a09df526a46de2c8945634f95605d2fc76", [ref: "5042f6a09df526a46de2c8945634f95605d2fc76"]}, "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:hex, :http_signatures, "0.1.3", "19f26aee35b4684e37efdce3ac4638605e6e41a04368186bd39d2b6138a60ea9", [:mix], [{:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20313a65516db88006f85b090f6f76cc5b04e9609b45943657e6781eb91174f4"},