Update the connection status during the initial choice of NURLs.

This commit is contained in:
Itamar Turner-Trauring 2023-03-24 10:10:18 -04:00
parent 559e2ecdab
commit 0da059b644
2 changed files with 41 additions and 32 deletions

View File

@ -1024,7 +1024,7 @@ async def _pick_a_http_server(
reactor, reactor,
nurls: list[DecodedURL], nurls: list[DecodedURL],
request: Callable[[Any, DecodedURL], defer.Deferred[Any]] request: Callable[[Any, DecodedURL], defer.Deferred[Any]]
) -> Optional[DecodedURL]: ) -> DecodedURL:
"""Pick the first server we successfully send a request to. """Pick the first server we successfully send a request to.
Fires with ``None`` if no server was found, or with the ``DecodedURL`` of Fires with ``None`` if no server was found, or with the ``DecodedURL`` of
@ -1035,18 +1035,8 @@ async def _pick_a_http_server(
for nurl in nurls for nurl in nurls
]) ])
try: _, nurl = await queries
_, nurl = await queries return nurl
return nurl
except Exception as e:
# Logging errors breaks a bunch of tests, and it's not a _bug_ to
# have a failed connection, it's often expected and transient. More
# of a warning, really?
log.msg(
"Failed to connect to a storage server advertised by NURL: {}".format(
e)
)
return None
@implementer(IServer) @implementer(IServer)
@ -1223,19 +1213,31 @@ class HTTPNativeStorageServer(service.MultiService):
picking = _pick_a_http_server(reactor, self._nurls, request) picking = _pick_a_http_server(reactor, self._nurls, request)
self._connecting_deferred = picking self._connecting_deferred = picking
try: try:
nurl = await picking try:
finally: nurl = await picking
self._connecting_deferred = None finally:
self._connecting_deferred = None
if nurl is None: except Exception as e:
# We failed to find a server to connect to. Perhaps the next # Logging errors breaks a bunch of tests, and it's not a _bug_ to
# iteration of the loop will succeed. # have a failed connection, it's often expected and transient. More
return # of a warning, really?
else: log.msg(
self._istorage_server = _HTTPStorageServer.from_http_client( "Failed to connect to a storage server advertised by NURL: {}".format(e)
StorageClient.from_nurl(nurl, reactor)
) )
# Update the connection status:
self._failed_to_connect(Failure(e))
# Since we failed to find a server to connect to, give up
# for now. Perhaps the next iteration of the loop will
# succeed.
return
# iF we've gotten this far, we've found a working NURL.
self._istorage_server = _HTTPStorageServer.from_http_client(
StorageClient.from_nurl(nurl, reactor)
)
result = self._istorage_server.get_version() result = self._istorage_server.get_version()
def remove_connecting_deferred(result): def remove_connecting_deferred(result):

View File

@ -63,6 +63,8 @@ from foolscap.ipb import (
IConnectionHintHandler, IConnectionHintHandler,
) )
from allmydata.util.deferredutil import MultiFailure
from .no_network import LocalWrapper from .no_network import LocalWrapper
from .common import ( from .common import (
EMPTY_CLIENT_CONFIG, EMPTY_CLIENT_CONFIG,
@ -782,7 +784,7 @@ storage:
class PickHTTPServerTests(unittest.SynchronousTestCase): class PickHTTPServerTests(unittest.SynchronousTestCase):
"""Tests for ``_pick_a_http_server``.""" """Tests for ``_pick_a_http_server``."""
def pick_result(self, url_to_results: dict[DecodedURL, tuple[float, Union[Exception, Any]]]) -> Optional[DecodedURL]: def pick_result(self, url_to_results: dict[DecodedURL, tuple[float, Union[Exception, Any]]]) -> Deferred[DecodedURL]:
""" """
Given mapping of URLs to (delay, result), return the URL of the Given mapping of URLs to (delay, result), return the URL of the
first selected server, or None. first selected server, or None.
@ -803,7 +805,7 @@ class PickHTTPServerTests(unittest.SynchronousTestCase):
d = _pick_a_http_server(clock, list(url_to_results.keys()), request) d = _pick_a_http_server(clock, list(url_to_results.keys()), request)
for i in range(100): for i in range(100):
clock.advance(0.1) clock.advance(0.1)
return self.successResultOf(d) return d
def test_first_successful_connect_is_picked(self): def test_first_successful_connect_is_picked(self):
""" """
@ -817,16 +819,21 @@ class PickHTTPServerTests(unittest.SynchronousTestCase):
earliest_url: (1, None), earliest_url: (1, None),
bad_url: (0.5, RuntimeError()), bad_url: (0.5, RuntimeError()),
}) })
self.assertEqual(result, earliest_url) self.assertEqual(self.successResultOf(result), earliest_url)
def test_failures_are_turned_into_none(self): def test_failures_include_all_reasons(self):
""" """
If the requests all fail, ``_pick_a_http_server`` returns ``None``. If all the requests fail, ``_pick_a_http_server`` raises a
``allmydata.util.deferredutil.MultiFailure``.
""" """
eventually_good_url = DecodedURL.from_text("http://good") eventually_good_url = DecodedURL.from_text("http://good")
bad_url = DecodedURL.from_text("http://bad") bad_url = DecodedURL.from_text("http://bad")
exception1 = RuntimeError()
exception2 = ZeroDivisionError()
result = self.pick_result({ result = self.pick_result({
eventually_good_url: (1, ZeroDivisionError()), eventually_good_url: (1, exception1),
bad_url: (0.1, RuntimeError()) bad_url: (0.1, exception2),
}) })
self.assertEqual(result, None) exc = self.failureResultOf(result).value
self.assertIsInstance(exc, MultiFailure)
self.assertEqual({f.value for f in exc.failures}, {exception2, exception1})