gitea/modules/globallock/lock.go

129 lines
2.4 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package globallock
import (
"context"
"sync"
"time"
"code.gitea.io/gitea/modules/nosql"
"code.gitea.io/gitea/modules/setting"
redsync "github.com/go-redsync/redsync/v4"
goredis "github.com/go-redsync/redsync/v4/redis/goredis/v9"
)
type Locker interface {
Lock() error
TryLock() (bool, error)
Unlock() (bool, error)
}
type LockService interface {
GetLock(name string) Locker
}
type memoryLock struct {
mutex sync.Mutex
}
func (r *memoryLock) Lock() error {
r.mutex.Lock()
return nil
}
func (r *memoryLock) TryLock() (bool, error) {
return r.mutex.TryLock(), nil
}
func (r *memoryLock) Unlock() (bool, error) {
r.mutex.Unlock()
return true, nil
}
var _ Locker = &memoryLock{}
type memoryLockService struct {
syncMap sync.Map
}
var _ LockService = &memoryLockService{}
func newMemoryLockService() *memoryLockService {
return &memoryLockService{
syncMap: sync.Map{},
}
}
func (l *memoryLockService) GetLock(name string) Locker {
v, _ := l.syncMap.LoadOrStore(name, &memoryLock{})
return v.(*memoryLock)
}
type redisLockService struct {
rs *redsync.Redsync
}
var _ LockService = &redisLockService{}
func newRedisLockService(connection string) *redisLockService {
client := nosql.GetManager().GetRedisClient(connection)
pool := goredis.NewPool(client)
// Create an instance of redisync to be used to obtain a mutual exclusion
// lock.
rs := redsync.New(pool)
return &redisLockService{
rs: rs,
}
}
type redisLock struct {
mutex *redsync.Mutex
}
func (r *redisLockService) GetLock(name string) Locker {
return &redisLock{mutex: r.rs.NewMutex(name)}
}
func (r *redisLock) Lock() error {
return r.mutex.Lock()
}
func (r *redisLock) TryLock() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
if err := r.mutex.LockContext(ctx); err != nil {
return false, err
}
return true, nil
}
func (r *redisLock) Unlock() (bool, error) {
return r.mutex.Unlock()
}
var (
syncOnce sync.Once
lockService LockService
)
func getLockService() LockService {
syncOnce.Do(func() {
if setting.GlobalLock.ServiceType == "redis" {
lockService = newRedisLockService(setting.GlobalLock.ServiceConnStr)
} else {
lockService = newMemoryLockService()
}
})
return lockService
}
func GetLock(name string) Locker {
return getLockService().GetLock(name)
}