core: retry Session access-point connection (#1345)

Tries multiple access-points with a retry for each one, unless there was a login failure.
This commit is contained in:
Nick Steel 2024-09-23 19:44:51 +01:00 committed by GitHub
parent 67d31959f5
commit e02e4ae224
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 48 additions and 7 deletions

View file

@ -57,6 +57,7 @@ https://github.com/librespot-org/librespot
- [core] Report actual platform data on login - [core] Report actual platform data on login
- [core] Support `Session` authentication with a Spotify access token - [core] Support `Session` authentication with a Spotify access token
- [core] `Credentials.username` is now an `Option` (breaking) - [core] `Credentials.username` is now an `Option` (breaking)
- [core] `Session::connect` tries multiple access points, retrying each one.
- [main] `autoplay {on|off}` now acts as an override. If unspecified, `librespot` - [main] `autoplay {on|off}` now acts as an override. If unspecified, `librespot`
now follows the setting in the Connect client that controls it. (breaking) now follows the setting in the Connect client that controls it. (breaking)
- [metadata] Most metadata is now retrieved with the `spclient` (breaking) - [metadata] Most metadata is now retrieved with the `spclient` (breaking)

View file

@ -68,6 +68,29 @@ pub async fn connect(host: &str, port: u16, proxy: Option<&Url>) -> io::Result<T
handshake(socket).await handshake(socket).await
} }
pub async fn connect_with_retry(
host: &str,
port: u16,
proxy: Option<&Url>,
max_retries: u8,
) -> io::Result<Transport> {
let mut num_retries = 0;
loop {
match connect(host, port, proxy).await {
Ok(f) => return Ok(f),
Err(e) => {
debug!("Connection failed: {e}");
if num_retries < max_retries {
num_retries += 1;
debug!("Retry access point...");
continue;
}
return Err(e);
}
}
}
}
pub async fn authenticate( pub async fn authenticate(
transport: &mut Transport, transport: &mut Transport,
credentials: Credentials, credentials: Credentials,

View file

@ -144,13 +144,15 @@ impl Session {
async fn connect_inner( async fn connect_inner(
&self, &self,
access_point: SocketAddress, access_point: &SocketAddress,
credentials: Credentials, credentials: Credentials,
) -> Result<(Credentials, Transport), Error> { ) -> Result<(Credentials, Transport), Error> {
let mut transport = connection::connect( const MAX_RETRIES: u8 = 1;
let mut transport = connection::connect_with_retry(
&access_point.0, &access_point.0,
access_point.1, access_point.1,
self.config().proxy.as_ref(), self.config().proxy.as_ref(),
MAX_RETRIES,
) )
.await?; .await?;
let mut reusable_credentials = connection::authenticate( let mut reusable_credentials = connection::authenticate(
@ -165,10 +167,11 @@ impl Session {
trace!( trace!(
"Reconnect using stored credentials as token authed sessions cannot use keymaster." "Reconnect using stored credentials as token authed sessions cannot use keymaster."
); );
transport = connection::connect( transport = connection::connect_with_retry(
&access_point.0, &access_point.0,
access_point.1, access_point.1,
self.config().proxy.as_ref(), self.config().proxy.as_ref(),
MAX_RETRIES,
) )
.await?; .await?;
reusable_credentials = connection::authenticate( reusable_credentials = connection::authenticate(
@ -187,19 +190,32 @@ impl Session {
credentials: Credentials, credentials: Credentials,
store_credentials: bool, store_credentials: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
// There currently happen to be 6 APs but anything will do to avoid an infinite loop.
const MAX_AP_TRIES: u8 = 6;
let mut num_ap_tries = 0;
let (reusable_credentials, transport) = loop { let (reusable_credentials, transport) = loop {
let ap = self.apresolver().resolve("accesspoint").await?; let ap = self.apresolver().resolve("accesspoint").await?;
info!("Connecting to AP \"{}:{}\"", ap.0, ap.1); info!("Connecting to AP \"{}:{}\"", ap.0, ap.1);
match self.connect_inner(ap, credentials.clone()).await { match self.connect_inner(&ap, credentials.clone()).await {
Ok(ct) => break ct, Ok(ct) => break ct,
Err(e) => { Err(e) => {
num_ap_tries += 1;
if MAX_AP_TRIES == num_ap_tries {
error!("Tried too many access points");
return Err(e);
}
if let Some(AuthenticationError::LoginFailed(ErrorCode::TryAnotherAP)) = if let Some(AuthenticationError::LoginFailed(ErrorCode::TryAnotherAP)) =
e.error.downcast_ref::<AuthenticationError>() e.error.downcast_ref::<AuthenticationError>()
{ {
warn!("Instructed to try another access point..."); warn!("Instructed to try another access point...");
continue; continue;
} else { } else if let Some(AuthenticationError::LoginFailed(..)) =
e.error.downcast_ref::<AuthenticationError>()
{
return Err(e); return Err(e);
} else {
warn!("Try another access point...");
continue;
} }
} }
} }
@ -567,7 +583,7 @@ impl Session {
} }
pub fn shutdown(&self) { pub fn shutdown(&self) {
debug!("Invalidating session"); debug!("Shutdown: Invalidating session");
self.0.data.write().invalid = true; self.0.data.write().invalid = true;
self.mercury().shutdown(); self.mercury().shutdown();
self.channel().shutdown(); self.channel().shutdown();
@ -624,6 +640,7 @@ where
return Poll::Ready(Ok(())); return Poll::Ready(Ok(()));
} }
Some(Err(e)) => { Some(Err(e)) => {
error!("Connection to server closed.");
session.shutdown(); session.shutdown();
return Poll::Ready(Err(e)); return Poll::Ready(Err(e));
} }