d28f005552
Fix limit for databases other than sqlite go mod tidy && go mod vendor Remove unneeded break statements Make everything work with the new xorm version Fix xorm logging Fix lint Fix redis init Fix using id field Fix database init for testing Change default database log level Add xorm logger Use const for postgres go mod tidy Merge branch 'master' into update/xorm # Conflicts: # go.mod # go.sum # vendor/modules.txt go mod vendor Fix loading fixtures for postgres Go mod vendor1 Update xorm to version 1 Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/323
243 lines
6.2 KiB
Go
243 lines
6.2 KiB
Go
package redis
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v7/internal/pool"
|
|
)
|
|
|
|
// Limiter is the interface of a rate limiter or a circuit breaker.
|
|
type Limiter interface {
|
|
// Allow returns nil if operation is allowed or an error otherwise.
|
|
// If operation is allowed client must ReportResult of the operation
|
|
// whether it is a success or a failure.
|
|
Allow() error
|
|
// ReportResult reports the result of the previously allowed operation.
|
|
// nil indicates a success, non-nil error usually indicates a failure.
|
|
ReportResult(result error)
|
|
}
|
|
|
|
type Options struct {
|
|
// The network type, either tcp or unix.
|
|
// Default is tcp.
|
|
Network string
|
|
// host:port address.
|
|
Addr string
|
|
|
|
// Dialer creates new network connection and has priority over
|
|
// Network and Addr options.
|
|
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
// Hook that is called when new connection is established.
|
|
OnConnect func(*Conn) error
|
|
|
|
// Optional password. Must match the password specified in the
|
|
// requirepass server configuration option.
|
|
Password string
|
|
// Database to be selected after connecting to the server.
|
|
DB int
|
|
|
|
// Maximum number of retries before giving up.
|
|
// Default is to not retry failed commands.
|
|
MaxRetries int
|
|
// Minimum backoff between each retry.
|
|
// Default is 8 milliseconds; -1 disables backoff.
|
|
MinRetryBackoff time.Duration
|
|
// Maximum backoff between each retry.
|
|
// Default is 512 milliseconds; -1 disables backoff.
|
|
MaxRetryBackoff time.Duration
|
|
|
|
// Dial timeout for establishing new connections.
|
|
// Default is 5 seconds.
|
|
DialTimeout time.Duration
|
|
// Timeout for socket reads. If reached, commands will fail
|
|
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
|
|
// Default is 3 seconds.
|
|
ReadTimeout time.Duration
|
|
// Timeout for socket writes. If reached, commands will fail
|
|
// with a timeout instead of blocking.
|
|
// Default is ReadTimeout.
|
|
WriteTimeout time.Duration
|
|
|
|
// Maximum number of socket connections.
|
|
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
|
|
PoolSize int
|
|
// Minimum number of idle connections which is useful when establishing
|
|
// new connection is slow.
|
|
MinIdleConns int
|
|
// Connection age at which client retires (closes) the connection.
|
|
// Default is to not close aged connections.
|
|
MaxConnAge time.Duration
|
|
// Amount of time client waits for connection if all connections
|
|
// are busy before returning an error.
|
|
// Default is ReadTimeout + 1 second.
|
|
PoolTimeout time.Duration
|
|
// Amount of time after which client closes idle connections.
|
|
// Should be less than server's timeout.
|
|
// Default is 5 minutes. -1 disables idle timeout check.
|
|
IdleTimeout time.Duration
|
|
// Frequency of idle checks made by idle connections reaper.
|
|
// Default is 1 minute. -1 disables idle connections reaper,
|
|
// but idle connections are still discarded by the client
|
|
// if IdleTimeout is set.
|
|
IdleCheckFrequency time.Duration
|
|
|
|
// Enables read only queries on slave nodes.
|
|
readOnly bool
|
|
|
|
// TLS Config to use. When set TLS will be negotiated.
|
|
TLSConfig *tls.Config
|
|
|
|
// Limiter interface used to implemented circuit breaker or rate limiter.
|
|
Limiter Limiter
|
|
}
|
|
|
|
func (opt *Options) init() {
|
|
if opt.Addr == "" {
|
|
opt.Addr = "localhost:6379"
|
|
}
|
|
if opt.Network == "" {
|
|
if strings.HasPrefix(opt.Addr, "/") {
|
|
opt.Network = "unix"
|
|
} else {
|
|
opt.Network = "tcp"
|
|
}
|
|
}
|
|
if opt.Dialer == nil {
|
|
opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
netDialer := &net.Dialer{
|
|
Timeout: opt.DialTimeout,
|
|
KeepAlive: 5 * time.Minute,
|
|
}
|
|
if opt.TLSConfig == nil {
|
|
return netDialer.DialContext(ctx, network, addr)
|
|
}
|
|
return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
|
|
}
|
|
}
|
|
if opt.PoolSize == 0 {
|
|
opt.PoolSize = 10 * runtime.NumCPU()
|
|
}
|
|
if opt.DialTimeout == 0 {
|
|
opt.DialTimeout = 5 * time.Second
|
|
}
|
|
switch opt.ReadTimeout {
|
|
case -1:
|
|
opt.ReadTimeout = 0
|
|
case 0:
|
|
opt.ReadTimeout = 3 * time.Second
|
|
}
|
|
switch opt.WriteTimeout {
|
|
case -1:
|
|
opt.WriteTimeout = 0
|
|
case 0:
|
|
opt.WriteTimeout = opt.ReadTimeout
|
|
}
|
|
if opt.PoolTimeout == 0 {
|
|
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
|
}
|
|
if opt.IdleTimeout == 0 {
|
|
opt.IdleTimeout = 5 * time.Minute
|
|
}
|
|
if opt.IdleCheckFrequency == 0 {
|
|
opt.IdleCheckFrequency = time.Minute
|
|
}
|
|
|
|
if opt.MaxRetries == -1 {
|
|
opt.MaxRetries = 0
|
|
}
|
|
switch opt.MinRetryBackoff {
|
|
case -1:
|
|
opt.MinRetryBackoff = 0
|
|
case 0:
|
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
|
}
|
|
switch opt.MaxRetryBackoff {
|
|
case -1:
|
|
opt.MaxRetryBackoff = 0
|
|
case 0:
|
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
|
}
|
|
}
|
|
|
|
func (opt *Options) clone() *Options {
|
|
clone := *opt
|
|
return &clone
|
|
}
|
|
|
|
// ParseURL parses an URL into Options that can be used to connect to Redis.
|
|
func ParseURL(redisURL string) (*Options, error) {
|
|
o := &Options{Network: "tcp"}
|
|
u, err := url.Parse(redisURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
|
return nil, errors.New("invalid redis URL scheme: " + u.Scheme)
|
|
}
|
|
|
|
if u.User != nil {
|
|
if p, ok := u.User.Password(); ok {
|
|
o.Password = p
|
|
}
|
|
}
|
|
|
|
if len(u.Query()) > 0 {
|
|
return nil, errors.New("no options supported")
|
|
}
|
|
|
|
h, p, err := net.SplitHostPort(u.Host)
|
|
if err != nil {
|
|
h = u.Host
|
|
}
|
|
if h == "" {
|
|
h = "localhost"
|
|
}
|
|
if p == "" {
|
|
p = "6379"
|
|
}
|
|
o.Addr = net.JoinHostPort(h, p)
|
|
|
|
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
|
return r == '/'
|
|
})
|
|
switch len(f) {
|
|
case 0:
|
|
o.DB = 0
|
|
case 1:
|
|
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
|
return nil, fmt.Errorf("invalid redis database number: %q", f[0])
|
|
}
|
|
default:
|
|
return nil, errors.New("invalid redis URL path: " + u.Path)
|
|
}
|
|
|
|
if u.Scheme == "rediss" {
|
|
o.TLSConfig = &tls.Config{ServerName: h}
|
|
}
|
|
return o, nil
|
|
}
|
|
|
|
func newConnPool(opt *Options) *pool.ConnPool {
|
|
return pool.NewConnPool(&pool.Options{
|
|
Dialer: func(ctx context.Context) (net.Conn, error) {
|
|
return opt.Dialer(ctx, opt.Network, opt.Addr)
|
|
},
|
|
PoolSize: opt.PoolSize,
|
|
MinIdleConns: opt.MinIdleConns,
|
|
MaxConnAge: opt.MaxConnAge,
|
|
PoolTimeout: opt.PoolTimeout,
|
|
IdleTimeout: opt.IdleTimeout,
|
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
|
})
|
|
}
|