Allow common redis and leveldb connections (#12385)

* Allow common redis and leveldb connections

Prevents multiple reopening of redis and leveldb connections to the same
place by sharing connections.

Further allows for more configurable redis connection type using the
redisURI and a leveldbURI scheme.

Signed-off-by: Andrew Thornton <art27@cantab.net>

* add unit-test

Signed-off-by: Andrew Thornton <art27@cantab.net>

* as per @lunny

Signed-off-by: Andrew Thornton <art27@cantab.net>

* add test

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Update modules/cache/cache_redis.go

* Update modules/queue/queue_disk.go

* Update modules/cache/cache_redis.go

* Update modules/cache/cache_redis.go

* Update modules/queue/unique_queue_disk.go

* Update modules/queue/queue_disk.go

* Update modules/queue/unique_queue_disk.go

* Update modules/session/redis.go

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
zeripath
2020-09-27 22:09:46 +01:00
committed by GitHub
parent f404bdde9b
commit 7f8e3192cd
71 changed files with 4927 additions and 3138 deletions

View File

@ -467,8 +467,10 @@ LENGTH = 20
BATCH_LENGTH = 20
; Connection string for redis queues this will store the redis connection string.
CONN_STR = "addrs=127.0.0.1:6379 db=0"
; Provide the suffix of the default redis queue name - specific queues can be overriden within in their [queue.name] sections.
; Provides the suffix of the default redis/disk queue name - specific queues can be overriden within in their [queue.name] sections.
QUEUE_NAME = "_queue"
; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overriden within in their [queue.name] sections.
SET_NAME = "_unique"
; If the queue cannot be created at startup - level queues may need a timeout at startup - wrap the queue:
WRAP_IF_NECESSARY = true
; Attempt to create the wrapped queue at max

View File

@ -308,15 +308,13 @@ relation to port exhaustion.
## Queue (`queue` and `queue.*`)
- `TYPE`: **persistable-channel**: General queue type, currently support: `persistable-channel`, `channel`, `level`, `redis`, `dummy`
- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for inidividual queues can be set in `queue.name` sections but will default to `DATADIR/`**`name`**.
- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`name`**.
- `LENGTH`: **20**: Maximal queue size before channel queues block
- `BATCH_LENGTH`: **20**: Batch data before passing to the handler
- `CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Connection string for the redis queue type.
- `QUEUE_NAME`: **_queue**: The suffix for default redis queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section.
- `SET_NAME`: **_unique**: The suffix that will added to the default redis
set name for unique queues. Individual queues will default to
**`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific
`queue.name` section.
- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. Options can be set using query params. Similarly LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**
- `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overriden in the specific `queue.name` section.
- `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to
**`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific `queue.name` section.
- `WRAP_IF_NECESSARY`: **true**: Will wrap queues with a timeoutable queue if the selected queue is not ready to be created - (Only relevant for the level queue.)
- `MAX_ATTEMPTS`: **10**: Maximum number of attempts to create the wrapped queue
- `TIMEOUT`: **GRACEFUL_HAMMER_TIME + 30s**: Timeout the creation of the wrapped queue if it takes longer than this to create.
@ -459,7 +457,7 @@ set name for unique queues. Individual queues will default to
- `ADAPTER`: **memory**: Cache engine adapter, either `memory`, `redis`, or `memcache`.
- `INTERVAL`: **60**: Garbage Collection interval (sec), for memory cache only.
- `HOST`: **\<empty\>**: Connection string for `redis` and `memcache`.
- Redis: `network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180`
- Redis: `redis://:macaron@127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
- Memcache: `127.0.0.1:9090;127.0.0.1:9091`
- `ITEM_TTL`: **16h**: Time to keep items in cache if not used, Setting it to 0 disables caching.
@ -708,7 +706,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
- `QUEUE_TYPE`: **channel**: Task queue type, could be `channel` or `redis`.
- `QUEUE_LENGTH`: **1000**: Task queue length, available only when `QUEUE_TYPE` is `channel`.
- `QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `addrs=127.0.0.1:6379 password=123 db=0`.
- `QUEUE_CONN_STR`: **redis://127.0.0.1:6379/0**: Task queue connection string, available only when `QUEUE_TYPE` is `redis`. If redis needs a password, use `redis://123@127.0.0.1:6379/0`.
## Migrations (`migrations`)

3
go.mod
View File

@ -38,7 +38,7 @@ require (
github.com/go-enry/go-enry/v2 v2.5.2
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.1.0
github.com/go-redis/redis v6.15.2+incompatible
github.com/go-redis/redis/v7 v7.4.0
github.com/go-sql-driver/mysql v1.5.0
github.com/go-swagger/go-swagger v0.25.0
github.com/go-testfixtures/testfixtures/v3 v3.4.0
@ -88,6 +88,7 @@ require (
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b // indirect
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/stretchr/testify v1.6.1
github.com/syndtr/goleveldb v1.0.0
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect
github.com/tinylib/msgp v1.1.2 // indirect
github.com/tstranex/u2f v1.0.0

7
go.sum
View File

@ -342,6 +342,8 @@ github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNP
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@ -730,9 +732,13 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -1014,6 +1020,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

View File

@ -13,7 +13,6 @@ import (
mc "gitea.com/macaron/cache"
_ "gitea.com/macaron/cache/memcache" // memcache plugin for cache
_ "gitea.com/macaron/cache/redis"
)
var (

View File

@ -1,35 +1,23 @@
// Copyright 2013 Beego Authors
// Copyright 2014 The Macaron Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package cache
import (
"fmt"
"strings"
"time"
"github.com/go-redis/redis"
"github.com/unknwon/com"
"gopkg.in/ini.v1"
"code.gitea.io/gitea/modules/nosql"
"gitea.com/macaron/cache"
"github.com/go-redis/redis/v7"
"github.com/unknwon/com"
)
// RedisCacher represents a redis cache adapter implementation.
type RedisCacher struct {
c *redis.Client
c redis.UniversalClient
prefix string
hsetName string
occupyMode bool
@ -112,7 +100,7 @@ func (c *RedisCacher) IsExist(key string) bool {
// Flush deletes all cached data.
func (c *RedisCacher) Flush() error {
if c.occupyMode {
return c.c.FlushDb().Err()
return c.c.FlushDB().Err()
}
keys, err := c.c.HKeys(c.hsetName).Result()
@ -131,46 +119,20 @@ func (c *RedisCacher) StartAndGC(opts cache.Options) error {
c.hsetName = "MacaronCache"
c.occupyMode = opts.OccupyMode
cfg, err := ini.Load([]byte(strings.Replace(opts.AdapterConfig, ",", "\n", -1)))
if err != nil {
return err
}
uri := nosql.ToRedisURI(opts.AdapterConfig)
opt := &redis.Options{
Network: "tcp",
}
for k, v := range cfg.Section("").KeysHash() {
c.c = nosql.GetManager().GetRedisClient(uri.String())
for k, v := range uri.Query() {
switch k {
case "network":
opt.Network = v
case "addr":
opt.Addr = v
case "password":
opt.Password = v
case "db":
opt.DB = com.StrTo(v).MustInt()
case "pool_size":
opt.PoolSize = com.StrTo(v).MustInt()
case "idle_timeout":
opt.IdleTimeout, err = time.ParseDuration(v + "s")
if err != nil {
return fmt.Errorf("error parsing idle timeout: %v", err)
}
case "hset_name":
c.hsetName = v
c.hsetName = v[0]
case "prefix":
c.prefix = v
default:
return fmt.Errorf("session/redis: unsupported option '%s'", k)
c.prefix = v[0]
}
}
c.c = redis.NewClient(opt)
if err = c.c.Ping().Err(); err != nil {
return err
}
return nil
return c.c.Ping().Err()
}
func init() {

25
modules/nosql/leveldb.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import "net/url"
// ToLevelDBURI converts old style connections to a LevelDBURI
//
// A LevelDBURI matches the pattern:
//
// leveldb://path[?[option=value]*]
//
// We have previously just provided the path but this prevent other options
func ToLevelDBURI(connection string) *url.URL {
uri, err := url.Parse(connection)
if err == nil && uri.Scheme == "leveldb" {
return uri
}
uri, _ = url.Parse("leveldb://common")
uri.Host = ""
uri.Path = connection
return uri
}

71
modules/nosql/manager.go Normal file
View File

@ -0,0 +1,71 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import (
"strconv"
"sync"
"time"
"github.com/go-redis/redis/v7"
"github.com/syndtr/goleveldb/leveldb"
)
var manager *Manager
// Manager is the nosql connection manager
type Manager struct {
mutex sync.Mutex
RedisConnections map[string]*redisClientHolder
LevelDBConnections map[string]*levelDBHolder
}
type redisClientHolder struct {
redis.UniversalClient
name []string
count int64
}
func (r *redisClientHolder) Close() error {
return manager.CloseRedisClient(r.name[0])
}
type levelDBHolder struct {
name []string
count int64
db *leveldb.DB
}
func init() {
_ = GetManager()
}
// GetManager returns a Manager and initializes one as singleton is there's none yet
func GetManager() *Manager {
if manager == nil {
manager = &Manager{
RedisConnections: make(map[string]*redisClientHolder),
LevelDBConnections: make(map[string]*levelDBHolder),
}
}
return manager
}
func valToTimeDuration(vs []string) (result time.Duration) {
var err error
for _, v := range vs {
result, err = time.ParseDuration(v)
if err != nil {
var val int
val, err = strconv.Atoi(v)
result = time.Duration(val)
}
if err == nil {
return
}
}
return
}

View File

@ -0,0 +1,151 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import (
"path"
"strconv"
"strings"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
)
// CloseLevelDB closes a levelDB
func (m *Manager) CloseLevelDB(connection string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
db, ok := m.LevelDBConnections[connection]
if !ok {
connection = ToLevelDBURI(connection).String()
db, ok = m.LevelDBConnections[connection]
}
if !ok {
return nil
}
db.count--
if db.count > 0 {
return nil
}
for _, name := range db.name {
delete(m.LevelDBConnections, name)
}
return db.db.Close()
}
// GetLevelDB gets a levelDB for a particular connection
func (m *Manager) GetLevelDB(connection string) (*leveldb.DB, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
db, ok := m.LevelDBConnections[connection]
if ok {
db.count++
return db.db, nil
}
dataDir := connection
uri := ToLevelDBURI(connection)
db = &levelDBHolder{
name: []string{connection, uri.String()},
}
dataDir = path.Join(uri.Host, uri.Path)
opts := &opt.Options{}
for k, v := range uri.Query() {
switch replacer.Replace(strings.ToLower(k)) {
case "blockcachecapacity":
opts.BlockCacheCapacity, _ = strconv.Atoi(v[0])
case "blockcacheevictremoved":
opts.BlockCacheEvictRemoved, _ = strconv.ParseBool(v[0])
case "blockrestartinterval":
opts.BlockRestartInterval, _ = strconv.Atoi(v[0])
case "blocksize":
opts.BlockSize, _ = strconv.Atoi(v[0])
case "compactionexpandlimitfactor":
opts.CompactionExpandLimitFactor, _ = strconv.Atoi(v[0])
case "compactiongpoverlapsfactor":
opts.CompactionGPOverlapsFactor, _ = strconv.Atoi(v[0])
case "compactionl0trigger":
opts.CompactionL0Trigger, _ = strconv.Atoi(v[0])
case "compactionsourcelimitfactor":
opts.CompactionSourceLimitFactor, _ = strconv.Atoi(v[0])
case "compactiontablesize":
opts.CompactionTableSize, _ = strconv.Atoi(v[0])
case "compactiontablesizemultiplier":
opts.CompactionTableSizeMultiplier, _ = strconv.ParseFloat(v[0], 64)
case "compactiontablesizemultiplierperlevel":
for _, val := range v {
f, _ := strconv.ParseFloat(val, 64)
opts.CompactionTableSizeMultiplierPerLevel = append(opts.CompactionTableSizeMultiplierPerLevel, f)
}
case "compactiontotalsize":
opts.CompactionTotalSize, _ = strconv.Atoi(v[0])
case "compactiontotalsizemultiplier":
opts.CompactionTotalSizeMultiplier, _ = strconv.ParseFloat(v[0], 64)
case "compactiontotalsizemultiplierperlevel":
for _, val := range v {
f, _ := strconv.ParseFloat(val, 64)
opts.CompactionTotalSizeMultiplierPerLevel = append(opts.CompactionTotalSizeMultiplierPerLevel, f)
}
case "compression":
val, _ := strconv.Atoi(v[0])
opts.Compression = opt.Compression(val)
case "disablebufferpool":
opts.DisableBufferPool, _ = strconv.ParseBool(v[0])
case "disableblockcache":
opts.DisableBlockCache, _ = strconv.ParseBool(v[0])
case "disablecompactionbackoff":
opts.DisableCompactionBackoff, _ = strconv.ParseBool(v[0])
case "disablelargebatchtransaction":
opts.DisableLargeBatchTransaction, _ = strconv.ParseBool(v[0])
case "errorifexist":
opts.ErrorIfExist, _ = strconv.ParseBool(v[0])
case "errorifmissing":
opts.ErrorIfMissing, _ = strconv.ParseBool(v[0])
case "iteratorsamplingrate":
opts.IteratorSamplingRate, _ = strconv.Atoi(v[0])
case "nosync":
opts.NoSync, _ = strconv.ParseBool(v[0])
case "nowritemerge":
opts.NoWriteMerge, _ = strconv.ParseBool(v[0])
case "openfilescachecapacity":
opts.OpenFilesCacheCapacity, _ = strconv.Atoi(v[0])
case "readonly":
opts.ReadOnly, _ = strconv.ParseBool(v[0])
case "strict":
val, _ := strconv.Atoi(v[0])
opts.Strict = opt.Strict(val)
case "writebuffer":
opts.WriteBuffer, _ = strconv.Atoi(v[0])
case "writel0pausetrigger":
opts.WriteL0PauseTrigger, _ = strconv.Atoi(v[0])
case "writel0slowdowntrigger":
opts.WriteL0SlowdownTrigger, _ = strconv.Atoi(v[0])
case "clientname":
db.name = append(db.name, v[0])
}
}
var err error
db.db, err = leveldb.OpenFile(dataDir, opts)
if err != nil {
if !errors.IsCorrupted(err) {
return nil, err
}
db.db, err = leveldb.RecoverFile(dataDir, opts)
if err != nil {
return nil, err
}
}
for _, name := range db.name {
m.LevelDBConnections[name] = db
}
db.count++
return db.db, nil
}

View File

@ -0,0 +1,205 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import (
"crypto/tls"
"path"
"strconv"
"strings"
"github.com/go-redis/redis/v7"
)
var replacer = strings.NewReplacer("_", "", "-", "")
// CloseRedisClient closes a redis client
func (m *Manager) CloseRedisClient(connection string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
client, ok := m.RedisConnections[connection]
if !ok {
connection = ToRedisURI(connection).String()
client, ok = m.RedisConnections[connection]
}
if !ok {
return nil
}
client.count--
if client.count > 0 {
return nil
}
for _, name := range client.name {
delete(m.RedisConnections, name)
}
return client.UniversalClient.Close()
}
// GetRedisClient gets a redis client for a particular connection
func (m *Manager) GetRedisClient(connection string) redis.UniversalClient {
m.mutex.Lock()
defer m.mutex.Unlock()
client, ok := m.RedisConnections[connection]
if ok {
client.count++
return client
}
uri := ToRedisURI(connection)
client, ok = m.RedisConnections[uri.String()]
if ok {
client.count++
return client
}
client = &redisClientHolder{
name: []string{connection, uri.String()},
}
opts := &redis.UniversalOptions{}
tlsConfig := &tls.Config{}
// Handle username/password
if password, ok := uri.User.Password(); ok {
opts.Password = password
// Username does not appear to be handled by redis.Options
opts.Username = uri.User.Username()
} else if uri.User.Username() != "" {
// assume this is the password
opts.Password = uri.User.Username()
}
// Now handle the uri query sets
for k, v := range uri.Query() {
switch replacer.Replace(strings.ToLower(k)) {
case "addr":
opts.Addrs = append(opts.Addrs, v...)
case "addrs":
opts.Addrs = append(opts.Addrs, strings.Split(v[0], ",")...)
case "username":
opts.Username = v[0]
case "password":
opts.Password = v[0]
case "database":
fallthrough
case "db":
opts.DB, _ = strconv.Atoi(v[0])
case "maxretries":
opts.MaxRetries, _ = strconv.Atoi(v[0])
case "minretrybackoff":
opts.MinRetryBackoff = valToTimeDuration(v)
case "maxretrybackoff":
opts.MaxRetryBackoff = valToTimeDuration(v)
case "timeout":
timeout := valToTimeDuration(v)
if timeout != 0 {
if opts.DialTimeout == 0 {
opts.DialTimeout = timeout
}
if opts.ReadTimeout == 0 {
opts.ReadTimeout = timeout
}
}
case "dialtimeout":
opts.DialTimeout = valToTimeDuration(v)
case "readtimeout":
opts.ReadTimeout = valToTimeDuration(v)
case "writetimeout":
opts.WriteTimeout = valToTimeDuration(v)
case "poolsize":
opts.PoolSize, _ = strconv.Atoi(v[0])
case "minidleconns":
opts.MinIdleConns, _ = strconv.Atoi(v[0])
case "pooltimeout":
opts.PoolTimeout = valToTimeDuration(v)
case "idletimeout":
opts.IdleTimeout = valToTimeDuration(v)
case "idlecheckfrequency":
opts.IdleCheckFrequency = valToTimeDuration(v)
case "maxredirects":
opts.MaxRedirects, _ = strconv.Atoi(v[0])
case "readonly":
opts.ReadOnly, _ = strconv.ParseBool(v[0])
case "routebylatency":
opts.RouteByLatency, _ = strconv.ParseBool(v[0])
case "routerandomly":
opts.RouteRandomly, _ = strconv.ParseBool(v[0])
case "sentinelmasterid":
fallthrough
case "mastername":
opts.MasterName = v[0]
case "skipverify":
fallthrough
case "insecureskipverify":
insecureSkipVerify, _ := strconv.ParseBool(v[0])
tlsConfig.InsecureSkipVerify = insecureSkipVerify
case "clientname":
client.name = append(client.name, v[0])
}
}
switch uri.Scheme {
case "redis+sentinels":
fallthrough
case "rediss+sentinel":
opts.TLSConfig = tlsConfig
fallthrough
case "redis+sentinel":
if uri.Host != "" {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
opts.DB = db
}
}
client.UniversalClient = redis.NewFailoverClient(opts.Failover())
case "redis+clusters":
fallthrough
case "rediss+cluster":
opts.TLSConfig = tlsConfig
fallthrough
case "redis+cluster":
if uri.Host != "" {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
opts.DB = db
}
}
client.UniversalClient = redis.NewClusterClient(opts.Cluster())
case "redis+socket":
simpleOpts := opts.Simple()
simpleOpts.Network = "unix"
simpleOpts.Addr = path.Join(uri.Host, uri.Path)
client.UniversalClient = redis.NewClient(simpleOpts)
case "rediss":
opts.TLSConfig = tlsConfig
fallthrough
case "redis":
if uri.Host != "" {
opts.Addrs = append(opts.Addrs, strings.Split(uri.Host, ",")...)
}
if uri.Path != "" {
if db, err := strconv.Atoi(uri.Path); err == nil {
opts.DB = db
}
}
client.UniversalClient = redis.NewClient(opts.Simple())
default:
return nil
}
for _, name := range client.name {
m.RedisConnections[name] = client
}
client.count++
return client
}

102
modules/nosql/redis.go Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import (
"net/url"
"strconv"
"strings"
)
// The file contains common redis connection functions
// ToRedisURI converts old style connections to a RedisURI
//
// A RedisURI matches the pattern:
//
// redis://[username:password@]host[:port][/database][?[option=value]*]
// rediss://[username:password@]host[:port][/database][?[option=value]*]
// redis+socket://[username:password@]path[/database][?[option=value]*]
// redis+sentinel://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*]
// redis+cluster://[password@]host1 [: port1][, host2 [:port2]][, hostN [:portN]][/ database][?[option=value]*]
//
// We have previously used a URI like:
// addrs=127.0.0.1:6379 db=0
// network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180
//
// We need to convert this old style to the new style
func ToRedisURI(connection string) *url.URL {
uri, err := url.Parse(connection)
if err == nil && strings.HasPrefix(uri.Scheme, "redis") {
// OK we're going to assume that this is a reasonable redis URI
return uri
}
// Let's set a nice default
uri, _ = url.Parse("redis://127.0.0.1:6379/0")
network := "tcp"
query := uri.Query()
// OK so there are two types: Space delimited and Comma delimited
// Let's assume that we have a space delimited string - as this is the most common
fields := strings.Fields(connection)
if len(fields) == 1 {
// It's a comma delimited string, then...
fields = strings.Split(connection, ",")
}
for _, f := range fields {
items := strings.SplitN(f, "=", 2)
if len(items) < 2 {
continue
}
switch strings.ToLower(items[0]) {
case "network":
if items[1] == "unix" {
uri.Scheme = "redis+socket"
}
network = items[1]
case "addrs":
uri.Host = items[1]
// now we need to handle the clustering
if strings.Contains(items[1], ",") && network == "tcp" {
uri.Scheme = "redis+cluster"
}
case "addr":
uri.Host = items[1]
case "password":
uri.User = url.UserPassword(uri.User.Username(), items[1])
case "username":
password, set := uri.User.Password()
if !set {
uri.User = url.User(items[1])
} else {
uri.User = url.UserPassword(items[1], password)
}
case "db":
uri.Path = "/" + items[1]
case "idle_timeout":
_, err := strconv.Atoi(items[1])
if err == nil {
query.Add("idle_timeout", items[1]+"s")
} else {
query.Add("idle_timeout", items[1])
}
default:
// Other options become query params
query.Add(items[0], items[1])
}
}
// Finally we need to fix up the Host if we have a unix port
if uri.Scheme == "redis+socket" {
query.Set("db", uri.Path)
uri.Path = uri.Host
uri.Host = ""
}
uri.RawQuery = query.Encode()
return uri
}

View File

@ -0,0 +1,35 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package nosql
import (
"testing"
)
func TestToRedisURI(t *testing.T) {
tests := []struct {
name string
connection string
want string
}{
{
name: "old_default",
connection: "addrs=127.0.0.1:6379 db=0",
want: "redis://127.0.0.1:6379/0",
},
{
name: "old_macaron_session_default",
connection: "network=tcp,addr=127.0.0.1:6379,password=macaron,db=0,pool_size=100,idle_timeout=180",
want: "redis://:macaron@127.0.0.1:6379/0?idle_timeout=180s&pool_size=100",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToRedisURI(tt.connection); got == nil || got.String() != tt.want {
t.Errorf(`ToRedisURI(%q) = %s, want %s`, tt.connection, got.String(), tt.want)
}
})
}
}

View File

@ -5,6 +5,8 @@
package queue
import (
"code.gitea.io/gitea/modules/nosql"
"gitea.com/lunny/levelqueue"
)
@ -15,6 +17,8 @@ const LevelQueueType Type = "level"
type LevelQueueConfiguration struct {
ByteFIFOQueueConfiguration
DataDir string
ConnectionString string
QueueName string
}
// LevelQueue implements a disk library queue
@ -30,7 +34,11 @@ func NewLevelQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue, error)
}
config := configInterface.(LevelQueueConfiguration)
byteFIFO, err := NewLevelQueueByteFIFO(config.DataDir)
if len(config.ConnectionString) == 0 {
config.ConnectionString = config.DataDir
}
byteFIFO, err := NewLevelQueueByteFIFO(config.ConnectionString, config.QueueName)
if err != nil {
return nil, err
}
@ -52,16 +60,23 @@ var _ (ByteFIFO) = &LevelQueueByteFIFO{}
// LevelQueueByteFIFO represents a ByteFIFO formed from a LevelQueue
type LevelQueueByteFIFO struct {
internal *levelqueue.Queue
connection string
}
// NewLevelQueueByteFIFO creates a ByteFIFO formed from a LevelQueue
func NewLevelQueueByteFIFO(dataDir string) (*LevelQueueByteFIFO, error) {
internal, err := levelqueue.Open(dataDir)
func NewLevelQueueByteFIFO(connection, prefix string) (*LevelQueueByteFIFO, error) {
db, err := nosql.GetManager().GetLevelDB(connection)
if err != nil {
return nil, err
}
internal, err := levelqueue.NewQueue(db, []byte(prefix), false)
if err != nil {
return nil, err
}
return &LevelQueueByteFIFO{
connection: connection,
internal: internal,
}, nil
}
@ -87,7 +102,9 @@ func (fifo *LevelQueueByteFIFO) Pop() ([]byte, error) {
// Close this fifo
func (fifo *LevelQueueByteFIFO) Close() error {
return fifo.internal.Close()
err := fifo.internal.Close()
_ = nosql.GetManager().CloseLevelDB(fifo.connection)
return err
}
// Len returns the length of the fifo

View File

@ -5,12 +5,10 @@
package queue
import (
"errors"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/nosql"
"github.com/go-redis/redis"
"github.com/go-redis/redis/v7"
)
// RedisQueueType is the type for redis queue
@ -75,10 +73,7 @@ type RedisByteFIFO struct {
// RedisByteFIFOConfiguration is the configuration for the RedisByteFIFO
type RedisByteFIFOConfiguration struct {
Network string
Addresses string
Password string
DBIndex int
ConnectionString string
QueueName string
}
@ -87,21 +82,7 @@ func NewRedisByteFIFO(config RedisByteFIFOConfiguration) (*RedisByteFIFO, error)
fifo := &RedisByteFIFO{
queueName: config.QueueName,
}
dbs := strings.Split(config.Addresses, ",")
if len(dbs) == 0 {
return nil, errors.New("no redis host specified")
} else if len(dbs) == 1 {
fifo.client = redis.NewClient(&redis.Options{
Network: config.Network,
Addr: strings.TrimSpace(dbs[0]), // use default Addr
Password: config.Password, // no password set
DB: config.DBIndex, // use default DB
})
} else {
fifo.client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: dbs,
})
}
fifo.client = nosql.GetManager().GetRedisClient(config.ConnectionString)
if err := fifo.client.Ping().Err(); err != nil {
return nil, err
}

View File

@ -5,6 +5,8 @@
package queue
import (
"code.gitea.io/gitea/modules/nosql"
"gitea.com/lunny/levelqueue"
)
@ -15,6 +17,8 @@ const LevelUniqueQueueType Type = "unique-level"
type LevelUniqueQueueConfiguration struct {
ByteFIFOQueueConfiguration
DataDir string
ConnectionString string
QueueName string
}
// LevelUniqueQueue implements a disk library queue
@ -34,7 +38,11 @@ func NewLevelUniqueQueue(handle HandlerFunc, cfg, exemplar interface{}) (Queue,
}
config := configInterface.(LevelUniqueQueueConfiguration)
byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.DataDir)
if len(config.ConnectionString) == 0 {
config.ConnectionString = config.DataDir
}
byteFIFO, err := NewLevelUniqueQueueByteFIFO(config.ConnectionString, config.QueueName)
if err != nil {
return nil, err
}
@ -56,16 +64,23 @@ var _ (UniqueByteFIFO) = &LevelUniqueQueueByteFIFO{}
// LevelUniqueQueueByteFIFO represents a ByteFIFO formed from a LevelUniqueQueue
type LevelUniqueQueueByteFIFO struct {
internal *levelqueue.UniqueQueue
connection string
}
// NewLevelUniqueQueueByteFIFO creates a new ByteFIFO formed from a LevelUniqueQueue
func NewLevelUniqueQueueByteFIFO(dataDir string) (*LevelUniqueQueueByteFIFO, error) {
internal, err := levelqueue.OpenUnique(dataDir)
func NewLevelUniqueQueueByteFIFO(connection, prefix string) (*LevelUniqueQueueByteFIFO, error) {
db, err := nosql.GetManager().GetLevelDB(connection)
if err != nil {
return nil, err
}
internal, err := levelqueue.NewUniqueQueue(db, []byte(prefix), []byte(prefix+"-unique"), false)
if err != nil {
return nil, err
}
return &LevelUniqueQueueByteFIFO{
connection: connection,
internal: internal,
}, nil
}
@ -96,7 +111,9 @@ func (fifo *LevelUniqueQueueByteFIFO) Has(data []byte) (bool, error) {
// Close this fifo
func (fifo *LevelUniqueQueueByteFIFO) Close() error {
return fifo.internal.Close()
err := fifo.internal.Close()
_ = nosql.GetManager().CloseLevelDB(fifo.connection)
return err
}
func init() {

View File

@ -4,7 +4,7 @@
package queue
import "github.com/go-redis/redis"
import "github.com/go-redis/redis/v7"
// RedisUniqueQueueType is the type for redis queue
const RedisUniqueQueueType Type = "unique-redis"

View File

@ -1,5 +1,6 @@
// Copyright 2013 Beego Authors
// Copyright 2014 The Macaron Authors
// Copyright 2020 The Gitea Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
@ -17,19 +18,18 @@ package session
import (
"fmt"
"strings"
"sync"
"time"
"code.gitea.io/gitea/modules/nosql"
"gitea.com/macaron/session"
"github.com/go-redis/redis"
"github.com/unknwon/com"
"gopkg.in/ini.v1"
"github.com/go-redis/redis/v7"
)
// RedisStore represents a redis session store implementation.
type RedisStore struct {
c *redis.Client
c redis.UniversalClient
prefix, sid string
duration time.Duration
lock sync.RWMutex
@ -37,7 +37,7 @@ type RedisStore struct {
}
// NewRedisStore creates and returns a redis session store.
func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore {
return &RedisStore{
c: c,
prefix: prefix,
@ -104,7 +104,7 @@ func (s *RedisStore) Flush() error {
// RedisProvider represents a redis session provider implementation.
type RedisProvider struct {
c *redis.Client
c redis.UniversalClient
duration time.Duration
prefix string
}
@ -117,39 +117,16 @@ func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
return err
}
cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1)))
if err != nil {
return err
}
uri := nosql.ToRedisURI(configs)
opt := &redis.Options{
Network: "tcp",
}
for k, v := range cfg.Section("").KeysHash() {
for k, v := range uri.Query() {
switch k {
case "network":
opt.Network = v
case "addr":
opt.Addr = v
case "password":
opt.Password = v
case "db":
opt.DB = com.StrTo(v).MustInt()
case "pool_size":
opt.PoolSize = com.StrTo(v).MustInt()
case "idle_timeout":
opt.IdleTimeout, err = time.ParseDuration(v + "s")
if err != nil {
return fmt.Errorf("error parsing idle timeout: %v", err)
}
case "prefix":
p.prefix = v
default:
return fmt.Errorf("session/redis: unsupported option '%s'", k)
p.prefix = v[0]
}
}
p.c = redis.NewClient(opt)
p.c = nosql.GetManager().GetRedisClient(uri.String())
return p.c.Ping().Err()
}
@ -228,11 +205,11 @@ func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err
// Count counts and returns number of sessions.
func (p *RedisProvider) Count() int {
return int(p.c.DbSize().Val())
return int(p.c.DBSize().Val())
}
// GC calls GC to clean expired sessions.
func (_ *RedisProvider) GC() {}
func (*RedisProvider) GC() {}
func init() {
session.Register("redis", &RedisProvider{})

View File

@ -15,7 +15,6 @@ import (
mysql "gitea.com/macaron/session/mysql"
nodb "gitea.com/macaron/session/nodb"
postgres "gitea.com/macaron/session/postgres"
redis "gitea.com/macaron/session/redis"
)
// VirtualSessionProvider represents a shadowed session provider implementation.
@ -40,7 +39,7 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
case "file":
o.provider = &session.FileProvider{}
case "redis":
o.provider = &redis.RedisProvider{}
o.provider = &RedisProvider{}
case "mysql":
o.provider = &mysql.MysqlProvider{}
case "postgres":

View File

@ -1 +0,0 @@
ignore

View File

@ -1 +0,0 @@
ignore

View File

@ -1,19 +0,0 @@
sudo: false
language: go
services:
- redis-server
go:
- 1.9.x
- 1.10.x
- 1.11.x
- tip
matrix:
allow_failures:
- go: tip
install:
- go get github.com/onsi/ginkgo
- go get github.com/onsi/gomega

View File

@ -1,25 +0,0 @@
# Changelog
## Unreleased
- Cluster and Ring pipelines process commands for each node in its own goroutine.
## 6.14
- Added Options.MinIdleConns.
- Added Options.MaxConnAge.
- PoolStats.FreeConns is renamed to PoolStats.IdleConns.
- Add Client.Do to simplify creating custom commands.
- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers.
- Lower memory usage.
## v6.13
- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards.
- Cluster client was optimized to use much less memory when reloading cluster state.
- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead.
- Dialer.KeepAlive is set to 5 minutes by default.
## v6.12
- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup

View File

@ -1,89 +0,0 @@
package internal
import (
"io"
"net"
"strings"
"github.com/go-redis/redis/internal/proto"
)
func IsRetryableError(err error, retryTimeout bool) bool {
if err == nil {
return false
}
if err == io.EOF {
return true
}
if netErr, ok := err.(net.Error); ok {
if netErr.Timeout() {
return retryTimeout
}
return true
}
s := err.Error()
if s == "ERR max number of clients reached" {
return true
}
if strings.HasPrefix(s, "LOADING ") {
return true
}
if strings.HasPrefix(s, "READONLY ") {
return true
}
if strings.HasPrefix(s, "CLUSTERDOWN ") {
return true
}
return false
}
func IsRedisError(err error) bool {
_, ok := err.(proto.RedisError)
return ok
}
func IsBadConn(err error, allowTimeout bool) bool {
if err == nil {
return false
}
if IsRedisError(err) {
// #790
return IsReadOnlyError(err)
}
if allowTimeout {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
return false
}
}
return true
}
func IsMovedError(err error) (moved bool, ask bool, addr string) {
if !IsRedisError(err) {
return
}
s := err.Error()
if strings.HasPrefix(s, "MOVED ") {
moved = true
} else if strings.HasPrefix(s, "ASK ") {
ask = true
} else {
return
}
ind := strings.LastIndex(s, " ")
if ind == -1 {
return false, false, ""
}
addr = s[ind+1:]
return
}
func IsLoadingError(err error) bool {
return strings.HasPrefix(err.Error(), "LOADING ")
}
func IsReadOnlyError(err error) bool {
return strings.HasPrefix(err.Error(), "READONLY ")
}

View File

@ -1,15 +0,0 @@
package internal
import (
"fmt"
"log"
)
var Logger *log.Logger
func Logf(s string, args ...interface{}) {
if Logger == nil {
return
}
Logger.Output(2, fmt.Sprintf(s, args...))
}

View File

@ -1,93 +0,0 @@
package pool
import (
"net"
"sync/atomic"
"time"
"github.com/go-redis/redis/internal/proto"
)
var noDeadline = time.Time{}
type Conn struct {
netConn net.Conn
rd *proto.Reader
rdLocked bool
wr *proto.Writer
InitedAt time.Time
pooled bool
usedAt atomic.Value
}
func NewConn(netConn net.Conn) *Conn {
cn := &Conn{
netConn: netConn,
}
cn.rd = proto.NewReader(netConn)
cn.wr = proto.NewWriter(netConn)
cn.SetUsedAt(time.Now())
return cn
}
func (cn *Conn) UsedAt() time.Time {
return cn.usedAt.Load().(time.Time)
}
func (cn *Conn) SetUsedAt(tm time.Time) {
cn.usedAt.Store(tm)
}
func (cn *Conn) SetNetConn(netConn net.Conn) {
cn.netConn = netConn
cn.rd.Reset(netConn)
cn.wr.Reset(netConn)
}
func (cn *Conn) setReadTimeout(timeout time.Duration) error {
now := time.Now()
cn.SetUsedAt(now)
if timeout > 0 {
return cn.netConn.SetReadDeadline(now.Add(timeout))
}
return cn.netConn.SetReadDeadline(noDeadline)
}
func (cn *Conn) setWriteTimeout(timeout time.Duration) error {
now := time.Now()
cn.SetUsedAt(now)
if timeout > 0 {
return cn.netConn.SetWriteDeadline(now.Add(timeout))
}
return cn.netConn.SetWriteDeadline(noDeadline)
}
func (cn *Conn) Write(b []byte) (int, error) {
return cn.netConn.Write(b)
}
func (cn *Conn) RemoteAddr() net.Addr {
return cn.netConn.RemoteAddr()
}
func (cn *Conn) WithReader(timeout time.Duration, fn func(rd *proto.Reader) error) error {
_ = cn.setReadTimeout(timeout)
return fn(cn.rd)
}
func (cn *Conn) WithWriter(timeout time.Duration, fn func(wr *proto.Writer) error) error {
_ = cn.setWriteTimeout(timeout)
firstErr := fn(cn.wr)
err := cn.wr.Flush()
if err != nil && firstErr == nil {
firstErr = err
}
return firstErr
}
func (cn *Conn) Close() error {
return cn.netConn.Close()
}

View File

@ -1,53 +0,0 @@
package pool
type SingleConnPool struct {
cn *Conn
}
var _ Pooler = (*SingleConnPool)(nil)
func NewSingleConnPool(cn *Conn) *SingleConnPool {
return &SingleConnPool{
cn: cn,
}
}
func (p *SingleConnPool) NewConn() (*Conn, error) {
panic("not implemented")
}
func (p *SingleConnPool) CloseConn(*Conn) error {
panic("not implemented")
}
func (p *SingleConnPool) Get() (*Conn, error) {
return p.cn, nil
}
func (p *SingleConnPool) Put(cn *Conn) {
if p.cn != cn {
panic("p.cn != cn")
}
}
func (p *SingleConnPool) Remove(cn *Conn) {
if p.cn != cn {
panic("p.cn != cn")
}
}
func (p *SingleConnPool) Len() int {
return 1
}
func (p *SingleConnPool) IdleLen() int {
return 0
}
func (p *SingleConnPool) Stats() *Stats {
return nil
}
func (p *SingleConnPool) Close() error {
return nil
}

View File

@ -1,29 +0,0 @@
package internal
import "github.com/go-redis/redis/internal/util"
func ToLower(s string) string {
if isLower(s) {
return s
}
b := make([]byte, len(s))
for i := range b {
c := s[i]
if c >= 'A' && c <= 'Z' {
c += 'a' - 'A'
}
b[i] = c
}
return util.BytesToString(b)
}
func isLower(s string) bool {
for i := 0; i < len(s); i++ {
c := s[i]
if c >= 'A' && c <= 'Z' {
return false
}
}
return true
}

File diff suppressed because it is too large Load Diff

15
vendor/github.com/go-redis/redis/v7/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,15 @@
run:
concurrency: 8
deadline: 5m
tests: false
linters:
enable-all: true
disable:
- funlen
- gochecknoglobals
- gocognit
- goconst
- godox
- gosec
- maligned
- wsl

Some files were not shown because too many files have changed in this diff Show More