netbirdio/netbird · warning

already waiting for peer to come online

Error message

already waiting for peer to come online

What it means

PeersStateSubscription tracks one in-flight wait per peer in its waitingPeers map; WaitToBeOnlineAndSubscribe returns this error when a wait for the same peerID is already pending, instead of double-subscribing. The one-subscription-per-peer behavior is by design, so the error signals a duplicate wait rather than a broken state.

Source

Thrown at shared/relay/client/peer_subscription.go:86

	for _, peerID := range peersID {
		if _, ok := s.listenForOfflinePeers[peerID]; ok {
			relevantPeers = append(relevantPeers, peerID)
		}
	}
	s.mu.Unlock()

	if len(relevantPeers) > 0 {
		s.offlineCallback(relevantPeers)
	}
}

// WaitToBeOnlineAndSubscribe waits for a specific peer to come online and subscribes to its state changes.
func (s *PeersStateSubscription) WaitToBeOnlineAndSubscribe(ctx context.Context, peerID messages.PeerID) error {
	// Check if already waiting for this peer
	s.mu.Lock()
	if _, exists := s.waitingPeers[peerID]; exists {
		s.mu.Unlock()
		return errors.New("already waiting for peer to come online")
	}

	// Create a channel to wait for the peer to come online
	waitCh := make(chan struct{}, 1)
	s.waitingPeers[peerID] = waitCh
	s.listenForOfflinePeers[peerID] = struct{}{}
	s.mu.Unlock()

	if err := s.subscribeStateChange(peerID); err != nil {
		s.log.Errorf("failed to subscribe to peer state: %s", err)
		s.mu.Lock()
		if ch, exists := s.waitingPeers[peerID]; exists && ch == waitCh {
			close(waitCh)
			delete(s.waitingPeers, peerID)
			delete(s.listenForOfflinePeers, peerID)
		}
		s.mu.Unlock()
		return err

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Track outstanding waits per peer and skip or join the existing one
  2. Cancel the first wait (its context) and let it clean up before starting another
  3. Treat the error as benign idempotence feedback rather than a failure
Defensive patterns

Strategy: try-catch

Validate before calling

if inflight, ok := myWaits[peerID]; ok {
	<-inflight.done // join the existing wait instead of starting a second one
	return nil
}
err := subscription.WaitToBeOnlineAndSubscribe(ctx, peerID)

Try / catch

if err := sub.WaitToBeOnlineAndSubscribe(ctx, peerID); err != nil {
	if err.Error() == "already waiting for peer to come online" {
		return nil // benign: a wait is already active for this peer
	}
	return err
}

Prevention

When it happens

Trigger: Two goroutines calling WaitToBeOnlineAndSubscribe for the same offline peer (e.g. racing connection attempts), or a retry loop starting a new wait before the previous one completed and cleaned up its entry.

Common situations: Concurrent dials to the same peer in tests or custom clients; reconnect storms re-issuing waits; missing cancellation of the first wait's context.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/87e6bac563191cef. Report an issue: GitHub.