fn infer_stream_capability(
policy_nodes: &[NodeConfig],
nodes: &HashMap<String, Box<dyn Plugin>>,
edges: &HashMap<String, HashMap<String, String>>,
) -> (HashSet<String>, Vec<BufferingReason>)Expand description
For each upstream node, walks only the success/outcome ports forward
to see whether anything before client reads the response body.
error ports are never walked, on the upstream itself or on any node
further along the chain: an error exit runs through the node’s own error
edge (unwalked here for the same reason), the policy’s error_handler
catch-all, or the engine’s built-in fallbacks — none of which this walk
can see.
This is safe only because of two separate facts, not one general guarantee:
- Every gateway-generated error body —
ErrorHandlerPlugin::execute, the engine’s no-handler 500 fallback, and theNODE_NOT_FOUNDfallback, all in this crate — explicitly clearsresponse.streambefore writingresponse.body, so none of those three specific sites can leave a stale stream alongside a generated body. - An error edge can in principle route to any node, including one that
writes
response.bodyitself without going through those three sites. Nothing here walks that edge to rule it out; it is safe today only because no plugin that opts out ofreads_response_body()(the set that can run downstream of a stream-capableupstreamwithout forcing it to buffer:client,opentelemetry,prometheus,proxy-rewrite,request-id,response-rewrite,skywalking,traffic-label,zipkin, andproxy-cachein itslookuprole only) ever returnsErrfromexecute— everyreturn Errin the first nine plugins’ source is infrom_config(construction-time validation), never inexecute;proxy-cache’slookuprole does call a fallible backend (ResponseCache::get) fromexecute, but matches on the result and degrades a failure to a miss rather than propagatingErr. This is exactly whyproxy-cache’sstoreandpurgeroles do not opt out despite neither reading the response body:storebecause it reads it to cache it, andpurgebecause it can returnErrfromexecuteon a failed backend, which this invariant forbids for an opt-out node. If a future change to any of these plugins starts erroring fromexecutewhere it previously didn’t, this walk would not catch it, and nothing else currently enforces it either.
Iterates policy_nodes (a Vec), not the nodes map, and visits each
node’s outgoing ports in sorted-name order: the same precedent as the
cycle check in compile_policy, so the reported blocker for an
unchanged policy is stable across compiles instead of depending on
HashMap iteration order.
An edge whose target doesn’t resolve to a real node in nodes is treated
as blocking rather than assumed harmless — to_nodes aren’t validated
against the node table the way from_nodes are, so this walk cannot
assume every edge target exists.