diff --git a/modules/sync/status_pool.go b/modules/sync/status_pool.go new file mode 100644 index 0000000000..6f075d54b7 --- /dev/null +++ b/modules/sync/status_pool.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package sync + +import ( + "sync" + + "code.gitea.io/gitea/modules/container" +) + +// StatusTable is a table maintains true/false values. +// +// This table is particularly useful for un/marking and checking values +// in different goroutines. +type StatusTable struct { + lock sync.RWMutex + pool container.Set[string] +} + +// NewStatusTable initializes and returns a new StatusTable object. +func NewStatusTable() *StatusTable { + return &StatusTable{ + pool: make(container.Set[string]), + } +} + +// StartIfNotRunning sets value of given name to true if not already in pool. +// Returns whether set value was set to true +func (p *StatusTable) StartIfNotRunning(name string) bool { + p.lock.Lock() + added := p.pool.Add(name) + p.lock.Unlock() + return added +} + +// Start sets value of given name to true in the pool. +func (p *StatusTable) Start(name string) { + p.lock.Lock() + p.pool.Add(name) + p.lock.Unlock() +} + +// Stop sets value of given name to false in the pool. +func (p *StatusTable) Stop(name string) { + p.lock.Lock() + p.pool.Remove(name) + p.lock.Unlock() +} + +// IsRunning checks if value of given name is set to true in the pool. +func (p *StatusTable) IsRunning(name string) bool { + p.lock.RLock() + exists := p.pool.Contains(name) + p.lock.RUnlock() + return exists +} diff --git a/modules/sync/status_pool_test.go b/modules/sync/status_pool_test.go new file mode 100644 index 0000000000..e2e48862f5 --- /dev/null +++ b/modules/sync/status_pool_test.go @@ -0,0 +1,31 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package sync + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_StatusTable(t *testing.T) { + table := NewStatusTable() + + assert.False(t, table.IsRunning("xyz")) + + table.Start("xyz") + assert.True(t, table.IsRunning("xyz")) + + assert.False(t, table.StartIfNotRunning("xyz")) + assert.True(t, table.IsRunning("xyz")) + + table.Stop("xyz") + assert.False(t, table.IsRunning("xyz")) + + assert.True(t, table.StartIfNotRunning("xyz")) + assert.True(t, table.IsRunning("xyz")) + + table.Stop("xyz") + assert.False(t, table.IsRunning("xyz")) +} diff --git a/services/cron/cron.go b/services/cron/cron.go index 1d803ed96d..56f8012b8e 100644 --- a/services/cron/cron.go +++ b/services/cron/cron.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/process" + "code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/translation" "github.com/go-co-op/gocron" @@ -18,6 +19,8 @@ import ( var scheduler = gocron.NewScheduler(time.Local) +var taskStatusTable = sync.NewStatusTable() + // NewContext begins cron tasks // Each cron task is run within the shutdown context as a running server // AtShutdown the cron server is stopped diff --git a/services/cron/tasks.go b/services/cron/tasks.go index 48d1fbcd53..f8a7444c49 100644 --- a/services/cron/tasks.go +++ b/services/cron/tasks.go @@ -14,7 +14,6 @@ import ( "code.gitea.io/gitea/models/db" system_model "code.gitea.io/gitea/models/system" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/process" @@ -74,12 +73,7 @@ func (t *Task) Run() { // RunWithUser will run the task incrementing the cron counter at the time with User func (t *Task) RunWithUser(doer *user_model.User, config Config) { - lock := globallock.GetLock(fmt.Sprintf("cron_tasks_%s", t.Name)) - if success, err := lock.TryLock(); err != nil { - log.Error("Unable to lock cron task %s Error: %v", t.Name, err) - return - } else if !success { - // get lock failed, so that there must be another task are running. + if !taskStatusTable.StartIfNotRunning(t.Name) { return } t.lock.Lock() @@ -89,9 +83,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) { t.ExecTimes++ t.lock.Unlock() defer func() { - if _, err := lock.Unlock(); err != nil { - log.Error("Unable to unlock cron task %s Error: %v", t.Name, err) - } + taskStatusTable.Stop(t.Name) }() graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) { defer func() {