tile38/internal/controller/controller.go

815 lines
20 KiB
Go
Raw Normal View History

2016-03-05 02:08:16 +03:00
package controller
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
2016-04-01 04:20:42 +03:00
"net"
2016-03-05 02:08:16 +03:00
"os"
2016-03-30 22:58:34 +03:00
"path"
2017-09-30 04:11:05 +03:00
"path/filepath"
2016-03-29 01:50:18 +03:00
"runtime"
2016-12-23 00:52:37 +03:00
"runtime/debug"
"strconv"
2016-03-08 03:37:39 +03:00
"strings"
2016-03-05 02:08:16 +03:00
"sync"
"time"
2016-09-11 17:49:48 +03:00
"github.com/tidwall/buntdb"
"github.com/tidwall/geojson"
2018-10-22 05:08:56 +03:00
"github.com/tidwall/geojson/geometry"
2016-03-28 18:57:41 +03:00
"github.com/tidwall/resp"
"github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/ds"
"github.com/tidwall/tile38/internal/endpoint"
"github.com/tidwall/tile38/internal/expire"
"github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
2016-03-05 02:08:16 +03:00
)
2016-05-24 05:44:25 +03:00
var errOOM = errors.New("OOM command not allowed when used memory > 'maxmemory'")
2018-08-14 03:05:30 +03:00
const goingLive = "going live"
2016-09-11 17:49:48 +03:00
const hookLogPrefix = "hook:log:"
2016-03-05 02:08:16 +03:00
type commandDetailsT struct {
command string
key, id string
field string
value float64
obj geojson.Object
fields []float64
2016-04-02 17:20:30 +03:00
fmap map[string]int
2016-03-05 02:08:16 +03:00
oldObj geojson.Object
oldFields []float64
2016-03-28 18:57:41 +03:00
updated bool
2016-04-02 17:20:30 +03:00
timestamp time.Time
parent bool // when true, only children are forwarded
pattern string // PDEL key pattern
children []*commandDetailsT // for multi actions such as "PDEL"
2016-03-05 02:08:16 +03:00
}
// Controller is a tile38 controller
type Controller struct {
2017-09-30 18:00:29 +03:00
// static values
host string
port int
http bool
2017-09-30 21:06:10 +03:00
dir string
2017-09-30 18:00:29 +03:00
started time.Time
config *Config
2018-04-19 19:25:39 +03:00
epc *endpoint.Manager
2017-09-30 18:00:29 +03:00
// env opts
geomParseOpts geojson.ParseOptions
2017-09-30 18:00:29 +03:00
// atomics
followc aint // counter increases when follow property changes
2017-09-30 18:11:10 +03:00
statsTotalConns aint // counter for total connections
statsTotalCommands aint // counter for total commands
statsExpired aint // item expiration counter
2017-09-30 18:00:29 +03:00
lastShrinkDuration aint
currentShrinkStart atime
stopBackgroundExpiring abool
stopWatchingMemory abool
stopWatchingAutoGC abool
outOfMemory abool
connsmu sync.RWMutex
2017-09-30 21:06:10 +03:00
conns map[*server.Conn]*clientConn
exlistmu sync.RWMutex
exlist []exitem
mu sync.RWMutex
aof *os.File // active aof file
aofsz int // active size of the aof file
qdb *buntdb.DB // hook queue log
qidx uint64 // hook queue log last idx
cols ds.BTree // data collections
2017-09-30 21:06:10 +03:00
expires map[string]map[string]time.Time // synced with cols
2017-10-05 18:20:40 +03:00
follows map[*bytes.Buffer]bool
fcond *sync.Cond
lstack []*commandDetailsT
lives map[*liveBuffer]bool
lcond *sync.Cond
fcup bool // follow caught up
fcuponce bool // follow caught up once
shrinking bool // aof shrinking flag
shrinklog [][]string // aof shrinking log
hooks map[string]*Hook // hook name
hookcols map[string]map[string]*Hook // col key
aofconnM map[net.Conn]bool
luascripts *lScriptMap
luapool *lStatePool
2018-08-14 03:05:30 +03:00
pubsub *pubsub
2018-08-14 03:36:02 +03:00
hookex expire.List
2016-03-05 02:08:16 +03:00
}
// ListenAndServe starts a new tile38 server
func ListenAndServe(host string, port int, dir string, http bool) error {
return ListenAndServeEx(host, port, dir, nil, http)
}
// ListenAndServeEx ...
func ListenAndServeEx(host string, port int, dir string, ln *net.Listener, http bool) error {
if core.AppendFileName == "" {
core.AppendFileName = path.Join(dir, "appendonly.aof")
}
if core.QueueFileName == "" {
core.QueueFileName = path.Join(dir, "queue.db")
}
2016-03-05 02:08:16 +03:00
log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA)
c := &Controller{
2016-03-19 17:16:19 +03:00
host: host,
port: port,
dir: dir,
follows: make(map[*bytes.Buffer]bool),
fcond: sync.NewCond(&sync.Mutex{}),
lives: make(map[*liveBuffer]bool),
lcond: sync.NewCond(&sync.Mutex{}),
hooks: make(map[string]*Hook),
hookcols: make(map[string]map[string]*Hook),
2016-04-01 04:20:42 +03:00
aofconnM: make(map[net.Conn]bool),
2016-07-15 22:22:48 +03:00
expires: make(map[string]map[string]time.Time),
2016-08-26 22:54:19 +03:00
started: time.Now(),
2017-09-30 21:06:10 +03:00
conns: make(map[*server.Conn]*clientConn),
http: http,
2018-08-14 03:05:30 +03:00
pubsub: newPubsub(),
2016-03-05 02:08:16 +03:00
}
2018-08-14 03:36:02 +03:00
c.hookex.Expired = func(item expire.Item) {
switch v := item.(type) {
case *Hook:
2018-08-14 06:27:22 +03:00
c.possiblyExpireHook(v.Name)
2018-08-14 03:36:02 +03:00
}
}
2018-08-14 03:05:30 +03:00
c.epc = endpoint.NewManager(c)
c.luascripts = c.newScriptMap()
c.luapool = c.newPool()
2017-10-05 18:20:40 +03:00
defer c.luapool.Shutdown()
2016-03-05 02:08:16 +03:00
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
2017-09-30 04:11:05 +03:00
var err error
c.config, err = loadConfig(filepath.Join(dir, "config"))
if err != nil {
2016-03-05 02:08:16 +03:00
return err
}
c.geomParseOpts = *geojson.DefaultParseOptions
n, err := strconv.ParseUint(os.Getenv("T38IDXGEOM"), 10, 32)
if err == nil {
c.geomParseOpts.IndexGeometry = int(n)
}
n, err = strconv.ParseUint(os.Getenv("T38IDXMULTI"), 10, 32)
if err == nil {
c.geomParseOpts.IndexChildren = int(n)
}
2018-10-22 05:08:56 +03:00
indexKind := os.Getenv("T38IDXGEOMKIND")
switch indexKind {
default:
log.Errorf("Unknown index kind: %s", indexKind)
case "":
case "None":
c.geomParseOpts.IndexGeometryKind = geometry.None
case "RTree":
c.geomParseOpts.IndexGeometryKind = geometry.RTree
case "RTreeCompressed":
c.geomParseOpts.IndexGeometryKind = geometry.RTreeCompressed
case "QuadTree":
c.geomParseOpts.IndexGeometryKind = geometry.QuadTree
case "QuadTreeCompressed":
c.geomParseOpts.IndexGeometryKind = geometry.QuadTreeCompressed
}
if c.geomParseOpts.IndexGeometryKind == geometry.None {
log.Debugf("Geom indexing: %s",
c.geomParseOpts.IndexGeometryKind,
)
} else {
log.Debugf("Geom indexing: %s (%d points)",
c.geomParseOpts.IndexGeometryKind,
c.geomParseOpts.IndexGeometry,
)
}
log.Debugf("Multi indexing: RTree (%d points)", c.geomParseOpts.IndexChildren)
2016-09-11 17:49:48 +03:00
// load the queue before the aof
qdb, err := buntdb.Open(core.QueueFileName)
2016-09-11 17:49:48 +03:00
if err != nil {
return err
}
var qidx uint64
if err := qdb.View(func(tx *buntdb.Tx) error {
val, err := tx.Get("hook:idx")
if err != nil {
if err == buntdb.ErrNotFound {
return nil
}
return err
}
qidx = stringToUint64(val)
return nil
}); err != nil {
return err
}
err = qdb.CreateIndex("hooks", hookLogPrefix+"*", buntdb.IndexJSONCaseSensitive("hook"))
if err != nil {
return err
}
2017-09-30 21:06:10 +03:00
2016-09-11 17:49:48 +03:00
c.qdb = qdb
c.qidx = qidx
2016-03-30 22:58:34 +03:00
if err := c.migrateAOF(); err != nil {
return err
}
if core.AppendOnly == "yes" {
f, err := os.OpenFile(core.AppendFileName, os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
}
c.aof = f
if err := c.loadAOF(); err != nil {
return err
}
2016-03-05 02:08:16 +03:00
}
c.fillExpiresList()
2017-09-30 04:11:05 +03:00
if c.config.followHost() != "" {
2017-09-30 17:34:08 +03:00
go c.follow(c.config.followHost(), c.config.followPort(), c.followc.get())
2016-03-05 02:08:16 +03:00
}
defer func() {
2017-09-30 17:34:08 +03:00
c.followc.add(1) // this will force any follow communication to die
2016-03-05 02:08:16 +03:00
}()
go c.processLives()
go c.watchOutOfMemory()
go c.watchLuaStatePool()
go c.watchAutoGC()
2016-07-15 22:22:48 +03:00
go c.backgroundExpiring()
2016-05-24 05:44:25 +03:00
defer func() {
2017-09-30 17:29:03 +03:00
c.stopBackgroundExpiring.set(true)
c.stopWatchingMemory.set(true)
c.stopWatchingAutoGC.set(true)
2016-05-24 05:44:25 +03:00
}()
handler := func(conn *server.Conn, msg *server.Message, rd *server.PipelineReader, w io.Writer, websocket bool) error {
2017-09-30 18:00:29 +03:00
c.connsmu.RLock()
2017-09-30 21:06:10 +03:00
if cc, ok := c.conns[conn]; ok {
2017-09-30 18:00:29 +03:00
cc.last.set(time.Now())
}
2017-09-30 18:00:29 +03:00
c.connsmu.RUnlock()
2017-09-30 17:34:08 +03:00
c.statsTotalCommands.add(1)
2016-03-28 18:57:41 +03:00
err := c.handleInputCommand(conn, msg, w)
2016-03-05 02:08:16 +03:00
if err != nil {
2018-08-14 03:05:30 +03:00
if err.Error() == goingLive {
2016-04-01 02:26:36 +03:00
return c.goLive(err, conn, rd, msg, websocket)
2016-03-05 02:08:16 +03:00
}
return err
}
return nil
}
2016-03-08 03:37:39 +03:00
protected := func() bool {
2016-03-08 16:11:03 +03:00
if core.ProtectedMode == "no" {
2016-03-08 03:37:39 +03:00
// --protected-mode no
return false
}
if host != "" && host != "127.0.0.1" && host != "::1" && host != "localhost" {
// -h address
return false
}
2017-09-30 04:11:05 +03:00
is := c.config.protectedMode() != "no" && c.config.requirePass() == ""
2016-03-08 03:37:39 +03:00
return is
}
2017-09-30 17:29:03 +03:00
2017-09-30 18:00:29 +03:00
var clientID aint
2016-08-26 22:54:19 +03:00
opened := func(conn *server.Conn) {
2017-09-30 04:11:05 +03:00
if c.config.keepAlive() > 0 {
err := conn.SetKeepAlive(
2017-09-30 04:11:05 +03:00
time.Duration(c.config.keepAlive()) * time.Second)
if err != nil {
log.Warnf("could not set keepalive for connection: %v",
conn.RemoteAddr().String())
}
}
2017-09-30 18:00:29 +03:00
cc := &clientConn{}
cc.id = clientID.add(1)
cc.opened.set(time.Now())
cc.conn = conn
c.connsmu.Lock()
2017-09-30 21:06:10 +03:00
c.conns[conn] = cc
2017-09-30 18:00:29 +03:00
c.connsmu.Unlock()
2017-09-30 17:29:03 +03:00
c.statsTotalConns.add(1)
2016-08-26 22:54:19 +03:00
}
2017-09-30 17:29:03 +03:00
2016-08-26 22:54:19 +03:00
closed := func(conn *server.Conn) {
2017-09-30 18:00:29 +03:00
c.connsmu.Lock()
2017-09-30 21:06:10 +03:00
delete(c.conns, conn)
2017-09-30 18:00:29 +03:00
c.connsmu.Unlock()
2016-08-26 22:54:19 +03:00
}
2017-09-30 21:06:10 +03:00
return server.ListenAndServe(host, port, protected, handler, opened, closed, ln, http)
2016-03-05 02:08:16 +03:00
}
func (c *Controller) watchAutoGC() {
2017-01-19 19:00:14 +03:00
t := time.NewTicker(time.Second)
defer t.Stop()
2017-01-20 12:09:39 +03:00
s := time.Now()
for range t.C {
2017-09-30 17:29:03 +03:00
if c.stopWatchingAutoGC.on() {
2017-01-20 12:09:39 +03:00
return
}
autoGC := c.config.autoGC()
if autoGC == 0 {
2017-01-20 12:09:39 +03:00
continue
}
if time.Now().Sub(s) < time.Second*time.Duration(autoGC) {
2017-01-20 12:09:39 +03:00
continue
}
2017-01-22 03:10:43 +03:00
var mem1, mem2 runtime.MemStats
runtime.ReadMemStats(&mem1)
log.Debugf("autogc(before): "+
"alloc: %v, heap_alloc: %v, heap_released: %v",
mem1.Alloc, mem1.HeapAlloc, mem1.HeapReleased)
2017-01-20 12:09:39 +03:00
runtime.GC()
debug.FreeOSMemory()
2017-01-22 03:10:43 +03:00
runtime.ReadMemStats(&mem2)
log.Debugf("autogc(after): "+
"alloc: %v, heap_alloc: %v, heap_released: %v",
mem2.Alloc, mem2.HeapAlloc, mem2.HeapReleased)
2017-01-20 12:09:39 +03:00
s = time.Now()
}
}
func (c *Controller) watchOutOfMemory() {
2016-05-24 05:44:25 +03:00
t := time.NewTicker(time.Second * 2)
defer t.Stop()
var mem runtime.MemStats
for range t.C {
func() {
2017-09-30 17:29:03 +03:00
if c.stopWatchingMemory.on() {
2016-05-24 05:44:25 +03:00
return
}
2017-09-30 17:29:03 +03:00
oom := c.outOfMemory.on()
2017-09-30 04:11:05 +03:00
if c.config.maxMemory() == 0 {
2016-05-24 05:44:25 +03:00
if oom {
2017-09-30 17:29:03 +03:00
c.outOfMemory.set(false)
2016-05-24 05:44:25 +03:00
}
return
}
if oom {
runtime.GC()
}
runtime.ReadMemStats(&mem)
2017-09-30 17:29:03 +03:00
c.outOfMemory.set(int(mem.HeapAlloc) > c.config.maxMemory())
2016-05-24 05:44:25 +03:00
}()
}
}
func (c *Controller) watchLuaStatePool() {
t := time.NewTicker(time.Second * 10)
defer t.Stop()
for range t.C {
func() {
c.luapool.Prune()
}()
}
}
2016-03-05 02:08:16 +03:00
func (c *Controller) setCol(key string, col *collection.Collection) {
c.cols.Set(key, col)
2016-03-05 02:08:16 +03:00
}
func (c *Controller) getCol(key string) *collection.Collection {
if value, ok := c.cols.Get(key); ok {
return value.(*collection.Collection)
2016-03-05 02:08:16 +03:00
}
return nil
2016-03-30 19:32:38 +03:00
}
func (c *Controller) scanGreaterOrEqual(
key string, iterator func(key string, col *collection.Collection) bool,
) {
c.cols.Ascend(key, func(ikey string, ivalue interface{}) bool {
return iterator(ikey, ivalue.(*collection.Collection))
2016-03-30 19:32:38 +03:00
})
2016-03-05 02:08:16 +03:00
}
func (c *Controller) deleteCol(key string) *collection.Collection {
if prev, ok := c.cols.Delete(key); ok {
return prev.(*collection.Collection)
2016-03-05 02:08:16 +03:00
}
return nil
2016-03-05 02:08:16 +03:00
}
func isReservedFieldName(field string) bool {
switch field {
case "z", "lat", "lon":
return true
}
return false
}
2016-03-28 18:57:41 +03:00
func (c *Controller) handleInputCommand(conn *server.Conn, msg *server.Message, w io.Writer) error {
var words []string
for _, v := range msg.Values {
words = append(words, v.String())
2016-03-05 02:08:16 +03:00
}
start := time.Now()
2017-10-05 18:20:40 +03:00
serializeOutput := func(res resp.Value) (string, error) {
var resStr string
var err error
switch msg.OutputType {
case server.JSON:
resStr = res.String()
case server.RESP:
var resBytes []byte
resBytes, err = res.MarshalRESP()
resStr = string(resBytes)
}
return resStr, err
}
2016-03-29 03:38:21 +03:00
writeOutput := func(res string) error {
switch msg.ConnType {
default:
2016-03-29 15:53:53 +03:00
err := fmt.Errorf("unsupported conn type: %v", msg.ConnType)
log.Error(err)
return err
case server.WebSocket:
return server.WriteWebSocketMessage(w, []byte(res))
case server.HTTP:
_, err := fmt.Fprintf(w, "HTTP/1.1 200 OK\r\n"+
"Connection: close\r\n"+
"Content-Length: %d\r\n"+
2016-08-26 17:31:09 +03:00
"Content-Type: application/json; charset=utf-8\r\n"+
2016-03-29 15:53:53 +03:00
"\r\n", len(res)+2)
if err != nil {
return err
}
2016-08-26 17:35:19 +03:00
_, err = io.WriteString(w, res)
if err != nil {
return err
}
_, err = io.WriteString(w, "\r\n")
2016-03-29 15:53:53 +03:00
return err
2016-03-29 03:38:21 +03:00
case server.RESP:
2016-03-29 22:29:15 +03:00
var err error
if msg.OutputType == server.JSON {
_, err = fmt.Fprintf(w, "$%d\r\n%s\r\n", len(res), res)
} else {
_, err = io.WriteString(w, res)
}
2016-03-29 03:38:21 +03:00
return err
case server.Native:
_, err := fmt.Fprintf(w, "$%d %s\r\n", len(res), res)
return err
}
}
2016-03-08 03:37:39 +03:00
// Ping. Just send back the response. No need to put through the pipeline.
2017-07-27 19:10:33 +03:00
if msg.Command == "ping" || msg.Command == "echo" {
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
2017-07-27 19:10:33 +03:00
if len(msg.Values) > 1 {
return writeOutput(`{"ok":true,"` + msg.Command + `":` + jsonString(msg.Values[1].String()) + `,"elapsed":"` + time.Now().Sub(start).String() + `"}`)
}
return writeOutput(`{"ok":true,"` + msg.Command + `":"pong","elapsed":"` + time.Now().Sub(start).String() + `"}`)
2016-03-28 18:57:41 +03:00
case server.RESP:
2017-07-27 19:10:33 +03:00
if len(msg.Values) > 1 {
data, _ := msg.Values[1].MarshalRESP()
return writeOutput(string(data))
}
2016-03-29 03:38:21 +03:00
return writeOutput("+PONG\r\n")
2016-03-28 18:57:41 +03:00
}
2016-03-05 02:08:16 +03:00
return nil
}
2017-10-05 18:20:40 +03:00
writeErr := func(errMsg string) error {
2016-03-28 18:57:41 +03:00
switch msg.OutputType {
case server.JSON:
2017-10-05 18:20:40 +03:00
return writeOutput(`{"ok":false,"err":` + jsonString(errMsg) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
2016-03-28 18:57:41 +03:00
case server.RESP:
2017-10-05 18:20:40 +03:00
if errMsg == errInvalidNumberOfArguments.Error() {
2016-03-29 03:38:21 +03:00
return writeOutput("-ERR wrong number of arguments for '" + msg.Command + "' command\r\n")
2016-03-28 18:57:41 +03:00
}
2017-10-05 18:20:40 +03:00
v, _ := resp.ErrorValue(errors.New("ERR " + errMsg)).MarshalRESP()
2016-04-03 05:16:36 +03:00
return writeOutput(string(v))
2016-03-05 02:08:16 +03:00
}
return nil
}
var write bool
2016-03-08 03:37:39 +03:00
2016-03-28 18:57:41 +03:00
if !conn.Authenticated || msg.Command == "auth" {
2017-09-30 04:11:05 +03:00
if c.config.requirePass() != "" {
2016-04-01 22:46:39 +03:00
password := ""
// This better be an AUTH command or the Message should contain an Auth
if msg.Command != "auth" && msg.Auth == "" {
2016-03-08 03:37:39 +03:00
// Just shut down the pipeline now. The less the client connection knows the better.
2017-10-05 18:20:40 +03:00
return writeErr("authentication required")
2016-03-08 03:37:39 +03:00
}
2016-04-01 22:46:39 +03:00
if msg.Auth != "" {
password = msg.Auth
} else {
if len(msg.Values) > 1 {
password = msg.Values[1].String()
}
2016-03-28 18:57:41 +03:00
}
2017-09-30 04:11:05 +03:00
if c.config.requirePass() != strings.TrimSpace(password) {
2017-10-05 18:20:40 +03:00
return writeErr("invalid password")
2016-03-08 03:37:39 +03:00
}
2016-03-08 18:35:43 +03:00
conn.Authenticated = true
2016-08-25 15:25:20 +03:00
if msg.ConnType != server.HTTP {
2017-10-05 18:20:40 +03:00
resStr, _ := serializeOutput(server.OKMessage(msg, start))
return writeOutput(resStr)
2016-08-25 15:25:20 +03:00
}
2016-03-28 18:57:41 +03:00
} else if msg.Command == "auth" {
2017-10-05 18:20:40 +03:00
return writeErr("invalid password")
2016-03-08 03:37:39 +03:00
}
}
2016-03-05 02:08:16 +03:00
// choose the locking strategy
2016-03-28 18:57:41 +03:00
switch msg.Command {
2016-03-05 02:08:16 +03:00
default:
c.mu.RLock()
defer c.mu.RUnlock()
2018-08-14 03:05:30 +03:00
case "set", "del", "drop", "fset", "flushdb",
"setchan", "pdelchan", "delchan",
"sethook", "pdelhook", "delhook",
"expire", "persist", "jset", "pdel":
2016-03-05 02:08:16 +03:00
// write operations
write = true
c.mu.Lock()
defer c.mu.Unlock()
2017-09-30 04:11:05 +03:00
if c.config.followHost() != "" {
2017-10-05 18:20:40 +03:00
return writeErr("not the leader")
}
if c.config.readOnly() {
return writeErr("read only")
}
case "eval", "evalsha":
// write operations (potentially) but no AOF for the script command itself
c.mu.Lock()
defer c.mu.Unlock()
if c.config.followHost() != "" {
return writeErr("not the leader")
2016-03-05 02:08:16 +03:00
}
2017-09-30 04:11:05 +03:00
if c.config.readOnly() {
2017-10-05 18:20:40 +03:00
return writeErr("read only")
2016-03-05 02:08:16 +03:00
}
2018-08-14 03:05:30 +03:00
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks",
"chans", "search", "ttl", "bounds", "server", "info", "type", "jget",
"evalro", "evalrosha":
2016-03-05 02:08:16 +03:00
// read operations
c.mu.RLock()
defer c.mu.RUnlock()
2017-09-30 04:11:05 +03:00
if c.config.followHost() != "" && !c.fcuponce {
2017-10-05 18:20:40 +03:00
return writeErr("catching up to leader")
2016-03-05 02:08:16 +03:00
}
2016-03-08 03:37:39 +03:00
case "follow", "readonly", "config":
2016-03-05 02:08:16 +03:00
// system operations
// does not write to aof, but requires a write lock.
c.mu.Lock()
defer c.mu.Unlock()
2016-03-29 22:29:15 +03:00
case "output":
// this is local connection operation. Locks not needed.
2017-07-27 19:10:33 +03:00
case "echo":
2016-03-05 02:08:16 +03:00
case "massinsert":
// dev operation
c.mu.Lock()
defer c.mu.Unlock()
2017-09-30 18:11:10 +03:00
case "sleep":
// dev operation
c.mu.RLock()
defer c.mu.RUnlock()
case "shutdown":
// dev operation
c.mu.Lock()
defer c.mu.Unlock()
case "aofshrink":
c.mu.RLock()
defer c.mu.RUnlock()
case "client":
c.mu.Lock()
defer c.mu.Unlock()
2017-10-05 18:20:40 +03:00
case "evalna", "evalnasha":
// No locking for scripts, otherwise writes cannot happen within scripts
2018-08-14 03:05:30 +03:00
case "subscribe", "psubscribe", "publish":
// No locking for pubsub
2016-03-05 02:08:16 +03:00
}
res, d, err := c.command(msg, w, conn)
2017-10-05 18:20:40 +03:00
if res.Type() == resp.Error {
return writeErr(res.String())
}
2016-03-05 02:08:16 +03:00
if err != nil {
2018-08-14 03:05:30 +03:00
if err.Error() == goingLive {
2016-03-05 02:08:16 +03:00
return err
}
2017-10-05 18:20:40 +03:00
return writeErr(err.Error())
2016-03-05 02:08:16 +03:00
}
if write {
2016-03-28 18:57:41 +03:00
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
2016-03-19 17:16:19 +03:00
if _, ok := err.(errAOFHook); ok {
2017-10-05 18:20:40 +03:00
return writeErr(err.Error())
2016-03-19 17:16:19 +03:00
}
2016-03-05 02:08:16 +03:00
log.Fatal(err)
return err
}
}
2017-10-05 18:20:40 +03:00
if !isRespValueEmptyString(res) {
var resStr string
resStr, err := serializeOutput(res)
if err != nil {
return err
}
if err := writeOutput(resStr); err != nil {
2016-03-05 02:08:16 +03:00
return err
}
}
2017-10-05 18:20:40 +03:00
2016-03-05 02:08:16 +03:00
return nil
}
2017-10-05 18:20:40 +03:00
func isRespValueEmptyString(val resp.Value) bool {
return !val.IsNull() && (val.Type() == resp.SimpleString || val.Type() == resp.BulkString) && len(val.Bytes()) == 0
}
2016-03-05 02:08:16 +03:00
func randomKey(n int) string {
b := make([]byte, n)
nn, err := rand.Read(b)
if err != nil {
panic(err)
}
if nn != n {
panic("random failed")
}
return fmt.Sprintf("%x", b)
}
func (c *Controller) reset() {
c.aofsz = 0
c.cols = ds.BTree{}
2018-01-30 20:30:03 +03:00
c.exlistmu.Lock()
c.exlist = nil
c.exlistmu.Unlock()
c.expires = make(map[string]map[string]time.Time)
2016-03-05 02:08:16 +03:00
}
func (c *Controller) command(
msg *server.Message, w io.Writer, conn *server.Conn,
) (
2017-10-05 18:20:40 +03:00
res resp.Value, d commandDetailsT, err error,
) {
2016-03-28 18:57:41 +03:00
switch msg.Command {
2016-03-05 02:08:16 +03:00
default:
2016-03-28 18:57:41 +03:00
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
2016-03-05 02:08:16 +03:00
case "set":
2016-03-28 18:57:41 +03:00
res, d, err = c.cmdSet(msg)
2016-03-05 02:08:16 +03:00
case "fset":
2016-03-28 18:57:41 +03:00
res, d, err = c.cmdFset(msg)
2016-03-05 02:08:16 +03:00
case "del":
2016-03-28 18:57:41 +03:00
res, d, err = c.cmdDel(msg)
case "pdel":
res, d, err = c.cmdPdel(msg)
2016-03-05 02:08:16 +03:00
case "drop":
2016-03-28 18:57:41 +03:00
res, d, err = c.cmdDrop(msg)
2016-03-05 02:08:16 +03:00
case "flushdb":
2016-03-28 18:57:41 +03:00
res, d, err = c.cmdFlushDB(msg)
2018-08-14 03:05:30 +03:00
2016-03-29 22:29:15 +03:00
case "sethook":
2018-08-14 03:05:30 +03:00
res, d, err = c.cmdSetHook(msg, false)
2016-03-29 22:29:15 +03:00
case "delhook":
2018-08-14 03:05:30 +03:00
res, d, err = c.cmdDelHook(msg, false)
2016-09-12 05:20:53 +03:00
case "pdelhook":
2018-08-14 03:05:30 +03:00
res, d, err = c.cmdPDelHook(msg, false)
case "hooks":
res, err = c.cmdHooks(msg, false)
case "setchan":
res, d, err = c.cmdSetHook(msg, true)
case "delchan":
res, d, err = c.cmdDelHook(msg, true)
case "pdelchan":
res, d, err = c.cmdPDelHook(msg, true)
case "chans":
res, err = c.cmdHooks(msg, true)
2016-07-15 22:22:48 +03:00
case "expire":
res, d, err = c.cmdExpire(msg)
case "persist":
res, d, err = c.cmdPersist(msg)
case "ttl":
res, err = c.cmdTTL(msg)
case "shutdown":
if !core.DevMode {
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
return
}
log.Fatal("shutdown requested by developer")
2016-03-30 19:32:38 +03:00
case "massinsert":
if !core.DevMode {
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
return
}
res, err = c.cmdMassInsert(msg)
2017-09-30 18:11:10 +03:00
case "sleep":
if !core.DevMode {
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
return
}
res, err = c.cmdSleep(msg)
2016-04-01 02:26:36 +03:00
case "follow":
res, err = c.cmdFollow(msg)
2016-03-29 15:53:53 +03:00
case "readonly":
res, err = c.cmdReadOnly(msg)
2016-03-29 02:11:29 +03:00
case "stats":
res, err = c.cmdStats(msg)
2016-03-29 01:50:18 +03:00
case "server":
res, err = c.cmdServer(msg)
2016-08-26 22:54:19 +03:00
case "info":
res, err = c.cmdInfo(msg)
2016-03-29 00:16:21 +03:00
case "scan":
res, err = c.cmdScan(msg)
case "nearby":
res, err = c.cmdNearby(msg)
case "within":
res, err = c.cmdWithin(msg)
case "intersects":
res, err = c.cmdIntersects(msg)
2016-07-11 07:40:18 +03:00
case "search":
res, err = c.cmdSearch(msg)
case "bounds":
res, err = c.cmdBounds(msg)
2016-03-05 02:08:16 +03:00
case "get":
2016-03-28 18:57:41 +03:00
res, err = c.cmdGet(msg)
case "jget":
res, err = c.cmdJget(msg)
case "jset":
res, d, err = c.cmdJset(msg)
case "jdel":
res, d, err = c.cmdJdel(msg)
2016-08-26 23:42:52 +03:00
case "type":
res, err = c.cmdType(msg)
2016-03-29 01:22:30 +03:00
case "keys":
res, err = c.cmdKeys(msg)
2016-03-29 22:29:15 +03:00
case "output":
res, err = c.cmdOutput(msg)
2016-04-01 02:26:36 +03:00
case "aof":
res, err = c.cmdAOF(msg)
case "aofmd5":
res, err = c.cmdAOFMD5(msg)
2016-03-29 01:50:18 +03:00
case "gc":
2016-12-23 00:52:37 +03:00
runtime.GC()
debug.FreeOSMemory()
2016-03-30 19:32:38 +03:00
res = server.OKMessage(msg, time.Now())
case "aofshrink":
go c.aofshrink()
res = server.OKMessage(msg, time.Now())
2016-03-29 15:53:53 +03:00
case "config get":
res, err = c.cmdConfigGet(msg)
case "config set":
res, err = c.cmdConfigSet(msg)
case "config rewrite":
res, err = c.cmdConfigRewrite(msg)
case "config", "script":
// These get rewritten into "config foo" and "script bar"
2016-03-29 15:53:53 +03:00
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
if len(msg.Values) > 1 {
command := msg.Values[0].String() + " " + msg.Values[1].String()
msg.Values[1] = resp.StringValue(command)
msg.Values = msg.Values[1:]
msg.Command = strings.ToLower(command)
return c.command(msg, w, conn)
2016-03-29 15:53:53 +03:00
}
case "client":
res, err = c.cmdClient(msg, conn)
2017-10-05 18:20:40 +03:00
case "eval", "evalro", "evalna":
res, err = c.cmdEvalUnified(false, msg)
case "evalsha", "evalrosha", "evalnasha":
res, err = c.cmdEvalUnified(true, msg)
case "script load":
res, err = c.cmdScriptLoad(msg)
case "script exists":
res, err = c.cmdScriptExists(msg)
case "script flush":
res, err = c.cmdScriptFlush(msg)
2018-08-14 03:05:30 +03:00
case "subscribe":
res, err = c.cmdSubscribe(msg)
case "psubscribe":
res, err = c.cmdPsubscribe(msg)
case "publish":
res, err = c.cmdPublish(msg)
2016-03-05 02:08:16 +03:00
}
return
}