mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-16 07:16:38 +00:00
The expose tracker used sync.Map for in-memory TTL tracking of active expose sessions, which broke and lost all sessions on restart. Replace with SQL-backed operations that reuse the existing meta_last_renewed_at column: - Add store methods: RenewEphemeralService, GetExpiredEphemeralServices, CountEphemeralServicesByPeer, EphemeralServiceExists - Move duplicate/limit checks inside a transaction with row-level locking (SELECT ... FOR UPDATE) to prevent concurrent bypass - Reaper re-checks expiry under row lock to avoid deleting a just-renewed service and prevent duplicate event emission - Add composite index on (source, source_peer) for efficient queries - Batch-limit and column-select the reaper query to avoid DB/GC spikes - Filter out malformed rows with empty source_peer
66 lines
1.6 KiB
Go
66 lines
1.6 KiB
Go
package manager
|
|
|
|
import (
|
|
"context"
|
|
"math/rand/v2"
|
|
"time"
|
|
|
|
"github.com/netbirdio/netbird/shared/management/status"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
exposeTTL = 90 * time.Second
|
|
exposeReapInterval = 30 * time.Second
|
|
maxExposesPerPeer = 10
|
|
exposeReapBatch = 100
|
|
)
|
|
|
|
type exposeReaper struct {
|
|
manager *Manager
|
|
}
|
|
|
|
// StartExposeReaper starts a background goroutine that reaps expired ephemeral services from the DB.
|
|
func (r *exposeReaper) StartExposeReaper(ctx context.Context) {
|
|
go func() {
|
|
// start with a random delay
|
|
rn := rand.IntN(10)
|
|
time.Sleep(time.Duration(rn) * time.Second)
|
|
|
|
ticker := time.NewTicker(exposeReapInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
r.reapExpiredExposes(ctx)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (r *exposeReaper) reapExpiredExposes(ctx context.Context) {
|
|
expired, err := r.manager.store.GetExpiredEphemeralServices(ctx, exposeTTL, exposeReapBatch)
|
|
if err != nil {
|
|
log.Errorf("failed to get expired ephemeral services: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, svc := range expired {
|
|
log.Infof("reaping expired expose session for peer %s, domain %s", svc.SourcePeer, svc.Domain)
|
|
|
|
err := r.manager.deleteExpiredPeerService(ctx, svc.AccountID, svc.SourcePeer, svc.ID)
|
|
if err == nil {
|
|
continue
|
|
}
|
|
|
|
if s, ok := status.FromError(err); ok && s.ErrorType == status.NotFound {
|
|
log.Debugf("service %s was already deleted by another instance", svc.Domain)
|
|
} else {
|
|
log.Errorf("failed to delete expired peer-exposed service for domain %s: %v", svc.Domain, err)
|
|
}
|
|
}
|
|
}
|