From 2dd6769429534c21f62e3f68ecd16e39a73538a0 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 15 Jul 2023 15:28:02 +0200 Subject: [PATCH] lock: Fix possible deadlock during refresh of stale lock A delayed lock refresh could send a signal on the `refreshed` channel while the `monitorLockRefresh` goroutine waits for a reply to its `refreshLockRequest`. As the channels are unbuffered, this resulted in a deadlock. --- cmd/restic/lock.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/cmd/restic/lock.go b/cmd/restic/lock.go index c7fb93a47..e1466a902 100644 --- a/cmd/restic/lock.go +++ b/cmd/restic/lock.go @@ -213,15 +213,21 @@ func monitorLockRefresh(ctx context.Context, lockInfo *lockContext, refreshed <- lockInfo.refreshWG.Done() }() + var refreshStaleLockResult chan bool + for { select { case <-ctx.Done(): debug.Log("terminate expiry monitoring") return case <-refreshed: + if refreshStaleLockResult != nil { + // ignore delayed refresh notifications while the stale lock is refreshed + continue + } lastRefresh = time.Now().UnixNano() case <-ticker.C: - if time.Now().UnixNano()-lastRefresh < refreshabilityTimeout.Nanoseconds() { + if time.Now().UnixNano()-lastRefresh < refreshabilityTimeout.Nanoseconds() || refreshStaleLockResult != nil { continue } @@ -229,19 +235,17 @@ func monitorLockRefresh(ctx context.Context, lockInfo *lockContext, refreshed <- refreshReq := refreshLockRequest{ result: make(chan bool), } + refreshStaleLockResult = refreshReq.result + // inform refresh goroutine about forced refresh select { case <-ctx.Done(): case forceRefresh <- refreshReq: } - var success bool - select { - case <-ctx.Done(): - case success = <-refreshReq.result: - } - + case success := <-refreshStaleLockResult: if success { lastRefresh = time.Now().UnixNano() + refreshStaleLockResult = nil continue }