Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlejandro Rodríguez <alejorro70@gmail.com>2018-02-05 22:38:45 +0300
committerAlejandro Rodríguez <alejorro70@gmail.com>2018-02-08 17:31:32 +0300
commit81d4e09b56d67b6271c24e8a3fe96820073a1d13 (patch)
tree4f60a34d7b8eab1dd4eaabdd6607fa7f848d45b3
parentc5d9d30b0ce076ac79eb17e16b249d9a2ebe1298 (diff)
Delete old lock files before performing Garbage Collection
-rw-r--r--CHANGELOG.md4
-rw-r--r--internal/service/repository/gc.go66
-rw-r--r--internal/service/repository/gc_test.go131
3 files changed, 193 insertions, 8 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 592b6ed03..98a9aa28e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,11 @@
UNRELEASED
+- Delete old lock files before performing Garbage Collection
+ https://gitlab.com/gitlab-org/gitaly/merge_requests/587
- Implement RepositoryService.IsSquashInProgress RPC
https://gitlab.com/gitlab-org/gitaly/merge_requests/593
-- Added test to prevent wiki page duplication
+- Added test to prevent wiki page duplication
https://gitlab.com/gitlab-org/gitaly/merge_requests/539
- Fixed bug in wiki_find_page method
https://gitlab.com/gitlab-org/gitaly/merge_requests/590
diff --git a/internal/service/repository/gc.go b/internal/service/repository/gc.go
index 9f6724eae..a8f7961d1 100644
--- a/internal/service/repository/gc.go
+++ b/internal/service/repository/gc.go
@@ -1,6 +1,11 @@
package repository
import (
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
log "github.com/sirupsen/logrus"
"gitlab.com/gitlab-org/gitaly/internal/helper/housekeeping"
@@ -19,6 +24,19 @@ func (server) GarbageCollect(ctx context.Context, in *pb.GarbageCollectRequest)
"WriteBitmaps": in.GetCreateBitmap(),
}).Debug("GarbageCollect")
+ repoPath, err := helper.GetRepoPath(in.GetRepository())
+ if err != nil {
+ return nil, err
+ }
+
+ threshold := time.Now().Add(-1 * time.Hour)
+ if err := cleanRefsLocks(filepath.Join(repoPath, "refs"), threshold); err != nil {
+ return nil, status.Errorf(codes.Internal, "GarbageCollect: cleanRefsLocks: %v", err)
+ }
+ if err := cleanPackedRefsLock(repoPath, threshold); err != nil {
+ return nil, status.Errorf(codes.Internal, "GarbageCollect: cleanPackedRefsLock: %v", err)
+ }
+
args := []string{"-c"}
if in.GetCreateBitmap() {
args = append(args, "repack.writeBitmaps=true")
@@ -31,19 +49,14 @@ func (server) GarbageCollect(ctx context.Context, in *pb.GarbageCollectRequest)
if _, ok := status.FromError(err); ok {
return nil, err
}
- return nil, status.Errorf(codes.Internal, err.Error())
+ return nil, status.Errorf(codes.Internal, "GarbageCollect: gitCommand: %v", err)
}
if err := cmd.Wait(); err != nil {
- return nil, status.Errorf(codes.Internal, err.Error())
+ return nil, status.Errorf(codes.Internal, "GarbageCollect: cmd wait: %v", err)
}
// Perform housekeeping post GC
- repoPath, err := helper.GetRepoPath(in.GetRepository())
- if err != nil {
- return nil, err
- }
-
err = housekeeping.Perform(ctx, repoPath)
if err != nil {
ctxlogger.WithError(err).Warn("Post gc housekeeping failed")
@@ -51,3 +64,42 @@ func (server) GarbageCollect(ctx context.Context, in *pb.GarbageCollectRequest)
return &pb.GarbageCollectResponse{}, nil
}
+
+func cleanRefsLocks(rootPath string, threshold time.Time) error {
+ return filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ if info.IsDir() {
+ return nil
+ }
+
+ if strings.HasSuffix(info.Name(), ".lock") && info.ModTime().Before(threshold) {
+ if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ return nil
+ })
+}
+
+func cleanPackedRefsLock(repoPath string, threshold time.Time) error {
+ path := filepath.Join(repoPath, "packed-refs.lock")
+ fileInfo, err := os.Stat(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+
+ if fileInfo.ModTime().Before(threshold) {
+ if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/internal/service/repository/gc_test.go b/internal/service/repository/gc_test.go
index 8a8b77b87..5dcdfa9de 100644
--- a/internal/service/repository/gc_test.go
+++ b/internal/service/repository/gc_test.go
@@ -3,9 +3,12 @@ package repository
import (
"context"
"fmt"
+ "io/ioutil"
+ "os"
"path"
"path/filepath"
"testing"
+ "time"
"google.golang.org/grpc/codes"
@@ -15,6 +18,11 @@ import (
pb "gitlab.com/gitlab-org/gitaly-proto/go"
)
+var (
+ freshTime = time.Now()
+ oldTime = freshTime.Add(-2 * time.Hour)
+)
+
func TestGarbageCollectSuccess(t *testing.T) {
server, serverSocketPath := runRepoServer(t)
defer server.Stop()
@@ -73,6 +81,124 @@ func TestGarbageCollectSuccess(t *testing.T) {
}
}
+func TestGarbageCollectDeletesRefsLocks(t *testing.T) {
+ server, serverSocketPath := runRepoServer(t)
+ defer server.Stop()
+
+ client, conn := newRepositoryClient(t, serverSocketPath)
+ defer conn.Close()
+
+ testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t)
+ defer cleanupFn()
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ req := &pb.GarbageCollectRequest{Repository: testRepo}
+ refsPath := filepath.Join(testRepoPath, "refs")
+
+ // Note: Creating refs this way makes `git gc` crash but this actually works
+ // in our favor for this test since we can ensure that the files kept and
+ // deleted are all due to our *.lock cleanup step before gc runs (since
+ // `git gc` also deletes files from /refs when packing).
+ keepRefPath := filepath.Join(refsPath, "heads", "keepthis")
+ createFileWithTimes(keepRefPath, freshTime)
+ keepOldRefPath := filepath.Join(refsPath, "heads", "keepthisalso")
+ createFileWithTimes(keepOldRefPath, oldTime)
+ keepDeceitfulRef := filepath.Join(refsPath, "heads", " .lock.not-actually-a-lock.lock ")
+ createFileWithTimes(keepDeceitfulRef, oldTime)
+
+ keepLockPath := filepath.Join(refsPath, "heads", "keepthis.lock")
+ createFileWithTimes(keepLockPath, freshTime)
+
+ deleteLockPath := filepath.Join(refsPath, "heads", "deletethis.lock")
+ createFileWithTimes(deleteLockPath, oldTime)
+
+ c, err := client.GarbageCollect(ctx, req)
+ testhelper.AssertGrpcError(t, err, codes.Internal, "GarbageCollect: cmd wait")
+ assert.Nil(t, c)
+
+ // Sanity checks
+ assert.FileExists(t, keepRefPath)
+ assert.FileExists(t, keepOldRefPath)
+ assert.FileExists(t, keepDeceitfulRef)
+
+ assert.FileExists(t, keepLockPath)
+
+ // There's assert.FileExists but no assert.NotFileExists ¯\_(ツ)_/¯
+ _, err = os.Stat(deleteLockPath)
+ assert.True(t, os.IsNotExist(err))
+}
+
+func TestGarbageCollectDeletesPackedRefsLock(t *testing.T) {
+ server, serverSocketPath := runRepoServer(t)
+ defer server.Stop()
+
+ client, conn := newRepositoryClient(t, serverSocketPath)
+ defer conn.Close()
+
+ testCases := []struct {
+ desc string
+ lockTime *time.Time
+ shouldExist bool
+ }{
+ {
+ desc: "with a recent lock",
+ lockTime: &freshTime,
+ shouldExist: true,
+ },
+ {
+ desc: "with an old lock",
+ lockTime: &oldTime,
+ shouldExist: false,
+ },
+ {
+ desc: "with a non-existing lock",
+ lockTime: nil,
+ shouldExist: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ testRepo, testRepoPath, cleanupFn := testhelper.NewTestRepo(t)
+ defer cleanupFn()
+
+ // Force the packed-refs file to have an old time to test that even
+ // in that case it doesn't get deleted
+ packedRefsPath := filepath.Join(testRepoPath, "packed-refs")
+ os.Chtimes(packedRefsPath, oldTime, oldTime)
+
+ req := &pb.GarbageCollectRequest{Repository: testRepo}
+ lockPath := filepath.Join(testRepoPath, "packed-refs.lock")
+
+ if tc.lockTime != nil {
+ createFileWithTimes(lockPath, *tc.lockTime)
+ }
+
+ ctx, cancel := testhelper.Context()
+ defer cancel()
+
+ c, err := client.GarbageCollect(ctx, req)
+
+ // Sanity checks
+ assert.FileExists(t, filepath.Join(testRepoPath, "HEAD")) // For good measure
+ assert.FileExists(t, packedRefsPath)
+
+ if tc.shouldExist {
+ assert.FileExists(t, lockPath)
+ } else {
+ assert.NoError(t, err)
+ assert.NotNil(t, c)
+
+ // There's assert.FileExists but no assert.NotFileExists ¯\_(ツ)_/¯
+ _, err = os.Stat(lockPath)
+ assert.True(t, os.IsNotExist(err))
+ }
+ })
+ }
+}
+
func TestGarbageCollectFailure(t *testing.T) {
server, serverSocketPath := runRepoServer(t)
defer server.Stop()
@@ -103,3 +229,8 @@ func TestGarbageCollectFailure(t *testing.T) {
}
}
+
+func createFileWithTimes(path string, mTime time.Time) {
+ ioutil.WriteFile(path, nil, 0644)
+ os.Chtimes(path, mTime, mTime)
+}