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  }