Repository navigation
Description
Package
http2_adapter
Version
2.9.0
Operating-System
Android, iOS, MacOS, Linux, Windows
Adapter
Http2Adapter
Output of flutter doctor -v
Dart Version
No response
Steps to Reproduce
- Configure a
Dioinstance withHttp2Adapter(ConnectionManager())against a TLS endpoint that negotiatesh2. - Download a resource ≥ 1 MiB (e.g. 10 MiB) from that endpoint.
- Compare wall-clock throughput against the same endpoint/payload served over HTTP/1.1 with the default
IOHttpClientAdapter.
Minimal repro sketch:
final dio = Dio()..httpClientAdapter = Http2Adapter(ConnectionManager());
final sw = Stopwatch()..start();
await dio.get<List<int>>(
'https://example.com/10mb-payload',
options: Options(responseType: ResponseType.bytes),
);
sw.stop();
print('h2 10MiB download: ${sw.elapsedMilliseconds} ms');Run the same request through IOHttpClientAdapter() (HTTP/1.1) against the identical route/payload and compare.
Expected Result
Large-body downloads over HTTP/2 should perform comparably to (or reasonably close to) HTTP/1.1 on the same connection quality, since HTTP/2 is expected to be at least as capable for single large transfers.
Actual Result
In a controlled same-route, same-payload, ALPN-only-differing benchmark (Dart VM tier, 3 independent runs, results in research-dio-adapters/bench/results-summary.md):
| Scenario | HTTP/1.1 (IOHttpClientAdapter) |
HTTP/2 (Http2Adapter) |
Ratio |
|---|---|---|---|
| 32-concurrent small requests | 5818.55 req/s | 9772.78 req/s | h2 +68% (expected win) |
| 1 MiB download | 207.91 MiB/s | 95.06 MiB/s | h1 2.2× faster |
| 10 MiB download | 231.43 MiB/s | 10.81 MiB/s | h1 21.4× faster |
| 1 MiB upload | 212.32 MiB/s | 165.55 MiB/s | h1 +28% |
Root cause: _ConnectionManager._createSocket calls ClientTransportConnection.viaSocket(socket) without a third settings argument (plugins/http2_adapter/lib/src/connection_manager_imp.dart), so every connection uses package:http2's default ClientSettings() — a 65535-byte initial per-stream flow-control window. A 10 MiB body needs roughly 160 round trips of WINDOW_UPDATE before the peer is allowed to send more data; that RTT-bound ping-pong is the entire gap. Confirmed by control experiment: driving the same server with a raw package:http2 client that advertises a 32 MiB window brings the same 10 MiB transfer down to ~91 ms.
Neither ConnectionManager's public factory (plugins/http2_adapter/lib/src/connection_manager.dart) nor ClientSetting (plugins/http2_adapter/lib/src/client_setting.dart) exposes any way to override the flow-control window or concurrentStreamLimit. There is no user-facing workaround short of forking the package.
Solution Brainstorm (proposed fix)
Add an optional settings override to ConnectionManager's public factory and thread it through to viaSocket:
factory ConnectionManager({
Duration idleTimeout = const Duration(seconds: 15),
Duration? handshakeTimeout,
void Function(Uri uri, ClientSetting)? onClientCreate,
ProxyConnectedPredicate proxyConnectedPredicate = defaultProxyConnectedPredicate,
List<String> supportedProtocols = const ['h2'],
// NEW:
int? streamWindowSize,
int? concurrentStreamLimit,
}) => _ConnectionManager(
...,
clientSettings: ClientSettings(
streamWindowSize: streamWindowSize,
concurrentStreamLimit: concurrentStreamLimit,
),
);And in _createSocket/the connect path:
final transport = ClientTransportConnection.viaSocket(socket, settings: _clientSettings);Default behavior (no window override) stays byte-for-byte identical, so this is non-breaking. Suggested doc note on the new parameters: "Increase streamWindowSize for workloads that mix large downloads with the same Http2Adapter instance; the default 65535-byte window can make large transfers dramatically slower than HTTP/1.1 due to flow-control round trips."
Additional context
Two related, longer-standing upstream issues in package:http2 describe the same window-update cost from a different angle and would benefit from this fix landing on the dio side regardless of upstream timeline:
dart-lang/http#1365"http2 seems to be slow" (2020)dart-lang/http#1384"Performance checks and enhancements" (2024)
Full benchmark methodology, harness source, and three independent raw result files are available on request (Dart VM tier: 5 candidates × 3 rounds, all failures fields empty; Android real-device tier corroborates the qualitative direction but with different absolute numbers due to adb reverse latency skew — see accompanying research write-up if useful for the PR description).
Activity