github.com/advanderveer/restic@v0.8.1-0.20171209104529-42a8c19aaea6/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  	debug.Log("unlocking repository with lock %p", lock)
    95  	if err := lock.Unlock(); err != nil {
    96  		debug.Log("error while unlocking: %v", err)
    97  		return err
    98  	}
    99  
   100  	for i := 0; i < len(globalLocks.locks); i++ {
   101  		if lock == globalLocks.locks[i] {
   102  			globalLocks.locks = append(globalLocks.locks[:i], globalLocks.locks[i+1:]...)
   103  			return nil
   104  		}
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func unlockAll() error {
   111  	globalLocks.Lock()
   112  	defer globalLocks.Unlock()
   113  
   114  	debug.Log("unlocking %d locks", len(globalLocks.locks))
   115  	for _, lock := range globalLocks.locks {
   116  		if err := lock.Unlock(); err != nil {
   117  			debug.Log("error while unlocking: %v", err)
   118  			return err
   119  		}
   120  		debug.Log("successfully removed lock")
   121  	}
   122  
   123  	return nil
   124  }
   125  
   126  func init() {
   127  	AddCleanupHandler(unlockAll)
   128  }