diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bbd0e5d49..da7431295 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,6 +37,13 @@ jobs: check_changelog: true install_verb: install + - job_name: Linux (race) + go: 1.19.x + os: ubuntu-latest + test_fuse: true + test_opts: "-race" + install_verb: install + - job_name: Linux go: 1.18.x os: ubuntu-latest @@ -152,7 +159,7 @@ jobs: env: RESTIC_TEST_FUSE: ${{ matrix.test_fuse }} run: | - go test -cover ./... + go test -cover ${{matrix.test_opts}} ./... - name: Test cloud backends env: diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index c09b77f19..a99064b8f 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "sync" "testing" "time" @@ -54,7 +55,8 @@ func waitForMount(t testing.TB, dir string) { t.Errorf("subdir %q of dir %s never appeared", mountTestSubdir, dir) } -func testRunMount(t testing.TB, gopts GlobalOptions, dir string) { +func testRunMount(t testing.TB, gopts GlobalOptions, dir string, wg *sync.WaitGroup) { + defer wg.Done() opts := MountOptions{ TimeTemplate: time.RFC3339, } @@ -87,8 +89,11 @@ func listSnapshots(t testing.TB, dir string) []string { func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Repository, mountpoint, repodir string, snapshotIDs restic.IDs, expectedSnapshotsInFuseDir int) { t.Logf("checking for %d snapshots: %v", len(snapshotIDs), snapshotIDs) - go testRunMount(t, global, mountpoint) + var wg sync.WaitGroup + wg.Add(1) + go testRunMount(t, global, mountpoint, &wg) waitForMount(t, mountpoint) + defer wg.Wait() defer testRunUmount(t, global, mountpoint) if !snapshotsDirExists(t, mountpoint) { diff --git a/internal/restic/lock.go b/internal/restic/lock.go index acccb1d22..8f49aee49 100644 --- a/internal/restic/lock.go +++ b/internal/restic/lock.go @@ -26,6 +26,7 @@ import ( // A lock must be refreshed regularly to not be considered stale, this must be // triggered by regularly calling Refresh. type Lock struct { + lock sync.Mutex Time time.Time `json:"time"` Exclusive bool `json:"exclusive"` Hostname string `json:"hostname"` @@ -195,6 +196,8 @@ var StaleLockTimeout = 30 * time.Minute // older than 30 minutes or if it was created on the current machine and the // process isn't alive any more. func (l *Lock) Stale() bool { + l.lock.Lock() + defer l.lock.Unlock() debug.Log("testing if lock %v for process %d is stale", l, l.PID) if time.Since(l.Time) > StaleLockTimeout { debug.Log("lock is stale, timestamp is too old: %v\n", l.Time) @@ -229,12 +232,17 @@ func (l *Lock) Stale() bool { // timestamp. Afterwards the old lock is removed. func (l *Lock) Refresh(ctx context.Context) error { debug.Log("refreshing lock %v", l.lockID) + l.lock.Lock() l.Time = time.Now() + l.lock.Unlock() id, err := l.createLock(ctx) if err != nil { return err } + l.lock.Lock() + defer l.lock.Unlock() + debug.Log("new lock ID %v", id) oldLockID := l.lockID l.lockID = &id @@ -242,7 +250,10 @@ func (l *Lock) Refresh(ctx context.Context) error { return l.repo.Backend().Remove(context.TODO(), Handle{Type: LockFile, Name: oldLockID.String()}) } -func (l Lock) String() string { +func (l *Lock) String() string { + l.lock.Lock() + defer l.lock.Unlock() + text := fmt.Sprintf("PID %d on %s by %s (UID %d, GID %d)\nlock was created at %s (%s ago)\nstorage ID %v", l.PID, l.Hostname, l.Username, l.UID, l.GID, l.Time.Format("2006-01-02 15:04:05"), time.Since(l.Time), diff --git a/internal/restic/lock_unix.go b/internal/restic/lock_unix.go index c11bc4ca7..3f426ae32 100644 --- a/internal/restic/lock_unix.go +++ b/internal/restic/lock_unix.go @@ -29,7 +29,7 @@ func uidGidInt(u *user.User) (uid, gid uint32, err error) { // checkProcess will check if the process retaining the lock // exists and responds to SIGHUP signal. // Returns true if the process exists and responds. -func (l Lock) processExists() bool { +func (l *Lock) processExists() bool { proc, err := os.FindProcess(l.PID) if err != nil { debug.Log("error searching for process %d: %v\n", l.PID, err)