Fix deadlock issues

This commit is contained in:
M Essam Hamed
2025-08-25 20:49:44 +03:00
parent 762b9b7b56
commit 84501a3f56
3 changed files with 146 additions and 103 deletions

View File

@@ -244,7 +244,6 @@ func NewEngine(
statusRecorder: statusRecorder, statusRecorder: statusRecorder,
checks: checks, checks: checks,
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit), connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
updateManager: updatemanager.NewUpdateManager(clientCtx, statusRecorder),
} }
sm := profilemanager.NewServiceManager("") sm := profilemanager.NewServiceManager("")
@@ -705,7 +704,14 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
e.syncMsgMux.Lock() e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock() defer e.syncMsgMux.Unlock()
if update.GetAutoUpdateVersion() != "skip" { if e.updateManager == nil && update.GetAutoUpdateVersion() != "disabled" {
e.updateManager = updatemanager.NewUpdateManager(e.statusRecorder)
e.updateManager.Start(e.ctx)
} else if e.updateManager != nil && update.GetAutoUpdateVersion() == "disabled" {
e.updateManager.Stop()
e.updateManager = nil
}
if e.updateManager != nil {
e.updateManager.SetVersion(update.GetAutoUpdateVersion()) e.updateManager.SetVersion(update.GetAutoUpdateVersion())
} }
if update.GetNetbirdConfig() != nil { if update.GetNetbirdConfig() != nil {

View File

@@ -22,113 +22,134 @@ import (
const ( const (
latestVersion = "latest" latestVersion = "latest"
disableAutoUpdate = "disabled"
unknownVersion = "Unknown"
) )
type UpdateManager struct { type UpdateManager struct {
ctx context.Context
cancel context.CancelFunc
version string
latestVersion string
update *version.Update
lastTrigger time.Time lastTrigger time.Time
statusRecorder *peer.Status statusRecorder *peer.Status
mutex sync.Mutex mgmUpdateChan chan struct{}
updateChannel chan string updateChannel chan struct{}
doneChannel chan struct{} wg sync.WaitGroup
cancel context.CancelFunc
update *version.Update
expectedVersion string
expectedVersionMutex sync.Mutex
} }
func NewUpdateManager(ctx context.Context, statusRecorder *peer.Status) *UpdateManager { func NewUpdateManager(statusRecorder *peer.Status) *UpdateManager {
update := version.NewUpdate("nb/client")
ctx, cancel := context.WithCancel(ctx)
manager := &UpdateManager{ manager := &UpdateManager{
update: update,
lastTrigger: time.Now().Add(-10 * time.Minute),
statusRecorder: statusRecorder, statusRecorder: statusRecorder,
ctx: ctx, mgmUpdateChan: make(chan struct{}, 1),
cancel: cancel, updateChannel: make(chan struct{}, 1),
version: disableAutoUpdate,
latestVersion: unknownVersion,
updateChannel: make(chan string, 4),
doneChannel: make(chan struct{}),
} }
update.SetDaemonVersion(version.NetbirdVersion())
update.SetOnUpdateChannel(manager.updateChannel)
go manager.UpdateLoop()
return manager return manager
} }
func (u *UpdateManager) Start(ctx context.Context) {
if u.cancel != nil {
log.Errorf("UpdateManager already started")
return
}
u.update = version.NewUpdate("nb/client")
u.update.SetDaemonVersion(version.NetbirdVersion())
u.update.SetOnUpdateListener(func() {
select {
case u.updateChannel <- struct{}{}:
default:
}
})
ctx, cancel := context.WithCancel(ctx)
u.cancel = cancel
u.wg.Add(1)
go u.updateLoop(ctx)
}
func (u *UpdateManager) SetVersion(v string) { func (u *UpdateManager) SetVersion(v string) {
u.mutex.Lock() if u.cancel == nil {
if u.version != v { log.Errorf("UpdateManager not started")
log.Tracef("Auto-update version set to %s", v) return
u.version = v }
u.mutex.Unlock()
u.updateChannel <- unknownVersion u.expectedVersionMutex.Lock()
} else { defer u.expectedVersionMutex.Unlock()
u.mutex.Unlock() if u.expectedVersion == v {
return
}
u.expectedVersion = v
select {
case u.mgmUpdateChan <- struct{}{}:
default:
} }
} }
func (u *UpdateManager) Stop() { func (u *UpdateManager) Stop() {
if u.cancel == nil {
return
}
u.cancel() u.cancel()
u.mutex.Lock()
defer u.mutex.Unlock()
if u.update != nil { if u.update != nil {
u.update.StopWatch() u.update.StopWatch()
u.update = nil u.update = nil
} }
<-u.doneChannel
u.wg.Wait()
} }
func (u *UpdateManager) UpdateLoop() { func (u *UpdateManager) updateLoop(ctx context.Context) {
defer u.wg.Done()
for { for {
select { select {
case <-u.ctx.Done(): case <-ctx.Done():
u.doneChannel <- struct{}{}
return return
case latestVersion := <-u.updateChannel: case <-u.mgmUpdateChan:
u.mutex.Lock() case <-u.updateChannel:
if latestVersion != unknownVersion {
u.latestVersion = latestVersion
}
u.mutex.Unlock()
ctx, cancel := context.WithDeadline(u.ctx, time.Now().Add(time.Minute))
u.CheckForUpdates(ctx)
cancel()
} }
u.handleUpdate(ctx)
} }
} }
func (u *UpdateManager) CheckForUpdates(ctx context.Context) { func (u *UpdateManager) handleUpdate(ctx context.Context) {
if u.version == disableAutoUpdate { var updateVersion *v.Version
log.Trace("Skipped checking for updates, auto-update is disabled")
return u.expectedVersionMutex.Lock()
} expectedVersion := u.expectedVersion
currentVersionString := version.NetbirdVersion() u.expectedVersionMutex.Unlock()
updateVersionString := u.version
if updateVersionString == latestVersion || updateVersionString == "" { // Resolve "latest" to actual version
if u.latestVersion == unknownVersion { if expectedVersion == latestVersion {
if !u.isVersionAvailable() {
log.Tracef("Latest version not fetched yet") log.Tracef("Latest version not fetched yet")
return return
} }
updateVersionString = u.latestVersion updateVersion = u.update.LatestVersion()
} } else {
currentVersion, err := v.NewVersion(currentVersionString) var err error
updateVersion, err = v.NewSemver(expectedVersion)
if err != nil { if err != nil {
log.Errorf("Error checking for update, error parsing version `%s`: %v", currentVersionString, err) log.Errorf("Failed to parse latest version: %v", err)
return return
} }
updateVersion, err := v.NewVersion(updateVersionString) }
if err != nil {
log.Errorf("Error checking for update, error parsing version `%s`: %v", updateVersionString, err) if !u.shouldUpdate(updateVersion) {
return return
} }
if currentVersion.LessThan(updateVersion) {
if u.lastTrigger.Add(5 * time.Minute).Before(time.Now()) { ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Minute))
defer cancel()
u.lastTrigger = time.Now() u.lastTrigger = time.Now()
log.Debugf("Auto-update triggered, current version: %s, target version: %s", currentVersionString, updateVersionString) log.Debugf("Auto-update triggered, current version: %s, target version: %s", version.NetbirdVersion(), updateVersion)
u.statusRecorder.PublishEvent( u.statusRecorder.PublishEvent(
cProto.SystemEvent_INFO, cProto.SystemEvent_INFO,
cProto.SystemEvent_SYSTEM, cProto.SystemEvent_SYSTEM,
@@ -136,14 +157,38 @@ func (u *UpdateManager) CheckForUpdates(ctx context.Context) {
"Your client version is older than auto-update version set in Management, updating client now.", "Your client version is older than auto-update version set in Management, updating client now.",
nil, nil,
) )
err = u.triggerUpdate(ctx, updateVersionString)
err := u.triggerUpdate(ctx, updateVersion.String())
if err != nil { if err != nil {
log.Errorf("Error triggering auto-update: %v", err) log.Errorf("Error triggering auto-update: %v", err)
} }
}
func (u *UpdateManager) shouldUpdate(updateVersion *v.Version) bool {
currentVersionString := version.NetbirdVersion()
currentVersion, err := v.NewVersion(currentVersionString)
if err != nil {
log.Errorf("Error checking for update, error parsing version `%s`: %v", currentVersionString, err)
return false
} }
} else { if currentVersion.GreaterThanOrEqual(updateVersion) {
log.Debugf("Current version (%s) is equal to or higher than auto-update version (%s)", currentVersionString, updateVersionString) log.Debugf("Current version (%s) is equal to or higher than auto-update version (%s)", currentVersionString, updateVersion)
return false
} }
if time.Since(u.lastTrigger) < 5*time.Minute {
log.Tracef("No need to update")
return false
}
return true
}
func (u *UpdateManager) isVersionAvailable() bool {
if u.update.LatestVersion() == nil {
return false
}
return true
} }
func downloadFileToTemporaryDir(ctx context.Context, fileURL string) (string, error) { //nolint:unused func downloadFileToTemporaryDir(ctx context.Context, fileURL string) (string, error) { //nolint:unused

View File

@@ -31,7 +31,6 @@ type Update struct {
fetchDone chan struct{} fetchDone chan struct{}
onUpdateListener func() onUpdateListener func()
onUpdateChannel chan string
listenerLock sync.Mutex listenerLock sync.Mutex
} }
@@ -42,11 +41,8 @@ func NewUpdate(httpAgent string) *Update {
currentVersion, _ = goversion.NewVersion("0.0.0") currentVersion, _ = goversion.NewVersion("0.0.0")
} }
latestAvailable, _ := goversion.NewVersion("0.0.0")
u := &Update{ u := &Update{
httpAgent: httpAgent, httpAgent: httpAgent,
latestAvailable: latestAvailable,
uiVersion: currentVersion, uiVersion: currentVersion,
fetchTicker: time.NewTicker(fetchPeriod), fetchTicker: time.NewTicker(fetchPeriod),
fetchDone: make(chan struct{}), fetchDone: make(chan struct{}),
@@ -95,15 +91,10 @@ func (u *Update) SetOnUpdateListener(updateFn func()) {
} }
} }
func (u *Update) SetOnUpdateChannel(updateChannel chan string) { func (u *Update) LatestVersion() *goversion.Version {
u.listenerLock.Lock()
defer u.listenerLock.Unlock()
u.onUpdateChannel = updateChannel
if u.isUpdateAvailable() {
u.versionsLock.Lock() u.versionsLock.Lock()
defer u.versionsLock.Unlock() defer u.versionsLock.Unlock()
u.onUpdateChannel <- u.latestAvailable.String() return u.latestAvailable
}
} }
func (u *Update) startFetcher() { func (u *Update) startFetcher() {
@@ -181,9 +172,6 @@ func (u *Update) checkUpdate() bool {
u.listenerLock.Lock() u.listenerLock.Lock()
defer u.listenerLock.Unlock() defer u.listenerLock.Unlock()
if u.onUpdateChannel != nil {
u.onUpdateChannel <- u.latestAvailable.String()
}
if u.onUpdateListener == nil { if u.onUpdateListener == nil {
return true return true
} }
@@ -196,6 +184,10 @@ func (u *Update) isUpdateAvailable() bool {
u.versionsLock.Lock() u.versionsLock.Lock()
defer u.versionsLock.Unlock() defer u.versionsLock.Unlock()
if u.latestAvailable == nil {
return false
}
if u.latestAvailable.GreaterThan(u.uiVersion) { if u.latestAvailable.GreaterThan(u.uiVersion) {
return true return true
} }