From 414509801681fd7b8d18e9c5cb3d66bcec0f0c97 Mon Sep 17 00:00:00 2001 From: Istvan Soos Date: Wed, 7 May 2025 12:03:19 +0200 Subject: [PATCH] HTTP Client that can be renewed / reset after a retry. --- app/lib/search/search_client.dart | 2 +- pkg/_pub_shared/lib/utils/http.dart | 65 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/app/lib/search/search_client.dart b/app/lib/search/search_client.dart index a87fa37bbd..60d95ed166 100644 --- a/app/lib/search/search_client.dart +++ b/app/lib/search/search_client.dart @@ -32,7 +32,7 @@ SearchClient get searchClient => ss.lookup(#_searchClient) as SearchClient; /// indexed data. class SearchClient { /// The HTTP client used for making calls to our search service. - final _httpClient = httpRetryClient(); + final _httpClient = httpRenewableClient(); /// Before this timestamp we may use the fallback search service URL, which /// is the unversioned service URL, potentially getting responses from an diff --git a/pkg/_pub_shared/lib/utils/http.dart b/pkg/_pub_shared/lib/utils/http.dart index 81ecf0efa6..b43bb4f8e9 100644 --- a/pkg/_pub_shared/lib/utils/http.dart +++ b/pkg/_pub_shared/lib/utils/http.dart @@ -83,6 +83,11 @@ Future httpGetWithRetry( }, maxAttempts: maxAttempts, retryIf: (e) => _retryIf(e) || (retryIf != null && retryIf(e)), + onRetry: (_) { + if (client is _RenewableClient) { + client.renew(); + } + }, ); } @@ -113,3 +118,63 @@ class UnexpectedStatusException implements Exception { @override String toString() => 'UnexpectedStatusException: $message'; } + +/// Creates a client that will be renewed when a HTTP retry happens. +http.Client httpRenewableClient() => _RenewableClient(); + +class _RenewableClient extends http.BaseClient { + var _client = _Client(http.Client()); + + @override + Future send(http.BaseRequest request) async { + return await _client.send(request); + } + + void renew() { + final c = _client; + _client = _Client(http.Client()); + c.close(); + } + + @override + void close() { + _client.close(); + } +} + +class _Client extends http.BaseClient { + final http.Client _client; + final _pending = []; + var _closing = false; + + _Client(this._client); + + @override + Future send(http.BaseRequest request) async { + if (_closing) { + throw StateError('HTTP client is closed.'); + } + final f = _client.send(request); + _pending.add(f); + try { + return await f; + } finally { + _pending.remove(f); + if (_closing && _pending.isEmpty) { + _client.close(); + } + } + } + + @override + void close() { + _closing = true; + if (_pending.isEmpty) { + _client.close(); + return; + } + unawaited(Future.delayed(Duration(minutes: 1), () { + _client.close(); + })); + } +}