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)
@ -1222,16 +1212,28 @@ class HTTPNativeStorageServer(service.MultiService):
# store the Deferred so it gets cancelled. # store the Deferred so it gets cancelled.
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: try:
nurl = await picking nurl = await picking
finally: finally:
self._connecting_deferred = None self._connecting_deferred = None
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)
)
if nurl is None: # Update the connection status:
# We failed to find a server to connect to. Perhaps the next self._failed_to_connect(Failure(e))
# iteration of the loop will succeed.
# Since we failed to find a server to connect to, give up
# for now. Perhaps the next iteration of the loop will
# succeed.
return return
else:
# iF we've gotten this far, we've found a working NURL.
self._istorage_server = _HTTPStorageServer.from_http_client( self._istorage_server = _HTTPStorageServer.from_http_client(
StorageClient.from_nurl(nurl, reactor) StorageClient.from_nurl(nurl, reactor)
) )

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})