2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-29 08:14:03 +00:00

lock: rework stale lock refresh to avoid data race

This commit is contained in:
Michael Eischer 2023-06-17 21:29:18 +02:00
parent 05e5e29a8c
commit b2fcbc21cb

View File

@ -110,12 +110,12 @@ retryLoop:
} }
lockInfo.refreshWG.Add(2) lockInfo.refreshWG.Add(2)
refreshChan := make(chan struct{}) refreshChan := make(chan struct{})
forcedRefreshChan := make(chan struct{}) forceRefreshChan := make(chan refreshLockRequest)
globalLocks.Lock() globalLocks.Lock()
globalLocks.locks[lock] = lockInfo globalLocks.locks[lock] = lockInfo
go refreshLocks(ctx, lockInfo, refreshChan, forcedRefreshChan) go refreshLocks(ctx, repo.Backend(), lockInfo, refreshChan, forceRefreshChan)
go monitorLockRefresh(ctx, repo.Backend(), lockInfo, refreshChan, forcedRefreshChan) go monitorLockRefresh(ctx, lockInfo, refreshChan, forceRefreshChan)
globalLocks.Unlock() globalLocks.Unlock()
return lock, ctx, err return lock, ctx, err
@ -127,7 +127,11 @@ var refreshInterval = 5 * time.Minute
// the difference allows to compensate for a small time drift between clients. // the difference allows to compensate for a small time drift between clients.
var refreshabilityTimeout = restic.StaleLockTimeout - refreshInterval*3/2 var refreshabilityTimeout = restic.StaleLockTimeout - refreshInterval*3/2
func refreshLocks(ctx context.Context, lockInfo *lockContext, refreshed chan<- struct{}, forcedRefresh <-chan struct{}) { type refreshLockRequest struct {
result chan bool
}
func refreshLocks(ctx context.Context, backend restic.Backend, lockInfo *lockContext, refreshed chan<- struct{}, forceRefresh <-chan refreshLockRequest) {
debug.Log("start") debug.Log("start")
lock := lockInfo.lock lock := lockInfo.lock
ticker := time.NewTicker(refreshInterval) ticker := time.NewTicker(refreshInterval)
@ -154,9 +158,19 @@ func refreshLocks(ctx context.Context, lockInfo *lockContext, refreshed chan<- s
debug.Log("terminate") debug.Log("terminate")
return return
case <-forcedRefresh: case req := <-forceRefresh:
// update lock refresh time // keep on going if our current lock still exists
lastRefresh = lock.Time success := tryRefreshStaleLock(ctx, backend, lock, lockInfo.cancel)
// inform refresh goroutine about forced refresh
select {
case <-ctx.Done():
case req.result <- success:
}
if success {
// update lock refresh time
lastRefresh = lock.Time
}
case <-ticker.C: case <-ticker.C:
if time.Since(lastRefresh) > refreshabilityTimeout { if time.Since(lastRefresh) > refreshabilityTimeout {
@ -180,7 +194,7 @@ func refreshLocks(ctx context.Context, lockInfo *lockContext, refreshed chan<- s
} }
} }
func monitorLockRefresh(ctx context.Context, backend restic.Backend, lockInfo *lockContext, refreshed <-chan struct{}, forcedRefresh chan<- struct{}) { func monitorLockRefresh(ctx context.Context, lockInfo *lockContext, refreshed <-chan struct{}, forceRefresh chan<- refreshLockRequest) {
// time.Now() might use a monotonic timer which is paused during standby // time.Now() might use a monotonic timer which is paused during standby
// convert to unix time to ensure we compare real time values // convert to unix time to ensure we compare real time values
lastRefresh := time.Now().UnixNano() lastRefresh := time.Now().UnixNano()
@ -212,14 +226,22 @@ func monitorLockRefresh(ctx context.Context, backend restic.Backend, lockInfo *l
} }
// keep on going if our current lock still exists // keep on going if our current lock still exists
if tryRefreshStaleLock(ctx, backend, lockInfo.lock, lockInfo.cancel) { refreshReq := refreshLockRequest{
lastRefresh = time.Now().UnixNano() result: make(chan bool),
}
// inform refresh goroutine about forced refresh
select {
case <-ctx.Done():
case forceRefresh <- refreshReq:
}
var success bool
select {
case <-ctx.Done():
case success = <-refreshReq.result:
}
// inform refresh gorountine about forced refresh if success {
select { lastRefresh = time.Now().UnixNano()
case <-ctx.Done():
case forcedRefresh <- struct{}{}:
}
continue continue
} }