Skip to content

mainloop: don't panic when discovery stops without credentials - #1424

Open
Hayao0819 wants to merge 1 commit into
Spotifyd:masterfrom
Hayao0819:fix-discovery-panic
Open

mainloop: don't panic when discovery stops without credentials#1424
Hayao0819 wants to merge 1 commit into
Spotifyd:masterfrom
Hayao0819:fix-discovery-panic

Conversation

@Hayao0819

Copy link
Copy Markdown

spotifyd panics with a bare Option::unwrap() when the zeroconf responder fails to come up. My service sat in a systemd restart loop with nothing but this in the journal:

spotifyd[1029740]: The application panicked (crashed).
spotifyd[1029740]: Message:  called `Option::unwrap()` on a `None` value
spotifyd[1029740]: Location: src/main_loop.rs:244
systemd[1]: spotifyd.service: Main process exited, code=exited, status=101/n/a
systemd[1]: spotifyd.service: Scheduled restart job, restart counter is at 78.

The cause was net.ipv4.igmp_max_memberships, default 20, against the 27 interfaces carrying addresses on this box (docker, tailscale, a pile of emulator taps). libmdns gives up joining the multicast group, and spotifyd dies two lines later:

[INFO] Starting zeroconf server to advertise on local network.
[ERROR] libmdns error: Setting up dns-sd failed: No buffer space available (os error 105)
The application panicked (crashed).
Message:  called `Option::unwrap()` on a `None` value
Location: src/main_loop.rs:52

(:52 is current master, :244 is the same code in 0.4.1.)

librespot does report the failure, we just never see it. launch_libmdns logs it and pushes it into the event channel (discovery/src/lib.rs#L418-L421):

        if let Err(e) = inner() {
            log::error!("libmdns error: {e}");
            let _ = status_tx.send(DiscoveryEvent::ZeroconfError(e));
        }

The Stream impl then turns that into end-of-stream, since Item is Credentials and there's nowhere to put an error (discovery/src/lib.rs#L539-L553):

impl Stream for Discovery {
    type Item = Credentials;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        match Pin::new(&mut self.event_rx).poll_recv(cx) {
            // Yields credentials
            Poll::Ready(Some(DiscoveryEvent::Credentials(creds))) => Poll::Ready(Some(creds)),
            // Also terminate the stream on fatal server or MDNS/DNS-SD errors.
            Poll::Ready(Some(
                DiscoveryEvent::ServerError(_) | DiscoveryEvent::ZeroconfError(_),
            )) => Poll::Ready(None),

All of it happens inside spawn_blocking, long after launch() handed us an Ok, so the retry loop in setup.rs never gets a chance either.

None here means "no credentials, ever", not "shutting down". Handling it gives:

Error:
   0: failed to connect to spotify
   1: Discovery stopped without providing credentials.

Suggestion: Check the log for zeroconf errors, or log in with `spotifyd authenticate` so that spotifyd doesn't depend on discovery.

Only a discovery-only start with no cached credentials reaches that branch. With credentials present, get_credentials takes the non-blocking arm and behaves as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant