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
Hackney 4 ignores `with_body` and always returns a fully read binary, so
Tesla.Adapter.Hackney never reaches the clause that honours :max_body and
the option became a no-op. Only the gun path still enforced it, through
Pleroma.HTTP.Adapter.Gun. Pleroma.Web.RelMe relies on nothing else, so it
was fetching remote pages unbounded.
Add a middleware that applies the limit for every adapter: reject when
content-length already announces too much, and reject a body that came
back over the limit. Streaming bodies are left alone -- neither adapter
limits those and the caller owns the lease, so failing here on the header
would strand a connection RichMedia is still expected to release.
On the hackney path this bounds what callers are handed rather than peak
memory, since the adapter has read the body before any middleware runs.
Conflict resolution notes:
- mix.lock: kept this branch's versions for cowlib, h2, hackney and hpax.
Those upgrades are the point of the branch; develop is still on hackney
1.20.1.
- .woodpecker/docker-armv7.yaml: accepted develop's deletion. It only
existed here to build mdex_native on armv7, and mdex was already
replaced with earmark again.
- lib/pleroma/reverse_proxy.ex: took develop's version. It independently
grew request_with_constraints/5 and the invalid-status close, which
supersede the equivalent fixes on this branch. Re-applied this branch's
recv_timeout seeding, which develop does not have.
- lib/pleroma/gun/connection_pool/worker.ex and
lib/pleroma/reverse_proxy/client/tesla.ex: took develop's versions. The
gun pool rework (#7934) already guards the demonitor and makes
release_conn/cancel_stream exit-safe, and it splits the Tesla client's
graceful release from its abort path, so this branch's changes there are
obsolete.
That leaves the client leak fix as a Hackney-only concern, which
stream_body/1 now handles the same way the gun client does.
Three paths obtained a client ref and abandoned it without closing:
the content-length rejection in call/3, the invalid-status clause in
request/4, and the success branch of response/6.
Under Hackney 4 an abandoned ref is a permanent process leak. A conn
that finishes or errors without an explicit stop parks in `closed` or
`connected` and never terminates: closed/3 only arms its stop timer for
pooled conns, `connected` gets no state_timeout when idle_timeout is
infinity, and a non-pooled conn monitors hackney_conn_sup rather than
its caller, so the owner-DOWN cleanup never fires. Each parked process
pins every refc binary it read, which for proxied video is tens of MB.
Releasing on every outcome means the gun client must stop releasing in
stream_body/1, or the ref would be removed twice.
Hackney does reclaim a pooled connection itself for a response that
cannot carry a body (204, 304, HEAD), so those refs are marked and left
alone; stopping one would evict a connection another request may
already hold. The mark is gated on the request actually being pooled,
since a direct or proxy-tunnelled conn is never checked in and leaks
unless we stop it.
My instance has been having problems with push federation since
updating to v2.10.2, using the docker image. Posts are not reaching
their destination instance until some other interaction initiates a
fetch from source.
The logs contain lots of errors such as:
```
pleroma | 09:34:40.885 [notice] TLS :client: In state :wait_cert_cr at ssl_handshake.erl:2158 generated CLIENT ALERT: Fatal - Unsupported Certificate
pleroma | - {:key_usage_mismatch,
pleroma | {{:Extension, {2, 5, 29, 15}, true, [:keyCertSign, :cRLSign]},
pleroma | {:Extension, {2, 5, 29, 37}, false, [{1, 3, 6, 1, 5, 5, 7, 3, 1}]}}}
```
This erlang/OTP issue seems to be the same problem:
https://github.com/erlang/otp/issues/9208
The erlang version committed here was selected after discussion in the
merge request.
I have not noticed any federation problems with an image built from
this dockerfile.
The core feed reader issue was that RSS 2.0 <link> is a simple text
element and can only appear once per <item>.
By having multiple <link> tags with rel attributes, parsers were
getting confused and resolving entries to the wrong URL.
Now each item has exactly one <link>: the post URL. Everything else
uses valid <atom:link> or dedicated extension elements where
appropriate.