Handle reconnection for Sessions #609
Replies: 16 comments 26 replies
-
So here's my brain dump about the whole reconnection thing. Currently one The alternative is to move to a model where a impl Session {
fn new(config: SessionConfig, cache: Option<Cache>, handle: Handle) -> Session { ... }
fn connect(&self, credentials: Credentials) -> impl Future<Credentials, ConnectionError> { ... }
} The Trying to use an unconnected session (to get metadata, audio key/data, etc...) should fail gracefully. All the APIs already use Future/Stream, so these just need to resolve into errors. Same happens if the connection is lost while a request is in flight. Player and Spirc need to behave correctly when those requests fail. For Player it just means returning to the NotPlaying state and signal the caller that playback has completed with an error. For Spirc, when Player signals a connection problem it should probably stop playback. Reading from or sending to the mercury channel may start failing as well. Now to get back up, I suggest the Session signals in some way to This can mean various things depending on the component. The components need to expose an API that looks like: impl Component {
fn connection_established(&self, username: String) { ... }
fn dispatch(&self, cmd: u8, mut data: Bytes) { ... }
fn connection_closed(&self) { ... }
} There are some risks of race conditions that need to be carefully though about. I think I have some initial implementation of this somewhere, I'll try and find it again. |
Beta Was this translation helpful? Give feedback.
-
@plietar did you find that initial implementation anywhere? |
Beta Was this translation helpful? Give feedback.
-
Regardless, I think we should be aiming to make this as simple to integrate as possible, as handling reconnection logic from whatever project uses librespot sounds like plenty of headache for anyone who tries to use librespot, whilst also being pretty low level functionality that I personally would expect to be handled in any library I used. Anyway, these are just my two cents since I'm not sufficently versed in rust to be able to rewrite how sessions are handled. Would be good to hear others thoughts as to how this should be handled. |
Beta Was this translation helpful? Give feedback.
-
I am trying to wrap my head around this issue. AFAICS, the disconnect error starts at Since
So this works for the binary case. For the library case @plietar suggests to let main reconnect using last credentials and let Spirc, Player, etc. pick up the new session and reset its state accordingly. I have two problems trying to implement this
If you look at examples/play.rs you can do a match on |
Beta Was this translation helpful? Give feedback.
-
Is there any way to mitigate this problem? |
Beta Was this translation helpful? Give feedback.
-
This doesn't address the cause but aids with the symptoms: Script to be run as a cronjob every minute which checks the status and restarts the service if an error is spotted. |
Beta Was this translation helpful? Give feedback.
-
Is the cronjob patch the best solution for this issue currently? Sometimes I'm 25 songs in, sometimes I'm 4 when I lose connection. This is running on a server connected directly to my modem/router via ethernet. I can't imagine it's actually losing internet connection this consistently. |
Beta Was this translation helpful? Give feedback.
-
Sounds like this is closely related to, and would probably help solve #266 |
Beta Was this translation helpful? Give feedback.
-
Let's solve this as part of the new api. Mercury will not be central anymore for the session. There will be the http api and the websocket. I implemented the websocket already (#753), and considered reconnection from the start. There is the The websocket works almost independent from the rest of the session. Only we need to request a token from mercury to establish a connection. I solved this as follows: One must pass an "async" closure to The closure holds a weak reference to the session. What happens if Mercury is down as well or the session object is gone? The closure shouldn't return anything but have it's own little reconnection logic and try it periodically again. The This wasn't so hard, since the websocket receives only things and isn't used to send requests. There's no one else who has to care if the session is down. Nevertheless, I think a similar approach could work for Mercury. First of all, we should split the Mercury connection into another struct which is parallel to Then, we create a background task to handle reconnection like in If a request is done, it should be possible to pass an optional timeout, and it should fail with a Audio channels just won't yield anything, and maybe it's possible to make it look exactly as if it's lagging because of slow internet connection. In this case, no big changes would be required for the player. For every other case it has to be investigated how to handle a Your feedback? |
Beta Was this translation helpful? Give feedback.
-
@Johannesd3 having started work on this now I realize the same holds not only for Mercury but also What do you think would be the best approach to copy this behavior to Mercury and |
Beta Was this translation helpful? Give feedback.
-
I want to add here a use case which makes these silent disconnects very frequent: laptops with docks. Undocking my laptop will continue playing until the current song ends, then go silent. It seems like it dies at a point at which it would have downloaded something, maybe? Where the buffer ends, basically. The client should be able to survive network cards going missing (while another is still there, like a wifi), imo. If it does that, we'd be golden on all possible fronts. |
Beta Was this translation helpful? Give feedback.
-
Since this feature request is still open, I'm adding another use case. I use power over ethernet for my raspberry pi. Sometimes the connection drops for a second and it crashes raspotify. As the track data is cached for a few seconds, it could keep playing whatever is cached and attempt a reconnect in the background using an exponential backoff. Here is the error log I'm seeing when the connection drops:
|
Beta Was this translation helpful? Give feedback.
-
Until more gets done I wrote a better monitoring script to restart librespot, if anyone wants it. I need it due to using Starlink, which loses connection for a moment at least once every hour. It will actively follow librespot's journal and restart it if core::session throws an error (ignoring other common errors that don't break it, like 502's from ::spirc) /root/bin/monitor-librespot.sh: #!/bin/bash
last_restart=$(</proc/uptime)
last_restart=${last_restart%%.*}
echo "Starting librespot monitoring at uptime: $last_restart"
journalctl -b -f -o cat _SYSTEMD_UNIT=librespot.service --since now | stdbuf -o0 awk '$2 == "ERROR" && $3 == "librespot_core::session]" { print }' | while read -r line; do
now=$(</proc/uptime)
now=${now%%.*}
echo "$line"
if [ "$now" -gt $((last_restart + 5)) ]; then
last_restart="$now"
echo "Error found! Restarting librespot service at uptime: $now"
systemctl restart librespot.service
fi
done /etc/systemd/system/monitor-librespot.service:
Once created, run |
Beta Was this translation helpful? Give feedback.
-
Is there any work being done to add some session recovery to 4d402e6? |
Beta Was this translation helpful? Give feedback.
-
Hi, |
Beta Was this translation helpful? Give feedback.
-
Reconnection logic has been greatly improved, closing for now but feel free to reopen if you still have specific use cases. |
Beta Was this translation helpful? Give feedback.
-
This issue serves as a placeholder for discussion around the rewrite of the session handling logic, as it is currently one of the less stable parts of librespot. Issue #103 is related to this.
Beta Was this translation helpful? Give feedback.
All reactions