diff --git a/modules/cron/cron.go b/modules/cron/cron.go index 692642e4cec2a..4c45ef4f6e8a6 100644 --- a/modules/cron/cron.go +++ b/modules/cron/cron.go @@ -50,6 +50,45 @@ func WithUnique(name string, body func(context.Context)) Func { } } +// UpdateMirror runs the UpdateMirror cron task uniquely +func UpdateMirror() { + WithUnique(mirrorUpdate, mirror_service.Update)() +} + +// RepoHealthCheck runs the RepoHealthCheck cron task uniquely +func RepoHealthCheck() { + WithUnique(gitFsck, func(ctx context.Context) { + if err := repo_module.GitFsck(ctx); err != nil { + log.Error("Error whilst performing repository health checks (git fsck): %s", err) + } + })() +} + +// CheckRepoStats checks the repository statistics uniquely +func CheckRepoStats() { + WithUnique(checkRepos, models.CheckRepoStats)() +} + +// ArchiveCleanup cleans the archives uniquely +func ArchiveCleanup() { + WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)() +} + +// SyncExternalUsers syncs external users uniquely +func SyncExternalUsers() { + WithUnique(syncExternalUsers, models.SyncExternalUsers)() +} + +// DeletedBranchesCleanup performs branch deletion cleanup uniquely +func DeletedBranchesCleanup() { + WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)() +} + +// UpdateMigrationPosterID updates the migrations uniquely +func UpdateMigrationPosterID() { + WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID)() +} + // NewContext begins cron tasks // Each cron task is run within the shutdown context as a running server // AtShutdown the cron server is stopped @@ -59,87 +98,79 @@ func NewContext() { err error ) if setting.Cron.UpdateMirror.Enabled { - entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, WithUnique(mirrorUpdate, mirror_service.Update)) + entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, UpdateMirror) if err != nil { log.Fatal("Cron[Update mirrors]: %v", err) } if setting.Cron.UpdateMirror.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(mirrorUpdate, mirror_service.Update)() + go UpdateMirror() } } if setting.Cron.RepoHealthCheck.Enabled { - entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, WithUnique(gitFsck, func(ctx context.Context) { - if err := repo_module.GitFsck(ctx); err != nil { - log.Error("GitFsck: %s", err) - } - })) + entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, RepoHealthCheck) if err != nil { log.Fatal("Cron[Repository health check]: %v", err) } if setting.Cron.RepoHealthCheck.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(gitFsck, func(ctx context.Context) { - if err := repo_module.GitFsck(ctx); err != nil { - log.Error("GitFsck: %s", err) - } - })() + go RepoHealthCheck() } } if setting.Cron.CheckRepoStats.Enabled { - entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, WithUnique(checkRepos, models.CheckRepoStats)) + entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, CheckRepoStats) if err != nil { log.Fatal("Cron[Check repository statistics]: %v", err) } if setting.Cron.CheckRepoStats.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(checkRepos, models.CheckRepoStats)() + go CheckRepoStats() } } if setting.Cron.ArchiveCleanup.Enabled { - entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)) + entry, err = c.AddFunc("Clean up old repository archives", setting.Cron.ArchiveCleanup.Schedule, ArchiveCleanup) if err != nil { log.Fatal("Cron[Clean up old repository archives]: %v", err) } if setting.Cron.ArchiveCleanup.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(archiveCleanup, models.DeleteOldRepositoryArchives)() + go ArchiveCleanup() } } if setting.Cron.SyncExternalUsers.Enabled { - entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, WithUnique(syncExternalUsers, models.SyncExternalUsers)) + entry, err = c.AddFunc("Synchronize external users", setting.Cron.SyncExternalUsers.Schedule, SyncExternalUsers) if err != nil { log.Fatal("Cron[Synchronize external users]: %v", err) } if setting.Cron.SyncExternalUsers.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(syncExternalUsers, models.SyncExternalUsers)() + go SyncExternalUsers() } } if setting.Cron.DeletedBranchesCleanup.Enabled { - entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)) + entry, err = c.AddFunc("Remove old deleted branches", setting.Cron.DeletedBranchesCleanup.Schedule, DeletedBranchesCleanup) if err != nil { log.Fatal("Cron[Remove old deleted branches]: %v", err) } if setting.Cron.DeletedBranchesCleanup.RunAtStart { entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(deletedBranchesCleanup, models.RemoveOldDeletedBranches)() + go DeletedBranchesCleanup() } } - entry, err = c.AddFunc("Update migrated repositories' issues and comments' posterid", setting.Cron.UpdateMigrationPosterID.Schedule, WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID)) + entry, err = c.AddFunc("Update migrated repositories' issues and comments' posterid", setting.Cron.UpdateMigrationPosterID.Schedule, UpdateMigrationPosterID) if err != nil { log.Fatal("Cron[Update migrated repositories]: %v", err) } entry.Prev = time.Now() entry.ExecTimes++ - go WithUnique(updateMigrationPosterID, migrations.UpdateMigrationPosterID)() + go UpdateMigrationPosterID() c.Start() graceful.GetManager().RunAtShutdown(context.Background(), c.Stop) diff --git a/modules/repository/check.go b/modules/repository/check.go index fcaf76308f068..8ee0b5c11770d 100644 --- a/modules/repository/check.go +++ b/modules/repository/check.go @@ -34,10 +34,10 @@ func GitFsck(ctx context.Context) error { } repo := bean.(*models.Repository) repoPath := repo.RepoPath() - log.Trace("Running health check on repository %s", repoPath) + log.Trace("Running health check on repository %v", repo) if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil { + log.Warn("Failed to health check repository (%v): %v", repo, err) desc := fmt.Sprintf("Failed to health check repository (%s): %v", repoPath, err) - log.Warn(desc) if err = models.CreateRepositoryNotice(desc); err != nil { log.Error("CreateRepositoryNotice: %v", err) } @@ -72,13 +72,18 @@ func GitGcRepos(ctx context.Context) error { if err := repo.GetOwner(); err != nil { return err } + log.Trace("Running git gc on %v", repo) if stdout, err := git.NewCommand(args...). SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())). RunInDirTimeout( time.Duration(setting.Git.Timeout.GC)*time.Second, repo.RepoPath()); err != nil { log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("Repository garbage collection failed: Error: %v", err) + desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err) + if err = models.CreateRepositoryNotice(desc); err != nil { + log.Error("CreateRepositoryNotice: %v", err) + } + return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err) } return nil }, diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 483970e032846..9980be3778940 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1781,7 +1781,7 @@ dashboard.delete_missing_repos_success = All repositories missing their Git file dashboard.delete_generated_repository_avatars = Delete generated repository avatars dashboard.delete_generated_repository_avatars_success = Generated repository avatars were deleted. dashboard.git_gc_repos = Garbage collect all repositories -dashboard.git_gc_repos_success = All repositories have finished garbage collection. +dashboard.git_gc_repos_started = Garbage collection started for all repositories. dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.) dashboard.resync_all_sshkeys_success = The public SSH keys controlled by Gitea have been updated. dashboard.resync_all_hooks = Resynchronize pre-receive, update and post-receive hooks of all repositories. diff --git a/routers/admin/admin.go b/routers/admin/admin.go index 1b4a8631cdc81..901f5495ad9e7 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -6,6 +6,7 @@ package admin import ( + gocontext "context" "encoding/json" "fmt" "net/url" @@ -178,8 +179,12 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) { success = ctx.Tr("admin.dashboard.delete_missing_repos_success") err = repo_module.DeleteMissingRepositories(ctx.User) case gitGCRepos: - success = ctx.Tr("admin.dashboard.git_gc_repos_success") - err = repo_module.GitGcRepos(shutdownCtx) + success = ctx.Tr("admin.dashboard.git_gc_repos_started") + go cron.WithUnique("git_gc", func(ctx gocontext.Context) { + if err := repo_module.GitGcRepos(ctx); err != nil { + log.Error("Error whilst running git gc: %v", err) + } + }) case syncSSHAuthorizedKey: success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success") err = models.RewriteAllPublicKeys() @@ -191,10 +196,10 @@ func DashboardPost(ctx *context.Context, form auth.AdminDashboardForm) { err = repo_module.ReinitMissingRepositories() case syncExternalUsers: success = ctx.Tr("admin.dashboard.sync_external_users_started") - go graceful.GetManager().RunWithShutdownContext(models.SyncExternalUsers) + go cron.SyncExternalUsers() case gitFsck: success = ctx.Tr("admin.dashboard.git_fsck_started") - err = repo_module.GitFsck(shutdownCtx) + go cron.RepoHealthCheck() case deleteGeneratedRepositoryAvatars: success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success") err = models.RemoveRandomAvatars()