github.com/mckael/restic@v0.8.3/cmd/restic/lock.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "sync" 8 "time" 9 10 "github.com/restic/restic/internal/debug" 11 "github.com/restic/restic/internal/errors" 12 "github.com/restic/restic/internal/repository" 13 "github.com/restic/restic/internal/restic" 14 ) 15 16 var globalLocks struct { 17 locks []*restic.Lock 18 cancelRefresh chan struct{} 19 refreshWG sync.WaitGroup 20 sync.Mutex 21 } 22 23 func lockRepo(repo *repository.Repository) (*restic.Lock, error) { 24 return lockRepository(repo, false) 25 } 26 27 func lockRepoExclusive(repo *repository.Repository) (*restic.Lock, error) { 28 return lockRepository(repo, true) 29 } 30 31 func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, error) { 32 lockFn := restic.NewLock 33 if exclusive { 34 lockFn = restic.NewExclusiveLock 35 } 36 37 lock, err := lockFn(context.TODO(), repo) 38 if err != nil { 39 return nil, errors.Fatalf("unable to create lock in backend: %v", err) 40 } 41 debug.Log("create lock %p (exclusive %v)", lock, exclusive) 42 43 globalLocks.Lock() 44 if globalLocks.cancelRefresh == nil { 45 debug.Log("start goroutine for lock refresh") 46 globalLocks.cancelRefresh = make(chan struct{}) 47 globalLocks.refreshWG = sync.WaitGroup{} 48 globalLocks.refreshWG.Add(1) 49 go refreshLocks(&globalLocks.refreshWG, globalLocks.cancelRefresh) 50 } 51 52 globalLocks.locks = append(globalLocks.locks, lock) 53 globalLocks.Unlock() 54 55 return lock, err 56 } 57 58 var refreshInterval = 5 * time.Minute 59 60 func refreshLocks(wg *sync.WaitGroup, done <-chan struct{}) { 61 debug.Log("start") 62 defer func() { 63 wg.Done() 64 globalLocks.Lock() 65 globalLocks.cancelRefresh = nil 66 globalLocks.Unlock() 67 }() 68 69 ticker := time.NewTicker(refreshInterval) 70 71 for { 72 select { 73 case <-done: 74 debug.Log("terminate") 75 return 76 case <-ticker.C: 77 debug.Log("refreshing locks") 78 globalLocks.Lock() 79 for _, lock := range globalLocks.locks { 80 err := lock.Refresh(context.TODO()) 81 if err != nil { 82 fmt.Fprintf(os.Stderr, "unable to refresh lock: %v\n", err) 83 } 84 } 85 globalLocks.Unlock() 86 } 87 } 88 } 89 90 func unlockRepo(lock *restic.Lock) error { 91 globalLocks.Lock() 92 defer globalLocks.Unlock() 93 94 for i := 0; i < len(globalLocks.locks); i++ { 95 if lock == globalLocks.locks[i] { 96 // remove the lock from the repo 97 debug.Log("unlocking repository with lock %v", lock) 98 if err := lock.Unlock(); err != nil { 99 debug.Log("error while unlocking: %v", err) 100 return err 101 } 102 103 // remove the lock from the list of locks 104 globalLocks.locks = append(globalLocks.locks[:i], globalLocks.locks[i+1:]...) 105 return nil 106 } 107 } 108 109 debug.Log("unable to find lock %v in the global list of locks, ignoring", lock) 110 111 return nil 112 } 113 114 func unlockAll() error { 115 globalLocks.Lock() 116 defer globalLocks.Unlock() 117 118 debug.Log("unlocking %d locks", len(globalLocks.locks)) 119 for _, lock := range globalLocks.locks { 120 if err := lock.Unlock(); err != nil { 121 debug.Log("error while unlocking: %v", err) 122 return err 123 } 124 debug.Log("successfully removed lock") 125 } 126 globalLocks.locks = globalLocks.locks[:0] 127 128 return nil 129 } 130 131 func init() { 132 AddCleanupHandler(unlockAll) 133 }