fix(streaming): exclude HORSE_PROVIDER_NGHTTP2 from the default stream-writer registration - #552
Open
freitasjca wants to merge 1 commit into
Open
Conversation
…m-writer registration FStreamWriterFactory is a last-writer-wins class var, and both Horse core and a provider that supplies its own writer register into it from unit initialization sections. Which one survives is therefore decided by the compiler's dependency walk, not by anything in either source file. Three providers are already excluded from the default registration for exactly that reason. HORSE_PROVIDER_NGHTTP2 was missing. The consequence is not cosmetic. On FPC trunk 3.3.1 the provider's factory happens to initialize last and streaming works; on FPC 3.2.2 the order differs, the WebBroker default wins, and it cannot write to an HTTP/2 stream — so every Res.SendStream request returns complete silence: no headers, no body, no error, just a client timeout. Nothing else is affected, which is what made it hard to find. That also means streaming on trunk has been passing by accident of initialization order rather than by design, and could have flipped at any time. Verified: FPC 3.2.2 goes from curl exit=28 with zero bytes to five NDJSON records and exit=0; the full suite passes 24 stages with one explicit skip. FPC trunk 3.3.1 stays at 27/27, confirming this is a no-op where the ordering already favoured the provider.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Horse.Response.pasregisters its default WebBroker stream writer from a unitinitialization section, guarded to exclude providers that supply their own.
HORSE_PROVIDER_NGHTTP2is missing from that guard.Because
FStreamWriterFactoryis a last-writer-wins class var, and both Horsecore and the provider register into it from initialization sections, the
compiler's dependency walk decides which one survives — nothing in either
source file does.
src/Horse.Response.pas:1361(atmaster,ff41415):Three providers are already excluded for exactly this reason. Adding the fourth
restores the evident intent.
Why it matters more than a missing define
On FPC trunk 3.3.1 the provider's factory happens to initialize last, so it
wins and streaming works. On FPC 3.2.2 the order differs, the WebBroker
default wins, and it cannot write to an HTTP/2 stream — so every streaming
request returns complete silence: no headers, no body, no error. The client
simply waits until it times out.
That means streaming on trunk has been passing by accident of initialization
order, not by design. A compiler upgrade, a new unit, or a reordered
usesclause could flip it at any time, in either direction, silently.
Nothing else is affected — which is what made it hard to find. Ordinary
requests, TLS, mTLS, graceful shutdown and WebSocket all work normally; only
Res.SendStreamis silently redirected to a writer that cannot serve it.Reproduction
Build an nghttp2-provider server on FPC 3.2.2 with a
Res.SendStreamroute:exit=28is curl's timeout, with zero bytes received. Verbose output showsthe request sent and no response line at all — not even
< HTTP/2 200, whichdistinguishes this from a stalled body.
With the one-line fix:
Full suite on FPC 3.2.2 after the fix: 24 stages pass, 0 fail, 1 explicit skip
(gRPC, which needs trunk for
TCustomAttribute). On FPC trunk 3.3.1: 27/27unchanged, confirming the fix is a no-op where the ordering already favoured the
provider.
The change
One condition, plus a comment recording why the list exists — the failure mode
is invisible enough that the next person adding a provider will want to know.
No behavioural change on any build where the ordering already favoured the
provider, and none at all on Delphi or on builds that use the WebBroker writer.
A suggestion, not part of this PR
This guard scales by requiring every new provider to remember to add itself,
and forgetting produces a silent failure rather than an error. Two sturdier
shapes, if you would like either as a follow-up:
RegisterStreamWriterFactoryrefuse to overwrite an already-registeredfactory, so the first specific registration wins and core's default only
applies when nothing else claimed it.
initialization, removing the ordering dependency entirely.Both are larger changes than this one, and this PR fixes the immediate bug
without prejudging either.
Context
Found while making
horse-provider-nghttp2build on the FPC that Horse's own CIinstalls (
apt-get install -y fpc→ 3.2.2). Companion to #549, #550 and #551.