mirror of
https://github.com/offen/docker-volume-backup.git
synced 2026-04-23 08:55:36 +02:00
Support identical cron schedule (#87)
* Retry on lock being unavailable * Refactor locking to return plain error * Collect LockedTime in stats * Add test case * Add documentation for LOCK_TIMEOUT * Log in case lock needs to be awaited * Release resources created for awaiting lock
This commit is contained in:
@@ -41,4 +41,5 @@ type Config struct {
|
||||
WebdavPassword string `split_words:"true"`
|
||||
ExecLabel string `split_words:"true"`
|
||||
ExecForwardOutput bool `split_words:"true"`
|
||||
LockTimeout time.Duration `split_words:"true" default:"60m"`
|
||||
}
|
||||
|
||||
58
cmd/backup/lock.go
Normal file
58
cmd/backup/lock.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2022 - Offen Authors <hioffen@posteo.de>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
)
|
||||
|
||||
// lock opens a lockfile at the given location, keeping it locked until the
|
||||
// caller invokes the returned release func. In case the lock is currently blocked
|
||||
// by another execution, it will repeatedly retry until the lock is available
|
||||
// or the given timeout is exceeded.
|
||||
func (s *script) lock(lockfile string) (func() error, error) {
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
s.stats.LockedTime = time.Now().Sub(start)
|
||||
}()
|
||||
|
||||
retry := time.NewTicker(5 * time.Second)
|
||||
defer retry.Stop()
|
||||
deadline := time.NewTimer(s.c.LockTimeout)
|
||||
defer deadline.Stop()
|
||||
|
||||
fileLock := flock.New(lockfile)
|
||||
|
||||
for {
|
||||
acquired, err := fileLock.TryLock()
|
||||
if err != nil {
|
||||
return noop, fmt.Errorf("lock: error trying lock: %w", err)
|
||||
}
|
||||
if acquired {
|
||||
if s.encounteredLock {
|
||||
s.logger.Info("Acquired exclusive lock on subsequent attempt, ready to continue.")
|
||||
}
|
||||
return fileLock.Unlock, nil
|
||||
}
|
||||
|
||||
if !s.encounteredLock {
|
||||
s.logger.Infof(
|
||||
"Exclusive lock was not available on first attempt. Will retry until it becomes available or the timeout of %s is exceeded.",
|
||||
s.c.LockTimeout,
|
||||
)
|
||||
s.encounteredLock = true
|
||||
}
|
||||
|
||||
select {
|
||||
case <-retry.C:
|
||||
continue
|
||||
case <-deadline.C:
|
||||
return noop, errors.New("lock: timed out waiting for lockfile to become available")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,15 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
unlock := lock("/var/lock/dockervolumebackup.lock")
|
||||
defer unlock()
|
||||
|
||||
s, err := newScript()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
unlock, err := s.lock("/var/lock/dockervolumebackup.lock")
|
||||
defer unlock()
|
||||
s.must(err)
|
||||
|
||||
defer func() {
|
||||
if pArg := recover(); pArg != nil {
|
||||
if err, ok := pArg.(error); ok {
|
||||
|
||||
@@ -47,6 +47,8 @@ type script struct {
|
||||
file string
|
||||
stats *Stats
|
||||
|
||||
encounteredLock bool
|
||||
|
||||
c *Config
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ type Stats struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
TookTime time.Duration
|
||||
LockedTime time.Duration
|
||||
LogOutput *bytes.Buffer
|
||||
Containers ContainersStats
|
||||
BackupFile BackupFileStats
|
||||
|
||||
@@ -10,27 +10,10 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
)
|
||||
|
||||
var noop = func() error { return nil }
|
||||
|
||||
// lock opens a lockfile at the given location, keeping it locked until the
|
||||
// caller invokes the returned release func. When invoked while the file is
|
||||
// still locked the function panics.
|
||||
func lock(lockfile string) func() error {
|
||||
fileLock := flock.New(lockfile)
|
||||
acquired, err := fileLock.TryLock()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !acquired {
|
||||
panic("unable to acquire file lock")
|
||||
}
|
||||
return fileLock.Unlock
|
||||
}
|
||||
|
||||
// copy creates a copy of the file located at `dst` at `src`.
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
|
||||
Reference in New Issue
Block a user