Replaced net package with evio

- Added threads startup flag
- Replaced net package with evio
- Refactored controller into server
This commit is contained in:
tidwall 2018-10-28 15:49:45 -07:00
parent 745579b56b
commit 555e47036c
103 changed files with 6862 additions and 2260 deletions

20
Gopkg.lock generated
View File

@ -133,6 +133,14 @@
pruneopts = "" pruneopts = ""
revision = "0b12d6b5" revision = "0b12d6b5"
[[projects]]
digest = "1:be1d3b7623f11b628068933282015f3dbcd522fa8e6b16d2931edffd42ef2c0b"
name = "github.com/kavu/go_reuseport"
packages = ["."]
pruneopts = ""
revision = "ffa96de2479e10ecd06aca8069bf9c55a86701b5"
version = "v1.4.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:75dddee0eb82002b5aff6937fdf6d544b85322d2414524a521768fe4b4e5ed3d" digest = "1:75dddee0eb82002b5aff6937fdf6d544b85322d2414524a521768fe4b4e5ed3d"
@ -225,6 +233,17 @@
pruneopts = "" pruneopts = ""
revision = "b67b1b8c1658cb01502801c14e33c61e6c4cbb95" revision = "b67b1b8c1658cb01502801c14e33c61e6c4cbb95"
[[projects]]
digest = "1:8aa59623aefb49c419e5b24179583e41df4b8c2f6a567f2cb8156a78a32e554a"
name = "github.com/tidwall/evio"
packages = [
".",
"internal",
]
pruneopts = ""
revision = "3a190d6d209c66b1fee96ee3db9e70c71e3635d5"
version = "v1.0.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c5ac96e72d3ff6694602f3273dd71ef04a67c9591465aac92dc1aa8c821b8f91" digest = "1:c5ac96e72d3ff6694602f3273dd71ef04a67c9591465aac92dc1aa8c821b8f91"
@ -449,6 +468,7 @@
"github.com/tidwall/boxtree/d2", "github.com/tidwall/boxtree/d2",
"github.com/tidwall/btree", "github.com/tidwall/btree",
"github.com/tidwall/buntdb", "github.com/tidwall/buntdb",
"github.com/tidwall/evio",
"github.com/tidwall/geojson", "github.com/tidwall/geojson",
"github.com/tidwall/geojson/geometry", "github.com/tidwall/geojson/geometry",
"github.com/tidwall/gjson", "github.com/tidwall/gjson",

View File

@ -23,7 +23,8 @@
required = [ required = [
"github.com/tidwall/lotsa", "github.com/tidwall/lotsa",
"github.com/mmcloughlin/geohash", "github.com/mmcloughlin/geohash",
"github.com/tidwall/geojson" "github.com/tidwall/geojson",
"github.com/tidwall/evio"
] ]
[[constraint]] [[constraint]]

View File

@ -16,8 +16,8 @@ import (
"github.com/peterh/liner" "github.com/peterh/liner"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/cmd/tile38-cli/internal/client"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/client"
) )
func userHomeDir() string { func userHomeDir() string {

View File

@ -16,9 +16,9 @@ import (
"syscall" "syscall"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/controller"
"github.com/tidwall/tile38/internal/hservice" "github.com/tidwall/tile38/internal/hservice"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -79,6 +79,7 @@ Advanced Options:
--queuefilename path : Event queue path (default:data/queue.db) --queuefilename path : Event queue path (default:data/queue.db)
--http-transport yes/no : HTTP transport (default: yes) --http-transport yes/no : HTTP transport (default: yes)
--protected-mode yes/no : protected mode (default: yes) --protected-mode yes/no : protected mode (default: yes)
--threads num : number of network threads (default: num cores)
Developer Options: Developer Options:
--dev : enable developer mode --dev : enable developer mode
@ -146,11 +147,12 @@ Developer Options:
switch strings.ToLower(os.Args[i]) { switch strings.ToLower(os.Args[i]) {
case "no": case "no":
core.ProtectedMode = "no" core.ProtectedMode = "no"
continue
case "yes": case "yes":
core.ProtectedMode = "yes" core.ProtectedMode = "yes"
}
continue continue
} }
}
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n") fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
os.Exit(1) os.Exit(1)
case "--dev", "-dev": case "--dev", "-dev":
@ -162,11 +164,12 @@ Developer Options:
switch strings.ToLower(os.Args[i]) { switch strings.ToLower(os.Args[i]) {
case "no": case "no":
core.AppendOnly = "no" core.AppendOnly = "no"
continue
case "yes": case "yes":
core.AppendOnly = "yes" core.AppendOnly = "yes"
}
continue continue
} }
}
fmt.Fprintf(os.Stderr, "appendonly must be 'yes' or 'no'\n") fmt.Fprintf(os.Stderr, "appendonly must be 'yes' or 'no'\n")
os.Exit(1) os.Exit(1)
case "--appendfilename", "-appendfilename": case "--appendfilename", "-appendfilename":
@ -189,9 +192,23 @@ Developer Options:
switch strings.ToLower(os.Args[i]) { switch strings.ToLower(os.Args[i]) {
case "1", "true", "yes": case "1", "true", "yes":
httpTransport = true httpTransport = true
continue
case "0", "false", "no": case "0", "false", "no":
httpTransport = false httpTransport = false
continue
} }
}
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
os.Exit(1)
case "--threads", "-threads":
i++
if i < len(os.Args) {
n, err := strconv.ParseUint(os.Args[i], 10, 16)
if err != nil {
fmt.Fprintf(os.Stderr, "threads must be a valid number\n")
os.Exit(1)
}
core.NumThreads = int(n)
continue continue
} }
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n") fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
@ -294,7 +311,7 @@ Developer Options:
if pidferr != nil { if pidferr != nil {
log.Warnf("pidfile: %v", pidferr) log.Warnf("pidfile: %v", pidferr)
} }
if err := controller.ListenAndServe(host, port, dir, httpTransport); err != nil { if err := server.Serve(host, port, dir, httpTransport); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -17,3 +17,6 @@ var AppendFileName string
// QueueFileName allows for custom queue.db file path // QueueFileName allows for custom queue.db file path
var QueueFileName string var QueueFileName string
// NumThreads is the number of network threads to use.
var NumThreads int

View File

@ -1,250 +0,0 @@
package controller
import (
"errors"
"fmt"
"net"
"sort"
"strings"
"time"
"github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/server"
)
// Conn represents a simple resp connection.
type Conn struct {
conn net.Conn
rd *resp.Reader
wr *resp.Writer
}
type clientConn struct {
id int
name astring
opened atime
last atime
conn *server.Conn
}
// DialTimeout dials a resp server.
func DialTimeout(address string, timeout time.Duration) (*Conn, error) {
tcpconn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return nil, err
}
conn := &Conn{
conn: tcpconn,
rd: resp.NewReader(tcpconn),
wr: resp.NewWriter(tcpconn),
}
return conn, nil
}
// Close closes the connection.
func (conn *Conn) Close() error {
conn.wr.WriteMultiBulk("quit")
return conn.conn.Close()
}
// Do performs a command and returns a resp value.
func (conn *Conn) Do(commandName string, args ...interface{}) (val resp.Value, err error) {
if err := conn.wr.WriteMultiBulk(commandName, args...); err != nil {
return val, err
}
val, _, err = conn.rd.ReadValue()
return val, err
}
type byID []*clientConn
func (arr byID) Len() int {
return len(arr)
}
func (arr byID) Less(a, b int) bool {
return arr[a].id < arr[b].id
}
func (arr byID) Swap(a, b int) {
arr[a], arr[b] = arr[b], arr[a]
}
func (c *Controller) cmdClient(msg *server.Message, conn *server.Conn) (resp.Value, error) {
start := time.Now()
if len(msg.Values) == 1 {
return server.NOMessage, errInvalidNumberOfArguments
}
switch strings.ToLower(msg.Values[1].String()) {
default:
return server.NOMessage, errors.New("Syntax error, try CLIENT " +
"(LIST | KILL | GETNAME | SETNAME)")
case "list":
if len(msg.Values) != 2 {
return server.NOMessage, errInvalidNumberOfArguments
}
var list []*clientConn
c.connsmu.RLock()
for _, cc := range c.conns {
list = append(list, cc)
}
c.connsmu.RUnlock()
sort.Sort(byID(list))
now := time.Now()
var buf []byte
for _, cc := range list {
buf = append(buf,
fmt.Sprintf("id=%d addr=%s name=%s age=%d idle=%d\n",
cc.id, cc.conn.RemoteAddr().String(), cc.name.get(),
now.Sub(cc.opened.get())/time.Second,
now.Sub(cc.last.get())/time.Second,
)...,
)
}
switch msg.OutputType {
case server.JSON:
return resp.StringValue(`{"ok":true,"list":` + jsonString(string(buf)) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case server.RESP:
return resp.BytesValue(buf), nil
}
return server.NOMessage, nil
case "getname":
if len(msg.Values) != 2 {
return server.NOMessage, errInvalidNumberOfArguments
}
name := ""
c.connsmu.RLock()
if cc, ok := c.conns[conn]; ok {
name = cc.name.get()
}
c.connsmu.RUnlock()
switch msg.OutputType {
case server.JSON:
return resp.StringValue(`{"ok":true,"name":` + jsonString(name) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case server.RESP:
return resp.StringValue(name), nil
}
case "setname":
if len(msg.Values) != 3 {
return server.NOMessage, errInvalidNumberOfArguments
}
name := msg.Values[2].String()
for i := 0; i < len(name); i++ {
if name[i] < '!' || name[i] > '~' {
errstr := "Client names cannot contain spaces, newlines or special characters."
return server.NOMessage, errors.New(errstr)
}
}
c.connsmu.RLock()
if cc, ok := c.conns[conn]; ok {
cc.name.set(name)
}
c.connsmu.RUnlock()
switch msg.OutputType {
case server.JSON:
return resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case server.RESP:
return resp.SimpleStringValue("OK"), nil
}
case "kill":
if len(msg.Values) < 3 {
return server.NOMessage, errInvalidNumberOfArguments
}
var useAddr bool
var addr string
var useID bool
var id string
for i := 2; i < len(msg.Values); i++ {
arg := msg.Values[i].String()
if strings.Contains(arg, ":") {
addr = arg
useAddr = true
break
}
switch strings.ToLower(arg) {
default:
return server.NOMessage, errors.New("No such client")
case "addr":
i++
if i == len(msg.Values) {
return server.NOMessage, errors.New("syntax error")
}
addr = msg.Values[i].String()
useAddr = true
case "id":
i++
if i == len(msg.Values) {
return server.NOMessage, errors.New("syntax error")
}
id = msg.Values[i].String()
useID = true
}
}
var cclose *clientConn
c.connsmu.RLock()
for _, cc := range c.conns {
if useID && fmt.Sprintf("%d", cc.id) == id {
cclose = cc
break
} else if useAddr && cc.conn.RemoteAddr().String() == addr {
cclose = cc
break
}
}
c.connsmu.RUnlock()
if cclose == nil {
return server.NOMessage, errors.New("No such client")
}
var res resp.Value
switch msg.OutputType {
case server.JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP:
res = resp.SimpleStringValue("OK")
}
if cclose.conn == conn {
// closing self, return response now
// NOTE: This is the only exception where we do convert response to a string
var outBytes []byte
switch msg.OutputType {
case server.JSON:
outBytes = res.Bytes()
case server.RESP:
outBytes, _ = res.MarshalRESP()
}
cclose.conn.Write(outBytes)
}
cclose.conn.Close()
return res, nil
}
return server.NOMessage, errors.New("invalid output type")
}
/*
func (c *Controller) cmdClientList(msg *server.Message) (string, error) {
var ok bool
var key string
if vs, key, ok = tokenval(vs); !ok || key == "" {
return "", errInvalidNumberOfArguments
}
col := c.getCol(key)
if col == nil {
if msg.OutputType == server.RESP {
return "+none\r\n", nil
}
return "", errKeyNotFound
}
typ := "hash"
switch msg.OutputType {
case server.JSON:
return `{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}", nil
case server.RESP:
return "+" + typ + "\r\n", nil
}
return "", nil
}
*/

View File

@ -1,810 +0,0 @@
package controller
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"net"
"os"
"path"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
"time"
"github.com/tidwall/buntdb"
"github.com/tidwall/geojson"
"github.com/tidwall/geojson/geometry"
"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"
)
var errOOM = errors.New("OOM command not allowed when used memory > 'maxmemory'")
const goingLive = "going live"
const hookLogPrefix = "hook:log:"
type commandDetailsT struct {
command string
key, id string
field string
value float64
obj geojson.Object
fields []float64
fmap map[string]int
oldObj geojson.Object
oldFields []float64
updated bool
timestamp time.Time
parent bool // when true, only children are forwarded
pattern string // PDEL key pattern
children []*commandDetailsT // for multi actions such as "PDEL"
}
// Controller is a tile38 controller
type Controller struct {
// static values
host string
port int
http bool
dir string
started time.Time
config *Config
epc *endpoint.Manager
// env opts
geomParseOpts geojson.ParseOptions
// atomics
followc aint // counter increases when follow property changes
statsTotalConns aint // counter for total connections
statsTotalCommands aint // counter for total commands
statsExpired aint // item expiration counter
lastShrinkDuration aint
currentShrinkStart atime
stopBackgroundExpiring abool
stopWatchingMemory abool
stopWatchingAutoGC abool
outOfMemory abool
connsmu sync.RWMutex
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
expires map[string]map[string]time.Time // synced with cols
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
pubsub *pubsub
hookex expire.List
}
// 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")
}
log.Infof("Server started, Tile38 version %s, git %s", core.Version, core.GitSHA)
c := &Controller{
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),
aofconnM: make(map[net.Conn]bool),
expires: make(map[string]map[string]time.Time),
started: time.Now(),
conns: make(map[*server.Conn]*clientConn),
http: http,
pubsub: newPubsub(),
}
c.hookex.Expired = func(item expire.Item) {
switch v := item.(type) {
case *Hook:
c.possiblyExpireHook(v.Name)
}
}
c.epc = endpoint.NewManager(c)
c.luascripts = c.newScriptMap()
c.luapool = c.newPool()
defer c.luapool.Shutdown()
if err := os.MkdirAll(dir, 0700); err != nil {
return err
}
var err error
c.config, err = loadConfig(filepath.Join(dir, "config"))
if err != nil {
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)
}
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 "QuadTree":
c.geomParseOpts.IndexGeometryKind = geometry.QuadTree
}
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)
// load the queue before the aof
qdb, err := buntdb.Open(core.QueueFileName)
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
}
c.qdb = qdb
c.qidx = qidx
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
}
}
c.fillExpiresList()
if c.config.followHost() != "" {
go c.follow(c.config.followHost(), c.config.followPort(), c.followc.get())
}
defer func() {
c.followc.add(1) // this will force any follow communication to die
}()
go c.processLives()
go c.watchOutOfMemory()
go c.watchLuaStatePool()
go c.watchAutoGC()
go c.backgroundExpiring()
defer func() {
c.stopBackgroundExpiring.set(true)
c.stopWatchingMemory.set(true)
c.stopWatchingAutoGC.set(true)
}()
handler := func(conn *server.Conn, msg *server.Message, rd *server.PipelineReader, w io.Writer, websocket bool) error {
c.connsmu.RLock()
if cc, ok := c.conns[conn]; ok {
cc.last.set(time.Now())
}
c.connsmu.RUnlock()
c.statsTotalCommands.add(1)
err := c.handleInputCommand(conn, msg, w)
if err != nil {
if err.Error() == goingLive {
return c.goLive(err, conn, rd, msg, websocket)
}
return err
}
return nil
}
protected := func() bool {
if core.ProtectedMode == "no" {
// --protected-mode no
return false
}
if host != "" && host != "127.0.0.1" && host != "::1" && host != "localhost" {
// -h address
return false
}
is := c.config.protectedMode() != "no" && c.config.requirePass() == ""
return is
}
var clientID aint
opened := func(conn *server.Conn) {
if c.config.keepAlive() > 0 {
err := conn.SetKeepAlive(
time.Duration(c.config.keepAlive()) * time.Second)
if err != nil {
log.Warnf("could not set keepalive for connection: %v",
conn.RemoteAddr().String())
}
}
cc := &clientConn{}
cc.id = clientID.add(1)
cc.opened.set(time.Now())
cc.conn = conn
c.connsmu.Lock()
c.conns[conn] = cc
c.connsmu.Unlock()
c.statsTotalConns.add(1)
}
closed := func(conn *server.Conn) {
c.connsmu.Lock()
delete(c.conns, conn)
c.connsmu.Unlock()
}
return server.ListenAndServe(host, port, protected, handler, opened, closed, ln, http)
}
func (c *Controller) watchAutoGC() {
t := time.NewTicker(time.Second)
defer t.Stop()
s := time.Now()
for range t.C {
if c.stopWatchingAutoGC.on() {
return
}
autoGC := c.config.autoGC()
if autoGC == 0 {
continue
}
if time.Now().Sub(s) < time.Second*time.Duration(autoGC) {
continue
}
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)
runtime.GC()
debug.FreeOSMemory()
runtime.ReadMemStats(&mem2)
log.Debugf("autogc(after): "+
"alloc: %v, heap_alloc: %v, heap_released: %v",
mem2.Alloc, mem2.HeapAlloc, mem2.HeapReleased)
s = time.Now()
}
}
func (c *Controller) watchOutOfMemory() {
t := time.NewTicker(time.Second * 2)
defer t.Stop()
var mem runtime.MemStats
for range t.C {
func() {
if c.stopWatchingMemory.on() {
return
}
oom := c.outOfMemory.on()
if c.config.maxMemory() == 0 {
if oom {
c.outOfMemory.set(false)
}
return
}
if oom {
runtime.GC()
}
runtime.ReadMemStats(&mem)
c.outOfMemory.set(int(mem.HeapAlloc) > c.config.maxMemory())
}()
}
}
func (c *Controller) watchLuaStatePool() {
t := time.NewTicker(time.Second * 10)
defer t.Stop()
for range t.C {
func() {
c.luapool.Prune()
}()
}
}
func (c *Controller) setCol(key string, col *collection.Collection) {
c.cols.Set(key, col)
}
func (c *Controller) getCol(key string) *collection.Collection {
if value, ok := c.cols.Get(key); ok {
return value.(*collection.Collection)
}
return nil
}
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))
})
}
func (c *Controller) deleteCol(key string) *collection.Collection {
if prev, ok := c.cols.Delete(key); ok {
return prev.(*collection.Collection)
}
return nil
}
func isReservedFieldName(field string) bool {
switch field {
case "z", "lat", "lon":
return true
}
return false
}
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())
}
start := time.Now()
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
}
writeOutput := func(res string) error {
switch msg.ConnType {
default:
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"+
"Content-Type: application/json; charset=utf-8\r\n"+
"\r\n", len(res)+2)
if err != nil {
return err
}
_, err = io.WriteString(w, res)
if err != nil {
return err
}
_, err = io.WriteString(w, "\r\n")
return err
case server.RESP:
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)
}
return err
case server.Native:
_, err := fmt.Fprintf(w, "$%d %s\r\n", len(res), res)
return err
}
}
// Ping. Just send back the response. No need to put through the pipeline.
if msg.Command == "ping" || msg.Command == "echo" {
switch msg.OutputType {
case server.JSON:
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() + `"}`)
case server.RESP:
if len(msg.Values) > 1 {
data, _ := msg.Values[1].MarshalRESP()
return writeOutput(string(data))
}
return writeOutput("+PONG\r\n")
}
return nil
}
writeErr := func(errMsg string) error {
switch msg.OutputType {
case server.JSON:
return writeOutput(`{"ok":false,"err":` + jsonString(errMsg) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP:
if errMsg == errInvalidNumberOfArguments.Error() {
return writeOutput("-ERR wrong number of arguments for '" + msg.Command + "' command\r\n")
}
v, _ := resp.ErrorValue(errors.New("ERR " + errMsg)).MarshalRESP()
return writeOutput(string(v))
}
return nil
}
var write bool
if !conn.Authenticated || msg.Command == "auth" {
if c.config.requirePass() != "" {
password := ""
// This better be an AUTH command or the Message should contain an Auth
if msg.Command != "auth" && msg.Auth == "" {
// Just shut down the pipeline now. The less the client connection knows the better.
return writeErr("authentication required")
}
if msg.Auth != "" {
password = msg.Auth
} else {
if len(msg.Values) > 1 {
password = msg.Values[1].String()
}
}
if c.config.requirePass() != strings.TrimSpace(password) {
return writeErr("invalid password")
}
conn.Authenticated = true
if msg.ConnType != server.HTTP {
resStr, _ := serializeOutput(server.OKMessage(msg, start))
return writeOutput(resStr)
}
} else if msg.Command == "auth" {
return writeErr("invalid password")
}
}
// choose the locking strategy
switch msg.Command {
default:
c.mu.RLock()
defer c.mu.RUnlock()
case "set", "del", "drop", "fset", "flushdb",
"setchan", "pdelchan", "delchan",
"sethook", "pdelhook", "delhook",
"expire", "persist", "jset", "pdel":
// write operations
write = true
c.mu.Lock()
defer c.mu.Unlock()
if c.config.followHost() != "" {
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")
}
if c.config.readOnly() {
return writeErr("read only")
}
case "get", "keys", "scan", "nearby", "within", "intersects", "hooks",
"chans", "search", "ttl", "bounds", "server", "info", "type", "jget",
"evalro", "evalrosha":
// read operations
c.mu.RLock()
defer c.mu.RUnlock()
if c.config.followHost() != "" && !c.fcuponce {
return writeErr("catching up to leader")
}
case "follow", "readonly", "config":
// system operations
// does not write to aof, but requires a write lock.
c.mu.Lock()
defer c.mu.Unlock()
case "output":
// this is local connection operation. Locks not needed.
case "echo":
case "massinsert":
// dev operation
c.mu.Lock()
defer c.mu.Unlock()
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()
case "evalna", "evalnasha":
// No locking for scripts, otherwise writes cannot happen within scripts
case "subscribe", "psubscribe", "publish":
// No locking for pubsub
}
res, d, err := c.command(msg, w, conn)
if res.Type() == resp.Error {
return writeErr(res.String())
}
if err != nil {
if err.Error() == goingLive {
return err
}
return writeErr(err.Error())
}
if write {
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
if _, ok := err.(errAOFHook); ok {
return writeErr(err.Error())
}
log.Fatal(err)
return err
}
}
if !isRespValueEmptyString(res) {
var resStr string
resStr, err := serializeOutput(res)
if err != nil {
return err
}
if err := writeOutput(resStr); err != nil {
return err
}
}
return nil
}
func isRespValueEmptyString(val resp.Value) bool {
return !val.IsNull() && (val.Type() == resp.SimpleString || val.Type() == resp.BulkString) && len(val.Bytes()) == 0
}
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{}
c.exlistmu.Lock()
c.exlist = nil
c.exlistmu.Unlock()
c.expires = make(map[string]map[string]time.Time)
}
func (c *Controller) command(
msg *server.Message, w io.Writer, conn *server.Conn,
) (
res resp.Value, d commandDetailsT, err error,
) {
switch msg.Command {
default:
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
case "set":
res, d, err = c.cmdSet(msg)
case "fset":
res, d, err = c.cmdFset(msg)
case "del":
res, d, err = c.cmdDel(msg)
case "pdel":
res, d, err = c.cmdPdel(msg)
case "drop":
res, d, err = c.cmdDrop(msg)
case "flushdb":
res, d, err = c.cmdFlushDB(msg)
case "sethook":
res, d, err = c.cmdSetHook(msg, false)
case "delhook":
res, d, err = c.cmdDelHook(msg, false)
case "pdelhook":
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)
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")
case "massinsert":
if !core.DevMode {
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
return
}
res, err = c.cmdMassInsert(msg)
case "sleep":
if !core.DevMode {
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
return
}
res, err = c.cmdSleep(msg)
case "follow":
res, err = c.cmdFollow(msg)
case "readonly":
res, err = c.cmdReadOnly(msg)
case "stats":
res, err = c.cmdStats(msg)
case "server":
res, err = c.cmdServer(msg)
case "info":
res, err = c.cmdInfo(msg)
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)
case "search":
res, err = c.cmdSearch(msg)
case "bounds":
res, err = c.cmdBounds(msg)
case "get":
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)
case "type":
res, err = c.cmdType(msg)
case "keys":
res, err = c.cmdKeys(msg)
case "output":
res, err = c.cmdOutput(msg)
case "aof":
res, err = c.cmdAOF(msg)
case "aofmd5":
res, err = c.cmdAOFMD5(msg)
case "gc":
runtime.GC()
debug.FreeOSMemory()
res = server.OKMessage(msg, time.Now())
case "aofshrink":
go c.aofshrink()
res = server.OKMessage(msg, time.Now())
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"
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)
}
case "client":
res, err = c.cmdClient(msg, conn)
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)
case "subscribe":
res, err = c.cmdSubscribe(msg)
case "psubscribe":
res, err = c.cmdPsubscribe(msg)
case "publish":
res, err = c.cmdPublish(msg)
}
return
}

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"errors" "errors"
@ -15,11 +15,8 @@ import (
"github.com/tidwall/redcon" "github.com/tidwall/redcon"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
// AsyncHooks indicates that the hooks should happen in the background.
type errAOFHook struct { type errAOFHook struct {
err error err error
} }
@ -30,8 +27,8 @@ func (err errAOFHook) Error() string {
var errInvalidAOF = errors.New("invalid aof file") var errInvalidAOF = errors.New("invalid aof file")
func (c *Controller) loadAOF() error { func (server *Server) loadAOF() error {
fi, err := c.aof.Stat() fi, err := server.aof.Stat()
if err != nil { if err != nil {
return err return err
} }
@ -56,9 +53,9 @@ func (c *Controller) loadAOF() error {
var buf []byte var buf []byte
var args [][]byte var args [][]byte
var packet [0xFFFF]byte var packet [0xFFFF]byte
var msg server.Message var msg Message
for { for {
n, err := c.aof.Read(packet[:]) n, err := server.aof.Read(packet[:])
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
if len(buf) > 0 { if len(buf) > 0 {
@ -68,7 +65,7 @@ func (c *Controller) loadAOF() error {
} }
return err return err
} }
c.aofsz += n server.aofsz += n
data := packet[:n] data := packet[:n]
if len(buf) > 0 { if len(buf) > 0 {
data = append(buf, data...) data = append(buf, data...)
@ -83,12 +80,11 @@ func (c *Controller) loadAOF() error {
break break
} }
if len(args) > 0 { if len(args) > 0 {
msg.Values = msg.Values[:0] msg.Args = msg.Args[:0]
for _, arg := range args { for _, arg := range args {
msg.Values = append(msg.Values, resp.BytesValue(arg)) msg.Args = append(msg.Args, string(arg))
} }
msg.Command = qlower(args[0]) if _, _, err := server.command(&msg, nil); err != nil {
if _, _, err := c.command(&msg, nil, nil); err != nil {
if commandErrIsFatal(err) { if commandErrIsFatal(err) {
return err return err
} }
@ -104,22 +100,22 @@ func (c *Controller) loadAOF() error {
} }
} }
func qlower(s []byte) string { // func qlower(s []byte) string {
if len(s) == 3 { // if len(s) == 3 {
if s[0] == 'S' && s[1] == 'E' && s[2] == 'T' { // if s[0] == 'S' && s[1] == 'E' && s[2] == 'T' {
return "set" // return "set"
} // }
if s[0] == 'D' && s[1] == 'E' && s[2] == 'L' { // if s[0] == 'D' && s[1] == 'E' && s[2] == 'L' {
return "del" // return "del"
} // }
} // }
for i := 0; i < len(s); i++ { // for i := 0; i < len(s); i++ {
if s[i] >= 'A' || s[i] <= 'Z' { // if s[i] >= 'A' || s[i] <= 'Z' {
return strings.ToLower(string(s)) // return strings.ToLower(string(s))
} // }
} // }
return string(s) // return string(s)
} // }
func commandErrIsFatal(err error) bool { func commandErrIsFatal(err error) bool {
// FSET (and other writable commands) may return errors that we need // FSET (and other writable commands) may return errors that we need
@ -132,83 +128,91 @@ func commandErrIsFatal(err error) bool {
return true return true
} }
func (c *Controller) writeAOF(value resp.Value, d *commandDetailsT) error { func (server *Server) flushAOF() {
if len(server.aofbuf) > 0 {
_, err := server.aof.Write(server.aofbuf)
if err != nil {
panic(err)
}
server.aofbuf = server.aofbuf[:0]
}
}
func (server *Server) writeAOF(args []string, d *commandDetailsT) error {
if d != nil && !d.updated { if d != nil && !d.updated {
// just ignore writes if the command did not update // just ignore writes if the command did not update
return nil return nil
} }
if c.shrinking {
var values []string if server.shrinking {
for _, value := range value.Array() { nargs := make([]string, len(args))
values = append(values, value.String()) copy(nargs, args)
server.shrinklog = append(server.shrinklog, nargs)
} }
c.shrinklog = append(c.shrinklog, values)
if server.aof != nil {
n := len(server.aofbuf)
server.aofbuf = redcon.AppendArray(server.aofbuf, len(args))
for _, arg := range args {
server.aofbuf = redcon.AppendBulkString(server.aofbuf, arg)
} }
data, err := value.MarshalRESP() server.aofsz += len(server.aofbuf) - n
if err != nil {
return err
}
if c.aof != nil {
n, err := c.aof.Write(data)
if err != nil {
return err
}
c.aofsz += n
} }
// notify aof live connections that we have new data // notify aof live connections that we have new data
c.fcond.L.Lock() server.fcond.L.Lock()
c.fcond.Broadcast() server.fcond.Broadcast()
c.fcond.L.Unlock() server.fcond.L.Unlock()
// process geofences // process geofences
if d != nil { if d != nil {
// webhook geofences // webhook geofences
if c.config.followHost() == "" { if server.config.followHost() == "" {
// for leader only // for leader only
if d.parent { if d.parent {
// queue children // queue children
for _, d := range d.children { for _, d := range d.children {
if err := c.queueHooks(d); err != nil { if err := server.queueHooks(d); err != nil {
return err return err
} }
} }
} else { } else {
// queue parent // queue parent
if err := c.queueHooks(d); err != nil { if err := server.queueHooks(d); err != nil {
return err return err
} }
} }
} }
// live geofences // live geofences
c.lcond.L.Lock() server.lcond.L.Lock()
if d.parent { if d.parent {
// queue children // queue children
for _, d := range d.children { for _, d := range d.children {
c.lstack = append(c.lstack, d) server.lstack = append(server.lstack, d)
} }
} else { } else {
// queue parent // queue parent
c.lstack = append(c.lstack, d) server.lstack = append(server.lstack, d)
} }
c.lcond.Broadcast() server.lcond.Broadcast()
c.lcond.L.Unlock() server.lcond.L.Unlock()
} }
return nil return nil
} }
func (c *Controller) queueHooks(d *commandDetailsT) error { func (server *Server) queueHooks(d *commandDetailsT) error {
// big list of all of the messages // big list of all of the messages
var hmsgs []string var hmsgs []string
var hooks []*Hook var hooks []*Hook
// find the hook by the key // find the hook by the key
if hm, ok := c.hookcols[d.key]; ok { if hm, ok := server.hookcols[d.key]; ok {
for _, hook := range hm { for _, hook := range hm {
// match the fence // match the fence
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, hook.Metas, d) msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, hook.Metas, d)
if len(msgs) > 0 { if len(msgs) > 0 {
if hook.channel { if hook.channel {
c.Publish(hook.Name, msgs...) server.Publish(hook.Name, msgs...)
} else { } else {
// append each msg to the big list // append each msg to the big list
hmsgs = append(hmsgs, msgs...) hmsgs = append(hmsgs, msgs...)
@ -222,17 +226,17 @@ func (c *Controller) queueHooks(d *commandDetailsT) error {
} }
// queue the message in the buntdb database // queue the message in the buntdb database
err := c.qdb.Update(func(tx *buntdb.Tx) error { err := server.qdb.Update(func(tx *buntdb.Tx) error {
for _, msg := range hmsgs { for _, msg := range hmsgs {
c.qidx++ // increment the log id server.qidx++ // increment the log id
key := hookLogPrefix + uint64ToString(c.qidx) key := hookLogPrefix + uint64ToString(server.qidx)
_, _, err := tx.Set(key, string(msg), hookLogSetDefaults) _, _, err := tx.Set(key, string(msg), hookLogSetDefaults)
if err != nil { if err != nil {
return err return err
} }
log.Debugf("queued hook: %d", c.qidx) log.Debugf("queued hook: %d", server.qidx)
} }
_, _, err := tx.Set("hook:idx", uint64ToString(c.qidx), nil) _, _, err := tx.Set("hook:idx", uint64ToString(server.qidx), nil)
if err != nil { if err != nil {
return err return err
} }
@ -269,86 +273,86 @@ func (s liveAOFSwitches) Error() string {
return goingLive return goingLive
} }
func (c *Controller) cmdAOFMD5(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdAOFMD5(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var spos, ssize string var spos, ssize string
if vs, spos, ok = tokenval(vs); !ok || spos == "" { if vs, spos, ok = tokenval(vs); !ok || spos == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, ssize, ok = tokenval(vs); !ok || ssize == "" { if vs, ssize, ok = tokenval(vs); !ok || ssize == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
pos, err := strconv.ParseInt(spos, 10, 64) pos, err := strconv.ParseInt(spos, 10, 64)
if err != nil || pos < 0 { if err != nil || pos < 0 {
return server.NOMessage, errInvalidArgument(spos) return NOMessage, errInvalidArgument(spos)
} }
size, err := strconv.ParseInt(ssize, 10, 64) size, err := strconv.ParseInt(ssize, 10, 64)
if err != nil || size < 0 { if err != nil || size < 0 {
return server.NOMessage, errInvalidArgument(ssize) return NOMessage, errInvalidArgument(ssize)
} }
sum, err := c.checksum(pos, size) sum, err := server.checksum(pos, size)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue( res = resp.StringValue(
fmt.Sprintf(`{"ok":true,"md5":"%s","elapsed":"%s"}`, sum, time.Now().Sub(start))) fmt.Sprintf(`{"ok":true,"md5":"%s","elapsed":"%s"}`, sum, time.Now().Sub(start)))
case server.RESP: case RESP:
res = resp.SimpleStringValue(sum) res = resp.SimpleStringValue(sum)
} }
return res, nil return res, nil
} }
func (c *Controller) cmdAOF(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdAOF(msg *Message) (res resp.Value, err error) {
if c.aof == nil { if server.aof == nil {
return server.NOMessage, errors.New("aof disabled") return NOMessage, errors.New("aof disabled")
} }
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var spos string var spos string
if vs, spos, ok = tokenval(vs); !ok || spos == "" { if vs, spos, ok = tokenval(vs); !ok || spos == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
pos, err := strconv.ParseInt(spos, 10, 64) pos, err := strconv.ParseInt(spos, 10, 64)
if err != nil || pos < 0 { if err != nil || pos < 0 {
return server.NOMessage, errInvalidArgument(spos) return NOMessage, errInvalidArgument(spos)
} }
f, err := os.Open(c.aof.Name()) f, err := os.Open(server.aof.Name())
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
defer f.Close() defer f.Close()
n, err := f.Seek(0, 2) n, err := f.Seek(0, 2)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
if n < pos { if n < pos {
return server.NOMessage, errors.New("pos is too big, must be less that the aof_size of leader") return NOMessage, errors.New("pos is too big, must be less that the aof_size of leader")
} }
var s liveAOFSwitches var s liveAOFSwitches
s.pos = pos s.pos = pos
return server.NOMessage, s return NOMessage, s
} }
func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader, msg *server.Message) error { func (server *Server) liveAOF(pos int64, conn net.Conn, rd *PipelineReader, msg *Message) error {
c.mu.Lock() server.mu.Lock()
c.aofconnM[conn] = true server.aofconnM[conn] = true
c.mu.Unlock() server.mu.Unlock()
defer func() { defer func() {
c.mu.Lock() server.mu.Lock()
delete(c.aofconnM, conn) delete(server.aofconnM, conn)
c.mu.Unlock() server.mu.Unlock()
conn.Close() conn.Close()
}() }()
@ -356,9 +360,9 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
return err return err
} }
c.mu.RLock() server.mu.RLock()
f, err := os.Open(c.aof.Name()) f, err := os.Open(server.aof.Name())
c.mu.RUnlock() server.mu.RUnlock()
if err != nil { if err != nil {
return err return err
} }
@ -384,7 +388,7 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
return return
} }
for _, v := range vs { for _, v := range vs {
switch v.Command { switch v.Command() {
default: default:
log.Error("received a live command that was not QUIT") log.Error("received a live command that was not QUIT")
return return
@ -420,9 +424,9 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
} }
continue continue
} }
c.fcond.L.Lock() server.fcond.L.Lock()
c.fcond.Wait() server.fcond.Wait()
c.fcond.L.Unlock() server.fcond.L.Unlock()
} }
}() }()
if err != nil { if err != nil {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bufio" "bufio"
@ -83,7 +83,7 @@ func NewLegacyAOFReader(r io.Reader) *LegacyAOFReader {
return rd return rd
} }
func (c *Controller) migrateAOF() error { func (c *Server) migrateAOF() error {
_, err := os.Stat(path.Join(c.dir, "appendonly.aof")) _, err := os.Stat(path.Join(c.dir, "appendonly.aof"))
if err == nil { if err == nil {
return nil return nil

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"math" "math"
@ -8,9 +8,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/tidwall/geojson"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/geojson"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
) )
@ -18,25 +18,25 @@ const maxkeys = 8
const maxids = 32 const maxids = 32
const maxchunk = 4 * 1024 * 1024 const maxchunk = 4 * 1024 * 1024
func (c *Controller) aofshrink() { func (server *Server) aofshrink() {
if c.aof == nil { if server.aof == nil {
return return
} }
start := time.Now() start := time.Now()
c.mu.Lock() server.mu.Lock()
if c.shrinking { if server.shrinking {
c.mu.Unlock() server.mu.Unlock()
return return
} }
c.shrinking = true server.shrinking = true
c.shrinklog = nil server.shrinklog = nil
c.mu.Unlock() server.mu.Unlock()
defer func() { defer func() {
c.mu.Lock() server.mu.Lock()
c.shrinking = false server.shrinking = false
c.shrinklog = nil server.shrinklog = nil
c.mu.Unlock() server.mu.Unlock()
log.Infof("aof shrink ended %v", time.Now().Sub(start)) log.Infof("aof shrink ended %v", time.Now().Sub(start))
return return
}() }()
@ -60,9 +60,9 @@ func (c *Controller) aofshrink() {
} }
keysdone = true keysdone = true
func() { func() {
c.mu.Lock() server.mu.Lock()
defer c.mu.Unlock() defer server.mu.Unlock()
c.scanGreaterOrEqual(nextkey, func(key string, col *collection.Collection) bool { server.scanGreaterOrEqual(nextkey, func(key string, col *collection.Collection) bool {
if len(keys) == maxkeys { if len(keys) == maxkeys {
keysdone = false keysdone = false
nextkey = key nextkey = key
@ -86,14 +86,14 @@ func (c *Controller) aofshrink() {
// load more objects // load more objects
func() { func() {
idsdone = true idsdone = true
c.mu.Lock() server.mu.Lock()
defer c.mu.Unlock() defer server.mu.Unlock()
col := c.getCol(keys[0]) col := server.getCol(keys[0])
if col == nil { if col == nil {
return return
} }
var fnames = col.FieldArr() // reload an array of field names to match each object var fnames = col.FieldArr() // reload an array of field names to match each object
var exm = c.expires[keys[0]] // the expiration map var exm = server.expires[keys[0]] // the expiration map
var now = time.Now() // used for expiration var now = time.Now() // used for expiration
var count = 0 // the object count var count = 0 // the object count
col.ScanGreaterOrEqual(nextid, false, col.ScanGreaterOrEqual(nextid, false,
@ -167,9 +167,9 @@ func (c *Controller) aofshrink() {
// first load the names of the hooks // first load the names of the hooks
var hnames []string var hnames []string
func() { func() {
c.mu.Lock() server.mu.Lock()
defer c.mu.Unlock() defer server.mu.Unlock()
for name := range c.hooks { for name := range server.hooks {
hnames = append(hnames, name) hnames = append(hnames, name)
} }
}() }()
@ -177,9 +177,9 @@ func (c *Controller) aofshrink() {
sort.Strings(hnames) sort.Strings(hnames)
for _, name := range hnames { for _, name := range hnames {
func() { func() {
c.mu.Lock() server.mu.Lock()
defer c.mu.Unlock() defer server.mu.Unlock()
hook := c.hooks[name] hook := server.hooks[name]
if hook == nil { if hook == nil {
return return
} }
@ -203,8 +203,8 @@ func (c *Controller) aofshrink() {
values = append(values, "ex", values = append(values, "ex",
strconv.FormatFloat(ex, 'f', 1, 64)) strconv.FormatFloat(ex, 'f', 1, 64))
} }
for _, value := range hook.Message.Values { for _, value := range hook.Message.Args {
values = append(values, value.String()) values = append(values, value)
} }
// append the values to the aof buffer // append the values to the aof buffer
aofbuf = append(aofbuf, '*') aofbuf = append(aofbuf, '*')
@ -232,10 +232,14 @@ func (c *Controller) aofshrink() {
// finally grab any new data that may have been written since // finally grab any new data that may have been written since
// the aofshrink has started and swap out the files. // the aofshrink has started and swap out the files.
return func() error { return func() error {
c.mu.Lock() server.mu.Lock()
defer c.mu.Unlock() defer server.mu.Unlock()
// flush the aof buffer
server.flushAOF()
aofbuf = aofbuf[:0] aofbuf = aofbuf[:0]
for _, values := range c.shrinklog { for _, values := range server.shrinklog {
// append the values to the aof buffer // append the values to the aof buffer
aofbuf = append(aofbuf, '*') aofbuf = append(aofbuf, '*')
aofbuf = append(aofbuf, strconv.FormatInt(int64(len(values)), 10)...) aofbuf = append(aofbuf, strconv.FormatInt(int64(len(values)), 10)...)
@ -260,7 +264,7 @@ func (c *Controller) aofshrink() {
// anything below this point is unrecoverable. just log and exit process // anything below this point is unrecoverable. just log and exit process
// back up the live aof, just in case of fatal error // back up the live aof, just in case of fatal error
if err := c.aof.Close(); err != nil { if err := server.aof.Close(); err != nil {
log.Fatalf("shrink live aof close fatal operation: %v", err) log.Fatalf("shrink live aof close fatal operation: %v", err)
} }
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
@ -272,21 +276,21 @@ func (c *Controller) aofshrink() {
if err := os.Rename(core.AppendFileName+"-shrink", core.AppendFileName); err != nil { if err := os.Rename(core.AppendFileName+"-shrink", core.AppendFileName); err != nil {
log.Fatalf("shrink rename fatal operation: %v", err) log.Fatalf("shrink rename fatal operation: %v", err)
} }
c.aof, err = os.OpenFile(core.AppendFileName, os.O_CREATE|os.O_RDWR, 0600) server.aof, err = os.OpenFile(core.AppendFileName, os.O_CREATE|os.O_RDWR, 0600)
if err != nil { if err != nil {
log.Fatalf("shrink openfile fatal operation: %v", err) log.Fatalf("shrink openfile fatal operation: %v", err)
} }
var n int64 var n int64
n, err = c.aof.Seek(0, 2) n, err = server.aof.Seek(0, 2)
if err != nil { if err != nil {
log.Fatalf("shrink seek end fatal operation: %v", err) log.Fatalf("shrink seek end fatal operation: %v", err)
} }
c.aofsz = int(n) server.aofsz = int(n)
os.Remove(core.AppendFileName + "-bak") // ignore error os.Remove(core.AppendFileName + "-bak") // ignore error
// kill all followers connections // kill all followers connections
for conn := range c.aofconnM { for conn := range server.aofconnM {
conn.Close() conn.Close()
} }
return nil return nil

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"sync" "sync"

View File

@ -1,4 +1,4 @@
package controller package server
import "testing" import "testing"

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"crypto/md5" "crypto/md5"

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"crypto/md5" "crypto/md5"
@ -14,7 +14,7 @@ import (
) )
// checksum performs a simple md5 checksum on the aof file // checksum performs a simple md5 checksum on the aof file
func (c *Controller) checksum(pos, size int64) (sum string, err error) { func (c *Server) checksum(pos, size int64) (sum string, err error) {
if pos+size > int64(c.aofsz) { if pos+size > int64(c.aofsz) {
return "", io.EOF return "", io.EOF
} }
@ -55,7 +55,7 @@ func (c *Controller) checksum(pos, size int64) (sum string, err error) {
return fmt.Sprintf("%x", sumr.Sum(nil)), nil return fmt.Sprintf("%x", sumr.Sum(nil)), nil
} }
func connAOFMD5(conn *Conn, pos, size int64) (sum string, err error) { func connAOFMD5(conn *RESPConn, pos, size int64) (sum string, err error) {
v, err := conn.Do("aofmd5", pos, size) v, err := conn.Do("aofmd5", pos, size)
if err != nil { if err != nil {
return "", err return "", err
@ -74,7 +74,7 @@ func connAOFMD5(conn *Conn, pos, size int64) (sum string, err error) {
return sum, nil return sum, nil
} }
func (c *Controller) matchChecksums(conn *Conn, pos, size int64) (match bool, err error) { func (c *Server) matchChecksums(conn *RESPConn, pos, size int64) (match bool, err error) {
sum, err := c.checksum(pos, size) sum, err := c.checksum(pos, size)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
@ -138,7 +138,7 @@ func getEndOfLastValuePositionInFile(fname string, startPos int64) (int64, error
// followCheckSome is not a full checksum. It just "checks some" data. // followCheckSome is not a full checksum. It just "checks some" data.
// We will do some various checksums on the leader until we find the correct position to start at. // We will do some various checksums on the leader until we find the correct position to start at.
func (c *Controller) followCheckSome(addr string, followc int) (pos int64, err error) { func (c *Server) followCheckSome(addr string, followc int) (pos int64, err error) {
if core.ShowDebugMessages { if core.ShowDebugMessages {
log.Debug("follow:", addr, ":check some") log.Debug("follow:", addr, ":check some")
} }

236
internal/server/client.go Normal file
View File

@ -0,0 +1,236 @@
package server
import (
"errors"
"fmt"
"io"
"sort"
"strings"
"sync"
"time"
"github.com/tidwall/evio"
"github.com/tidwall/resp"
)
// Client is an remote connection into to Tile38
type Client struct {
id int // unique id
authd bool // client has been authenticated
outputType Type // Null, JSON, or RESP
remoteAddr string // original remote address
in evio.InputStream // input stream
pr PipelineReader // command reader
out []byte // output write buffer
goLiveErr error // error type used for going line
goLiveMsg *Message // last message for go live
mu sync.Mutex // guard
conn io.ReadWriteCloser // out-of-loop connection.
name string // optional defined name
opened time.Time // when the client was created/opened, unix nano
last time.Time // last client request/response, unix nano
}
// Write ...
func (client *Client) Write(b []byte) (n int, err error) {
client.out = append(client.out, b...)
return len(b), nil
}
type byID []*Client
func (arr byID) Len() int {
return len(arr)
}
func (arr byID) Less(a, b int) bool {
return arr[a].id < arr[b].id
}
func (arr byID) Swap(a, b int) {
arr[a], arr[b] = arr[b], arr[a]
}
func (c *Server) cmdClient(msg *Message, client *Client) (resp.Value, error) {
start := time.Now()
if len(msg.Args) == 1 {
return NOMessage, errInvalidNumberOfArguments
}
switch strings.ToLower(msg.Args[1]) {
default:
return NOMessage, errors.New("Syntax error, try CLIENT " +
"(LIST | KILL | GETNAME | SETNAME)")
case "list":
if len(msg.Args) != 2 {
return NOMessage, errInvalidNumberOfArguments
}
var list []*Client
c.connsmu.RLock()
for _, cc := range c.conns {
list = append(list, cc)
}
c.connsmu.RUnlock()
sort.Sort(byID(list))
now := time.Now()
var buf []byte
for _, client := range list {
client.mu.Lock()
buf = append(buf,
fmt.Sprintf("id=%d addr=%s name=%s age=%d idle=%d\n",
client.id,
client.remoteAddr,
client.name,
now.Sub(client.opened)/time.Second,
now.Sub(client.last)/time.Second,
)...,
)
client.mu.Unlock()
}
switch msg.OutputType {
case JSON:
return resp.StringValue(`{"ok":true,"list":` + jsonString(string(buf)) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case RESP:
return resp.BytesValue(buf), nil
}
return NOMessage, nil
case "getname":
if len(msg.Args) != 2 {
return NOMessage, errInvalidNumberOfArguments
}
name := ""
switch msg.OutputType {
case JSON:
client.mu.Lock()
name := client.name
client.mu.Unlock()
return resp.StringValue(`{"ok":true,"name":` +
jsonString(name) +
`,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case RESP:
return resp.StringValue(name), nil
}
case "setname":
if len(msg.Args) != 3 {
return NOMessage, errInvalidNumberOfArguments
}
name := msg.Args[2]
for i := 0; i < len(name); i++ {
if name[i] < '!' || name[i] > '~' {
errstr := "Client names cannot contain spaces, newlines or special characters."
return NOMessage, errors.New(errstr)
}
}
client.mu.Lock()
client.name = name
client.mu.Unlock()
switch msg.OutputType {
case JSON:
return resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case RESP:
return resp.SimpleStringValue("OK"), nil
}
case "kill":
if len(msg.Args) < 3 {
return NOMessage, errInvalidNumberOfArguments
}
var useAddr bool
var addr string
var useID bool
var id string
for i := 2; i < len(msg.Args); i++ {
arg := msg.Args[i]
if strings.Contains(arg, ":") {
addr = arg
useAddr = true
break
}
switch strings.ToLower(arg) {
default:
return NOMessage, errors.New("No such client")
case "addr":
i++
if i == len(msg.Args) {
return NOMessage, errors.New("syntax error")
}
addr = msg.Args[i]
useAddr = true
case "id":
i++
if i == len(msg.Args) {
return NOMessage, errors.New("syntax error")
}
id = msg.Args[i]
useID = true
}
}
var cclose *Client
c.connsmu.RLock()
for _, cc := range c.conns {
if useID && fmt.Sprintf("%d", cc.id) == id {
cclose = cc
break
} else if useAddr && client.remoteAddr == addr {
cclose = cc
break
}
}
c.connsmu.RUnlock()
if cclose == nil {
return NOMessage, errors.New("No such client")
}
var res resp.Value
switch msg.OutputType {
case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case RESP:
res = resp.SimpleStringValue("OK")
}
client.conn.Close()
// closing self, return response now
// NOTE: This is the only exception where we do convert response to a string
var outBytes []byte
switch msg.OutputType {
case JSON:
outBytes = res.Bytes()
case RESP:
outBytes, _ = res.MarshalRESP()
}
cclose.conn.Write(outBytes)
cclose.conn.Close()
return res, nil
}
return NOMessage, errors.New("invalid output type")
}
/*
func (c *Controller) cmdClientList(msg *Message) (string, error) {
var ok bool
var key string
if vs, key, ok = tokenval(vs); !ok || key == "" {
return "", errInvalidNumberOfArguments
}
col := c.getCol(key)
if col == nil {
if msg.OutputType == RESP {
return "+none\r\n", nil
}
return "", errKeyNotFound
}
typ := "hash"
switch msg.OutputType {
case JSON:
return `{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}", nil
case RESP:
return "+" + typ + "\r\n", nil
}
return "", nil
}
*/

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"encoding/json" "encoding/json"
@ -13,7 +13,6 @@ import (
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
const ( const (
@ -327,64 +326,64 @@ func (config *Config) getProperty(name string) string {
} }
} }
func (c *Controller) cmdConfigGet(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdConfigGet(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var name string var name string
if vs, name, ok = tokenval(vs); !ok { if vs, name, ok = tokenval(vs); !ok {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
m := c.config.getProperties(name) m := c.config.getProperties(name)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
data, err := json.Marshal(m) data, err := json.Marshal(m)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
res = resp.StringValue(`{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
vals := respValuesSimpleMap(m) vals := respValuesSimpleMap(m)
res = resp.ArrayValue(vals) res = resp.ArrayValue(vals)
} }
return return
} }
func (c *Controller) cmdConfigSet(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdConfigSet(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var name string var name string
if vs, name, ok = tokenval(vs); !ok { if vs, name, ok = tokenval(vs); !ok {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var value string var value string
if vs, value, ok = tokenval(vs); !ok { if vs, value, ok = tokenval(vs); !ok {
if strings.ToLower(name) != RequirePass { if strings.ToLower(name) != RequirePass {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if err := c.config.setProperty(name, value, false); err != nil { if err := c.config.setProperty(name, value, false); err != nil {
return server.NOMessage, err return NOMessage, err
} }
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }
func (c *Controller) cmdConfigRewrite(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdConfigRewrite(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
c.config.write(true) c.config.write(true)
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }
func (config *Config) followHost() string { func (config *Config) followHost() string {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -14,7 +14,6 @@ import (
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/ds" "github.com/tidwall/tile38/internal/ds"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
type fvt struct { type fvt struct {
@ -49,30 +48,30 @@ func orderFields(fmap map[string]int, fields []float64) []fvt {
sort.Sort(byField(fvs)) sort.Sort(byField(fvs))
return fvs return fvs
} }
func (c *Controller) cmdBounds(msg *server.Message) (resp.Value, error) { func (server *Server) cmdBounds(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var key string var key string
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
col := c.getCol(key) col := server.getCol(key)
if col == nil { if col == nil {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return server.NOMessage, errKeyNotFound return NOMessage, errKeyNotFound
} }
vals := make([]resp.Value, 0, 2) vals := make([]resp.Value, 0, 2)
var buf bytes.Buffer var buf bytes.Buffer
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
} }
minX, minY, maxX, maxY := col.Bounds() minX, minY, maxX, maxY := col.Bounds()
@ -81,7 +80,7 @@ func (c *Controller) cmdBounds(msg *server.Message) (resp.Value, error) {
Min: geometry.Point{X: minX, Y: minY}, Min: geometry.Point{X: minX, Y: minY},
Max: geometry.Point{X: maxX, Y: maxY}, Max: geometry.Point{X: maxX, Y: maxY},
}) })
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"bounds":`) buf.WriteString(`,"bounds":`)
buf.WriteString(string(bbox.AppendJSON(nil))) buf.WriteString(string(bbox.AppendJSON(nil)))
} else { } else {
@ -97,55 +96,55 @@ func (c *Controller) cmdBounds(msg *server.Message) (resp.Value, error) {
})) }))
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
return vals[0], nil return vals[0], nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdType(msg *server.Message) (resp.Value, error) { func (server *Server) cmdType(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var key string var key string
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
col := c.getCol(key) col := server.getCol(key)
if col == nil { if col == nil {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.SimpleStringValue("none"), nil return resp.SimpleStringValue("none"), nil
} }
return server.NOMessage, errKeyNotFound return NOMessage, errKeyNotFound
} }
typ := "hash" typ := "hash"
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return resp.StringValue(`{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil return resp.StringValue(`{"ok":true,"type":` + string(typ) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}"), nil
case server.RESP: case RESP:
return resp.SimpleStringValue(typ), nil return resp.SimpleStringValue(typ), nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) { func (server *Server) cmdGet(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var key, id, typ, sprecision string var key, id, typ, sprecision string
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, id, ok = tokenval(vs); !ok || id == "" { if vs, id, ok = tokenval(vs); !ok || id == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
withfields := false withfields := false
@ -154,25 +153,25 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
vs = vs[1:] vs = vs[1:]
} }
col := c.getCol(key) col := server.getCol(key)
if col == nil { if col == nil {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return server.NOMessage, errKeyNotFound return NOMessage, errKeyNotFound
} }
o, fields, ok := col.Get(id) o, fields, ok := col.Get(id)
ok = ok && !c.hasExpired(key, id) ok = ok && !server.hasExpired(key, id)
if !ok { if !ok {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return server.NOMessage, errIDNotFound return NOMessage, errIDNotFound
} }
vals := make([]resp.Value, 0, 2) vals := make([]resp.Value, 0, 2)
var buf bytes.Buffer var buf bytes.Buffer
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
} }
vs, typ, ok = tokenval(vs) vs, typ, ok = tokenval(vs)
@ -182,16 +181,16 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
switch typ { switch typ {
default: default:
return server.NOMessage, errInvalidArgument(typ) return NOMessage, errInvalidArgument(typ)
case "object": case "object":
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"object":`) buf.WriteString(`,"object":`)
buf.WriteString(string(o.AppendJSON(nil))) buf.WriteString(string(o.AppendJSON(nil)))
} else { } else {
vals = append(vals, resp.StringValue(o.String())) vals = append(vals, resp.StringValue(o.String()))
} }
case "point": case "point":
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"point":`) buf.WriteString(`,"point":`)
buf.Write(appendJSONSimplePoint(nil, o)) buf.Write(appendJSONSimplePoint(nil, o))
} else { } else {
@ -215,24 +214,24 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
case "hash": case "hash":
if vs, sprecision, ok = tokenval(vs); !ok || sprecision == "" { if vs, sprecision, ok = tokenval(vs); !ok || sprecision == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"hash":`) buf.WriteString(`,"hash":`)
} }
precision, err := strconv.ParseInt(sprecision, 10, 64) precision, err := strconv.ParseInt(sprecision, 10, 64)
if err != nil || precision < 1 || precision > 64 { if err != nil || precision < 1 || precision > 64 {
return server.NOMessage, errInvalidArgument(sprecision) return NOMessage, errInvalidArgument(sprecision)
} }
center := o.Center() center := o.Center()
p := geohash.EncodeWithPrecision(center.Y, center.X, uint(precision)) p := geohash.EncodeWithPrecision(center.Y, center.X, uint(precision))
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`"` + p + `"`) buf.WriteString(`"` + p + `"`)
} else { } else {
vals = append(vals, resp.StringValue(p)) vals = append(vals, resp.StringValue(p))
} }
case "bounds": case "bounds":
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"bounds":`) buf.WriteString(`,"bounds":`)
buf.Write(appendJSONSimpleBounds(nil, o)) buf.Write(appendJSONSimpleBounds(nil, o))
} else { } else {
@ -251,17 +250,17 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if withfields { if withfields {
fvs := orderFields(col.FieldMap(), fields) fvs := orderFields(col.FieldMap(), fields)
if len(fvs) > 0 { if len(fvs) > 0 {
fvals := make([]resp.Value, 0, len(fvs)*2) fvals := make([]resp.Value, 0, len(fvs)*2)
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`,"fields":{`) buf.WriteString(`,"fields":{`)
} }
for i, fv := range fvs { for i, fv := range fvs {
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
if i > 0 { if i > 0 {
buf.WriteString(`,`) buf.WriteString(`,`)
} }
@ -271,7 +270,7 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
i++ i++
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`}`) buf.WriteString(`}`)
} else { } else {
vals = append(vals, resp.ArrayValue(fvals)) vals = append(vals, resp.ArrayValue(fvals))
@ -279,10 +278,10 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
var oval resp.Value var oval resp.Value
if withfields { if withfields {
oval = resp.ArrayValue(vals) oval = resp.ArrayValue(vals)
@ -291,12 +290,12 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
} }
return oval, nil return oval, nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdDel(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdDel(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" { if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
@ -311,24 +310,24 @@ func (c *Controller) cmdDel(msg *server.Message) (res resp.Value, d commandDetai
return return
} }
found := false found := false
col := c.getCol(d.key) col := server.getCol(d.key)
if col != nil { if col != nil {
d.obj, d.fields, ok = col.Delete(d.id) d.obj, d.fields, ok = col.Delete(d.id)
if ok { if ok {
if col.Count() == 0 { if col.Count() == 0 {
c.deleteCol(d.key) server.deleteCol(d.key)
} }
found = true found = true
} }
} }
c.clearIDExpires(d.key, d.id) server.clearIDExpires(d.key, d.id)
d.command = "del" d.command = "del"
d.updated = found d.updated = found
d.timestamp = time.Now() d.timestamp = time.Now()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
if d.updated { if d.updated {
res = resp.IntegerValue(1) res = resp.IntegerValue(1)
} else { } else {
@ -338,9 +337,9 @@ func (c *Controller) cmdDel(msg *server.Message) (res resp.Value, d commandDetai
return return
} }
func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdPdel(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" { if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
@ -369,7 +368,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
} }
var expired int var expired int
col := c.getCol(d.key) col := server.getCol(d.key)
if col != nil { if col != nil {
g := glob.Parse(d.pattern, false) g := glob.Parse(d.pattern, false)
if g.Limits[0] == "" && g.Limits[1] == "" { if g.Limits[0] == "" && g.Limits[1] == "" {
@ -386,7 +385,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
} else { } else {
d.children[i] = dc d.children[i] = dc
} }
c.clearIDExpires(d.key, dc.id) server.clearIDExpires(d.key, dc.id)
} }
if atLeastOneNotDeleted { if atLeastOneNotDeleted {
var nchildren []*commandDetailsT var nchildren []*commandDetailsT
@ -398,7 +397,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
d.children = nchildren d.children = nchildren
} }
if col.Count() == 0 { if col.Count() == 0 {
c.deleteCol(d.key) server.deleteCol(d.key)
} }
} }
d.command = "pdel" d.command = "pdel"
@ -406,9 +405,9 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
d.timestamp = now d.timestamp = now
d.parent = true d.parent = true
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
total := len(d.children) - expired total := len(d.children) - expired
if total < 0 { if total < 0 {
total = 0 total = 0
@ -418,9 +417,9 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
return return
} }
func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdDrop(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" { if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
@ -430,9 +429,9 @@ func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDeta
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
col := c.getCol(d.key) col := server.getCol(d.key)
if col != nil { if col != nil {
c.deleteCol(d.key) server.deleteCol(d.key)
d.updated = true d.updated = true
} else { } else {
d.key = "" // ignore the details d.key = "" // ignore the details
@ -440,11 +439,11 @@ func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDeta
} }
d.command = "drop" d.command = "drop"
d.timestamp = time.Now() d.timestamp = time.Now()
c.clearKeyExpires(d.key) server.clearKeyExpires(d.key)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
if d.updated { if d.updated {
res = resp.IntegerValue(1) res = resp.IntegerValue(1)
} else { } else {
@ -454,36 +453,36 @@ func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDeta
return return
} }
func (c *Controller) cmdFlushDB(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdFlushDB(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
if len(vs) != 0 { if len(vs) != 0 {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
c.cols = ds.BTree{} server.cols = ds.BTree{}
c.exlistmu.Lock() server.exlistmu.Lock()
c.exlist = nil server.exlist = nil
c.exlistmu.Unlock() server.exlistmu.Unlock()
c.expires = make(map[string]map[string]time.Time) server.expires = make(map[string]map[string]time.Time)
c.hooks = make(map[string]*Hook) server.hooks = make(map[string]*Hook)
c.hookcols = make(map[string]map[string]*Hook) server.hookcols = make(map[string]map[string]*Hook)
d.command = "flushdb" d.command = "flushdb"
d.updated = true d.updated = true
d.timestamp = time.Now() d.timestamp = time.Now()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
res = resp.SimpleStringValue("OK") res = resp.SimpleStringValue("OK")
} }
return return
} }
func (c *Controller) parseSetArgs(vs []resp.Value) ( func (server *Server) parseSetArgs(vs []string) (
d commandDetailsT, fields []string, values []float64, d commandDetailsT, fields []string, values []float64,
xx, nx bool, xx, nx bool,
expires *float64, etype []byte, evs []resp.Value, err error, expires *float64, etype []byte, evs []string, err error,
) { ) {
var ok bool var ok bool
var typ []byte var typ []byte
@ -496,7 +495,7 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
return return
} }
var arg []byte var arg []byte
var nvs []resp.Value var nvs []string
for { for {
if nvs, arg, ok = tokenvalbytes(vs); !ok || len(arg) == 0 { if nvs, arg, ok = tokenvalbytes(vs); !ok || len(arg) == 0 {
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
@ -689,7 +688,7 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
d.obj, err = geojson.Parse(object, &c.geomParseOpts) d.obj, err = geojson.Parse(object, &server.geomParseOpts)
if err != nil { if err != nil {
return return
} }
@ -700,29 +699,29 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
return return
} }
func (c *Controller) cmdSet(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdSet(msg *Message) (res resp.Value, d commandDetailsT, err error) {
if c.config.maxMemory() > 0 && c.outOfMemory.on() { if server.config.maxMemory() > 0 && server.outOfMemory.on() {
err = errOOM err = errOOM
return return
} }
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var fmap map[string]int var fmap map[string]int
var fields []string var fields []string
var values []float64 var values []float64
var xx, nx bool var xx, nx bool
var ex *float64 var ex *float64
d, fields, values, xx, nx, ex, _, _, err = c.parseSetArgs(vs) d, fields, values, xx, nx, ex, _, _, err = server.parseSetArgs(vs)
if err != nil { if err != nil {
return return
} }
col := c.getCol(d.key) col := server.getCol(d.key)
if col == nil { if col == nil {
if xx { if xx {
goto notok goto notok
} }
col = collection.New() col = collection.New()
c.setCol(d.key, col) server.setCol(d.key, col)
} }
if xx || nx { if xx || nx {
_, _, ok := col.Get(d.id) _, _, ok := col.Get(d.id)
@ -730,12 +729,12 @@ func (c *Controller) cmdSet(msg *server.Message) (res resp.Value, d commandDetai
goto notok goto notok
} }
} }
c.clearIDExpires(d.key, d.id) server.clearIDExpires(d.key, d.id)
d.oldObj, d.oldFields, d.fields = col.Set(d.id, d.obj, fields, values) d.oldObj, d.oldFields, d.fields = col.Set(d.id, d.obj, fields, values)
d.command = "set" d.command = "set"
d.updated = true // perhaps we should do a diff on the previous object? d.updated = true // perhaps we should do a diff on the previous object?
d.timestamp = time.Now() d.timestamp = time.Now()
if msg.ConnType != server.Null || msg.OutputType != server.Null { if msg.ConnType != Null || msg.OutputType != Null {
// likely loaded from aof at server startup, ignore field remapping. // likely loaded from aof at server startup, ignore field remapping.
fmap = col.FieldMap() fmap = col.FieldMap()
d.fmap = make(map[string]int) d.fmap = make(map[string]int)
@ -744,33 +743,33 @@ func (c *Controller) cmdSet(msg *server.Message) (res resp.Value, d commandDetai
} }
} }
if ex != nil { if ex != nil {
c.expireAt(d.key, d.id, d.timestamp.Add(time.Duration(float64(time.Second)*(*ex)))) server.expireAt(d.key, d.id, d.timestamp.Add(time.Duration(float64(time.Second)*(*ex))))
} }
switch msg.OutputType { switch msg.OutputType {
default: default:
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
res = resp.SimpleStringValue("OK") res = resp.SimpleStringValue("OK")
} }
return return
notok: notok:
switch msg.OutputType { switch msg.OutputType {
default: default:
case server.JSON: case JSON:
if nx { if nx {
err = errIDAlreadyExists err = errIDAlreadyExists
} else { } else {
err = errIDNotFound err = errIDNotFound
} }
return return
case server.RESP: case RESP:
res = resp.NullValue() res = resp.NullValue()
} }
return return
} }
func (c *Controller) parseFSetArgs(vs []resp.Value) ( func (server *Server) parseFSetArgs(vs []string) (
d commandDetailsT, fields []string, values []float64, xx bool, err error, d commandDetailsT, fields []string, values []float64, xx bool, err error,
) { ) {
var ok bool var ok bool
@ -813,20 +812,20 @@ func (c *Controller) parseFSetArgs(vs []resp.Value) (
return return
} }
func (c *Controller) cmdFset(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdFset(msg *Message) (res resp.Value, d commandDetailsT, err error) {
if c.config.maxMemory() > 0 && c.outOfMemory.on() { if server.config.maxMemory() > 0 && server.outOfMemory.on() {
err = errOOM err = errOOM
return return
} }
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var fields []string var fields []string
var values []float64 var values []float64
var xx bool var xx bool
var updateCount int var updateCount int
d, fields, values, xx, err = c.parseFSetArgs(vs) d, fields, values, xx, err = server.parseFSetArgs(vs)
col := c.getCol(d.key) col := server.getCol(d.key)
if col == nil { if col == nil {
err = errKeyNotFound err = errKeyNotFound
return return
@ -849,17 +848,17 @@ func (c *Controller) cmdFset(msg *server.Message) (res resp.Value, d commandDeta
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
res = resp.IntegerValue(updateCount) res = resp.IntegerValue(updateCount)
} }
return return
} }
func (c *Controller) cmdExpire(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdExpire(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var key, id, svalue string var key, id, svalue string
var ok bool var ok bool
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
@ -885,23 +884,23 @@ func (c *Controller) cmdExpire(msg *server.Message) (res resp.Value, d commandDe
return return
} }
ok = false ok = false
col := c.getCol(key) col := server.getCol(key)
if col != nil { if col != nil {
_, _, ok = col.Get(id) _, _, ok = col.Get(id)
ok = ok && !c.hasExpired(key, id) ok = ok && !server.hasExpired(key, id)
} }
if ok { if ok {
c.expireAt(key, id, time.Now().Add(time.Duration(float64(time.Second)*value))) server.expireAt(key, id, time.Now().Add(time.Duration(float64(time.Second)*value)))
d.updated = true d.updated = true
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
if ok { if ok {
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
} else { } else {
return resp.SimpleStringValue(""), d, errIDNotFound return resp.SimpleStringValue(""), d, errIDNotFound
} }
case server.RESP: case RESP:
if ok { if ok {
res = resp.IntegerValue(1) res = resp.IntegerValue(1)
} else { } else {
@ -911,9 +910,9 @@ func (c *Controller) cmdExpire(msg *server.Message) (res resp.Value, d commandDe
return return
} }
func (c *Controller) cmdPersist(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (server *Server) cmdPersist(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var key, id string var key, id string
var ok bool var ok bool
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
@ -930,16 +929,16 @@ func (c *Controller) cmdPersist(msg *server.Message) (res resp.Value, d commandD
} }
var cleared bool var cleared bool
ok = false ok = false
col := c.getCol(key) col := server.getCol(key)
if col != nil { if col != nil {
_, _, ok = col.Get(id) _, _, ok = col.Get(id)
ok = ok && !c.hasExpired(key, id) ok = ok && !server.hasExpired(key, id)
if ok { if ok {
cleared = c.clearIDExpires(key, id) cleared = server.clearIDExpires(key, id)
} }
} }
if !ok { if !ok {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.IntegerValue(0), d, nil return resp.IntegerValue(0), d, nil
} }
return resp.SimpleStringValue(""), d, errIDNotFound return resp.SimpleStringValue(""), d, errIDNotFound
@ -948,9 +947,9 @@ func (c *Controller) cmdPersist(msg *server.Message) (res resp.Value, d commandD
d.updated = cleared d.updated = cleared
d.timestamp = time.Now() d.timestamp = time.Now()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.SimpleStringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.SimpleStringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
if cleared { if cleared {
res = resp.IntegerValue(1) res = resp.IntegerValue(1)
} else { } else {
@ -960,9 +959,9 @@ func (c *Controller) cmdPersist(msg *server.Message) (res resp.Value, d commandD
return return
} }
func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdTTL(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var key, id string var key, id string
var ok bool var ok bool
if vs, key, ok = tokenval(vs); !ok || key == "" { if vs, key, ok = tokenval(vs); !ok || key == "" {
@ -980,13 +979,13 @@ func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) {
var v float64 var v float64
ok = false ok = false
var ok2 bool var ok2 bool
col := c.getCol(key) col := server.getCol(key)
if col != nil { if col != nil {
_, _, ok = col.Get(id) _, _, ok = col.Get(id)
ok = ok && !c.hasExpired(key, id) ok = ok && !server.hasExpired(key, id)
if ok { if ok {
var at time.Time var at time.Time
at, ok2 = c.getExpires(key, id) at, ok2 = server.getExpires(key, id)
if ok2 { if ok2 {
if time.Now().After(at) { if time.Now().After(at) {
ok2 = false ok2 = false
@ -1000,7 +999,7 @@ func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) {
} }
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
if ok { if ok {
var ttl string var ttl string
if ok2 { if ok2 {
@ -1013,7 +1012,7 @@ func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) {
} else { } else {
return resp.SimpleStringValue(""), errIDNotFound return resp.SimpleStringValue(""), errIDNotFound
} }
case server.RESP: case RESP:
if ok { if ok {
if ok2 { if ok2 {
res = resp.IntegerValue(int(v)) res = resp.IntegerValue(int(v))

View File

@ -1,17 +1,15 @@
package controller package server
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"strconv" "strconv"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
// MASSINSERT num_keys num_points [minx miny maxx maxy] // MASSINSERT num_keys num_points [minx miny maxx maxy]
@ -23,9 +21,9 @@ func randMassInsertPosition(minLat, minLon, maxLat, maxLon float64) (float64, fl
return lat, lon return lat, lon
} }
func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdMassInsert(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
minLat, minLon, maxLat, maxLon := -90.0, -180.0, 90.0, 180.0 //37.10776, -122.67145, 38.19502, -121.62775 minLat, minLon, maxLat, maxLon := -90.0, -180.0, 90.0, 180.0 //37.10776, -122.67145, 38.19502, -121.62775
@ -33,62 +31,63 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
var cols, objs int var cols, objs int
var ok bool var ok bool
if vs, snumCols, ok = tokenval(vs); !ok || snumCols == "" { if vs, snumCols, ok = tokenval(vs); !ok || snumCols == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, snumPoints, ok = tokenval(vs); !ok || snumPoints == "" { if vs, snumPoints, ok = tokenval(vs); !ok || snumPoints == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
var sminLat, sminLon, smaxLat, smaxLon string var sminLat, sminLon, smaxLat, smaxLon string
if vs, sminLat, ok = tokenval(vs); !ok || sminLat == "" { if vs, sminLat, ok = tokenval(vs); !ok || sminLat == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, sminLon, ok = tokenval(vs); !ok || sminLon == "" { if vs, sminLon, ok = tokenval(vs); !ok || sminLon == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, smaxLat, ok = tokenval(vs); !ok || smaxLat == "" { if vs, smaxLat, ok = tokenval(vs); !ok || smaxLat == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, smaxLon, ok = tokenval(vs); !ok || smaxLon == "" { if vs, smaxLon, ok = tokenval(vs); !ok || smaxLon == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var err error var err error
if minLat, err = strconv.ParseFloat(sminLat, 64); err != nil { if minLat, err = strconv.ParseFloat(sminLat, 64); err != nil {
return server.NOMessage, err return NOMessage, err
} }
if minLon, err = strconv.ParseFloat(sminLon, 64); err != nil { if minLon, err = strconv.ParseFloat(sminLon, 64); err != nil {
return server.NOMessage, err return NOMessage, err
} }
if maxLat, err = strconv.ParseFloat(smaxLat, 64); err != nil { if maxLat, err = strconv.ParseFloat(smaxLat, 64); err != nil {
return server.NOMessage, err return NOMessage, err
} }
if maxLon, err = strconv.ParseFloat(smaxLon, 64); err != nil { if maxLon, err = strconv.ParseFloat(smaxLon, 64); err != nil {
return server.NOMessage, err return NOMessage, err
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errors.New("invalid number of arguments") return NOMessage, errors.New("invalid number of arguments")
} }
} }
n, err := strconv.ParseUint(snumCols, 10, 64) n, err := strconv.ParseUint(snumCols, 10, 64)
if err != nil { if err != nil {
return server.NOMessage, errInvalidArgument(snumCols) return NOMessage, errInvalidArgument(snumCols)
} }
cols = int(n) cols = int(n)
n, err = strconv.ParseUint(snumPoints, 10, 64) n, err = strconv.ParseUint(snumPoints, 10, 64)
if err != nil { if err != nil {
return server.NOMessage, errInvalidArgument(snumPoints) return NOMessage, errInvalidArgument(snumPoints)
} }
docmd := func(values []resp.Value) error { docmd := func(args []string) error {
nmsg := &server.Message{} nmsg := &Message{}
*nmsg = *msg *nmsg = *msg
nmsg.Values = values nmsg.Args = args
nmsg.Command = strings.ToLower(values[0].String())
var d commandDetailsT var d commandDetailsT
_, d, err = c.command(nmsg, nil, nil) _, d, err = c.command(nmsg, nil)
if err != nil { if err != nil {
return err return err
} }
return c.writeAOF(resp.ArrayValue(nmsg.Values), &d)
return c.writeAOF(nmsg.Args, &d)
} }
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
objs = int(n) objs = int(n)
@ -99,19 +98,21 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
// lock cycle // lock cycle
for j := 0; j < objs; j++ { for j := 0; j < objs; j++ {
id := strconv.FormatInt(int64(j), 10) id := strconv.FormatInt(int64(j), 10)
var values []resp.Value var values []string
if j%8 == 0 { if j%8 == 0 {
values = append(values, resp.StringValue("set"), values = append(values, "set", key, id, "STRING", fmt.Sprintf("str%v", j))
resp.StringValue(key), resp.StringValue(id),
resp.StringValue("STRING"), resp.StringValue(fmt.Sprintf("str%v", j)))
} else { } else {
lat, lon := randMassInsertPosition(minLat, minLon, maxLat, maxLon) lat, lon := randMassInsertPosition(minLat, minLon, maxLat, maxLon)
values = make([]resp.Value, 0, 16) values = make([]string, 0, 16)
values = append(values, resp.StringValue("set"), resp.StringValue(key), resp.StringValue(id)) values = append(values, "set", key, id)
if useRandField { if useRandField {
values = append(values, resp.StringValue("FIELD"), resp.StringValue("fname"), resp.FloatValue(rand.Float64()*10)) values = append(values, "FIELD", "fname",
strconv.FormatFloat(rand.Float64()*10, 'f', -1, 64))
} }
values = append(values, resp.StringValue("POINT"), resp.FloatValue(lat), resp.FloatValue(lon)) values = append(values, "POINT",
strconv.FormatFloat(lat, 'f', -1, 64),
strconv.FormatFloat(lon, 'f', -1, 64),
)
} }
if err := docmd(values); err != nil { if err := docmd(values); err != nil {
log.Fatal(err) log.Fatal(err)
@ -125,15 +126,15 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
}(key) }(key)
} }
log.Infof("massinsert: done %d objects", atomic.LoadUint64(&k)) log.Infof("massinsert: done %d objects", atomic.LoadUint64(&k))
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }
func (c *Controller) cmdSleep(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdSleep(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
if len(msg.Values) != 2 { if len(msg.Args) != 2 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
d, _ := strconv.ParseFloat(msg.Values[1].String(), 64) d, _ := strconv.ParseFloat(msg.Args[1], 64)
time.Sleep(time.Duration(float64(time.Second) * d)) time.Sleep(time.Duration(float64(time.Second) * d))
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"log" "log"
@ -6,8 +6,6 @@ import (
"time" "time"
"github.com/tidwall/btree" "github.com/tidwall/btree"
"github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/server"
) )
type exitem struct { type exitem struct {
@ -33,7 +31,7 @@ func (a *exitem) Less(v btree.Item, ctx interface{}) bool {
} }
// fillExpiresList occurs once at startup // fillExpiresList occurs once at startup
func (c *Controller) fillExpiresList() { func (c *Server) fillExpiresList() {
c.exlistmu.Lock() c.exlistmu.Lock()
c.exlist = c.exlist[:0] c.exlist = c.exlist[:0]
for key, m := range c.expires { for key, m := range c.expires {
@ -45,7 +43,7 @@ func (c *Controller) fillExpiresList() {
} }
// clearIDExpires clears a single item from the expires list. // clearIDExpires clears a single item from the expires list.
func (c *Controller) clearIDExpires(key, id string) (cleared bool) { func (c *Server) clearIDExpires(key, id string) (cleared bool) {
if len(c.expires) == 0 { if len(c.expires) == 0 {
return false return false
} }
@ -62,12 +60,12 @@ func (c *Controller) clearIDExpires(key, id string) (cleared bool) {
} }
// clearKeyExpires clears all items that are marked as expires from a single key. // clearKeyExpires clears all items that are marked as expires from a single key.
func (c *Controller) clearKeyExpires(key string) { func (c *Server) clearKeyExpires(key string) {
delete(c.expires, key) delete(c.expires, key)
} }
// expireAt marks an item as expires at a specific time. // expireAt marks an item as expires at a specific time.
func (c *Controller) expireAt(key, id string, at time.Time) { func (c *Server) expireAt(key, id string, at time.Time) {
m := c.expires[key] m := c.expires[key]
if m == nil { if m == nil {
m = make(map[string]time.Time) m = make(map[string]time.Time)
@ -80,7 +78,7 @@ func (c *Controller) expireAt(key, id string, at time.Time) {
} }
// getExpires returns the when an item expires. // getExpires returns the when an item expires.
func (c *Controller) getExpires(key, id string) (at time.Time, ok bool) { func (c *Server) getExpires(key, id string) (at time.Time, ok bool) {
if len(c.expires) == 0 { if len(c.expires) == 0 {
return at, false return at, false
} }
@ -93,7 +91,7 @@ func (c *Controller) getExpires(key, id string) (at time.Time, ok bool) {
} }
// hasExpired returns true if an item has expired. // hasExpired returns true if an item has expired.
func (c *Controller) hasExpired(key, id string) bool { func (c *Server) hasExpired(key, id string) bool {
at, ok := c.getExpires(key, id) at, ok := c.getExpires(key, id)
if !ok { if !ok {
return false return false
@ -103,7 +101,7 @@ func (c *Controller) hasExpired(key, id string) bool {
// backgroundExpiring watches for when items that have expired must be purged // backgroundExpiring watches for when items that have expired must be purged
// from the database. It's executes 10 times a seconds. // from the database. It's executes 10 times a seconds.
func (c *Controller) backgroundExpiring() { func (c *Server) backgroundExpiring() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
var purgelist []exitem var purgelist []exitem
for { for {
@ -128,16 +126,15 @@ func (c *Controller) backgroundExpiring() {
for _, item := range purgelist { for _, item := range purgelist {
if c.hasExpired(item.key, item.id) { if c.hasExpired(item.key, item.id) {
// purge from database // purge from database
msg := &server.Message{} msg := &Message{}
msg.Values = resp.MultiBulkValue("del", item.key, item.id).Array() msg.Args = []string{"del", item.key, item.id}
msg.Command = "del"
_, d, err := c.cmdDel(msg) _, d, err := c.cmdDel(msg)
if err != nil { if err != nil {
c.mu.Unlock() c.mu.Unlock()
log.Fatal(err) log.Fatal(err)
continue continue
} }
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil { if err := c.writeAOF(msg.Args, &d); err != nil {
c.mu.Unlock() c.mu.Unlock()
log.Fatal(err) log.Fatal(err)
continue continue

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"math" "math"
@ -9,7 +9,6 @@ import (
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
// FenceMatch executes a fence match returns back json messages for fence detection. // FenceMatch executes a fence match returns back json messages for fence detection.
@ -175,7 +174,7 @@ func fenceMatch(
} }
sw.fmap = details.fmap sw.fmap = details.fmap
sw.fullFields = true sw.fullFields = true
sw.msg.OutputType = server.JSON sw.msg.OutputType = JSON
sw.writeObject(ScanWriterParams{ sw.writeObject(ScanWriterParams{
id: details.id, id: details.id,
o: details.obj, o: details.obj,
@ -354,7 +353,7 @@ func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
} }
func fenceMatchRoam( func fenceMatchRoam(
c *Controller, fence *liveFenceSwitches, c *Server, fence *liveFenceSwitches,
tkey, tid string, obj geojson.Object, tkey, tid string, obj geojson.Object,
) (nearbys, faraways []roamMatch) { ) (nearbys, faraways []roamMatch) {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"errors" "errors"
@ -12,27 +12,26 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
var errNoLongerFollowing = errors.New("no longer following") var errNoLongerFollowing = errors.New("no longer following")
const checksumsz = 512 * 1024 const checksumsz = 512 * 1024
func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdFollow(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var host, sport string var host, sport string
if vs, host, ok = tokenval(vs); !ok || host == "" { if vs, host, ok = tokenval(vs); !ok || host == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, sport, ok = tokenval(vs); !ok || sport == "" { if vs, sport, ok = tokenval(vs); !ok || sport == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
host = strings.ToLower(host) host = strings.ToLower(host)
sport = strings.ToLower(sport) sport = strings.ToLower(sport)
@ -44,7 +43,7 @@ func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error)
} else { } else {
n, err := strconv.ParseUint(sport, 10, 64) n, err := strconv.ParseUint(sport, 10, 64)
if err != nil { if err != nil {
return server.NOMessage, errInvalidArgument(sport) return NOMessage, errInvalidArgument(sport)
} }
port := int(n) port := int(n)
update = c.config.followHost() != host || c.config.followPort() != port update = c.config.followHost() != host || c.config.followPort() != port
@ -54,30 +53,30 @@ func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error)
conn, err := DialTimeout(fmt.Sprintf("%s:%d", host, port), time.Second*2) conn, err := DialTimeout(fmt.Sprintf("%s:%d", host, port), time.Second*2)
if err != nil { if err != nil {
c.mu.Lock() c.mu.Lock()
return server.NOMessage, fmt.Errorf("cannot follow: %v", err) return NOMessage, fmt.Errorf("cannot follow: %v", err)
} }
defer conn.Close() defer conn.Close()
if auth != "" { if auth != "" {
if err := c.followDoLeaderAuth(conn, auth); err != nil { if err := c.followDoLeaderAuth(conn, auth); err != nil {
return server.NOMessage, fmt.Errorf("cannot follow: %v", err) return NOMessage, fmt.Errorf("cannot follow: %v", err)
} }
} }
m, err := doServer(conn) m, err := doServer(conn)
if err != nil { if err != nil {
c.mu.Lock() c.mu.Lock()
return server.NOMessage, fmt.Errorf("cannot follow: %v", err) return NOMessage, fmt.Errorf("cannot follow: %v", err)
} }
if m["id"] == "" { if m["id"] == "" {
c.mu.Lock() c.mu.Lock()
return server.NOMessage, fmt.Errorf("cannot follow: invalid id") return NOMessage, fmt.Errorf("cannot follow: invalid id")
} }
if m["id"] == c.config.serverID() { if m["id"] == c.config.serverID() {
c.mu.Lock() c.mu.Lock()
return server.NOMessage, fmt.Errorf("cannot follow self") return NOMessage, fmt.Errorf("cannot follow self")
} }
if m["following"] != "" { if m["following"] != "" {
c.mu.Lock() c.mu.Lock()
return server.NOMessage, fmt.Errorf("cannot follow a follower") return NOMessage, fmt.Errorf("cannot follow a follower")
} }
c.mu.Lock() c.mu.Lock()
} }
@ -94,10 +93,10 @@ func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error)
log.Infof("following no one") log.Infof("following no one")
} }
} }
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }
func doServer(conn *Conn) (map[string]string, error) { func doServer(conn *RESPConn) (map[string]string, error) {
v, err := conn.Do("server") v, err := conn.Do("server")
if err != nil { if err != nil {
return nil, err return nil, err
@ -113,29 +112,27 @@ func doServer(conn *Conn) (map[string]string, error) {
return m, err return m, err
} }
func (c *Controller) followHandleCommand(values []resp.Value, followc int, w io.Writer) (int, error) { func (c *Server) followHandleCommand(args []string, followc int, w io.Writer) (int, error) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if c.followc.get() != followc { if c.followc.get() != followc {
return c.aofsz, errNoLongerFollowing return c.aofsz, errNoLongerFollowing
} }
msg := &server.Message{ msg := &Message{Args: args}
Command: strings.ToLower(values[0].String()),
Values: values, _, d, err := c.command(msg, nil)
}
_, d, err := c.command(msg, nil, nil)
if err != nil { if err != nil {
if commandErrIsFatal(err) { if commandErrIsFatal(err) {
return c.aofsz, err return c.aofsz, err
} }
} }
if err := c.writeAOF(resp.ArrayValue(values), &d); err != nil { if err := c.writeAOF(args, &d); err != nil {
return c.aofsz, err return c.aofsz, err
} }
return c.aofsz, nil return c.aofsz, nil
} }
func (c *Controller) followDoLeaderAuth(conn *Conn, auth string) error { func (c *Server) followDoLeaderAuth(conn *RESPConn, auth string) error {
v, err := conn.Do("auth", auth) v, err := conn.Do("auth", auth)
if err != nil { if err != nil {
return err return err
@ -149,7 +146,7 @@ func (c *Controller) followDoLeaderAuth(conn *Conn, auth string) error {
return nil return nil
} }
func (c *Controller) followStep(host string, port int, followc int) error { func (c *Server) followStep(host string, port int, followc int) error {
if c.followc.get() != followc { if c.followc.get() != followc {
return errNoLongerFollowing return errNoLongerFollowing
} }
@ -228,8 +225,12 @@ func (c *Controller) followStep(host string, port int, followc int) error {
if telnet || v.Type() != resp.Array { if telnet || v.Type() != resp.Array {
return errors.New("invalid multibulk") return errors.New("invalid multibulk")
} }
svals := make([]string, len(vals))
for i := 0; i < len(vals); i++ {
svals[i] = vals[i].String()
}
aofsz, err := c.followHandleCommand(vals, followc, nullw) aofsz, err := c.followHandleCommand(svals, followc, nullw)
if err != nil { if err != nil {
return err return err
} }
@ -247,7 +248,7 @@ func (c *Controller) followStep(host string, port int, followc int) error {
} }
} }
func (c *Controller) follow(host string, port int, followc int) { func (c *Server) follow(host string, port int, followc int) {
for { for {
err := c.followStep(host, port, followc) err := c.followStep(host, port, followc)
if err == errNoLongerFollowing { if err == errNoLongerFollowing {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -14,7 +14,6 @@ import (
"github.com/tidwall/tile38/internal/endpoint" "github.com/tidwall/tile38/internal/endpoint"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
var hookLogSetDefaults = &buntdb.SetOptions{ var hookLogSetDefaults = &buntdb.SetOptions{
@ -36,22 +35,22 @@ func (a hooksByName) Swap(i, j int) {
a[i], a[j] = a[j], a[i] a[i], a[j] = a[j], a[i]
} }
func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) ( func (c *Server) cmdSetHook(msg *Message, chanCmd bool) (
res resp.Value, d commandDetailsT, err error, res resp.Value, d commandDetailsT, err error,
) { ) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var name, urls, cmd string var name, urls, cmd string
var ok bool var ok bool
if vs, name, ok = tokenval(vs); !ok || name == "" { if vs, name, ok = tokenval(vs); !ok || name == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
var endpoints []string var endpoints []string
if chanCmd { if chanCmd {
endpoints = []string{"local://" + name} endpoints = []string{"local://" + name}
} else { } else {
if vs, urls, ok = tokenval(vs); !ok || urls == "" { if vs, urls, ok = tokenval(vs); !ok || urls == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
for _, url := range strings.Split(urls, ",") { for _, url := range strings.Split(urls, ",") {
url = strings.TrimSpace(url) url = strings.TrimSpace(url)
@ -63,7 +62,7 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
endpoints = append(endpoints, url) endpoints = append(endpoints, url)
} }
} }
var commandvs []resp.Value var commandvs []string
var cmdlc string var cmdlc string
var types []string var types []string
var expires float64 var expires float64
@ -72,31 +71,31 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
for { for {
commandvs = vs commandvs = vs
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" { if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
cmdlc = strings.ToLower(cmd) cmdlc = strings.ToLower(cmd)
switch cmdlc { switch cmdlc {
default: default:
return server.NOMessage, d, errInvalidArgument(cmd) return NOMessage, d, errInvalidArgument(cmd)
case "meta": case "meta":
var metakey string var metakey string
var metaval string var metaval string
if vs, metakey, ok = tokenval(vs); !ok || metakey == "" { if vs, metakey, ok = tokenval(vs); !ok || metakey == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
if vs, metaval, ok = tokenval(vs); !ok || metaval == "" { if vs, metaval, ok = tokenval(vs); !ok || metaval == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
metaMap[metakey] = metaval metaMap[metakey] = metaval
continue continue
case "ex": case "ex":
var s string var s string
if vs, s, ok = tokenval(vs); !ok || s == "" { if vs, s, ok = tokenval(vs); !ok || s == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
v, err := strconv.ParseFloat(s, 64) v, err := strconv.ParseFloat(s, 64)
if err != nil { if err != nil {
return server.NOMessage, d, errInvalidArgument(s) return NOMessage, d, errInvalidArgument(s)
} }
expires = v expires = v
expiresSet = true expiresSet = true
@ -111,20 +110,18 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
s, err := c.cmdSearchArgs(true, cmdlc, vs, types) s, err := c.cmdSearchArgs(true, cmdlc, vs, types)
defer s.Close() defer s.Close()
if err != nil { if err != nil {
return server.NOMessage, d, err return NOMessage, d, err
} }
if !s.fence { if !s.fence {
return server.NOMessage, d, errors.New("missing FENCE argument") return NOMessage, d, errors.New("missing FENCE argument")
} }
s.cmd = cmdlc s.cmd = cmdlc
cmsg := &server.Message{} cmsg := &Message{}
*cmsg = *msg *cmsg = *msg
cmsg.Values = make([]resp.Value, len(commandvs)) cmsg.Args = make([]string, len(commandvs))
for i := 0; i < len(commandvs); i++ { for i := 0; i < len(commandvs); i++ {
cmsg.Values[i] = commandvs[i] cmsg.Args[i] = commandvs[i]
} }
cmsg.Command = strings.ToLower(cmsg.Values[0].String())
metas := make([]FenceMeta, 0, len(metaMap)) metas := make([]FenceMeta, 0, len(metaMap))
for key, val := range metaMap { for key, val := range metaMap {
metas = append(metas, FenceMeta{key, val}) metas = append(metas, FenceMeta{key, val})
@ -155,11 +152,11 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields) s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
if err != nil { if err != nil {
return server.NOMessage, d, err return NOMessage, d, err
} }
if h, ok := c.hooks[name]; ok { if h, ok := c.hooks[name]; ok {
if h.channel != chanCmd { if h.channel != chanCmd {
return server.NOMessage, d, return NOMessage, d,
errors.New("hooks and channels cannot share the same name") errors.New("hooks and channels cannot share the same name")
} }
if h.Equals(hook) { if h.Equals(hook) {
@ -170,9 +167,9 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
c.hookex.Push(hook) c.hookex.Push(hook)
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return server.OKMessage(msg, start), d, nil return OKMessage(msg, start), d, nil
case server.RESP: case RESP:
return resp.IntegerValue(0), d, nil return resp.IntegerValue(0), d, nil
} }
} }
@ -198,27 +195,27 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
c.hookex.Push(hook) c.hookex.Push(hook)
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return server.OKMessage(msg, start), d, nil return OKMessage(msg, start), d, nil
case server.RESP: case RESP:
return resp.IntegerValue(1), d, nil return resp.IntegerValue(1), d, nil
} }
return server.NOMessage, d, nil return NOMessage, d, nil
} }
func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) ( func (c *Server) cmdDelHook(msg *Message, chanCmd bool) (
res resp.Value, d commandDetailsT, err error, res resp.Value, d commandDetailsT, err error,
) { ) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var name string var name string
var ok bool var ok bool
if vs, name, ok = tokenval(vs); !ok || name == "" { if vs, name, ok = tokenval(vs); !ok || name == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
if h, ok := c.hooks[name]; ok && h.channel == chanCmd { if h, ok := c.hooks[name]; ok && h.channel == chanCmd {
h.Close() h.Close()
@ -231,9 +228,9 @@ func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) (
d.timestamp = time.Now() d.timestamp = time.Now()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return server.OKMessage(msg, start), d, nil return OKMessage(msg, start), d, nil
case server.RESP: case RESP:
if d.updated { if d.updated {
return resp.IntegerValue(1), d, nil return resp.IntegerValue(1), d, nil
} }
@ -242,19 +239,19 @@ func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) (
return return
} }
func (c *Controller) cmdPDelHook(msg *server.Message, channel bool) ( func (c *Server) cmdPDelHook(msg *Message, channel bool) (
res resp.Value, d commandDetailsT, err error, res resp.Value, d commandDetailsT, err error,
) { ) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var pattern string var pattern string
var ok bool var ok bool
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" { if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
count := 0 count := 0
@ -277,9 +274,9 @@ func (c *Controller) cmdPDelHook(msg *server.Message, channel bool) (
d.timestamp = time.Now() d.timestamp = time.Now()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return server.OKMessage(msg, start), d, nil return OKMessage(msg, start), d, nil
case server.RESP: case RESP:
return resp.IntegerValue(count), d, nil return resp.IntegerValue(count), d, nil
} }
return return
@ -288,25 +285,23 @@ func (c *Controller) cmdPDelHook(msg *server.Message, channel bool) (
// possiblyExpireHook will evaluate a hook by it's name for expiration and // possiblyExpireHook will evaluate a hook by it's name for expiration and
// purge it from the database if needed. This operation is called from an // purge it from the database if needed. This operation is called from an
// independent goroutine // independent goroutine
func (c *Controller) possiblyExpireHook(name string) { func (c *Server) possiblyExpireHook(name string) {
c.mu.Lock() c.mu.Lock()
if h, ok := c.hooks[name]; ok { if h, ok := c.hooks[name]; ok {
if !h.expires.IsZero() && time.Now().After(h.expires) { if !h.expires.IsZero() && time.Now().After(h.expires) {
// purge from database // purge from database
msg := &server.Message{} msg := &Message{}
if h.channel { if h.channel {
msg.Values = resp.MultiBulkValue("delchan", h.Name).Array() msg.Args = []string{"delchan", h.Name}
msg.Command = "delchan"
} else { } else {
msg.Values = resp.MultiBulkValue("delhook", h.Name).Array() msg.Args = []string{"delhook", h.Name}
msg.Command = "delhook"
} }
_, d, err := c.cmdDelHook(msg, h.channel) _, d, err := c.cmdDelHook(msg, h.channel)
if err != nil { if err != nil {
c.mu.Unlock() c.mu.Unlock()
panic(err) panic(err)
} }
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil { if err := c.writeAOF(msg.Args, &d); err != nil {
c.mu.Unlock() c.mu.Unlock()
panic(err) panic(err)
} }
@ -316,20 +311,20 @@ func (c *Controller) possiblyExpireHook(name string) {
c.mu.Unlock() c.mu.Unlock()
} }
func (c *Controller) cmdHooks(msg *server.Message, channel bool) ( func (c *Server) cmdHooks(msg *Message, channel bool) (
res resp.Value, err error, res resp.Value, err error,
) { ) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var pattern string var pattern string
var ok bool var ok bool
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" { if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var hooks []*Hook var hooks []*Hook
@ -345,7 +340,7 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
sort.Sort(hooksByName(hooks)) sort.Sort(hooksByName(hooks))
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
buf.WriteString(`{"ok":true,`) buf.WriteString(`{"ok":true,`)
if channel { if channel {
@ -370,11 +365,11 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
} }
} }
buf.WriteString(`],"command":[`) buf.WriteString(`],"command":[`)
for i, v := range hook.Message.Values { for i, v := range hook.Message.Args {
if i > 0 { if i > 0 {
buf.WriteString(`,`) buf.WriteString(`,`)
} }
buf.WriteString(jsonString(v.String())) buf.WriteString(jsonString(v))
} }
buf.WriteString(`],"meta":{`) buf.WriteString(`],"meta":{`)
for i, meta := range hook.Metas { for i, meta := range hook.Metas {
@ -390,7 +385,7 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
buf.WriteString(`],"elapsed":"` + buf.WriteString(`],"elapsed":"` +
time.Now().Sub(start).String() + "\"}") time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
var vals []resp.Value var vals []resp.Value
for _, hook := range hooks { for _, hook := range hooks {
var hvals []resp.Value var hvals []resp.Value
@ -401,7 +396,11 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
evals = append(evals, resp.StringValue(endpoint)) evals = append(evals, resp.StringValue(endpoint))
} }
hvals = append(hvals, resp.ArrayValue(evals)) hvals = append(hvals, resp.ArrayValue(evals))
hvals = append(hvals, resp.ArrayValue(hook.Message.Values)) avals := make([]resp.Value, len(hook.Message.Args))
for i := 0; i < len(hook.Message.Args); i++ {
avals[i] = resp.StringValue(hook.Message.Args[i])
}
hvals = append(hvals, resp.ArrayValue(avals))
var metas []resp.Value var metas []resp.Value
for _, meta := range hook.Metas { for _, meta := range hook.Metas {
metas = append(metas, resp.StringValue(meta.Name)) metas = append(metas, resp.StringValue(meta.Name))
@ -421,7 +420,7 @@ type Hook struct {
Key string Key string
Name string Name string
Endpoints []string Endpoints []string
Message *server.Message Message *Message
Fence *liveFenceSwitches Fence *liveFenceSwitches
ScanWriter *scanWriter ScanWriter *scanWriter
Metas []FenceMeta Metas []FenceMeta
@ -461,9 +460,15 @@ func (h *Hook) Equals(hook *Hook) bool {
return false return false
} }
} }
if len(h.Message.Args) != len(hook.Message.Args) {
return resp.ArrayValue(h.Message.Values).Equals( return false
resp.ArrayValue(hook.Message.Values)) }
for i := 0; i < len(h.Message.Args); i++ {
if h.Message.Args[i] != hook.Message.Args[i] {
return false
}
}
return true
} }
// FenceMeta is a meta key/value pair for fences // FenceMeta is a meta key/value pair for fences

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -12,7 +12,6 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/sjson" "github.com/tidwall/sjson"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/server"
) )
func appendJSONString(b []byte, s string) []byte { func appendJSONString(b []byte, s string) []byte {
@ -86,44 +85,44 @@ func jsonTimeFormat(t time.Time) string {
return string(b) return string(b)
} }
func (c *Controller) cmdJget(msg *server.Message) (resp.Value, error) { func (c *Server) cmdJget(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
if len(msg.Values) < 3 { if len(msg.Args) < 3 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(msg.Values) > 5 { if len(msg.Args) > 5 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
key := msg.Values[1].String() key := msg.Args[1]
id := msg.Values[2].String() id := msg.Args[2]
var doget bool var doget bool
var path string var path string
var raw bool var raw bool
if len(msg.Values) > 3 { if len(msg.Args) > 3 {
doget = true doget = true
path = msg.Values[3].String() path = msg.Args[3]
if len(msg.Values) == 5 { if len(msg.Args) == 5 {
if strings.ToLower(msg.Values[4].String()) == "raw" { if strings.ToLower(msg.Args[4]) == "raw" {
raw = true raw = true
} else { } else {
return server.NOMessage, errInvalidArgument(msg.Values[4].String()) return NOMessage, errInvalidArgument(msg.Args[4])
} }
} }
} }
col := c.getCol(key) col := c.getCol(key)
if col == nil { if col == nil {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return server.NOMessage, errKeyNotFound return NOMessage, errKeyNotFound
} }
o, _, ok := col.Get(id) o, _, ok := col.Get(id)
if !ok { if !ok {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return server.NOMessage, errIDNotFound return NOMessage, errIDNotFound
} }
var res gjson.Result var res gjson.Result
if doget { if doget {
@ -138,38 +137,38 @@ func (c *Controller) cmdJget(msg *server.Message) (resp.Value, error) {
val = res.String() val = res.String()
} }
var buf bytes.Buffer var buf bytes.Buffer
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
if res.Exists() { if res.Exists() {
buf.WriteString(`,"value":` + jsonString(val)) buf.WriteString(`,"value":` + jsonString(val))
} }
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
if !res.Exists() { if !res.Exists() {
return resp.NullValue(), nil return resp.NullValue(), nil
} }
return resp.StringValue(val), nil return resp.StringValue(val), nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (c *Server) cmdJset(msg *Message) (res resp.Value, d commandDetailsT, err error) {
// JSET key path value [RAW] // JSET key path value [RAW]
start := time.Now() start := time.Now()
var raw, str bool var raw, str bool
switch len(msg.Values) { switch len(msg.Args) {
default: default:
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
case 5: case 5:
case 6: case 6:
switch strings.ToLower(msg.Values[5].String()) { switch strings.ToLower(msg.Args[5]) {
default: default:
return server.NOMessage, d, errInvalidArgument(msg.Values[5].String()) return NOMessage, d, errInvalidArgument(msg.Args[5])
case "raw": case "raw":
raw = true raw = true
case "str": case "str":
@ -177,10 +176,10 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
} }
} }
key := msg.Values[1].String() key := msg.Args[1]
id := msg.Values[2].String() id := msg.Args[2]
path := msg.Values[3].String() path := msg.Args[3]
val := msg.Values[4].String() val := msg.Args[4]
if !str && !raw { if !str && !raw {
switch val { switch val {
default: default:
@ -216,18 +215,12 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
json, err = sjson.Set(json, path, val) json, err = sjson.Set(json, path, val)
} }
if err != nil { if err != nil {
return server.NOMessage, d, err return NOMessage, d, err
} }
if geoobj { if geoobj {
nmsg := *msg nmsg := *msg
nmsg.Values = []resp.Value{ nmsg.Args = []string{"SET", key, id, "OBJECT", json}
resp.StringValue("SET"),
resp.StringValue(key),
resp.StringValue(id),
resp.StringValue("OBJECT"),
resp.StringValue(json),
}
// SET key id OBJECT json // SET key id OBJECT json
return c.cmdSet(&nmsg) return c.cmdSet(&nmsg)
} }
@ -244,33 +237,33 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
c.clearIDExpires(key, id) c.clearIDExpires(key, id)
col.Set(d.id, d.obj, nil, nil) col.Set(d.id, d.obj, nil, nil)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), d, nil return resp.StringValue(buf.String()), d, nil
case server.RESP: case RESP:
return resp.SimpleStringValue("OK"), d, nil return resp.SimpleStringValue("OK"), d, nil
} }
return server.NOMessage, d, nil return NOMessage, d, nil
} }
func (c *Controller) cmdJdel(msg *server.Message) (res resp.Value, d commandDetailsT, err error) { func (c *Server) cmdJdel(msg *Message) (res resp.Value, d commandDetailsT, err error) {
start := time.Now() start := time.Now()
if len(msg.Values) != 4 { if len(msg.Args) != 4 {
return server.NOMessage, d, errInvalidNumberOfArguments return NOMessage, d, errInvalidNumberOfArguments
} }
key := msg.Values[1].String() key := msg.Args[1]
id := msg.Values[2].String() id := msg.Args[2]
path := msg.Values[3].String() path := msg.Args[3]
col := c.getCol(key) col := c.getCol(key)
if col == nil { if col == nil {
if msg.OutputType == server.RESP { if msg.OutputType == RESP {
return resp.IntegerValue(0), d, nil return resp.IntegerValue(0), d, nil
} }
return server.NOMessage, d, errKeyNotFound return NOMessage, d, errKeyNotFound
} }
var json string var json string
@ -282,27 +275,21 @@ func (c *Controller) cmdJdel(msg *server.Message) (res resp.Value, d commandDeta
} }
njson, err := sjson.Delete(json, path) njson, err := sjson.Delete(json, path)
if err != nil { if err != nil {
return server.NOMessage, d, err return NOMessage, d, err
} }
if njson == json { if njson == json {
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
return server.NOMessage, d, errPathNotFound return NOMessage, d, errPathNotFound
case server.RESP: case RESP:
return resp.IntegerValue(0), d, nil return resp.IntegerValue(0), d, nil
} }
return server.NOMessage, d, nil return NOMessage, d, nil
} }
json = njson json = njson
if geoobj { if geoobj {
nmsg := *msg nmsg := *msg
nmsg.Values = []resp.Value{ nmsg.Args = []string{"SET", key, id, "OBJECT", json}
resp.StringValue("SET"),
resp.StringValue(key),
resp.StringValue(id),
resp.StringValue("OBJECT"),
resp.StringValue(json),
}
// SET key id OBJECT json // SET key id OBJECT json
return c.cmdSet(&nmsg) return c.cmdSet(&nmsg)
} }
@ -316,13 +303,13 @@ func (c *Controller) cmdJdel(msg *server.Message) (res resp.Value, d commandDeta
c.clearIDExpires(d.key, d.id) c.clearIDExpires(d.key, d.id)
col.Set(d.id, d.obj, nil, nil) col.Set(d.id, d.obj, nil, nil)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), d, nil return resp.StringValue(buf.String()), d, nil
case server.RESP: case RESP:
return resp.IntegerValue(1), d, nil return resp.IntegerValue(1), d, nil
} }
return server.NOMessage, d, nil return NOMessage, d, nil
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -7,25 +7,24 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
func (c *Controller) cmdKeys(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdKeys(msg *Message) (res resp.Value, err error) {
var start = time.Now() var start = time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var pattern string var pattern string
var ok bool var ok bool
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" { if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var wr = &bytes.Buffer{} var wr = &bytes.Buffer{}
var once bool var once bool
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`{"ok":true,"keys":[`) wr.WriteString(`{"ok":true,"keys":[`)
} }
var everything bool var everything bool
@ -47,16 +46,16 @@ func (c *Controller) cmdKeys(msg *server.Message) (res resp.Value, err error) {
} }
if match { if match {
if once { if once {
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteByte(',') wr.WriteByte(',')
} }
} else { } else {
once = true once = true
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
wr.WriteString(jsonString(key)) wr.WriteString(jsonString(key))
case server.RESP: case RESP:
vals = append(vals, resp.StringValue(key)) vals = append(vals, resp.StringValue(key))
} }
} }
@ -84,7 +83,7 @@ func (c *Controller) cmdKeys(msg *server.Message) (res resp.Value, err error) {
c.cols.Ascend(greaterPivot, iterator) c.cols.Ascend(greaterPivot, iterator)
} }
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`],"elapsed":"` + time.Now().Sub(start).String() + "\"}") wr.WriteString(`],"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(wr.String()), nil return resp.StringValue(wr.String()), nil
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -9,7 +9,6 @@ import (
"sync" "sync"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
type liveBuffer struct { type liveBuffer struct {
@ -20,7 +19,7 @@ type liveBuffer struct {
cond *sync.Cond cond *sync.Cond
} }
func (c *Controller) processLives() { func (c *Server) processLives() {
for { for {
c.lcond.L.Lock() c.lcond.L.Lock()
for len(c.lstack) > 0 { for len(c.lstack) > 0 {
@ -47,30 +46,31 @@ func writeLiveMessage(
conn net.Conn, conn net.Conn,
message []byte, message []byte,
wrapRESP bool, wrapRESP bool,
connType server.Type, connType Type, websocket bool,
websocket bool,
) error { ) error {
if len(message) == 0 { if len(message) == 0 {
return nil return nil
} }
if websocket { if websocket {
return server.WriteWebSocketMessage(conn, message) return WriteWebSocketMessage(conn, message)
} }
var err error var err error
switch connType { switch connType {
case server.RESP: case RESP:
if wrapRESP { if wrapRESP {
_, err = fmt.Fprintf(conn, "$%d\r\n%s\r\n", len(message), string(message)) _, err = fmt.Fprintf(conn, "$%d\r\n%s\r\n", len(message), string(message))
} else { } else {
_, err = conn.Write(message) _, err = conn.Write(message)
} }
case server.Native: case Native:
_, err = fmt.Fprintf(conn, "$%d %s\r\n", len(message), string(message)) _, err = fmt.Fprintf(conn, "$%d %s\r\n", len(message), string(message))
} }
return err return err
} }
func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReader, msg *server.Message, websocket bool) error { func (c *Server) goLive(
inerr error, conn net.Conn, rd *PipelineReader, msg *Message, websocket bool,
) error {
addr := conn.RemoteAddr().String() addr := conn.RemoteAddr().String()
log.Info("live " + addr) log.Info("live " + addr)
defer func() { defer func() {
@ -139,7 +139,7 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
if v == nil { if v == nil {
continue continue
} }
switch v.Command { switch v.Command() {
default: default:
log.Error("received a live command that was not QUIT") log.Error("received a live command that was not QUIT")
return return
@ -152,13 +152,13 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
outputType := msg.OutputType outputType := msg.OutputType
connType := msg.ConnType connType := msg.ConnType
if websocket { if websocket {
outputType = server.JSON outputType = JSON
} }
var livemsg []byte var livemsg []byte
switch outputType { switch outputType {
case server.JSON: case JSON:
livemsg = []byte(`{"ok":true,"live":true}`) livemsg = []byte(`{"ok":true,"live":true}`)
case server.RESP: case RESP:
livemsg = []byte("+OK\r\n") livemsg = []byte("+OK\r\n")
} }
if err := writeLiveMessage(conn, livemsg, false, connType, websocket); err != nil { if err := writeLiveMessage(conn, livemsg, false, connType, websocket); err != nil {

View File

@ -1,42 +1,41 @@
package controller package server
import ( import (
"strings" "strings"
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/server"
) )
func (c *Controller) cmdOutput(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdOutput(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var arg string var arg string
var ok bool var ok bool
if len(vs) != 0 { if len(vs) != 0 {
if _, arg, ok = tokenval(vs); !ok || arg == "" { if _, arg, ok = tokenval(vs); !ok || arg == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
// Setting the original message output type will be picked up by the // Setting the original message output type will be picked up by the
// server prior to the next command being executed. // server prior to the next command being executed.
switch strings.ToLower(arg) { switch strings.ToLower(arg) {
default: default:
return server.NOMessage, errInvalidArgument(arg) return NOMessage, errInvalidArgument(arg)
case "json": case "json":
msg.OutputType = server.JSON msg.OutputType = JSON
case "resp": case "resp":
msg.OutputType = server.RESP msg.OutputType = RESP
} }
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }
// return the output // return the output
switch msg.OutputType { switch msg.OutputType {
default: default:
return server.NOMessage, nil return NOMessage, nil
case server.JSON: case JSON:
return resp.StringValue(`{"ok":true,"output":"json","elapsed":` + time.Now().Sub(start).String() + `}`), nil return resp.StringValue(`{"ok":true,"output":"json","elapsed":` + time.Now().Sub(start).String() + `}`), nil
case server.RESP: case RESP:
return resp.StringValue("resp"), nil return resp.StringValue("resp"), nil
} }
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"io" "io"
@ -12,7 +12,6 @@ import (
"github.com/tidwall/redcon" "github.com/tidwall/redcon"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
const ( const (
@ -35,7 +34,7 @@ func newPubsub() *pubsub {
} }
// Publish a message to subscribers // Publish a message to subscribers
func (c *Controller) Publish(channel string, message ...string) int { func (c *Server) Publish(channel string, message ...string) int {
var msgs []submsg var msgs []submsg
c.pubsub.mu.RLock() c.pubsub.mu.RLock()
if hub := c.pubsub.hubs[pubsubChannel][channel]; hub != nil { if hub := c.pubsub.hubs[pubsubChannel][channel]; hub != nil {
@ -131,53 +130,53 @@ func newSubhub() *subhub {
} }
type liveSubscriptionSwitches struct { type liveSubscriptionSwitches struct {
// no fields. everything is managed through the server.Message // no fields. everything is managed through the Message
} }
func (sub liveSubscriptionSwitches) Error() string { func (sub liveSubscriptionSwitches) Error() string {
return goingLive return goingLive
} }
func (c *Controller) cmdSubscribe(msg *server.Message) (resp.Value, error) { func (c *Server) cmdSubscribe(msg *Message) (resp.Value, error) {
if len(msg.Values) < 2 { if len(msg.Args) < 2 {
return resp.Value{}, errInvalidNumberOfArguments return resp.Value{}, errInvalidNumberOfArguments
} }
return server.NOMessage, liveSubscriptionSwitches{} return NOMessage, liveSubscriptionSwitches{}
} }
func (c *Controller) cmdPsubscribe(msg *server.Message) (resp.Value, error) { func (c *Server) cmdPsubscribe(msg *Message) (resp.Value, error) {
if len(msg.Values) < 2 { if len(msg.Args) < 2 {
return resp.Value{}, errInvalidNumberOfArguments return resp.Value{}, errInvalidNumberOfArguments
} }
return server.NOMessage, liveSubscriptionSwitches{} return NOMessage, liveSubscriptionSwitches{}
} }
func (c *Controller) cmdPublish(msg *server.Message) (resp.Value, error) { func (c *Server) cmdPublish(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
if len(msg.Values) != 3 { if len(msg.Args) != 3 {
return resp.Value{}, errInvalidNumberOfArguments return resp.Value{}, errInvalidNumberOfArguments
} }
channel := msg.Values[1].String() channel := msg.Args[1]
message := msg.Values[2].String() message := msg.Args[2]
//geofence := gjson.Valid(message) && gjson.Get(message, "fence").Bool() //geofence := gjson.Valid(message) && gjson.Get(message, "fence").Bool()
n := c.Publish(channel, message) //, geofence) n := c.Publish(channel, message) //, geofence)
var res resp.Value var res resp.Value
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
res = resp.StringValue(`{"ok":true` + res = resp.StringValue(`{"ok":true` +
`,"published":` + strconv.FormatInt(int64(n), 10) + `,"published":` + strconv.FormatInt(int64(n), 10) +
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`) `,"elapsed":"` + time.Now().Sub(start).String() + `"}`)
case server.RESP: case RESP:
res = resp.IntegerValue(n) res = resp.IntegerValue(n)
} }
return res, nil return res, nil
} }
func (c *Controller) liveSubscription( func (c *Server) liveSubscription(
conn net.Conn, conn net.Conn,
rd *server.PipelineReader, rd *PipelineReader,
msg *server.Message, msg *Message,
websocket bool, websocket bool,
) error { ) error {
defer conn.Close() // close connection when we are done defer conn.Close() // close connection when we are done
@ -185,7 +184,7 @@ func (c *Controller) liveSubscription(
outputType := msg.OutputType outputType := msg.OutputType
connType := msg.ConnType connType := msg.ConnType
if websocket { if websocket {
outputType = server.JSON outputType = JSON
} }
var start time.Time var start time.Time
@ -199,44 +198,44 @@ func (c *Controller) liveSubscription(
} }
writeOK := func() { writeOK := func() {
switch outputType { switch outputType {
case server.JSON: case JSON:
write([]byte(`{"ok":true` + write([]byte(`{"ok":true` +
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)) `,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
case server.RESP: case RESP:
write([]byte(`+OK\r\n`)) write([]byte(`+OK\r\n`))
} }
} }
writeWrongNumberOfArgsErr := func(command string) { writeWrongNumberOfArgsErr := func(command string) {
switch outputType { switch outputType {
case server.JSON: case JSON:
write([]byte(`{"ok":false,"err":"invalid number of arguments"` + write([]byte(`{"ok":false,"err":"invalid number of arguments"` +
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)) `,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
case server.RESP: case RESP:
write([]byte(`-ERR wrong number of arguments ` + write([]byte(`-ERR wrong number of arguments ` +
`for '` + command + `' command\r\n`)) `for '` + command + `' command\r\n`))
} }
} }
writeOnlyPubsubErr := func() { writeOnlyPubsubErr := func() {
switch outputType { switch outputType {
case server.JSON: case JSON:
write([]byte(`{"ok":false` + write([]byte(`{"ok":false` +
`,"err":"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / ` + `,"err":"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / ` +
`PING / QUIT allowed in this context"` + `PING / QUIT allowed in this context"` +
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)) `,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
case server.RESP: case RESP:
write([]byte("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / " + write([]byte("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / " +
"PING / QUIT allowed in this context\r\n")) "PING / QUIT allowed in this context\r\n"))
} }
} }
writeSubscribe := func(command, channel string, num int) { writeSubscribe := func(command, channel string, num int) {
switch outputType { switch outputType {
case server.JSON: case JSON:
write([]byte(`{"ok":true` + write([]byte(`{"ok":true` +
`,"command":` + jsonString(command) + `,"command":` + jsonString(command) +
`,"channel":` + jsonString(channel) + `,"channel":` + jsonString(channel) +
`,"num":` + strconv.FormatInt(int64(num), 10) + `,"num":` + strconv.FormatInt(int64(num), 10) +
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)) `,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
case server.RESP: case RESP:
b := redcon.AppendArray(nil, 3) b := redcon.AppendArray(nil, 3)
b = redcon.AppendBulkString(b, command) b = redcon.AppendBulkString(b, command)
b = redcon.AppendBulkString(b, channel) b = redcon.AppendBulkString(b, channel)
@ -247,7 +246,7 @@ func (c *Controller) liveSubscription(
writeMessage := func(msg submsg) { writeMessage := func(msg submsg) {
if msg.kind == pubsubChannel { if msg.kind == pubsubChannel {
switch outputType { switch outputType {
case server.JSON: case JSON:
var data []byte var data []byte
if !gjson.Valid(msg.message) { if !gjson.Valid(msg.message) {
data = appendJSONString(nil, msg.message) data = appendJSONString(nil, msg.message)
@ -255,7 +254,7 @@ func (c *Controller) liveSubscription(
data = []byte(msg.message) data = []byte(msg.message)
} }
write(data) write(data)
case server.RESP: case RESP:
b := redcon.AppendArray(nil, 3) b := redcon.AppendArray(nil, 3)
b = redcon.AppendBulkString(b, "message") b = redcon.AppendBulkString(b, "message")
b = redcon.AppendBulkString(b, msg.channel) b = redcon.AppendBulkString(b, msg.channel)
@ -264,7 +263,7 @@ func (c *Controller) liveSubscription(
} }
} else { } else {
switch outputType { switch outputType {
case server.JSON: case JSON:
var data []byte var data []byte
if !gjson.Valid(msg.message) { if !gjson.Valid(msg.message) {
data = appendJSONString(nil, msg.message) data = appendJSONString(nil, msg.message)
@ -272,7 +271,7 @@ func (c *Controller) liveSubscription(
data = []byte(msg.message) data = []byte(msg.message)
} }
write(data) write(data)
case server.RESP: case RESP:
b := redcon.AppendArray(nil, 4) b := redcon.AppendArray(nil, 4)
b = redcon.AppendBulkString(b, "pmessage") b = redcon.AppendBulkString(b, "pmessage")
b = redcon.AppendBulkString(b, msg.pattern) b = redcon.AppendBulkString(b, msg.pattern)
@ -325,12 +324,12 @@ func (c *Controller) liveSubscription(
} }
}() }()
msgs := []*server.Message{msg} msgs := []*Message{msg}
for { for {
for _, msg := range msgs { for _, msg := range msgs {
start = time.Now() start = time.Now()
var kind int var kind int
switch msg.Command { switch msg.Command() {
case "quit": case "quit":
writeOK() writeOK()
return nil return nil
@ -341,14 +340,14 @@ func (c *Controller) liveSubscription(
default: default:
writeOnlyPubsubErr() writeOnlyPubsubErr()
} }
if len(msg.Values) < 2 { if len(msg.Args) < 2 {
writeWrongNumberOfArgsErr(msg.Command) writeWrongNumberOfArgsErr(msg.Command())
} }
for i := 1; i < len(msg.Values); i++ { for i := 1; i < len(msg.Args); i++ {
channel := msg.Values[i].String() channel := msg.Args[i]
m[kind][channel] = true m[kind][channel] = true
c.pubsub.register(kind, channel, target) c.pubsub.register(kind, channel, target)
writeSubscribe(msg.Command, channel, len(m[0])+len(m[1])) writeSubscribe(msg.Command(), channel, len(m[0])+len(m[1]))
} }
} }
var err error var err error

View File

@ -1,305 +0,0 @@
package server
import (
"crypto/sha1"
"encoding/base64"
"errors"
"io"
"net/url"
"strconv"
"strings"
"github.com/tidwall/redcon"
"github.com/tidwall/resp"
)
var errInvalidHTTP = errors.New("invalid HTTP request")
// Type is resp type
type Type int
// Message types
const (
Null Type = iota
RESP
Telnet
Native
HTTP
WebSocket
JSON
)
// Message is a resp message
type Message struct {
Command string
Values []resp.Value
ConnType Type
OutputType Type
Auth string
}
// PipelineReader ...
type PipelineReader struct {
rd io.Reader
wr io.Writer
packet [0xFFFF]byte
buf []byte
}
const kindHTTP redcon.Kind = 9999
// NewPipelineReader ...
func NewPipelineReader(rd io.ReadWriter) *PipelineReader {
return &PipelineReader{rd: rd, wr: rd}
}
func readcrlfline(packet []byte) (line string, leftover []byte, ok bool) {
for i := 1; i < len(packet); i++ {
if packet[i] == '\n' && packet[i-1] == '\r' {
return string(packet[:i-1]), packet[i+1:], true
}
}
return "", packet, false
}
func readNextHTTPCommand(packet []byte, argsIn [][]byte, msg *Message, wr io.Writer) (
complete bool, args [][]byte, kind redcon.Kind, leftover []byte, err error,
) {
args = argsIn[:0]
msg.ConnType = HTTP
msg.OutputType = JSON
opacket := packet
ready, err := func() (bool, error) {
var line string
var ok bool
// read header
var headers []string
for {
line, packet, ok = readcrlfline(packet)
if !ok {
return false, nil
}
if line == "" {
break
}
headers = append(headers, line)
}
parts := strings.Split(headers[0], " ")
if len(parts) != 3 {
return false, errInvalidHTTP
}
method := parts[0]
path := parts[1]
if len(path) == 0 || path[0] != '/' {
return false, errInvalidHTTP
}
path, err = url.QueryUnescape(path[1:])
if err != nil {
return false, errInvalidHTTP
}
if method != "GET" && method != "POST" {
return false, errInvalidHTTP
}
contentLength := 0
websocket := false
websocketVersion := 0
websocketKey := ""
for _, header := range headers[1:] {
if header[0] == 'a' || header[0] == 'A' {
if strings.HasPrefix(strings.ToLower(header), "authorization:") {
msg.Auth = strings.TrimSpace(header[len("authorization:"):])
}
} else if header[0] == 'u' || header[0] == 'U' {
if strings.HasPrefix(strings.ToLower(header), "upgrade:") && strings.ToLower(strings.TrimSpace(header[len("upgrade:"):])) == "websocket" {
websocket = true
}
} else if header[0] == 's' || header[0] == 'S' {
if strings.HasPrefix(strings.ToLower(header), "sec-websocket-version:") {
var n uint64
n, err = strconv.ParseUint(strings.TrimSpace(header[len("sec-websocket-version:"):]), 10, 64)
if err != nil {
return false, err
}
websocketVersion = int(n)
} else if strings.HasPrefix(strings.ToLower(header), "sec-websocket-key:") {
websocketKey = strings.TrimSpace(header[len("sec-websocket-key:"):])
}
} else if header[0] == 'c' || header[0] == 'C' {
if strings.HasPrefix(strings.ToLower(header), "content-length:") {
var n uint64
n, err = strconv.ParseUint(strings.TrimSpace(header[len("content-length:"):]), 10, 64)
if err != nil {
return false, err
}
contentLength = int(n)
}
}
}
if websocket && websocketVersion >= 13 && websocketKey != "" {
msg.ConnType = WebSocket
if wr == nil {
return false, errors.New("connection is nil")
}
sum := sha1.Sum([]byte(websocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
accept := base64.StdEncoding.EncodeToString(sum[:])
wshead := "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + accept + "\r\n\r\n"
if _, err = wr.Write([]byte(wshead)); err != nil {
return false, err
}
} else if contentLength > 0 {
msg.ConnType = HTTP
if len(packet) < contentLength {
return false, nil
}
path += string(packet[:contentLength])
packet = packet[contentLength:]
}
if path == "" {
return true, nil
}
nmsg, err := readNativeMessageLine([]byte(path))
if err != nil {
return false, err
}
msg.OutputType = JSON
msg.Values = nmsg.Values
msg.Command = commandValues(nmsg.Values)
return true, nil
}()
if err != nil || !ready {
return false, args[:0], kindHTTP, opacket, err
}
return true, args[:0], kindHTTP, packet, nil
}
func readNextCommand(packet []byte, argsIn [][]byte, msg *Message, wr io.Writer) (
complete bool, args [][]byte, kind redcon.Kind, leftover []byte, err error,
) {
if packet[0] == 'G' || packet[0] == 'P' {
// could be an HTTP request
var line []byte
for i := 1; i < len(packet); i++ {
if packet[i] == '\n' {
if packet[i-1] == '\r' {
line = packet[:i+1]
break
}
}
}
if len(line) == 0 {
return false, argsIn[:0], redcon.Redis, packet, nil
}
if len(line) > 11 && string(line[len(line)-11:len(line)-5]) == " HTTP/" {
return readNextHTTPCommand(packet, argsIn, msg, wr)
}
}
return redcon.ReadNextCommand(packet, args)
}
// ReadMessages ...
func (rd *PipelineReader) ReadMessages() ([]*Message, error) {
var msgs []*Message
moreData:
n, err := rd.rd.Read(rd.packet[:])
if err != nil {
return nil, err
}
if n == 0 {
// need more data
goto moreData
}
data := rd.packet[:n]
if len(rd.buf) > 0 {
data = append(rd.buf, data...)
}
for len(data) > 0 {
msg := &Message{}
complete, args, kind, leftover, err := readNextCommand(data, nil, msg, rd.wr)
if err != nil {
break
}
if !complete {
break
}
if kind == kindHTTP {
if len(msg.Values) == 0 {
return nil, errInvalidHTTP
}
msgs = append(msgs, msg)
} else if len(args) > 0 {
msg.Command = strings.ToLower(string(args[0]))
for i := 0; i < len(args); i++ {
args[i] = append([]byte{}, args[i]...)
msg.Values = append(msg.Values, resp.BytesValue(args[i]))
}
switch kind {
case redcon.Redis:
msg.ConnType = RESP
msg.OutputType = RESP
case redcon.Tile38:
msg.ConnType = Native
msg.OutputType = JSON
case redcon.Telnet:
msg.ConnType = RESP
msg.OutputType = RESP
}
msgs = append(msgs, msg)
}
data = leftover
}
if len(data) > 0 {
rd.buf = append(rd.buf[:0], data...)
} else if len(rd.buf) > 0 {
rd.buf = rd.buf[:0]
}
if err != nil && len(msgs) == 0 {
return nil, err
}
return msgs, nil
}
func readNativeMessageLine(line []byte) (*Message, error) {
values := make([]resp.Value, 0, 16)
reading:
for len(line) != 0 {
if line[0] == '{' {
// The native protocol cannot understand json boundaries so it assumes that
// a json element must be at the end of the line.
values = append(values, resp.StringValue(string(line)))
break
}
if line[0] == '"' && line[len(line)-1] == '"' {
if len(values) > 0 &&
strings.ToLower(values[0].String()) == "set" &&
strings.ToLower(values[len(values)-1].String()) == "string" {
// Setting a string value that is contained inside double quotes.
// This is only because of the boundary issues of the native protocol.
values = append(values, resp.StringValue(string(line[1:len(line)-1])))
break
}
}
i := 0
for ; i < len(line); i++ {
if line[i] == ' ' {
value := string(line[:i])
if value != "" {
values = append(values, resp.StringValue(value))
}
line = line[i+1:]
continue reading
}
}
values = append(values, resp.StringValue(string(line)))
break
}
return &Message{Command: commandValues(values), Values: values, ConnType: Native, OutputType: JSON}, nil
}
func commandValues(values []resp.Value) string {
if len(values) == 0 {
return ""
}
return strings.ToLower(values[0].String())
}

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"strings" "strings"
@ -6,25 +6,24 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
func (c *Controller) cmdReadOnly(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdReadOnly(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var arg string var arg string
var ok bool var ok bool
if vs, arg, ok = tokenval(vs); !ok || arg == "" { if vs, arg, ok = tokenval(vs); !ok || arg == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if len(vs) != 0 { if len(vs) != 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
update := false update := false
switch strings.ToLower(arg) { switch strings.ToLower(arg) {
default: default:
return server.NOMessage, errInvalidArgument(arg) return NOMessage, errInvalidArgument(arg)
case "yes": case "yes":
if !c.config.readOnly() { if !c.config.readOnly() {
update = true update = true
@ -41,5 +40,5 @@ func (c *Controller) cmdReadOnly(msg *server.Message) (res resp.Value, err error
if update { if update {
c.config.write(false) c.config.write(false)
} }
return server.OKMessage(msg, start), nil return OKMessage(msg, start), nil
} }

View File

@ -0,0 +1,46 @@
package server
import (
"net"
"time"
"github.com/tidwall/resp"
)
// RESPConn represents a simple resp connection.
type RESPConn struct {
conn net.Conn
rd *resp.Reader
wr *resp.Writer
}
// DialTimeout dials a resp
func DialTimeout(address string, timeout time.Duration) (*RESPConn, error) {
tcpconn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
return nil, err
}
conn := &RESPConn{
conn: tcpconn,
rd: resp.NewReader(tcpconn),
wr: resp.NewWriter(tcpconn),
}
return conn, nil
}
// Close closes the connection.
func (conn *RESPConn) Close() error {
conn.wr.WriteMultiBulk("quit")
return conn.conn.Close()
}
// Do performs a command and returns a resp value.
func (conn *RESPConn) Do(commandName string, args ...interface{}) (
val resp.Value, err error,
) {
if err := conn.wr.WriteMultiBulk(commandName, args...); err != nil {
return val, err
}
val, _, err = conn.rd.ReadValue()
return val, err
}

View File

@ -1,17 +1,16 @@
package controller package server
import ( import (
"bytes" "bytes"
"errors" "errors"
"time" "time"
"github.com/tidwall/resp"
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
func (c *Controller) cmdScanArgs(vs []resp.Value) ( func (c *Server) cmdScanArgs(vs []string) (
s liveFenceSwitches, err error, s liveFenceSwitches, err error,
) { ) {
var t searchScanBaseTokens var t searchScanBaseTokens
@ -27,32 +26,32 @@ func (c *Controller) cmdScanArgs(vs []resp.Value) (
return return
} }
func (c *Controller) cmdScan(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdScan(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
s, err := c.cmdScanArgs(vs) s, err := c.cmdScanArgs(vs)
if s.usingLua() { if s.usingLua() {
defer s.Close() defer s.Close()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
res = server.NOMessage res = NOMessage
err = errors.New(r.(string)) err = errors.New(r.(string))
return return
} }
}() }()
} }
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
wr := &bytes.Buffer{} wr := &bytes.Buffer{}
sw, err := c.newScanWriter( sw, err := c.newScanWriter(
wr, msg, s.key, s.output, s.precision, s.glob, false, wr, msg, s.key, s.output, s.precision, s.glob, false,
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields) s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`{"ok":true`) wr.WriteString(`{"ok":true`)
} }
sw.writeHead() sw.writeHead()
@ -90,7 +89,7 @@ func (c *Controller) cmdScan(msg *server.Message) (res resp.Value, err error) {
} }
} }
sw.writeFoot() sw.writeFoot()
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.BytesValue(wr.Bytes()), nil return resp.BytesValue(wr.Bytes()), nil
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -13,7 +13,6 @@ import (
"github.com/tidwall/tile38/internal/clip" "github.com/tidwall/tile38/internal/clip"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
const limitItems = 100 const limitItems = 100
@ -32,9 +31,9 @@ const (
type scanWriter struct { type scanWriter struct {
mu sync.Mutex mu sync.Mutex
c *Controller c *Server
wr *bytes.Buffer wr *bytes.Buffer
msg *server.Message msg *Message
col *collection.Collection col *collection.Collection
fmap map[string]int fmap map[string]int
farr []string farr []string
@ -71,8 +70,8 @@ type ScanWriterParams struct {
clip geojson.Object clip geojson.Object
} }
func (c *Controller) newScanWriter( func (c *Server) newScanWriter(
wr *bytes.Buffer, msg *server.Message, key string, output outputT, wr *bytes.Buffer, msg *Message, key string, output outputT,
precision uint64, globPattern string, matchValues bool, precision uint64, globPattern string, matchValues bool,
cursor, limit uint64, wheres []whereT, whereins []whereinT, whereevals []whereevalT, nofields bool, cursor, limit uint64, wheres []whereT, whereins []whereinT, whereevals []whereevalT, nofields bool,
) ( ) (
@ -134,7 +133,7 @@ func (sw *scanWriter) writeHead() {
sw.mu.Lock() sw.mu.Lock()
defer sw.mu.Unlock() defer sw.mu.Unlock()
switch sw.msg.OutputType { switch sw.msg.OutputType {
case server.JSON: case JSON:
if len(sw.farr) > 0 && sw.hasFieldsOutput() { if len(sw.farr) > 0 && sw.hasFieldsOutput() {
sw.wr.WriteString(`,"fields":[`) sw.wr.WriteString(`,"fields":[`)
for i, field := range sw.farr { for i, field := range sw.farr {
@ -159,7 +158,7 @@ func (sw *scanWriter) writeHead() {
case outputCount: case outputCount:
} }
case server.RESP: case RESP:
} }
} }
@ -171,7 +170,7 @@ func (sw *scanWriter) writeFoot() {
cursor = 0 cursor = 0
} }
switch sw.msg.OutputType { switch sw.msg.OutputType {
case server.JSON: case JSON:
switch sw.output { switch sw.output {
default: default:
sw.wr.WriteByte(']') sw.wr.WriteByte(']')
@ -180,7 +179,7 @@ func (sw *scanWriter) writeFoot() {
} }
sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10)) sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10))
sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10)) sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10))
case server.RESP: case RESP:
if sw.output == outputCount { if sw.output == outputCount {
sw.respOut = resp.IntegerValue(int(sw.count)) sw.respOut = resp.IntegerValue(int(sw.count))
} else { } else {
@ -354,7 +353,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
opts.o = clip.Clip(opts.o, opts.clip) opts.o = clip.Clip(opts.o, opts.clip)
} }
switch sw.msg.OutputType { switch sw.msg.OutputType {
case server.JSON: case JSON:
var wr bytes.Buffer var wr bytes.Buffer
var jsfields string var jsfields string
if sw.once { if sw.once {
@ -418,7 +417,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
wr.WriteString(`}`) wr.WriteString(`}`)
} }
sw.wr.Write(wr.Bytes()) sw.wr.Write(wr.Bytes())
case server.RESP: case RESP:
vals := make([]resp.Value, 1, 3) vals := make([]resp.Value, 1, 3)
vals[0] = resp.StringValue(opts.id) vals[0] = resp.StringValue(opts.id)
if sw.output == outputIDs { if sw.output == outputIDs {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -14,7 +14,7 @@ import (
"time" "time"
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/server" "github.com/tidwall/tile38/internal/log"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
luajson "layeh.com/gopher-json" luajson "layeh.com/gopher-json"
) )
@ -34,13 +34,13 @@ var errNoLuasAvailable = errors.New("no interpreters available")
// Go-routine-safe pool of read-to-go lua states // Go-routine-safe pool of read-to-go lua states
type lStatePool struct { type lStatePool struct {
m sync.Mutex m sync.Mutex
c *Controller c *Server
saved []*lua.LState saved []*lua.LState
total int total int
} }
// newPool returns a new pool of lua states // newPool returns a new pool of lua states
func (c *Controller) newPool() *lStatePool { func (c *Server) newPool() *lStatePool {
pl := &lStatePool{ pl := &lStatePool{
saved: make([]*lua.LState, iniLuaPoolSize), saved: make([]*lua.LState, iniLuaPoolSize),
c: c, c: c,
@ -206,7 +206,7 @@ func (sm *lScriptMap) Flush() {
} }
// NewScriptMap returns a new map with lua scripts // NewScriptMap returns a new map with lua scripts
func (c *Controller) newScriptMap() *lScriptMap { func (c *Server) newScriptMap() *lScriptMap {
return &lScriptMap{ return &lScriptMap{
scripts: make(map[string]*lua.FunctionProto), scripts: make(map[string]*lua.FunctionProto),
} }
@ -356,18 +356,18 @@ func makeSafeErr(err error) error {
} }
// Run eval/evalro/evalna command or it's -sha variant // Run eval/evalro/evalna command or it's -sha variant
func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdEvalUnified(scriptIsSha bool, msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var script, numkeysStr, key, arg string var script, numkeysStr, key, arg string
if vs, script, ok = tokenval(vs); !ok || script == "" { if vs, script, ok = tokenval(vs); !ok || script == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
if vs, numkeysStr, ok = tokenval(vs); !ok || numkeysStr == "" { if vs, numkeysStr, ok = tokenval(vs); !ok || numkeysStr == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var i, numkeys uint64 var i, numkeys uint64
@ -411,7 +411,7 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
luaState, map[string]lua.LValue{ luaState, map[string]lua.LValue{
"KEYS": keysTbl, "KEYS": keysTbl,
"ARGV": argsTbl, "ARGV": argsTbl,
"EVAL_CMD": lua.LString(msg.Command), "EVAL_CMD": lua.LString(msg.Command()),
}) })
compiled, ok := c.luascripts.Get(shaSum) compiled, ok := c.luascripts.Get(shaSum)
@ -431,7 +431,7 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
} else { } else {
fn, err = luaState.Load(strings.NewReader(script), "f_"+shaSum) fn, err = luaState.Load(strings.NewReader(script), "f_"+shaSum)
if err != nil { if err != nil {
return server.NOMessage, makeSafeErr(err) return NOMessage, makeSafeErr(err)
} }
c.luascripts.Put(shaSum, fn.Proto) c.luascripts.Put(shaSum, fn.Proto)
} }
@ -442,66 +442,66 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
"ARGV": lua.LNil, "ARGV": lua.LNil,
"EVAL_CMD": lua.LNil, "EVAL_CMD": lua.LNil,
}) })
if err := luaState.PCall(0, 1, nil); err != nil { if err := luaState.PCall(0, 1, nil); err != nil {
return server.NOMessage, makeSafeErr(err) log.Debugf("%v", err.Error())
return NOMessage, makeSafeErr(err)
} }
ret := luaState.Get(-1) // returned value ret := luaState.Get(-1) // returned value
luaState.Pop(1) luaState.Pop(1)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
buf.WriteString(`,"result":` + ConvertToJSON(ret)) buf.WriteString(`,"result":` + ConvertToJSON(ret))
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
return ConvertToRESP(ret), nil return ConvertToRESP(ret), nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdScriptLoad(msg *server.Message) (resp.Value, error) { func (c *Server) cmdScriptLoad(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var script string var script string
if vs, script, ok = tokenval(vs); !ok || script == "" { if vs, script, ok = tokenval(vs); !ok || script == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
shaSum := Sha1Sum(script) shaSum := Sha1Sum(script)
luaState, err := c.luapool.Get() luaState, err := c.luapool.Get()
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
defer c.luapool.Put(luaState) defer c.luapool.Put(luaState)
fn, err := luaState.Load(strings.NewReader(script), "f_"+shaSum) fn, err := luaState.Load(strings.NewReader(script), "f_"+shaSum)
if err != nil { if err != nil {
return server.NOMessage, makeSafeErr(err) return NOMessage, makeSafeErr(err)
} }
c.luascripts.Put(shaSum, fn.Proto) c.luascripts.Put(shaSum, fn.Proto)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
buf.WriteString(`,"result":"` + shaSum + `"`) buf.WriteString(`,"result":"` + shaSum + `"`)
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
return resp.StringValue(shaSum), nil return resp.StringValue(shaSum), nil
} }
return server.NOMessage, nil return NOMessage, nil
} }
func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) { func (c *Server) cmdScriptExists(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ok bool var ok bool
var shaSum string var shaSum string
@ -509,7 +509,7 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
var ires int var ires int
for len(vs) > 0 { for len(vs) > 0 {
if vs, shaSum, ok = tokenval(vs); !ok || shaSum == "" { if vs, shaSum, ok = tokenval(vs); !ok || shaSum == "" {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
_, ok = c.luascripts.Get(shaSum) _, ok = c.luascripts.Get(shaSum)
if ok { if ok {
@ -521,7 +521,7 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
var resArray []string var resArray []string
@ -531,7 +531,7 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
buf.WriteString(`,"result":[` + strings.Join(resArray, ",") + `]`) buf.WriteString(`,"result":[` + strings.Join(resArray, ",") + `]`)
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
var resArray []resp.Value var resArray []resp.Value
for _, ires := range results { for _, ires := range results {
resArray = append(resArray, resp.IntegerValue(ires)) resArray = append(resArray, resp.IntegerValue(ires))
@ -541,28 +541,28 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
return resp.SimpleStringValue(""), nil return resp.SimpleStringValue(""), nil
} }
func (c *Controller) cmdScriptFlush(msg *server.Message) (resp.Value, error) { func (c *Server) cmdScriptFlush(msg *Message) (resp.Value, error) {
start := time.Now() start := time.Now()
c.luascripts.Flush() c.luascripts.Flush()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString(`{"ok":true`) buf.WriteString(`{"ok":true`)
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.StringValue(buf.String()), nil return resp.StringValue(buf.String()), nil
case server.RESP: case RESP:
return resp.StringValue("OK"), nil return resp.StringValue("OK"), nil
} }
return resp.SimpleStringValue(""), nil return resp.SimpleStringValue(""), nil
} }
func (c *Controller) commandInScript(msg *server.Message) ( func (c *Server) commandInScript(msg *Message) (
res resp.Value, d commandDetailsT, err error, res resp.Value, d commandDetailsT, err error,
) { ) {
switch msg.Command { switch msg.Command() {
default: default:
err = fmt.Errorf("unknown command '%s'", msg.Values[0]) err = fmt.Errorf("unknown command '%s'", msg.Args[0])
case "set": case "set":
res, d, err = c.cmdSet(msg) res, d, err = c.cmdSet(msg)
case "fset": case "fset":
@ -609,16 +609,11 @@ func (c *Controller) commandInScript(msg *server.Message) (
return return
} }
func (c *Controller) luaTile38Call(evalcmd string, cmd string, args ...string) (resp.Value, error) { func (c *Server) luaTile38Call(evalcmd string, cmd string, args ...string) (resp.Value, error) {
msg := &server.Message{} msg := &Message{}
msg.OutputType = server.RESP msg.OutputType = RESP
msg.Command = strings.ToLower(cmd) msg.Args = append([]string{cmd}, args...)
msg.Values = append(msg.Values, resp.StringValue(msg.Command)) switch msg.Command() {
for _, arg := range args {
msg.Values = append(msg.Values, resp.StringValue(arg))
}
switch msg.Command {
case "ping", "echo", "auth", "massinsert", "shutdown", "gc", case "ping", "echo", "auth", "massinsert", "shutdown", "gc",
"sethook", "pdelhook", "delhook", "sethook", "pdelhook", "delhook",
"follow", "readonly", "config", "output", "client", "follow", "readonly", "config", "output", "client",
@ -641,10 +636,10 @@ func (c *Controller) luaTile38Call(evalcmd string, cmd string, args ...string) (
} }
// The eval command has already got the lock. No locking on the call from within the script. // The eval command has already got the lock. No locking on the call from within the script.
func (c *Controller) luaTile38AtomicRW(msg *server.Message) (resp.Value, error) { func (c *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) {
var write bool var write bool
switch msg.Command { switch msg.Command() {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel": case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel":
@ -670,7 +665,7 @@ func (c *Controller) luaTile38AtomicRW(msg *server.Message) (resp.Value, error)
} }
if write { if write {
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil { if err := c.writeAOF(msg.Args, &d); err != nil {
return resp.NullValue(), err return resp.NullValue(), err
} }
} }
@ -678,8 +673,8 @@ func (c *Controller) luaTile38AtomicRW(msg *server.Message) (resp.Value, error)
return res, nil return res, nil
} }
func (c *Controller) luaTile38AtomicRO(msg *server.Message) (resp.Value, error) { func (c *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) {
switch msg.Command { switch msg.Command() {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
@ -702,11 +697,11 @@ func (c *Controller) luaTile38AtomicRO(msg *server.Message) (resp.Value, error)
return res, nil return res, nil
} }
func (c *Controller) luaTile38NonAtomic(msg *server.Message) (resp.Value, error) { func (c *Server) luaTile38NonAtomic(msg *Message) (resp.Value, error) {
var write bool var write bool
// choose the locking strategy // choose the locking strategy
switch msg.Command { switch msg.Command() {
default: default:
return resp.NullValue(), errCmdNotSupported return resp.NullValue(), errCmdNotSupported
case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel": case "set", "del", "drop", "fset", "flushdb", "expire", "persist", "jset", "pdel":
@ -736,7 +731,7 @@ func (c *Controller) luaTile38NonAtomic(msg *server.Message) (resp.Value, error)
} }
if write { if write {
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil { if err := c.writeAOF(msg.Args, &d); err != nil {
return resp.NullValue(), err return resp.NullValue(), err
} }
} }

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -14,7 +14,6 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/internal/bing" "github.com/tidwall/tile38/internal/bing"
"github.com/tidwall/tile38/internal/glob" "github.com/tidwall/tile38/internal/glob"
"github.com/tidwall/tile38/internal/server"
) )
const defaultCircleSteps = 64 const defaultCircleSteps = 64
@ -57,14 +56,14 @@ func (s liveFenceSwitches) usingLua() bool {
return len(s.whereevals) > 0 return len(s.whereevals) > 0
} }
func (c *Controller) cmdSearchArgs( func (server *Server) cmdSearchArgs(
fromFenceCmd bool, cmd string, vs []resp.Value, types []string, fromFenceCmd bool, cmd string, vs []string, types []string,
) (s liveFenceSwitches, err error) { ) (s liveFenceSwitches, err error) {
var t searchScanBaseTokens var t searchScanBaseTokens
if fromFenceCmd { if fromFenceCmd {
t.fence = true t.fence = true
} }
vs, t, err = c.parseSearchScanBaseTokens(cmd, t, vs) vs, t, err = server.parseSearchScanBaseTokens(cmd, t, vs)
if err != nil { if err != nil {
return return
} }
@ -80,7 +79,7 @@ func (c *Controller) cmdSearchArgs(
if _, err := strconv.ParseFloat(typ, 64); err == nil { if _, err := strconv.ParseFloat(typ, 64); err == nil {
// It's likely that the output was not specified, but rather the search bounds. // It's likely that the output was not specified, but rather the search bounds.
s.searchScanBaseTokens.output = defaultSearchOutput s.searchScanBaseTokens.output = defaultSearchOutput
vs = append([]resp.Value{resp.StringValue(typ)}, vs...) vs = append([]string{typ}, vs...)
typ = "BOUNDS" typ = "BOUNDS"
} }
} }
@ -283,7 +282,7 @@ func (c *Controller) cmdSearchArgs(
err = errInvalidNumberOfArguments err = errInvalidNumberOfArguments
return return
} }
col := c.getCol(key) col := server.getCol(key)
if col == nil { if col == nil {
err = errKeyNotFound err = errKeyNotFound
return return
@ -337,35 +336,35 @@ var nearbyTypes = []string{"point"}
var withinOrIntersectsTypes = []string{ var withinOrIntersectsTypes = []string{
"geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle"} "geo", "bounds", "hash", "tile", "quadkey", "get", "object", "circle"}
func (c *Controller) cmdNearby(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
wr := &bytes.Buffer{} wr := &bytes.Buffer{}
s, err := c.cmdSearchArgs(false, "nearby", vs, nearbyTypes) s, err := server.cmdSearchArgs(false, "nearby", vs, nearbyTypes)
if s.usingLua() { if s.usingLua() {
defer s.Close() defer s.Close()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
res = server.NOMessage res = NOMessage
err = errors.New(r.(string)) err = errors.New(r.(string))
return return
} }
}() }()
} }
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
s.cmd = "nearby" s.cmd = "nearby"
if s.fence { if s.fence {
return server.NOMessage, s return NOMessage, s
} }
sw, err := c.newScanWriter( sw, err := server.newScanWriter(
wr, msg, s.key, s.output, s.precision, s.glob, false, wr, msg, s.key, s.output, s.precision, s.glob, false,
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields) s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`{"ok":true`) wr.WriteString(`{"ok":true`)
} }
sw.writeHead() sw.writeHead()
@ -390,10 +389,10 @@ func (c *Controller) cmdNearby(msg *server.Message) (res resp.Value, err error)
ignoreGlobMatch: true, ignoreGlobMatch: true,
}) })
} }
c.nearestNeighbors(&s, sw, s.obj.(*geojson.Circle), &matched, iter) server.nearestNeighbors(&s, sw, s.obj.(*geojson.Circle), &matched, iter)
} }
sw.writeFoot() sw.writeFoot()
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.BytesValue(wr.Bytes()), nil return resp.BytesValue(wr.Bytes()), nil
} }
@ -407,14 +406,14 @@ type iterItem struct {
dist float64 dist float64
} }
func (c *Controller) nearestNeighbors( func (server *Server) nearestNeighbors(
s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle, matched *uint32, s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle, matched *uint32,
iter func(id string, o geojson.Object, fields []float64, dist *float64, iter func(id string, o geojson.Object, fields []float64, dist *float64,
) bool) { ) bool) {
limit := int(sw.cursor + sw.limit) limit := int(sw.cursor + sw.limit)
var items []iterItem var items []iterItem
sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool { sw.col.Nearby(target, func(id string, o geojson.Object, fields []float64) bool {
if c.hasExpired(s.key, id) { if server.hasExpired(s.key, id) {
return true return true
} }
if _, ok := sw.fieldMatch(fields, o); !ok { if _, ok := sw.fieldMatch(fields, o); !ok {
@ -444,44 +443,44 @@ func (c *Controller) nearestNeighbors(
} }
} }
func (c *Controller) cmdWithin(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdWithin(msg *Message) (res resp.Value, err error) {
return c.cmdWithinOrIntersects("within", msg) return server.cmdWithinOrIntersects("within", msg)
} }
func (c *Controller) cmdIntersects(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdIntersects(msg *Message) (res resp.Value, err error) {
return c.cmdWithinOrIntersects("intersects", msg) return server.cmdWithinOrIntersects("intersects", msg)
} }
func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdWithinOrIntersects(cmd string, msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
wr := &bytes.Buffer{} wr := &bytes.Buffer{}
s, err := c.cmdSearchArgs(false, cmd, vs, withinOrIntersectsTypes) s, err := server.cmdSearchArgs(false, cmd, vs, withinOrIntersectsTypes)
if s.usingLua() { if s.usingLua() {
defer s.Close() defer s.Close()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
res = server.NOMessage res = NOMessage
err = errors.New(r.(string)) err = errors.New(r.(string))
return return
} }
}() }()
} }
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
s.cmd = cmd s.cmd = cmd
if s.fence { if s.fence {
return server.NOMessage, s return NOMessage, s
} }
sw, err := c.newScanWriter( sw, err := server.newScanWriter(
wr, msg, s.key, s.output, s.precision, s.glob, false, wr, msg, s.key, s.output, s.precision, s.glob, false,
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields) s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`{"ok":true`) wr.WriteString(`{"ok":true`)
} }
sw.writeHead() sw.writeHead()
@ -490,7 +489,7 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
sw.col.Within(s.obj, s.sparse, func( sw.col.Within(s.obj, s.sparse, func(
id string, o geojson.Object, fields []float64, id string, o geojson.Object, fields []float64,
) bool { ) bool {
if c.hasExpired(s.key, id) { if server.hasExpired(s.key, id) {
return true return true
} }
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
@ -506,7 +505,7 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
o geojson.Object, o geojson.Object,
fields []float64, fields []float64,
) bool { ) bool {
if c.hasExpired(s.key, id) { if server.hasExpired(s.key, id) {
return true return true
} }
params := ScanWriterParams{ params := ScanWriterParams{
@ -523,18 +522,18 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
} }
} }
sw.writeFoot() sw.writeFoot()
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.BytesValue(wr.Bytes()), nil return resp.BytesValue(wr.Bytes()), nil
} }
return sw.respOut, nil return sw.respOut, nil
} }
func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) ( func (server *Server) cmdSeachValuesArgs(vs []string) (
s liveFenceSwitches, err error, s liveFenceSwitches, err error,
) { ) {
var t searchScanBaseTokens var t searchScanBaseTokens
vs, t, err = c.parseSearchScanBaseTokens("search", t, vs) vs, t, err = server.parseSearchScanBaseTokens("search", t, vs)
if err != nil { if err != nil {
return return
} }
@ -546,32 +545,32 @@ func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) (
return return
} }
func (c *Controller) cmdSearch(msg *server.Message) (res resp.Value, err error) { func (server *Server) cmdSearch(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
wr := &bytes.Buffer{} wr := &bytes.Buffer{}
s, err := c.cmdSeachValuesArgs(vs) s, err := server.cmdSeachValuesArgs(vs)
if s.usingLua() { if s.usingLua() {
defer s.Close() defer s.Close()
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
res = server.NOMessage res = NOMessage
err = errors.New(r.(string)) err = errors.New(r.(string))
return return
} }
}() }()
} }
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
sw, err := c.newScanWriter( sw, err := server.newScanWriter(
wr, msg, s.key, s.output, s.precision, s.glob, true, wr, msg, s.key, s.output, s.precision, s.glob, true,
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields) s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`{"ok":true`) wr.WriteString(`{"ok":true`)
} }
sw.writeHead() sw.writeHead()
@ -613,7 +612,7 @@ func (c *Controller) cmdSearch(msg *server.Message) (res resp.Value, err error)
} }
} }
sw.writeFoot() sw.writeFoot()
if msg.OutputType == server.JSON { if msg.OutputType == JSON {
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}") wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
return resp.BytesValue(wr.Bytes()), nil return resp.BytesValue(wr.Bytes()), nil
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"bytes" "bytes"
@ -13,16 +13,15 @@ import (
"github.com/tidwall/resp" "github.com/tidwall/resp"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/collection" "github.com/tidwall/tile38/internal/collection"
"github.com/tidwall/tile38/internal/server"
) )
func (c *Controller) cmdStats(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdStats(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
vs := msg.Values[1:] vs := msg.Args[1:]
var ms = []map[string]interface{}{} var ms = []map[string]interface{}{}
if len(vs) == 0 { if len(vs) == 0 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
var vals []resp.Value var vals []resp.Value
var key string var key string
@ -40,38 +39,38 @@ func (c *Controller) cmdStats(msg *server.Message) (res resp.Value, err error) {
m["num_objects"] = col.Count() m["num_objects"] = col.Count()
m["num_strings"] = col.StringCount() m["num_strings"] = col.StringCount()
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
ms = append(ms, m) ms = append(ms, m)
case server.RESP: case RESP:
vals = append(vals, resp.ArrayValue(respValuesSimpleMap(m))) vals = append(vals, resp.ArrayValue(respValuesSimpleMap(m)))
} }
} else { } else {
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
ms = append(ms, nil) ms = append(ms, nil)
case server.RESP: case RESP:
vals = append(vals, resp.NullValue()) vals = append(vals, resp.NullValue())
} }
} }
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
data, err := json.Marshal(ms) data, err := json.Marshal(ms)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
res = resp.ArrayValue(vals) res = resp.ArrayValue(vals)
} }
return res, nil return res, nil
} }
func (c *Controller) cmdServer(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdServer(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
if len(msg.Values) != 1 { if len(msg.Args) != 1 {
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
} }
m := make(map[string]interface{}) m := make(map[string]interface{})
m["id"] = c.config.serverID() m["id"] = c.config.serverID()
@ -122,30 +121,30 @@ func (c *Controller) cmdServer(msg *server.Message) (res resp.Value, err error)
m["threads"] = runtime.GOMAXPROCS(0) m["threads"] = runtime.GOMAXPROCS(0)
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
data, err := json.Marshal(m) data, err := json.Marshal(m)
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
vals := respValuesSimpleMap(m) vals := respValuesSimpleMap(m)
res = resp.ArrayValue(vals) res = resp.ArrayValue(vals)
} }
return res, nil return res, nil
} }
func (c *Controller) writeInfoServer(w *bytes.Buffer) { func (c *Server) writeInfoServer(w *bytes.Buffer) {
fmt.Fprintf(w, "tile38_version:%s\r\n", core.Version) fmt.Fprintf(w, "tile38_version:%s\r\n", core.Version)
fmt.Fprintf(w, "redis_version:%s\r\n", core.Version) //Version of the Redis server fmt.Fprintf(w, "redis_version:%s\r\n", core.Version) //Version of the Redis server
fmt.Fprintf(w, "uptime_in_seconds:%d\r\n", time.Now().Sub(c.started)/time.Second) //Number of seconds since Redis server start fmt.Fprintf(w, "uptime_in_seconds:%d\r\n", time.Now().Sub(c.started)/time.Second) //Number of seconds since Redis server start
} }
func (c *Controller) writeInfoClients(w *bytes.Buffer) { func (c *Server) writeInfoClients(w *bytes.Buffer) {
c.connsmu.RLock() c.connsmu.RLock()
fmt.Fprintf(w, "connected_clients:%d\r\n", len(c.conns)) // Number of client connections (excluding connections from slaves) fmt.Fprintf(w, "connected_clients:%d\r\n", len(c.conns)) // Number of client connections (excluding connections from slaves)
c.connsmu.RUnlock() c.connsmu.RUnlock()
} }
func (c *Controller) writeInfoMemory(w *bytes.Buffer) { func (c *Server) writeInfoMemory(w *bytes.Buffer) {
var mem runtime.MemStats var mem runtime.MemStats
runtime.ReadMemStats(&mem) runtime.ReadMemStats(&mem)
fmt.Fprintf(w, "used_memory:%d\r\n", mem.Alloc) // total number of bytes allocated by Redis using its allocator (either standard libc, jemalloc, or an alternative allocator such as tcmalloc fmt.Fprintf(w, "used_memory:%d\r\n", mem.Alloc) // total number of bytes allocated by Redis using its allocator (either standard libc, jemalloc, or an alternative allocator such as tcmalloc
@ -156,7 +155,7 @@ func boolInt(t bool) int {
} }
return 0 return 0
} }
func (c *Controller) writeInfoPersistence(w *bytes.Buffer) { func (c *Server) writeInfoPersistence(w *bytes.Buffer) {
fmt.Fprintf(w, "aof_enabled:1\r\n") fmt.Fprintf(w, "aof_enabled:1\r\n")
fmt.Fprintf(w, "aof_rewrite_in_progress:%d\r\n", boolInt(c.shrinking)) // Flag indicating a AOF rewrite operation is on-going fmt.Fprintf(w, "aof_rewrite_in_progress:%d\r\n", boolInt(c.shrinking)) // Flag indicating a AOF rewrite operation is on-going
fmt.Fprintf(w, "aof_last_rewrite_time_sec:%d\r\n", c.lastShrinkDuration.get()/int(time.Second)) // Duration of the last AOF rewrite operation in seconds fmt.Fprintf(w, "aof_last_rewrite_time_sec:%d\r\n", c.lastShrinkDuration.get()/int(time.Second)) // Duration of the last AOF rewrite operation in seconds
@ -168,28 +167,28 @@ func (c *Controller) writeInfoPersistence(w *bytes.Buffer) {
} }
} }
func (c *Controller) writeInfoStats(w *bytes.Buffer) { func (c *Server) writeInfoStats(w *bytes.Buffer) {
fmt.Fprintf(w, "total_connections_received:%d\r\n", c.statsTotalConns.get()) // Total number of connections accepted by the server fmt.Fprintf(w, "total_connections_received:%d\r\n", c.statsTotalConns.get()) // Total number of connections accepted by the server
fmt.Fprintf(w, "total_commands_processed:%d\r\n", c.statsTotalCommands.get()) // Total number of commands processed by the server fmt.Fprintf(w, "total_commands_processed:%d\r\n", c.statsTotalCommands.get()) // Total number of commands processed by the server
fmt.Fprintf(w, "expired_keys:%d\r\n", c.statsExpired.get()) // Total number of key expiration events fmt.Fprintf(w, "expired_keys:%d\r\n", c.statsExpired.get()) // Total number of key expiration events
} }
func (c *Controller) writeInfoReplication(w *bytes.Buffer) { func (c *Server) writeInfoReplication(w *bytes.Buffer) {
fmt.Fprintf(w, "connected_slaves:%d\r\n", len(c.aofconnM)) // Number of connected slaves fmt.Fprintf(w, "connected_slaves:%d\r\n", len(c.aofconnM)) // Number of connected slaves
} }
func (c *Controller) writeInfoCluster(w *bytes.Buffer) { func (c *Server) writeInfoCluster(w *bytes.Buffer) {
fmt.Fprintf(w, "cluster_enabled:0\r\n") fmt.Fprintf(w, "cluster_enabled:0\r\n")
} }
func (c *Controller) cmdInfo(msg *server.Message) (res resp.Value, err error) { func (c *Server) cmdInfo(msg *Message) (res resp.Value, err error) {
start := time.Now() start := time.Now()
sections := []string{"server", "clients", "memory", "persistence", "stats", "replication", "cpu", "cluster", "keyspace"} sections := []string{"server", "clients", "memory", "persistence", "stats", "replication", "cpu", "cluster", "keyspace"}
switch len(msg.Values) { switch len(msg.Args) {
default: default:
return server.NOMessage, errInvalidNumberOfArguments return NOMessage, errInvalidNumberOfArguments
case 1: case 1:
case 2: case 2:
section := strings.ToLower(msg.Values[1].String()) section := strings.ToLower(msg.Args[1])
switch section { switch section {
default: default:
sections = []string{section} sections = []string{section}
@ -235,13 +234,13 @@ func (c *Controller) cmdInfo(msg *server.Message) (res resp.Value, err error) {
} }
switch msg.OutputType { switch msg.OutputType {
case server.JSON: case JSON:
data, err := json.Marshal(w.String()) data, err := json.Marshal(w.String())
if err != nil { if err != nil {
return server.NOMessage, err return NOMessage, err
} }
res = resp.StringValue(`{"ok":true,"info":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}") res = resp.StringValue(`{"ok":true,"info":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
case server.RESP: case RESP:
res = resp.BytesValue(w.Bytes()) res = resp.BytesValue(w.Bytes())
} }
@ -262,7 +261,7 @@ func respValuesSimpleMap(m map[string]interface{}) []resp.Value {
return vals return vals
} }
func (c *Controller) statsCollections(line string) (string, error) { func (c *Server) statsCollections(line string) (string, error) {
start := time.Now() start := time.Now()
var key string var key string
var ms = []map[string]interface{}{} var ms = []map[string]interface{}{}

View File

@ -1,6 +1,6 @@
// +build !linux,!darwin // +build !linux,!darwin
package controller package server
import ( import (
"bytes" "bytes"

View File

@ -1,6 +1,6 @@
// +build linux darwin // +build linux darwin
package controller package server
import ( import (
"bytes" "bytes"
@ -8,7 +8,7 @@ import (
"syscall" "syscall"
) )
func (c *Controller) writeInfoCPU(w *bytes.Buffer) { func (c *Server) writeInfoCPU(w *bytes.Buffer) {
var selfRu syscall.Rusage var selfRu syscall.Rusage
var cRu syscall.Rusage var cRu syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &selfRu) syscall.Getrusage(syscall.RUSAGE_SELF, &selfRu)

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"errors" "errors"
@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tidwall/resp"
"github.com/yuin/gopher-lua" "github.com/yuin/gopher-lua"
) )
@ -34,18 +33,18 @@ func token(line string) (newLine, token string) {
return "", line return "", line
} }
func tokenval(vs []resp.Value) (nvs []resp.Value, token string, ok bool) { func tokenval(vs []string) (nvs []string, token string, ok bool) {
if len(vs) > 0 { if len(vs) > 0 {
token = vs[0].String() token = vs[0]
nvs = vs[1:] nvs = vs[1:]
ok = true ok = true
} }
return return
} }
func tokenvalbytes(vs []resp.Value) (nvs []resp.Value, token []byte, ok bool) { func tokenvalbytes(vs []string) (nvs []string, token []byte, ok bool) {
if len(vs) > 0 { if len(vs) > 0 {
token = vs[0].Bytes() token = []byte(vs[0])
nvs = vs[1:] nvs = vs[1:]
ok = true ok = true
} }
@ -168,7 +167,7 @@ func (wherein whereinT) match(value float64) bool {
} }
type whereevalT struct { type whereevalT struct {
c *Controller c *Server
luaState *lua.LState luaState *lua.LState
fn *lua.LFunction fn *lua.LFunction
} }
@ -249,10 +248,10 @@ type searchScanBaseTokens struct {
clip bool clip bool
} }
func (c *Controller) parseSearchScanBaseTokens( func (c *Server) parseSearchScanBaseTokens(
cmd string, t searchScanBaseTokens, vs []resp.Value, cmd string, t searchScanBaseTokens, vs []string,
) ( ) (
vsout []resp.Value, tout searchScanBaseTokens, err error, vsout []string, tout searchScanBaseTokens, err error,
) { ) {
var ok bool var ok bool
if vs, t.key, ok = tokenval(vs); !ok || t.key == "" { if vs, t.key, ok = tokenval(vs); !ok || t.key == "" {
@ -622,7 +621,7 @@ func (c *Controller) parseSearchScanBaseTokens(
} }
t.output = defaultSearchOutput t.output = defaultSearchOutput
var nvs []resp.Value var nvs []string
var sprecision string var sprecision string
var which string var which string
if nvs, which, ok = tokenval(vs); ok && which != "" { if nvs, which, ok = tokenval(vs); ok && which != "" {

View File

@ -1,4 +1,4 @@
package controller package server
import ( import (
"strings" "strings"

View File

@ -13,8 +13,8 @@ import (
"github.com/garyburd/redigo/redis" "github.com/garyburd/redigo/redis"
"github.com/tidwall/tile38/core" "github.com/tidwall/tile38/core"
"github.com/tidwall/tile38/internal/controller"
tlog "github.com/tidwall/tile38/internal/log" tlog "github.com/tidwall/tile38/internal/log"
"github.com/tidwall/tile38/internal/server"
) )
var errTimeout = errors.New("timeout") var errTimeout = errors.New("timeout")
@ -51,7 +51,7 @@ func mockOpenServer() (*mockServer, error) {
s := &mockServer{port: port} s := &mockServer{port: port}
tlog.SetOutput(logOutput) tlog.SetOutput(logOutput)
go func() { go func() {
if err := controller.ListenAndServe("localhost", port, dir, true); err != nil { if err := server.Serve("localhost", port, dir, true); err != nil {
log.Fatal(err) log.Fatal(err)
} }
}() }()

View File

@ -0,0 +1,14 @@
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.8
working_directory: /go/src/github.com/kavu/go_reuseport
steps:
- checkout
- run: go get -v -t -d ./...
- run: go test -v -cover ./...
- run: go test -v -cover -race ./... -coverprofile=coverage.txt -covermode=atomic
- run: go test -v -cover -race -benchmem -benchtime=5s -bench=.

23
vendor/github.com/kavu/go_reuseport/.gitignore generated vendored Normal file
View File

@ -0,0 +1,23 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
.DS_Store

30
vendor/github.com/kavu/go_reuseport/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,30 @@
dist: trusty
sudo: true
language: go
go:
- "1.6"
- "1.7"
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- tip
os:
- linux
- osx
before_install:
- uname -a
script: ./test.bash
matrix:
allow_failures:
- os: osx
- go: tip
after_success:
- bash <(curl -s https://codecov.io/bash)

21
vendor/github.com/kavu/go_reuseport/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Max Riveiro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
vendor/github.com/kavu/go_reuseport/Makefile generated vendored Normal file
View File

@ -0,0 +1,9 @@
lint:
@gometalinter \
--disable=errcheck \
--disable=dupl \
--min-const-length=5 \
--min-confidence=0.25 \
--cyclo-over=20 \
--enable=unused \
--deadline=100s

48
vendor/github.com/kavu/go_reuseport/README.md generated vendored Normal file
View File

@ -0,0 +1,48 @@
# GO_REUSEPORT
[![Build Status](https://travis-ci.org/kavu/go_reuseport.png?branch=master)](https://travis-ci.org/kavu/go_reuseport)
[![codecov](https://codecov.io/gh/kavu/go_reuseport/branch/master/graph/badge.svg)](https://codecov.io/gh/kavu/go_reuseport)
[![GoDoc](https://godoc.org/github.com/kavu/go_reuseport?status.png)](https://godoc.org/github.com/kavu/go_reuseport)
**GO_REUSEPORT** is a little expirement to create a `net.Listener` that supports [SO_REUSEPORT](http://lwn.net/Articles/542629/) socket option.
For now, Darwin and Linux (from 3.9) systems are supported. I'll be pleased if you'll test other systems and tell me the results.
documentation on [godoc.org](http://godoc.org/github.com/kavu/go_reuseport "go_reuseport documentation").
## Example ##
```go
package main
import (
"fmt"
"html"
"net/http"
"os"
"runtime"
"github.com/kavu/go_reuseport"
)
func main() {
listener, err := reuseport.Listen("tcp", "localhost:8881")
if err != nil {
panic(err)
}
defer listener.Close()
server := &http.Server{}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println(os.Getgid())
fmt.Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path))
})
panic(server.Serve(listener))
}
```
Now you can run several instances of this tiny server without `Address already in use` errors.
## Thanks
Inspired by [Artur Siekielski](https://github.com/aartur) [post](http://freeprogrammersblog.vhex.net/post/linux-39-introdued-new-way-of-writing-socket-servers/2) about `SO_REUSEPORT`.

1
vendor/github.com/kavu/go_reuseport/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/kavu/go_reuseport

50
vendor/github.com/kavu/go_reuseport/reuseport.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
// +build linux darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Package reuseport provides a function that returns a net.Listener powered
// by a net.FileListener with a SO_REUSEPORT option set to the socket.
package reuseport
import (
"errors"
"fmt"
"net"
"os"
"syscall"
)
const fileNameTemplate = "reuseport.%d.%s.%s"
var errUnsupportedProtocol = errors.New("only tcp, tcp4, tcp6, udp, udp4, udp6 are supported")
// getSockaddr parses protocol and address and returns implementor
// of syscall.Sockaddr: syscall.SockaddrInet4 or syscall.SockaddrInet6.
func getSockaddr(proto, addr string) (sa syscall.Sockaddr, soType int, err error) {
switch proto {
case "tcp", "tcp4", "tcp6":
return getTCPSockaddr(proto, addr)
case "udp", "udp4", "udp6":
return getUDPSockaddr(proto, addr)
default:
return nil, -1, errUnsupportedProtocol
}
}
func getSocketFileName(proto, addr string) string {
return fmt.Sprintf(fileNameTemplate, os.Getpid(), proto, addr)
}
// Listen function is an alias for NewReusablePortListener.
func Listen(proto, addr string) (l net.Listener, err error) {
return NewReusablePortListener(proto, addr)
}
// ListenPacket is an alias for NewReusablePortPacketConn.
func ListenPacket(proto, addr string) (l net.PacketConn, err error) {
return NewReusablePortPacketConn(proto, addr)
}

44
vendor/github.com/kavu/go_reuseport/reuseport_bsd.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// +build darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Ma Weiwei, Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import (
"runtime"
"syscall"
)
var reusePort = syscall.SO_REUSEPORT
func maxListenerBacklog() int {
var (
n uint32
err error
)
switch runtime.GOOS {
case "darwin", "freebsd":
n, err = syscall.SysctlUint32("kern.ipc.somaxconn")
case "netbsd":
// NOTE: NetBSD has no somaxconn-like kernel state so far
case "openbsd":
n, err = syscall.SysctlUint32("kern.somaxconn")
}
if n == 0 || err != nil {
return syscall.SOMAXCONN
}
// FreeBSD stores the backlog in a uint16, as does Linux.
// Assume the other BSDs do too. Truncate number to avoid wrapping.
// See issue 5030.
if n > 1<<16-1 {
n = 1<<16 - 1
}
return int(n)
}

52
vendor/github.com/kavu/go_reuseport/reuseport_linux.go generated vendored Normal file
View File

@ -0,0 +1,52 @@
// +build linux
// Copyright (C) 2017 Ma Weiwei, Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import (
"bufio"
"os"
"strconv"
"strings"
"syscall"
)
var reusePort = 0x0F
func maxListenerBacklog() int {
fd, err := os.Open("/proc/sys/net/core/somaxconn")
if err != nil {
return syscall.SOMAXCONN
}
defer fd.Close()
rd := bufio.NewReader(fd)
line, err := rd.ReadString('\n')
if err != nil {
return syscall.SOMAXCONN
}
f := strings.Fields(line)
if len(f) < 1 {
return syscall.SOMAXCONN
}
n, err := strconv.Atoi(f[0])
if err != nil || n == 0 {
return syscall.SOMAXCONN
}
// Linux stores the backlog in a uint16.
// Truncate number to avoid wrapping.
// See issue 5030.
if n > 1<<16-1 {
n = 1<<16 - 1
}
return n
}

View File

@ -0,0 +1,19 @@
// +build windows
// Copyright (C) 2017 Ma Weiwei, Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import "net"
func NewReusablePortListener(proto, addr string) (net.Listener, error) {
return net.Listen(proto, addr)
}
func NewReusablePortPacketConn(proto, addr string) (net.PacketConn, error) {
return net.ListenPacket(proto, addr)
}

143
vendor/github.com/kavu/go_reuseport/tcp.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
// +build linux darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import (
"errors"
"net"
"os"
"syscall"
)
var (
listenerBacklogMaxSize = maxListenerBacklog()
errUnsupportedTCPProtocol = errors.New("only tcp, tcp4, tcp6 are supported")
)
func getTCPSockaddr(proto, addr string) (sa syscall.Sockaddr, soType int, err error) {
var tcp *net.TCPAddr
tcp, err = net.ResolveTCPAddr(proto, addr)
if err != nil && tcp.IP != nil {
return nil, -1, err
}
tcpVersion, err := determineTCPProto(proto, tcp)
if err != nil {
return nil, -1, err
}
switch tcpVersion {
case "tcp":
return &syscall.SockaddrInet4{Port: tcp.Port}, syscall.AF_INET, nil
case "tcp4":
sa := &syscall.SockaddrInet4{Port: tcp.Port}
if tcp.IP != nil {
copy(sa.Addr[:], tcp.IP[12:16]) // copy last 4 bytes of slice to array
}
return sa, syscall.AF_INET, nil
case "tcp6":
sa := &syscall.SockaddrInet6{Port: tcp.Port}
if tcp.IP != nil {
copy(sa.Addr[:], tcp.IP) // copy all bytes of slice to array
}
if tcp.Zone != "" {
iface, err := net.InterfaceByName(tcp.Zone)
if err != nil {
return nil, -1, err
}
sa.ZoneId = uint32(iface.Index)
}
return sa, syscall.AF_INET6, nil
}
return nil, -1, errUnsupportedProtocol
}
func determineTCPProto(proto string, ip *net.TCPAddr) (string, error) {
// If the protocol is set to "tcp", we try to determine the actual protocol
// version from the size of the resolved IP address. Otherwise, we simple use
// the protcol given to us by the caller.
if ip.IP.To4() != nil {
return "tcp4", nil
}
if ip.IP.To16() != nil {
return "tcp6", nil
}
switch proto {
case "tcp", "tcp4", "tcp6":
return proto, nil
}
return "", errUnsupportedTCPProtocol
}
// NewReusablePortListener returns net.FileListener that created from
// a file discriptor for a socket with SO_REUSEPORT option.
func NewReusablePortListener(proto, addr string) (l net.Listener, err error) {
var (
soType, fd int
file *os.File
sockaddr syscall.Sockaddr
)
if sockaddr, soType, err = getSockaddr(proto, addr); err != nil {
return nil, err
}
syscall.ForkLock.RLock()
if fd, err = syscall.Socket(soType, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil {
syscall.ForkLock.RUnlock()
return nil, err
}
syscall.ForkLock.RUnlock()
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, reusePort, 1); err != nil {
syscall.Close(fd)
return nil, err
}
if err = syscall.Bind(fd, sockaddr); err != nil {
syscall.Close(fd)
return nil, err
}
// Set backlog size to the maximum
if err = syscall.Listen(fd, listenerBacklogMaxSize); err != nil {
syscall.Close(fd)
return nil, err
}
file = os.NewFile(uintptr(fd), getSocketFileName(proto, addr))
if l, err = net.FileListener(file); err != nil {
file.Close()
return nil, err
}
if err = file.Close(); err != nil {
return nil, err
}
return l, err
}

218
vendor/github.com/kavu/go_reuseport/tcp_test.go generated vendored Normal file
View File

@ -0,0 +1,218 @@
// +build linux darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import (
"fmt"
"html"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"testing"
)
const (
httpServerOneResponse = "1"
httpServerTwoResponse = "2"
)
var (
httpServerOne = NewHTTPServer(httpServerOneResponse)
httpServerTwo = NewHTTPServer(httpServerTwoResponse)
)
func NewHTTPServer(resp string) *httptest.Server {
return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, resp)
}))
}
func TestNewReusablePortListener(t *testing.T) {
listenerOne, err := NewReusablePortListener("tcp4", "localhost:10081")
if err != nil {
t.Error(err)
}
defer listenerOne.Close()
listenerTwo, err := NewReusablePortListener("tcp", "127.0.0.1:10081")
if err != nil {
t.Error(err)
}
defer listenerTwo.Close()
listenerThree, err := NewReusablePortListener("tcp6", "[::1]:10081")
if err != nil {
t.Error(err)
}
defer listenerThree.Close()
listenerFour, err := NewReusablePortListener("tcp6", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFour.Close()
listenerFive, err := NewReusablePortListener("tcp4", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFive.Close()
listenerSix, err := NewReusablePortListener("tcp", ":10081")
if err != nil {
t.Error(err)
}
defer listenerSix.Close()
}
func TestListen(t *testing.T) {
listenerOne, err := Listen("tcp4", "localhost:10081")
if err != nil {
t.Error(err)
}
defer listenerOne.Close()
listenerTwo, err := Listen("tcp", "127.0.0.1:10081")
if err != nil {
t.Error(err)
}
defer listenerTwo.Close()
listenerThree, err := Listen("tcp6", "[::1]:10081")
if err != nil {
t.Error(err)
}
defer listenerThree.Close()
listenerFour, err := Listen("tcp6", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFour.Close()
listenerFive, err := Listen("tcp4", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFive.Close()
listenerSix, err := Listen("tcp", ":10081")
if err != nil {
t.Error(err)
}
defer listenerSix.Close()
}
func TestNewReusablePortServers(t *testing.T) {
listenerOne, err := NewReusablePortListener("tcp4", "localhost:10081")
if err != nil {
t.Error(err)
}
defer listenerOne.Close()
listenerTwo, err := NewReusablePortListener("tcp6", ":10081")
if err != nil {
t.Error(err)
}
defer listenerTwo.Close()
httpServerOne.Listener = listenerOne
httpServerTwo.Listener = listenerTwo
httpServerOne.Start()
httpServerTwo.Start()
// Server One — First Response
resp1, err := http.Get(httpServerOne.URL)
if err != nil {
t.Error(err)
}
body1, err := ioutil.ReadAll(resp1.Body)
resp1.Body.Close()
if err != nil {
t.Error(err)
}
if string(body1) != httpServerOneResponse && string(body1) != httpServerTwoResponse {
t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body1))
}
// Server Two — First Response
resp2, err := http.Get(httpServerTwo.URL)
if err != nil {
t.Error(err)
}
body2, err := ioutil.ReadAll(resp2.Body)
resp1.Body.Close()
if err != nil {
t.Error(err)
}
if string(body2) != httpServerOneResponse && string(body2) != httpServerTwoResponse {
t.Errorf("Expected %#v or %#v, got %#v.", httpServerOneResponse, httpServerTwoResponse, string(body2))
}
httpServerTwo.Close()
// Server One — Second Response
resp3, err := http.Get(httpServerOne.URL)
if err != nil {
t.Error(err)
}
body3, err := ioutil.ReadAll(resp3.Body)
resp1.Body.Close()
if err != nil {
t.Error(err)
}
if string(body3) != httpServerOneResponse {
t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body3))
}
// Server One — Third Response
resp5, err := http.Get(httpServerOne.URL)
if err != nil {
t.Error(err)
}
body5, err := ioutil.ReadAll(resp5.Body)
resp1.Body.Close()
if err != nil {
t.Error(err)
}
if string(body5) != httpServerOneResponse {
t.Errorf("Expected %#v, got %#v.", httpServerOneResponse, string(body5))
}
httpServerOne.Close()
}
func BenchmarkNewReusablePortListener(b *testing.B) {
for i := 0; i < b.N; i++ {
listener, err := NewReusablePortListener("tcp", ":10081")
if err != nil {
b.Error(err)
} else {
listener.Close()
}
}
}
func ExampleNewReusablePortListener() {
listener, err := NewReusablePortListener("tcp", ":8881")
if err != nil {
panic(err)
}
defer listener.Close()
server := &http.Server{}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println(os.Getgid())
fmt.Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path))
})
panic(server.Serve(listener))
}

22
vendor/github.com/kavu/go_reuseport/test.bash generated vendored Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -e
# Thanks to IPFS team
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
if [[ "$TRAVIS_SUDO" == true ]]; then
# Ensure that IPv6 is enabled.
# While this is unsupported by TravisCI, it still works for localhost.
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0
fi
else
# OSX has a default file limit of 256, for some tests we need a
# maximum of 8192.
ulimit -Sn 8192
fi
go test -v -cover ./...
go test -v -cover -race ./... -coverprofile=coverage.txt -covermode=atomic
go test -v -cover -race -benchmem -benchtime=5s -bench=.

143
vendor/github.com/kavu/go_reuseport/udp.go generated vendored Normal file
View File

@ -0,0 +1,143 @@
// +build linux darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import (
"errors"
"net"
"os"
"syscall"
)
var errUnsupportedUDPProtocol = errors.New("only udp, udp4, udp6 are supported")
func getUDPSockaddr(proto, addr string) (sa syscall.Sockaddr, soType int, err error) {
var udp *net.UDPAddr
udp, err = net.ResolveUDPAddr(proto, addr)
if err != nil && udp.IP != nil {
return nil, -1, err
}
udpVersion, err := determineUDPProto(proto, udp)
if err != nil {
return nil, -1, err
}
switch udpVersion {
case "udp":
return &syscall.SockaddrInet4{Port: udp.Port}, syscall.AF_INET, nil
case "udp4":
sa := &syscall.SockaddrInet4{Port: udp.Port}
if udp.IP != nil {
copy(sa.Addr[:], udp.IP[12:16]) // copy last 4 bytes of slice to array
}
return sa, syscall.AF_INET, nil
case "udp6":
sa := &syscall.SockaddrInet6{Port: udp.Port}
if udp.IP != nil {
copy(sa.Addr[:], udp.IP) // copy all bytes of slice to array
}
if udp.Zone != "" {
iface, err := net.InterfaceByName(udp.Zone)
if err != nil {
return nil, -1, err
}
sa.ZoneId = uint32(iface.Index)
}
return sa, syscall.AF_INET6, nil
}
return nil, -1, errUnsupportedProtocol
}
func determineUDPProto(proto string, ip *net.UDPAddr) (string, error) {
// If the protocol is set to "udp", we try to determine the actual protocol
// version from the size of the resolved IP address. Otherwise, we simple use
// the protcol given to us by the caller.
if ip.IP.To4() != nil {
return "udp4", nil
}
if ip.IP.To16() != nil {
return "udp6", nil
}
switch proto {
case "udp", "udp4", "udp6":
return proto, nil
}
return "", errUnsupportedUDPProtocol
}
// NewReusablePortPacketConn returns net.FilePacketConn that created from
// a file discriptor for a socket with SO_REUSEPORT option.
func NewReusablePortPacketConn(proto, addr string) (l net.PacketConn, err error) {
var (
soType, fd int
file *os.File
sockaddr syscall.Sockaddr
)
if sockaddr, soType, err = getSockaddr(proto, addr); err != nil {
return nil, err
}
syscall.ForkLock.RLock()
fd, err = syscall.Socket(soType, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP)
if err == nil {
syscall.CloseOnExec(fd)
}
syscall.ForkLock.RUnlock()
if err != nil {
syscall.Close(fd)
return nil, err
}
defer func() {
if err != nil {
syscall.Close(fd)
}
}()
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, reusePort, 1); err != nil {
return nil, err
}
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil {
return nil, err
}
if err = syscall.Bind(fd, sockaddr); err != nil {
return nil, err
}
file = os.NewFile(uintptr(fd), getSocketFileName(proto, addr))
if l, err = net.FilePacketConn(file); err != nil {
return nil, err
}
if err = file.Close(); err != nil {
return nil, err
}
return l, err
}

99
vendor/github.com/kavu/go_reuseport/udp_test.go generated vendored Normal file
View File

@ -0,0 +1,99 @@
// +build linux darwin dragonfly freebsd netbsd openbsd
// Copyright (C) 2017 Max Riveiro
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package reuseport
import "testing"
func TestNewReusablePortPacketConn(t *testing.T) {
listenerOne, err := NewReusablePortPacketConn("udp4", "localhost:10082")
if err != nil {
t.Error(err)
}
defer listenerOne.Close()
listenerTwo, err := NewReusablePortPacketConn("udp", "127.0.0.1:10082")
if err != nil {
t.Error(err)
}
defer listenerTwo.Close()
listenerThree, err := NewReusablePortPacketConn("udp6", "[::1]:10082")
if err != nil {
t.Error(err)
}
defer listenerThree.Close()
listenerFour, err := NewReusablePortListener("udp6", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFour.Close()
listenerFive, err := NewReusablePortListener("udp4", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFive.Close()
listenerSix, err := NewReusablePortListener("udp", ":10081")
if err != nil {
t.Error(err)
}
defer listenerSix.Close()
}
func TestListenPacket(t *testing.T) {
listenerOne, err := ListenPacket("udp4", "localhost:10082")
if err != nil {
t.Error(err)
}
defer listenerOne.Close()
listenerTwo, err := ListenPacket("udp", "127.0.0.1:10082")
if err != nil {
t.Error(err)
}
defer listenerTwo.Close()
listenerThree, err := ListenPacket("udp6", "[::1]:10082")
if err != nil {
t.Error(err)
}
defer listenerThree.Close()
listenerFour, err := ListenPacket("udp6", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFour.Close()
listenerFive, err := ListenPacket("udp4", ":10081")
if err != nil {
t.Error(err)
}
defer listenerFive.Close()
listenerSix, err := ListenPacket("udp", ":10081")
if err != nil {
t.Error(err)
}
defer listenerSix.Close()
}
func BenchmarkNewReusableUDPPortListener(b *testing.B) {
for i := 0; i < b.N; i++ {
listener, err := NewReusablePortPacketConn("udp4", "localhost:10082")
if err != nil {
b.Error(err)
} else {
listener.Close()
}
}
}

2
vendor/github.com/tidwall/evio/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,2 @@
language: go
script: go test -run none

20
vendor/github.com/tidwall/evio/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2017 Joshua J Baker
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

176
vendor/github.com/tidwall/evio/README.md generated vendored Normal file
View File

@ -0,0 +1,176 @@
<p align="center">
<img
src="logo.png"
width="213" height="75" border="0" alt="evio">
<br>
<a href="https://travis-ci.org/tidwall/evio"><img src="https://img.shields.io/travis/tidwall/evio.svg?style=flat-square" alt="Build Status"></a>
<a href="https://godoc.org/github.com/tidwall/evio"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
</p>
`evio` is an event loop networking framework that is fast and small. It makes direct [epoll](https://en.wikipedia.org/wiki/Epoll) and [kqueue](https://en.wikipedia.org/wiki/Kqueue) syscalls rather than using the standard Go [net](https://golang.org/pkg/net/) package, and works in a similar manner as [libuv](https://github.com/libuv/libuv) and [libevent](https://github.com/libevent/libevent).
The goal of this project is to create a server framework for Go that performs on par with [Redis](http://redis.io) and [Haproxy](http://www.haproxy.org) for packet handling. My hope is to use this as a foundation for [Tile38](https://github.com/tidwall/tile38) and a future L7 proxy for Go... and a bunch of other stuff.
**Just to be perfectly clear**
This project is not intended to be a general purpose replacement for the standard Go net package or goroutines. It's for building specialized services such as key value stores, L7 proxies, static websites, etc.
You would not want to use this framework if you need to handle long-running requests (milliseconds or more). For example, a web api that needs to connect to a mongo database, authenticate, and respond; just use the Go net/http package instead.
There are many popular event loop based applications in the wild such as Nginx, Haproxy, Redis, and Memcached. All of these are very fast and written in C.
The reason I wrote this framework is so that I can build certain networking services that perform like the C apps above, but I also want to continue to work in Go.
## Features
- [Fast](#performance) single-threaded or [multithreaded](#multithreaded) event loop
- Built-in [load balancing](#load-balancing) options
- Simple API
- Low memory usage
- Supports tcp, [udp](#udp), and unix sockets
- Allows [multiple network binding](#multiple-addresses) on the same event loop
- Flexible [ticker](#ticker) event
- Fallback for non-epoll/kqueue operating systems by simulating events with the [net](https://golang.org/pkg/net/) package
- [SO_REUSEPORT](#so_reuseport) socket option
## Getting Started
### Installing
To start using evio, install Go and run `go get`:
```sh
$ go get -u github.com/tidwall/evio
```
This will retrieve the library.
### Usage
Starting a server is easy with `evio`. Just set up your events and pass them to the `Serve` function along with the binding address(es). Each connections is represented as an `evio.Conn` object that is passed to various events to differentiate the clients. At any point you can close a client or shutdown the server by return a `Close` or `Shutdown` action from an event.
Example echo server that binds to port 5000:
```go
package main
import "github.com/tidwall/evio"
func main() {
var events evio.Events
events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
out = in
return
}
if err := evio.Serve(events, "tcp://localhost:5000"); err != nil {
panic(err.Error())
}
}
```
Here the only event being used is `Data`, which fires when the server receives input data from a client.
The exact same input data is then passed through the output return value, which is then sent back to the client.
Connect to the echo server:
```sh
$ telnet localhost 5000
```
### Events
The event type has a bunch of handy events:
- `Serving` fires when the server is ready to accept new connections.
- `Opened` fires when a connection has opened.
- `Closed` fires when a connection has closed.
- `Detach` fires when a connection has been detached using the `Detach` return action.
- `Data` fires when the server receives new data from a connection.
- `Tick` fires immediately after the server starts and will fire again after a specified interval.
### Multiple addresses
A server can bind to multiple addresses and share the same event loop.
```go
evio.Serve(events, "tcp://192.168.0.10:5000", "unix://socket")
```
### Ticker
The `Tick` event fires ticks at a specified interval.
The first tick fires immediately after the `Serving` events.
```go
events.Tick = func() (delay time.Duration, action Action){
log.Printf("tick")
delay = time.Second
return
}
```
## UDP
The `Serve` function can bind to UDP addresses.
- All incoming and outgoing packets are not buffered and sent individually.
- The `Opened` and `Closed` events are not availble for UDP sockets, only the `Data` event.
## Multithreaded
The `events.NumLoops` options sets the number of loops to use for the server.
A value greater than 1 will effectively make the server multithreaded for multi-core machines.
Which means you must take care when synchonizing memory between event callbacks.
Setting to 0 or 1 will run the server as single-threaded.
Setting to -1 will automatically assign this value equal to `runtime.NumProcs()`.
## Load balancing
The `events.LoadBalance` options sets the load balancing method.
Load balancing is always a best effort to attempt to distribute the incoming connections between multiple loops.
This option is only available when `events.NumLoops` is set.
- `Random` requests that connections are randomly distributed.
- `RoundRobin` requests that connections are distributed to a loop in a round-robin fashion.
- `LeastConnections` assigns the next accepted connection to the loop with the least number of active connections.
## SO_REUSEPORT
Servers can utilize the [SO_REUSEPORT](https://lwn.net/Articles/542629/) option which allows multiple sockets on the same host to bind to the same port.
Just provide `reuseport=true` to an address:
```go
evio.Serve(events, "tcp://0.0.0.0:1234?reuseport=true"))
```
## More examples
Please check out the [examples](examples) subdirectory for a simplified [redis](examples/redis-server/main.go) clone, an [echo](examples/echo-server/main.go) server, and a very basic [http](examples/http-server/main.go) server.
To run an example:
```sh
$ go run examples/http-server/main.go
$ go run examples/redis-server/main.go
$ go run examples/echo-server/main.go
```
## Performance
### Benchmarks
These benchmarks were run on an ec2 c4.xlarge instance in single-threaded mode (GOMAXPROC=1) over Ipv4 localhost.
Check out [benchmarks](benchmarks) for more info.
<img src="benchmarks/out/echo.png" width="336" height="144" border="0" alt="echo benchmark"><img src="benchmarks/out/http.png" width="336" height="144" border="0" alt="http benchmark"><img src="benchmarks/out/redis_pipeline_1.png" width="336" height="144" border="0" alt="redis 1 benchmark"><img src="benchmarks/out/redis_pipeline_8.png" width="336" height="144" border="0" alt="redis 8 benchmark">
## Contact
Josh Baker [@tidwall](http://twitter.com/tidwall)
## License
`evio` source code is available under the MIT [License](/LICENSE).

2
vendor/github.com/tidwall/evio/benchmarks/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
bin/
socket

27
vendor/github.com/tidwall/evio/benchmarks/README.md generated vendored Normal file
View File

@ -0,0 +1,27 @@
## evio benchmark tools
Required tools:
- [bombardier](https://github.com/codesenberg/bombardier) for HTTP
- [tcpkali](https://github.com/machinezone/tcpkali) for Echo
- [Redis](http://redis.io) for Redis
Required Go packages:
```
go get gonum.org/v1/plot/...
go get -u github.com/valyala/fasthttp
go get -u github.com/tidwall/redcon
```
And of course [Go](https://golang.org) is required.
Run `bench.sh` for all benchmarks.
## Notes
- The current results were run on an Ec2 c4.xlarge instance.
- The servers started in single-threaded mode (GOMAXPROC=1).
- Network clients connected over Ipv4 localhost.
Like all benchmarks ever made in the history of whatever, YMMV. Please tweak and run in your environment and let me know if you see any glaring issues.

308
vendor/github.com/tidwall/evio/benchmarks/analyze.go generated vendored Normal file
View File

@ -0,0 +1,308 @@
package main
import (
"fmt"
"io/ioutil"
"math"
"strconv"
"strings"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
)
var category string
var kind string
var connections, commands, pipeline, seconds int
var rate float64
var values []float64
var names []string
func main() {
analyze()
autoplot()
}
func autoplot() {
if category == "" {
return
}
var title = category
path := strings.Replace("out/"+category+".png", " ", "_", -1)
plotit(
path,
title,
values,
names,
)
}
func analyze() {
lines := readlines("out/http.txt", "out/echo.txt", "out/redis1.txt", "out/redis8.txt", "out/redis16.txt")
var err error
for _, line := range lines {
rlines := strings.Split(line, "\r")
line = strings.TrimSpace(rlines[len(rlines)-1])
if strings.HasPrefix(line, "--- ") {
if strings.HasSuffix(line, " START ---") {
autoplot()
category = strings.ToLower(strings.Replace(strings.Replace(line, "--- ", "", -1), " START ---", "", -1))
category = strings.Replace(category, "bench ", "", -1)
values = nil
names = nil
} else {
kind = strings.ToLower(strings.Replace(strings.Replace(line, "--- ", "", -1), " ---", "", -1))
}
connections, commands, pipeline, seconds = 0, 0, 0, 0
} else if strings.HasPrefix(line, "*** ") {
details := strings.Split(strings.ToLower(strings.Replace(line, "*** ", "", -1)), ", ")
for _, item := range details {
if strings.HasSuffix(item, " connections") {
connections, err = strconv.Atoi(strings.Split(item, " ")[0])
must(err)
} else if strings.HasSuffix(item, " commands") {
commands, err = strconv.Atoi(strings.Split(item, " ")[0])
must(err)
} else if strings.HasSuffix(item, " commands pipeline") {
pipeline, err = strconv.Atoi(strings.Split(item, " ")[0])
must(err)
} else if strings.HasSuffix(item, " seconds") {
seconds, err = strconv.Atoi(strings.Split(item, " ")[0])
must(err)
}
}
} else {
switch {
case category == "echo":
if strings.HasPrefix(line, "Packet rate estimate: ") {
rate, err = strconv.ParseFloat(strings.Split(strings.Split(line, ": ")[1], "↓,")[0], 64)
must(err)
output()
}
case category == "http":
if strings.HasPrefix(line, "Reqs/sec ") {
rate, err = strconv.ParseFloat(
strings.Split(strings.TrimSpace(strings.Split(line, "Reqs/sec ")[1]), " ")[0], 64)
must(err)
output()
}
case strings.HasPrefix(category, "redis"):
if strings.HasPrefix(line, "PING_INLINE: ") {
rate, err = strconv.ParseFloat(strings.Split(strings.Split(line, ": ")[1], " ")[0], 64)
must(err)
output()
}
}
}
}
}
func output() {
name := kind
names = append(names, name)
values = append(values, rate)
//csv += fmt.Sprintf("%s,%s,%d,%d,%d,%d,%f\n", category, kind, connections, commands, pipeline, seconds, rate)
}
func readlines(paths ...string) (lines []string) {
for _, path := range paths {
data, err := ioutil.ReadFile(path)
must(err)
lines = append(lines, strings.Split(string(data), "\n")...)
}
return
}
func must(err error) {
if err != nil {
panic(err)
}
}
func plotit(path, title string, values []float64, names []string) {
plot.DefaultFont = "Helvetica"
var groups []plotter.Values
for _, value := range values {
groups = append(groups, plotter.Values{value})
}
p, err := plot.New()
if err != nil {
panic(err)
}
p.Title.Text = title
p.Y.Tick.Marker = commaTicks{}
p.Y.Label.Text = "Req/s"
bw := 25.0
w := vg.Points(bw)
var bars []plot.Plotter
var barsp []*plotter.BarChart
for i := 0; i < len(values); i++ {
bar, err := plotter.NewBarChart(groups[i], w)
if err != nil {
panic(err)
}
bar.LineStyle.Width = vg.Length(0)
bar.Color = plotutil.Color(i)
bar.Offset = vg.Length(
(float64(w) * float64(i)) -
(float64(w)*float64(len(values)))/2)
bars = append(bars, bar)
barsp = append(barsp, bar)
}
p.Add(bars...)
for i, name := range names {
p.Legend.Add(fmt.Sprintf("%s (%.0f req/s)", name, values[i]), barsp[i])
}
p.Legend.Top = true
p.NominalX("")
if err := p.Save(7*vg.Inch, 3*vg.Inch, path); err != nil {
panic(err)
}
}
// PreciseTicks is suitable for the Tick.Marker field of an Axis, it returns a
// set of tick marks with labels that have been rounded less agressively than
// what DefaultTicks provides.
type PreciseTicks struct{}
// Ticks returns Ticks in a specified range
func (PreciseTicks) Ticks(min, max float64) []plot.Tick {
const suggestedTicks = 3
if max <= min {
panic("illegal range")
}
tens := math.Pow10(int(math.Floor(math.Log10(max - min))))
n := (max - min) / tens
for n < suggestedTicks-1 {
tens /= 10
n = (max - min) / tens
}
majorMult := int(n / (suggestedTicks - 1))
switch majorMult {
case 7:
majorMult = 6
case 9:
majorMult = 8
}
majorDelta := float64(majorMult) * tens
val := math.Floor(min/majorDelta) * majorDelta
// Makes a list of non-truncated y-values.
var labels []float64
for val <= max {
if val >= min {
labels = append(labels, val)
}
val += majorDelta
}
prec := int(math.Ceil(math.Log10(val)) - math.Floor(math.Log10(majorDelta)))
// Makes a list of big ticks.
var ticks []plot.Tick
for _, v := range labels {
vRounded := round(v, prec)
ticks = append(ticks, plot.Tick{Value: vRounded, Label: strconv.FormatFloat(vRounded, 'f', -1, 64)})
}
minorDelta := majorDelta / 2
switch majorMult {
case 3, 6:
minorDelta = majorDelta / 3
case 5:
minorDelta = majorDelta / 5
}
val = math.Floor(min/minorDelta) * minorDelta
for val <= max {
found := false
for _, t := range ticks {
if t.Value == val {
found = true
}
}
if val >= min && val <= max && !found {
ticks = append(ticks, plot.Tick{Value: val})
}
val += minorDelta
}
return ticks
}
type commaTicks struct{}
// Ticks computes the default tick marks, but inserts commas
// into the labels for the major tick marks.
func (commaTicks) Ticks(min, max float64) []plot.Tick {
tks := PreciseTicks{}.Ticks(min, max)
for i, t := range tks {
if t.Label == "" { // Skip minor ticks, they are fine.
continue
}
tks[i].Label = addCommas(t.Label)
}
return tks
}
// AddCommas adds commas after every 3 characters from right to left.
// NOTE: This function is a quick hack, it doesn't work with decimal
// points, and may have a bunch of other problems.
func addCommas(s string) string {
rev := ""
n := 0
for i := len(s) - 1; i >= 0; i-- {
rev += string(s[i])
n++
if n%3 == 0 {
rev += ","
}
}
s = ""
for i := len(rev) - 1; i >= 0; i-- {
s += string(rev[i])
}
if strings.HasPrefix(s, ",") {
s = s[1:]
}
return s
}
// round returns the half away from zero rounded value of x with a prec precision.
//
// Special cases are:
// round(±0) = +0
// round(±Inf) = ±Inf
// round(NaN) = NaN
func round(x float64, prec int) float64 {
if x == 0 {
// Make sure zero is returned
// without the negative bit set.
return 0
}
// Fast path for positive precision on integers.
if prec >= 0 && x == math.Trunc(x) {
return x
}
pow := math.Pow10(prec)
intermed := x * pow
if math.IsInf(intermed, 0) {
return x
}
if x < 0 {
x = math.Ceil(intermed - 0.5)
} else {
x = math.Floor(intermed + 0.5)
}
if x == 0 {
return 0
}
return x / pow
}

36
vendor/github.com/tidwall/evio/benchmarks/bench-echo.sh generated vendored Executable file
View File

@ -0,0 +1,36 @@
#!/bin/bash
set -e
echo ""
echo "--- BENCH ECHO START ---"
echo ""
cd $(dirname "${BASH_SOURCE[0]}")
function cleanup {
echo "--- BENCH ECHO DONE ---"
kill -9 $(jobs -rp)
wait $(jobs -rp) 2>/dev/null
}
trap cleanup EXIT
mkdir -p bin
$(pkill -9 net-echo-server || printf "")
$(pkill -9 evio-echo-server || printf "")
function gobench {
echo "--- $1 ---"
if [ "$3" != "" ]; then
go build -o $2 $3
fi
GOMAXPROCS=1 $2 --port $4 &
sleep 1
echo "*** 50 connections, 10 seconds, 6 byte packets"
nl=$'\r\n'
tcpkali --workers 1 -c 50 -T 10s -m "PING{$nl}" 127.0.0.1:$4
echo "--- DONE ---"
echo ""
}
gobench "GO STDLIB" bin/net-echo-server net-echo-server/main.go 5001
gobench "EVIO" bin/evio-echo-server ../examples/echo-server/main.go 5002

37
vendor/github.com/tidwall/evio/benchmarks/bench-http.sh generated vendored Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash
set -e
echo ""
echo "--- BENCH HTTP START ---"
echo ""
cd $(dirname "${BASH_SOURCE[0]}")
function cleanup {
echo "--- BENCH HTTP DONE ---"
kill -9 $(jobs -rp)
wait $(jobs -rp) 2>/dev/null
}
trap cleanup EXIT
mkdir -p bin
$(pkill -9 net-http-server || printf "")
$(pkill -9 fasthttp-server || printf "")
$(pkill -9 evio-http-server || printf "")
function gobench {
echo "--- $1 ---"
if [ "$3" != "" ]; then
go build -o $2 $3
fi
GOMAXPROCS=1 $2 --port $4 &
sleep 1
echo "*** 50 connections, 10 seconds"
bombardier -c 50 http://127.0.0.1:$4
echo "--- DONE ---"
echo ""
}
gobench "GO STDLIB" bin/net-http-server net-http-server/main.go 8081
gobench "FASTHTTP" bin/fasthttp-server fasthttp-server/main.go 8083
gobench "EVIO" bin/evio-http-server ../examples/http-server/main.go 8084

43
vendor/github.com/tidwall/evio/benchmarks/bench-redis.sh generated vendored Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
set -e
pl=$1
if [ "$pl" == "" ]; then
pl="1"
fi
echo ""
echo "--- BENCH REDIS PIPELINE $pl START ---"
echo ""
cd $(dirname "${BASH_SOURCE[0]}")
function cleanup {
echo "--- BENCH REDIS PIPELINE $pl DONE ---"
kill -9 $(jobs -rp)
wait $(jobs -rp) 2>/dev/null
}
trap cleanup EXIT
mkdir -p bin
$(pkill -9 redis-server || printf "")
$(pkill -9 evio-redis-server || printf "")
function gobench {
echo "--- $1 ---"
if [ "$3" != "" ]; then
go build -o $2 $3
fi
GOMAXPROCS=1 $2 --port $4 &
sleep 1
echo "*** 50 connections, 1000000 commands, $pl commands pipeline"
redis-benchmark -p $4 -t ping_inline -q -c 50 -P $pl -n 1000000
# echo "*** 50 connections, 1000000 commands, 10 commands pipeline"
# redis-benchmark -p $4 -t ping_inline -q -c 50 -P 10 -n 1000000
# echo "*** 50 connections, 1000000 commands, 20 commands pipeline"
# redis-benchmark -p $4 -t ping_inline -q -c 50 -P 20 -n 1000000
echo "--- DONE ---"
echo ""
}
gobench "REAL REDIS" redis-server "" 6392
gobench "EVIO REDIS CLONE" bin/evio-redis-server ../examples/redis-server/main.go 6393

15
vendor/github.com/tidwall/evio/benchmarks/bench.sh generated vendored Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
set -e
cd $(dirname "${BASH_SOURCE[0]}")
mkdir -p out/
./bench-http.sh 2>&1 | tee out/http.txt
./bench-echo.sh 2>&1 | tee out/echo.txt
./bench-redis.sh 1 2>&1 | tee out/redis1.txt
./bench-redis.sh 8 2>&1 | tee out/redis8.txt
./bench-redis.sh 16 2>&1 | tee out/redis16.txt
go run analyze.go

View File

@ -0,0 +1,32 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"github.com/valyala/fasthttp"
)
var res string
func main() {
var port int
flag.IntVar(&port, "port", 8080, "server port")
flag.Parse()
go log.Printf("http server started on port %d", port)
err := fasthttp.ListenAndServe(fmt.Sprintf(":%d", port),
func(c *fasthttp.RequestCtx) {
_, werr := c.WriteString("Hello World!\r\n")
if werr != nil {
log.Fatal(werr)
}
})
if err != nil {
log.Fatal(err)
}
}

View File

@ -0,0 +1,47 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"net"
)
func main() {
var port int
flag.IntVar(&port, "port", 5000, "server port")
flag.Parse()
ln, err := net.Listen("tcp4", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatal(err)
}
defer ln.Close()
log.Printf("echo server started on port %d", port)
var id int
for {
conn, err := ln.Accept()
if err != nil {
log.Fatal(err)
}
id++
go func(id int, conn net.Conn) {
defer func() {
//log.Printf("closed: %d", id)
conn.Close()
}()
//log.Printf("opened: %d: %s", id, conn.RemoteAddr().String())
var packet [0xFFF]byte
for {
n, err := conn.Read(packet[:])
if err != nil {
return
}
conn.Write(packet[:n])
}
}(id, conn)
}
}

View File

@ -0,0 +1,36 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"net/http"
"strings"
)
var res string
func main() {
var port int
var aaaa bool
flag.IntVar(&port, "port", 8080, "server port")
flag.BoolVar(&aaaa, "aaaa", false, "aaaaa....")
flag.Parse()
if aaaa {
res = strings.Repeat("a", 1024)
} else {
res = "Hello World!\r\n"
}
log.Printf("http server started on port %d", port)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(res))
})
err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
if err != nil {
log.Fatal(err)
}
}

BIN
vendor/github.com/tidwall/evio/benchmarks/out/echo.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

34
vendor/github.com/tidwall/evio/benchmarks/out/echo.txt generated vendored Normal file
View File

@ -0,0 +1,34 @@
--- BENCH ECHO START ---
--- GO STDLIB ---
2017/11/04 13:13:38 echo server started on port 5001
*** 50 connections, 10 seconds, 6 byte packets
Destination: [127.0.0.1]:5001
Interface lo address [127.0.0.1]:0
Using interface lo to connect to [127.0.0.1]:5001
Ramped up to 50 connections.
Total data sent: 9165.8 MiB (9610985472 bytes)
Total data received: 8951.1 MiB (9385891515 bytes)
Bandwidth per channel: 303.867⇅ Mbps (37983.4 kBps)
Aggregate bandwidth: 7506.663↓, 7686.689↑ Mbps
Packet rate estimate: 732150.4↓, 659753.8↑ (6↓, 45↑ TCP MSS/op)
Test duration: 10.0027 s.
--- DONE ---
--- EVIO ---
2017/11/04 13:13:50 echo server started on port 5002
*** 50 connections, 10 seconds, 6 byte packets
Destination: [127.0.0.1]:5002
Interface lo address [127.0.0.1]:0
Using interface lo to connect to [127.0.0.1]:5002
Ramped up to 50 connections.
Total data sent: 15441.1 MiB (16191127552 bytes)
Total data received: 15430.5 MiB (16180050837 bytes)
Bandwidth per channel: 517.825⇅ Mbps (64728.2 kBps)
Aggregate bandwidth: 12941.205↓, 12950.064↑ Mbps
Packet rate estimate: 1184847.1↓, 1111512.9↑ (12↓, 45↑ TCP MSS/op)
Test duration: 10.0022 s.
--- DONE ---
--- BENCH ECHO DONE ---

BIN
vendor/github.com/tidwall/evio/benchmarks/out/http.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

49
vendor/github.com/tidwall/evio/benchmarks/out/http.txt generated vendored Normal file
View File

@ -0,0 +1,49 @@
--- BENCH HTTP START ---
--- GO STDLIB ---
2017/11/06 11:43:15 http server started on port 8081
*** 50 connections, 10 seconds
Bombarding http://127.0.0.1:8081 for 10s using 50 connections
[------------------------------------------------------------------------------] [=======>-------------------------------------------------------------------] 9s [==============>------------------------------------------------------------] 8s [======================>----------------------------------------------------] 7s [=============================>---------------------------------------------] 6s [=====================================>-------------------------------------] 5s [============================================>------------------------------] 4s [====================================================>----------------------] 3s [===========================================================>---------------] 2s [===================================================================>-------] 1s [===========================================================================] 0s [==========================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 42487.26 9452.41 53042
Latency 1.17ms 742.47us 12.53ms
HTTP codes:
1xx - 0, 2xx - 424966, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 7.82MB/s
--- DONE ---
--- FASTHTTP ---
2017/11/06 11:43:27 http server started on port 8083
*** 50 connections, 10 seconds
Bombarding http://127.0.0.1:8083 for 10s using 50 connections
[------------------------------------------------------------------------------] [=======>-------------------------------------------------------------------] 9s [==============>------------------------------------------------------------] 8s [======================>----------------------------------------------------] 7s [=============================>---------------------------------------------] 6s [=====================================>-------------------------------------] 5s [============================================>------------------------------] 4s [====================================================>----------------------] 3s [===========================================================>---------------] 2s [===================================================================>-------] 1s [===========================================================================] 0s [==========================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 104926.32 2744.15 117354
Latency 474.64us 255.41us 11.06ms
HTTP codes:
1xx - 0, 2xx - 1049311, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 21.11MB/s
--- DONE ---
--- EVIO ---
2017/11/06 11:43:38 http server started on port 8084
*** 50 connections, 10 seconds
Bombarding http://127.0.0.1:8084 for 10s using 50 connections
[------------------------------------------------------------------------------] [=======>-------------------------------------------------------------------] 9s [==============>------------------------------------------------------------] 8s [======================>----------------------------------------------------] 7s [=============================>---------------------------------------------] 6s [=====================================>-------------------------------------] 5s [============================================>------------------------------] 4s [====================================================>----------------------] 3s [===========================================================>---------------] 2s [===================================================================>-------] 1s [===========================================================================] 0s [==========================================================================] 10s
Done!
Statistics Avg Stdev Max
Reqs/sec 123821.87 2821.88 130897
Latency 401.99us 121.11us 12.88ms
HTTP codes:
1xx - 0, 2xx - 1238166, 3xx - 0, 4xx - 0, 5xx - 0
others - 0
Throughput: 19.60MB/s
--- DONE ---
--- BENCH HTTP DONE ---

View File

@ -0,0 +1,28 @@
--- BENCH REDIS PIPELINE 1 START ---
--- REAL REDIS ---
31889:C 04 Nov 13:14:02.373 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
31889:C 04 Nov 13:14:02.373 # Redis version=4.0.2, bits=64, commit=00000000, modified=0, pid=31889, just started
31889:C 04 Nov 13:14:02.373 # Configuration loaded
31889:M 04 Nov 13:14:02.374 * Increased maximum number of open files to 10032 (it was originally set to 1024).
31889:M 04 Nov 13:14:02.374 * Running mode=standalone, port=6392.
31889:M 04 Nov 13:14:02.374 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
31889:M 04 Nov 13:14:02.374 # Server initialized
31889:M 04 Nov 13:14:02.374 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
31889:M 04 Nov 13:14:02.374 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
31889:M 04 Nov 13:14:02.374 * Ready to accept connections
*** 50 connections, 1000000 commands, 1 commands pipeline
PING_INLINE: -nan PING_INLINE: 171620.00 PING_INLINE: 175064.00 PING_INLINE: 175986.67 PING_INLINE: 176586.00 PING_INLINE: 176886.41 PING_INLINE: 177081.33 PING_INLINE: 177301.14 PING_INLINE: 177444.50 PING_INLINE: 177331.11 PING_INLINE: 177247.20 PING_INLINE: 177178.91 PING_INLINE: 177169.00 PING_INLINE: 177084.31 PING_INLINE: 177083.72 PING_INLINE: 177058.67 PING_INLINE: 177036.50 PING_INLINE: 177041.17 PING_INLINE: 177017.11 PING_INLINE: 177013.69 PING_INLINE: 177009.80 PING_INLINE: 177003.81 PING_INLINE: 177004.36 PING_INLINE: 176991.14 requests per second
--- DONE ---
--- EVIO REDIS CLONE ---
2017/11/04 13:14:09 redis server started on port 6393
2017/11/04 13:14:09 redis server started at socket
*** 50 connections, 1000000 commands, 1 commands pipeline
PING_INLINE: -nan PING_INLINE: 167180.00 PING_INLINE: 173258.00 PING_INLINE: 175005.33 PING_INLINE: 176102.00 PING_INLINE: 176358.41 PING_INLINE: 176593.33 PING_INLINE: 176877.72 PING_INLINE: 177103.00 PING_INLINE: 177186.67 PING_INLINE: 177269.59 PING_INLINE: 177322.19 PING_INLINE: 177363.67 PING_INLINE: 177420.00 PING_INLINE: 177448.86 PING_INLINE: 177411.73 PING_INLINE: 177371.75 PING_INLINE: 177334.59 PING_INLINE: 177310.44 PING_INLINE: 177264.62 PING_INLINE: 177205.00 PING_INLINE: 177171.62 PING_INLINE: 177173.64 PING_INLINE: 177147.92 requests per second
--- DONE ---
--- BENCH REDIS PIPELINE 1 DONE ---

View File

@ -0,0 +1,28 @@
--- BENCH REDIS PIPELINE 16 START ---
--- REAL REDIS ---
32002:C 04 Nov 13:14:20.410 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
32002:C 04 Nov 13:14:20.410 # Redis version=4.0.2, bits=64, commit=00000000, modified=0, pid=32002, just started
32002:C 04 Nov 13:14:20.410 # Configuration loaded
32002:M 04 Nov 13:14:20.411 * Increased maximum number of open files to 10032 (it was originally set to 1024).
32002:M 04 Nov 13:14:20.411 * Running mode=standalone, port=6392.
32002:M 04 Nov 13:14:20.412 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
32002:M 04 Nov 13:14:20.412 # Server initialized
32002:M 04 Nov 13:14:20.412 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
32002:M 04 Nov 13:14:20.412 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
32002:M 04 Nov 13:14:20.412 * Ready to accept connections
*** 50 connections, 1000000 commands, 16 commands pipeline
PING_INLINE: 0.00 PING_INLINE: 874135.50 PING_INLINE: 877221.56 PING_INLINE: 877315.62 PING_INLINE: 877810.12 PING_INLINE: 879507.50 requests per second
--- DONE ---
--- EVIO REDIS CLONE ---
2017/11/04 13:14:22 redis server started on port 6393
2017/11/04 13:14:22 redis server started at socket
*** 50 connections, 1000000 commands, 16 commands pipeline
PING_INLINE: -nan PING_INLINE: 2127552.00 PING_INLINE: 2123142.25 requests per second
--- DONE ---
--- BENCH REDIS PIPELINE 16 DONE ---

View File

@ -0,0 +1,28 @@
--- BENCH REDIS PIPELINE 8 START ---
--- REAL REDIS ---
31946:C 04 Nov 13:14:16.084 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
31946:C 04 Nov 13:14:16.084 # Redis version=4.0.2, bits=64, commit=00000000, modified=0, pid=31946, just started
31946:C 04 Nov 13:14:16.084 # Configuration loaded
31946:M 04 Nov 13:14:16.084 * Increased maximum number of open files to 10032 (it was originally set to 1024).
31946:M 04 Nov 13:14:16.085 * Running mode=standalone, port=6392.
31946:M 04 Nov 13:14:16.085 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
31946:M 04 Nov 13:14:16.085 # Server initialized
31946:M 04 Nov 13:14:16.085 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
31946:M 04 Nov 13:14:16.085 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
31946:M 04 Nov 13:14:16.085 * Ready to accept connections
*** 50 connections, 1000000 commands, 8 commands pipeline
PING_INLINE: -nan PING_INLINE: 823072.00 PING_INLINE: 859168.00 PING_INLINE: 870933.31 PING_INLINE: 876680.00 PING_INLINE: 878734.62 requests per second
--- DONE ---
--- EVIO REDIS CLONE ---
2017/11/04 13:14:18 redis server started on port 6393
2017/11/04 13:14:18 redis server started at socket
*** 50 connections, 1000000 commands, 8 commands pipeline
PING_INLINE: -nan PING_INLINE: 1284896.00 PING_INLINE: 1284144.00 PING_INLINE: 1285141.38 PING_INLINE: 1285347.00 requests per second
--- DONE ---
--- BENCH REDIS PIPELINE 8 DONE ---

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

268
vendor/github.com/tidwall/evio/evio.go generated vendored Normal file
View File

@ -0,0 +1,268 @@
// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package evio
import (
"io"
"net"
"os"
"strings"
"time"
)
// Action is an action that occurs after the completion of an event.
type Action int
const (
// None indicates that no action should occur following an event.
None Action = iota
// Detach detaches a connection. Not available for UDP connections.
Detach
// Close closes the connection.
Close
// Shutdown shutdowns the server.
Shutdown
)
// Options are set when the client opens.
type Options struct {
// TCPKeepAlive (SO_KEEPALIVE) socket option.
TCPKeepAlive time.Duration
// ReuseInputBuffer will forces the connection to share and reuse the
// same input packet buffer with all other connections that also use
// this option.
// Default value is false, which means that all input data which is
// passed to the Data event will be a uniquely copied []byte slice.
ReuseInputBuffer bool
}
// Server represents a server context which provides information about the
// running server and has control functions for managing state.
type Server struct {
// The addrs parameter is an array of listening addresses that align
// with the addr strings passed to the Serve function.
Addrs []net.Addr
// NumLoops is the number of loops that the server is using.
NumLoops int
}
// Conn is an evio connection.
type Conn interface {
// Context returns a user-defined context.
Context() interface{}
// SetContext sets a user-defined context.
SetContext(interface{})
// AddrIndex is the index of server address that was passed to the Serve call.
AddrIndex() int
// LocalAddr is the connection's local socket address.
LocalAddr() net.Addr
// RemoteAddr is the connection's remote peer address.
RemoteAddr() net.Addr
// Wake triggers a Data event for this connection.
Wake()
}
// LoadBalance sets the load balancing method.
type LoadBalance int
const (
// Random requests that connections are randomly distributed.
Random LoadBalance = iota
// RoundRobin requests that connections are distributed to a loop in a
// round-robin fashion.
RoundRobin
// LeastConnections assigns the next accepted connection to the loop with
// the least number of active connections.
LeastConnections
)
// Events represents the server events for the Serve call.
// Each event has an Action return value that is used manage the state
// of the connection and server.
type Events struct {
// NumLoops sets the number of loops to use for the server. Setting this
// to a value greater than 1 will effectively make the server
// multithreaded for multi-core machines. Which means you must take care
// with synchonizing memory between all event callbacks. Setting to 0 or 1
// will run the server single-threaded. Setting to -1 will automatically
// assign this value equal to runtime.NumProcs().
NumLoops int
// LoadBalance sets the load balancing method. Load balancing is always a
// best effort to attempt to distribute the incoming connections between
// multiple loops. This option is only works when NumLoops is set.
LoadBalance LoadBalance
// Serving fires when the server can accept connections. The server
// parameter has information and various utilities.
Serving func(server Server) (action Action)
// Opened fires when a new connection has opened.
// The info parameter has information about the connection such as
// it's local and remote address.
// Use the out return value to write data to the connection.
// The opts return value is used to set connection options.
Opened func(c Conn) (out []byte, opts Options, action Action)
// Closed fires when a connection has closed.
// The err parameter is the last known connection error.
Closed func(c Conn, err error) (action Action)
// Detached fires when a connection has been previously detached.
// Once detached it's up to the receiver of this event to manage the
// state of the connection. The Closed event will not be called for
// this connection.
// The conn parameter is a ReadWriteCloser that represents the
// underlying socket connection. It can be freely used in goroutines
// and should be closed when it's no longer needed.
Detached func(c Conn, rwc io.ReadWriteCloser) (action Action)
// PreWrite fires just before any data is written to any client socket.
PreWrite func()
// Data fires when a connection sends the server data.
// The in parameter is the incoming data.
// Use the out return value to write data to the connection.
Data func(c Conn, in []byte) (out []byte, action Action)
// Tick fires immediately after the server starts and will fire again
// following the duration specified by the delay return value.
Tick func() (delay time.Duration, action Action)
}
// Serve starts handling events for the specified addresses.
//
// Addresses should use a scheme prefix and be formatted
// like `tcp://192.168.0.10:9851` or `unix://socket`.
// Valid network schemes:
// tcp - bind to both IPv4 and IPv6
// tcp4 - IPv4
// tcp6 - IPv6
// udp - bind to both IPv4 and IPv6
// udp4 - IPv4
// udp6 - IPv6
// unix - Unix Domain Socket
//
// The "tcp" network scheme is assumed when one is not specified.
func Serve(events Events, addr ...string) error {
var lns []*listener
defer func() {
for _, ln := range lns {
ln.close()
}
}()
var stdlib bool
for _, addr := range addr {
var ln listener
var stdlibt bool
ln.network, ln.addr, ln.opts, stdlibt = parseAddr(addr)
if stdlibt {
stdlib = true
}
if ln.network == "unix" {
os.RemoveAll(ln.addr)
}
var err error
if ln.network == "udp" {
if ln.opts.reusePort {
ln.pconn, err = reuseportListenPacket(ln.network, ln.addr)
} else {
ln.pconn, err = net.ListenPacket(ln.network, ln.addr)
}
} else {
if ln.opts.reusePort {
ln.ln, err = reuseportListen(ln.network, ln.addr)
} else {
ln.ln, err = net.Listen(ln.network, ln.addr)
}
}
if err != nil {
return err
}
if ln.pconn != nil {
ln.lnaddr = ln.pconn.LocalAddr()
} else {
ln.lnaddr = ln.ln.Addr()
}
if !stdlib {
if err := ln.system(); err != nil {
return err
}
}
lns = append(lns, &ln)
}
if stdlib {
return stdserve(events, lns)
}
return serve(events, lns)
}
// InputStream is a helper type for managing input streams from inside
// the Data event.
type InputStream struct{ b []byte }
// Begin accepts a new packet and returns a working sequence of
// unprocessed bytes.
func (is *InputStream) Begin(packet []byte) (data []byte) {
data = packet
if len(is.b) > 0 {
is.b = append(is.b, data...)
data = is.b
}
return data
}
// End shifts the stream to match the unprocessed data.
func (is *InputStream) End(data []byte) {
if len(data) > 0 {
if len(data) != len(is.b) {
is.b = append(is.b[:0], data...)
}
} else if len(is.b) > 0 {
is.b = is.b[:0]
}
}
type listener struct {
ln net.Listener
lnaddr net.Addr
pconn net.PacketConn
opts addrOpts
f *os.File
fd int
network string
addr string
}
type addrOpts struct {
reusePort bool
}
func parseAddr(addr string) (network, address string, opts addrOpts, stdlib bool) {
network = "tcp"
address = addr
opts.reusePort = false
if strings.Contains(address, "://") {
network = strings.Split(address, "://")[0]
address = strings.Split(address, "://")[1]
}
if strings.HasSuffix(network, "-net") {
stdlib = true
network = network[:len(network)-4]
}
q := strings.Index(address, "?")
if q != -1 {
for _, part := range strings.Split(address[q+1:], "&") {
kv := strings.Split(part, "=")
if len(kv) == 2 {
switch kv[0] {
case "reuseport":
if len(kv[1]) != 0 {
switch kv[1][0] {
default:
opts.reusePort = kv[1][0] >= '1' && kv[1][0] <= '9'
case 'T', 't', 'Y', 'y':
opts.reusePort = true
}
}
}
}
}
address = address[:q]
}
return
}

41
vendor/github.com/tidwall/evio/evio_other.go generated vendored Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build !darwin,!netbsd,!freebsd,!openbsd,!dragonfly,!linux
package evio
import (
"errors"
"net"
"os"
)
func (ln *listener) close() {
if ln.ln != nil {
ln.ln.Close()
}
if ln.pconn != nil {
ln.pconn.Close()
}
if ln.network == "unix" {
os.RemoveAll(ln.addr)
}
}
func (ln *listener) system() error {
return nil
}
func serve(events Events, listeners []*listener) error {
return stdserve(events, listeners)
}
func reuseportListenPacket(proto, addr string) (l net.PacketConn, err error) {
return nil, errors.New("reuseport is not available")
}
func reuseportListen(proto, addr string) (l net.Listener, err error) {
return nil, errors.New("reuseport is not available")
}

459
vendor/github.com/tidwall/evio/evio_std.go generated vendored Normal file
View File

@ -0,0 +1,459 @@
// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package evio
import (
"errors"
"io"
"net"
"runtime"
"sync"
"sync/atomic"
"time"
)
var errClosing = errors.New("closing")
var errCloseConns = errors.New("close conns")
type stdserver struct {
events Events // user events
loops []*stdloop // all the loops
lns []*listener // all the listeners
loopwg sync.WaitGroup // loop close waitgroup
lnwg sync.WaitGroup // listener close waitgroup
cond *sync.Cond // shutdown signaler
serr error // signal error
accepted uintptr // accept counter
}
type stdudpconn struct {
addrIndex int
localAddr net.Addr
remoteAddr net.Addr
in []byte
}
func (c *stdudpconn) Context() interface{} { return nil }
func (c *stdudpconn) SetContext(ctx interface{}) {}
func (c *stdudpconn) AddrIndex() int { return c.addrIndex }
func (c *stdudpconn) LocalAddr() net.Addr { return c.localAddr }
func (c *stdudpconn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *stdudpconn) Wake() {}
type stdloop struct {
idx int // loop index
ch chan interface{} // command channel
conns map[*stdconn]bool // track all the conns bound to this loop
}
type stdconn struct {
addrIndex int
localAddr net.Addr
remoteAddr net.Addr
conn net.Conn // original connection
ctx interface{} // user-defined context
loop *stdloop // owner loop
lnidx int // index of listener
donein []byte // extra data for done connection
done int32 // 0: attached, 1: closed, 2: detached
}
type wakeReq struct {
c *stdconn
}
func (c *stdconn) Context() interface{} { return c.ctx }
func (c *stdconn) SetContext(ctx interface{}) { c.ctx = ctx }
func (c *stdconn) AddrIndex() int { return c.addrIndex }
func (c *stdconn) LocalAddr() net.Addr { return c.localAddr }
func (c *stdconn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *stdconn) Wake() { c.loop.ch <- wakeReq{c} }
type stdin struct {
c *stdconn
in []byte
}
type stderr struct {
c *stdconn
err error
}
// waitForShutdown waits for a signal to shutdown
func (s *stdserver) waitForShutdown() error {
s.cond.L.Lock()
s.cond.Wait()
err := s.serr
s.cond.L.Unlock()
return err
}
// signalShutdown signals a shutdown an begins server closing
func (s *stdserver) signalShutdown(err error) {
s.cond.L.Lock()
s.serr = err
s.cond.Signal()
s.cond.L.Unlock()
}
func stdserve(events Events, listeners []*listener) error {
numLoops := events.NumLoops
if numLoops <= 0 {
if numLoops == 0 {
numLoops = 1
} else {
numLoops = runtime.NumCPU()
}
}
s := &stdserver{}
s.events = events
s.lns = listeners
s.cond = sync.NewCond(&sync.Mutex{})
//println("-- server starting")
if events.Serving != nil {
var svr Server
svr.NumLoops = numLoops
svr.Addrs = make([]net.Addr, len(listeners))
for i, ln := range listeners {
svr.Addrs[i] = ln.lnaddr
}
action := events.Serving(svr)
switch action {
case Shutdown:
return nil
}
}
for i := 0; i < numLoops; i++ {
s.loops = append(s.loops, &stdloop{
idx: i,
ch: make(chan interface{}),
conns: make(map[*stdconn]bool),
})
}
var ferr error
defer func() {
// wait on a signal for shutdown
ferr = s.waitForShutdown()
// notify all loops to close by closing all listeners
for _, l := range s.loops {
l.ch <- errClosing
}
// wait on all loops to main loop channel events
s.loopwg.Wait()
// shutdown all listeners
for i := 0; i < len(s.lns); i++ {
s.lns[i].close()
}
// wait on all listeners to complete
s.lnwg.Wait()
// close all connections
s.loopwg.Add(len(s.loops))
for _, l := range s.loops {
l.ch <- errCloseConns
}
s.loopwg.Wait()
}()
s.loopwg.Add(numLoops)
for i := 0; i < numLoops; i++ {
go stdloopRun(s, s.loops[i])
}
s.lnwg.Add(len(listeners))
for i := 0; i < len(listeners); i++ {
go stdlistenerRun(s, listeners[i], i)
}
return ferr
}
func stdlistenerRun(s *stdserver, ln *listener, lnidx int) {
var ferr error
defer func() {
s.signalShutdown(ferr)
s.lnwg.Done()
}()
var packet [0xFFFF]byte
for {
if ln.pconn != nil {
// udp
n, addr, err := ln.pconn.ReadFrom(packet[:])
if err != nil {
ferr = err
return
}
l := s.loops[int(atomic.AddUintptr(&s.accepted, 1))%len(s.loops)]
l.ch <- &stdudpconn{
addrIndex: lnidx,
localAddr: ln.lnaddr,
remoteAddr: addr,
in: append([]byte{}, packet[:n]...),
}
} else {
// tcp
conn, err := ln.ln.Accept()
if err != nil {
ferr = err
return
}
l := s.loops[int(atomic.AddUintptr(&s.accepted, 1))%len(s.loops)]
c := &stdconn{conn: conn, loop: l, lnidx: lnidx}
l.ch <- c
go func(c *stdconn) {
var packet [0xFFFF]byte
for {
n, err := c.conn.Read(packet[:])
if err != nil {
c.conn.SetReadDeadline(time.Time{})
l.ch <- &stderr{c, err}
return
}
l.ch <- &stdin{c, append([]byte{}, packet[:n]...)}
}
}(c)
}
}
}
func stdloopRun(s *stdserver, l *stdloop) {
var err error
tick := make(chan bool)
tock := make(chan time.Duration)
defer func() {
//fmt.Println("-- loop stopped --", l.idx)
if l.idx == 0 && s.events.Tick != nil {
close(tock)
go func() {
for range tick {
}
}()
}
s.signalShutdown(err)
s.loopwg.Done()
stdloopEgress(s, l)
s.loopwg.Done()
}()
if l.idx == 0 && s.events.Tick != nil {
go func() {
for {
tick <- true
delay, ok := <-tock
if !ok {
break
}
time.Sleep(delay)
}
}()
}
//fmt.Println("-- loop started --", l.idx)
for {
select {
case <-tick:
delay, action := s.events.Tick()
switch action {
case Shutdown:
err = errClosing
}
tock <- delay
case v := <-l.ch:
switch v := v.(type) {
case error:
err = v
case *stdconn:
err = stdloopAccept(s, l, v)
case *stdin:
err = stdloopRead(s, l, v.c, v.in)
case *stdudpconn:
err = stdloopReadUDP(s, l, v)
case *stderr:
err = stdloopError(s, l, v.c, v.err)
case wakeReq:
err = stdloopRead(s, l, v.c, nil)
}
}
if err != nil {
return
}
}
}
func stdloopEgress(s *stdserver, l *stdloop) {
var closed bool
loop:
for v := range l.ch {
switch v := v.(type) {
case error:
if v == errCloseConns {
closed = true
for c := range l.conns {
stdloopClose(s, l, c)
}
}
case *stderr:
stdloopError(s, l, v.c, v.err)
}
if len(l.conns) == 0 && closed {
break loop
}
}
}
func stdloopError(s *stdserver, l *stdloop, c *stdconn, err error) error {
delete(l.conns, c)
closeEvent := true
switch atomic.LoadInt32(&c.done) {
case 0: // read error
c.conn.Close()
if err == io.EOF {
err = nil
}
case 1: // closed
c.conn.Close()
err = nil
case 2: // detached
err = nil
if s.events.Detached == nil {
c.conn.Close()
} else {
closeEvent = false
switch s.events.Detached(c, &stddetachedConn{c.conn, c.donein}) {
case Shutdown:
return errClosing
}
}
}
if closeEvent {
if s.events.Closed != nil {
switch s.events.Closed(c, err) {
case Shutdown:
return errClosing
}
}
}
return nil
}
type stddetachedConn struct {
conn net.Conn // original conn
in []byte // extra input data
}
func (c *stddetachedConn) Read(p []byte) (n int, err error) {
if len(c.in) > 0 {
if len(c.in) <= len(p) {
copy(p, c.in)
n = len(c.in)
c.in = nil
return
}
copy(p, c.in[:len(p)])
n = len(p)
c.in = c.in[n:]
return
}
return c.conn.Read(p)
}
func (c *stddetachedConn) Write(p []byte) (n int, err error) {
return c.conn.Write(p)
}
func (c *stddetachedConn) Close() error {
return c.conn.Close()
}
func (c *stddetachedConn) Wake() {}
func stdloopRead(s *stdserver, l *stdloop, c *stdconn, in []byte) error {
if atomic.LoadInt32(&c.done) == 2 {
// should not ignore reads for detached connections
c.donein = append(c.donein, in...)
return nil
}
if s.events.Data != nil {
out, action := s.events.Data(c, in)
if len(out) > 0 {
if s.events.PreWrite != nil {
s.events.PreWrite()
}
c.conn.Write(out)
}
switch action {
case Shutdown:
return errClosing
case Detach:
return stdloopDetach(s, l, c)
case Close:
return stdloopClose(s, l, c)
}
}
return nil
}
func stdloopReadUDP(s *stdserver, l *stdloop, c *stdudpconn) error {
if s.events.Data != nil {
out, action := s.events.Data(c, c.in)
if len(out) > 0 {
if s.events.PreWrite != nil {
s.events.PreWrite()
}
s.lns[c.addrIndex].pconn.WriteTo(out, c.remoteAddr)
}
switch action {
case Shutdown:
return errClosing
}
}
return nil
}
func stdloopDetach(s *stdserver, l *stdloop, c *stdconn) error {
atomic.StoreInt32(&c.done, 2)
c.conn.SetReadDeadline(time.Now())
return nil
}
func stdloopClose(s *stdserver, l *stdloop, c *stdconn) error {
atomic.StoreInt32(&c.done, 1)
c.conn.SetReadDeadline(time.Now())
return nil
}
func stdloopAccept(s *stdserver, l *stdloop, c *stdconn) error {
l.conns[c] = true
c.addrIndex = c.lnidx
c.localAddr = s.lns[c.lnidx].lnaddr
c.remoteAddr = c.conn.RemoteAddr()
if s.events.Opened != nil {
out, opts, action := s.events.Opened(c)
if len(out) > 0 {
if s.events.PreWrite != nil {
s.events.PreWrite()
}
c.conn.Write(out)
}
if opts.TCPKeepAlive > 0 {
if c, ok := c.conn.(*net.TCPConn); ok {
c.SetKeepAlive(true)
c.SetKeepAlivePeriod(opts.TCPKeepAlive)
}
}
switch action {
case Shutdown:
return errClosing
case Detach:
return stdloopDetach(s, l, c)
case Close:
return stdloopClose(s, l, c)
}
}
return nil
}

478
vendor/github.com/tidwall/evio/evio_test.go generated vendored Normal file
View File

@ -0,0 +1,478 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package evio
import (
"bufio"
"fmt"
"io"
"math/rand"
"net"
"os"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestServe(t *testing.T) {
// start a server
// connect 10 clients
// each client will pipe random data for 1-3 seconds.
// the writes to the server will be random sizes. 0KB - 1MB.
// the server will echo back the data.
// waits for graceful connection closing.
t.Run("stdlib", func(t *testing.T) {
t.Run("tcp", func(t *testing.T) {
t.Run("1-loop", func(t *testing.T) {
testServe("tcp-net", ":9997", false, 10, 1, Random)
})
t.Run("5-loop", func(t *testing.T) {
testServe("tcp-net", ":9998", false, 10, 5, LeastConnections)
})
t.Run("N-loop", func(t *testing.T) {
testServe("tcp-net", ":9999", false, 10, -1, RoundRobin)
})
})
t.Run("unix", func(t *testing.T) {
t.Run("1-loop", func(t *testing.T) {
testServe("tcp-net", ":9989", true, 10, 1, Random)
})
t.Run("5-loop", func(t *testing.T) {
testServe("tcp-net", ":9988", true, 10, 5, LeastConnections)
})
t.Run("N-loop", func(t *testing.T) {
testServe("tcp-net", ":9987", true, 10, -1, RoundRobin)
})
})
})
t.Run("poll", func(t *testing.T) {
t.Run("tcp", func(t *testing.T) {
t.Run("1-loop", func(t *testing.T) {
testServe("tcp", ":9991", false, 10, 1, Random)
})
t.Run("5-loop", func(t *testing.T) {
testServe("tcp", ":9992", false, 10, 5, LeastConnections)
})
t.Run("N-loop", func(t *testing.T) {
testServe("tcp", ":9993", false, 10, -1, RoundRobin)
})
})
t.Run("unix", func(t *testing.T) {
t.Run("1-loop", func(t *testing.T) {
testServe("tcp", ":9994", true, 10, 1, Random)
})
t.Run("5-loop", func(t *testing.T) {
testServe("tcp", ":9995", true, 10, 5, LeastConnections)
})
t.Run("N-loop", func(t *testing.T) {
testServe("tcp", ":9996", true, 10, -1, RoundRobin)
})
})
})
}
func testServe(network, addr string, unix bool, nclients, nloops int, balance LoadBalance) {
var started int32
var connected int32
var disconnected int32
var events Events
events.LoadBalance = balance
events.NumLoops = nloops
events.Serving = func(srv Server) (action Action) {
return
}
events.Opened = func(c Conn) (out []byte, opts Options, action Action) {
c.SetContext(c)
atomic.AddInt32(&connected, 1)
out = []byte("sweetness\r\n")
opts.TCPKeepAlive = time.Minute * 5
if c.LocalAddr() == nil {
panic("nil local addr")
}
if c.RemoteAddr() == nil {
panic("nil local addr")
}
return
}
events.Closed = func(c Conn, err error) (action Action) {
if c.Context() != c {
panic("invalid context")
}
atomic.AddInt32(&disconnected, 1)
if atomic.LoadInt32(&connected) == atomic.LoadInt32(&disconnected) &&
atomic.LoadInt32(&disconnected) == int32(nclients) {
action = Shutdown
}
return
}
events.Data = func(c Conn, in []byte) (out []byte, action Action) {
out = in
return
}
events.Tick = func() (delay time.Duration, action Action) {
if atomic.LoadInt32(&started) == 0 {
for i := 0; i < nclients; i++ {
go startClient(network, addr, nloops)
}
atomic.StoreInt32(&started, 1)
}
delay = time.Second / 5
return
}
var err error
if unix {
socket := strings.Replace(addr, ":", "socket", 1)
os.RemoveAll(socket)
defer os.RemoveAll(socket)
err = Serve(events, network+"://"+addr, "unix://"+socket)
} else {
err = Serve(events, network+"://"+addr)
}
if err != nil {
panic(err)
}
}
func startClient(network, addr string, nloops int) {
onetwork := network
network = strings.Replace(network, "-net", "", -1)
rand.Seed(time.Now().UnixNano())
c, err := net.Dial(network, addr)
if err != nil {
panic(err)
}
defer c.Close()
rd := bufio.NewReader(c)
msg, err := rd.ReadBytes('\n')
if err != nil {
panic(err)
}
if string(msg) != "sweetness\r\n" {
panic("bad header")
}
duration := time.Duration((rand.Float64()*2+1)*float64(time.Second)) / 8
start := time.Now()
for time.Since(start) < duration {
sz := rand.Int() % (1024 * 1024)
data := make([]byte, sz)
if _, err := rand.Read(data); err != nil {
panic(err)
}
if _, err := c.Write(data); err != nil {
panic(err)
}
data2 := make([]byte, len(data))
if _, err := io.ReadFull(rd, data2); err != nil {
panic(err)
}
if string(data) != string(data2) {
fmt.Printf("mismatch %s/%d: %d vs %d bytes\n", onetwork, nloops, len(data), len(data2))
//panic("mismatch")
}
}
}
func must(err error) {
if err != nil {
panic(err)
}
}
func TestTick(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
testTick("tcp", ":9991", false)
}()
wg.Add(1)
go func() {
defer wg.Done()
testTick("tcp", ":9992", true)
}()
wg.Add(1)
go func() {
defer wg.Done()
testTick("unix", "socket1", false)
}()
wg.Add(1)
go func() {
defer wg.Done()
testTick("unix", "socket2", true)
}()
wg.Wait()
}
func testTick(network, addr string, stdlib bool) {
var events Events
var count int
start := time.Now()
events.Tick = func() (delay time.Duration, action Action) {
if count == 25 {
action = Shutdown
return
}
count++
delay = time.Millisecond * 10
return
}
if stdlib {
must(Serve(events, network+"-net://"+addr))
} else {
must(Serve(events, network+"://"+addr))
}
dur := time.Since(start)
if dur < 250&time.Millisecond || dur > time.Second {
panic("bad ticker timing")
}
}
func TestShutdown(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
testShutdown("tcp", ":9991", false)
}()
wg.Add(1)
go func() {
defer wg.Done()
testShutdown("tcp", ":9992", true)
}()
wg.Add(1)
go func() {
defer wg.Done()
testShutdown("unix", "socket1", false)
}()
wg.Add(1)
go func() {
defer wg.Done()
testShutdown("unix", "socket2", true)
}()
wg.Wait()
}
func testShutdown(network, addr string, stdlib bool) {
var events Events
var count int
var clients int64
var N = 10
events.Opened = func(c Conn) (out []byte, opts Options, action Action) {
atomic.AddInt64(&clients, 1)
return
}
events.Closed = func(c Conn, err error) (action Action) {
atomic.AddInt64(&clients, -1)
return
}
events.Tick = func() (delay time.Duration, action Action) {
if count == 0 {
// start clients
for i := 0; i < N; i++ {
go func() {
conn, err := net.Dial(network, addr)
must(err)
defer conn.Close()
_, err = conn.Read([]byte{0})
if err == nil {
panic("expected error")
}
}()
}
} else {
if int(atomic.LoadInt64(&clients)) == N {
action = Shutdown
}
}
count++
delay = time.Second / 20
return
}
if stdlib {
must(Serve(events, network+"-net://"+addr))
} else {
must(Serve(events, network+"://"+addr))
}
if clients != 0 {
panic("did not call close on all clients")
}
}
func TestDetach(t *testing.T) {
t.Run("poll", func(t *testing.T) {
t.Run("tcp", func(t *testing.T) {
testDetach("tcp", ":9991", false)
})
t.Run("unix", func(t *testing.T) {
testDetach("unix", "socket1", false)
})
})
t.Run("stdlib", func(t *testing.T) {
t.Run("tcp", func(t *testing.T) {
testDetach("tcp", ":9992", true)
})
t.Run("unix", func(t *testing.T) {
testDetach("unix", "socket2", true)
})
})
}
func testDetach(network, addr string, stdlib bool) {
// we will write a bunch of data with the text "--detached--" in the
// middle followed by a bunch of data.
rand.Seed(time.Now().UnixNano())
rdat := make([]byte, 10*1024)
if _, err := rand.Read(rdat); err != nil {
panic("random error: " + err.Error())
}
expected := []byte(string(rdat) + "--detached--" + string(rdat))
var cin []byte
var events Events
events.Data = func(c Conn, in []byte) (out []byte, action Action) {
cin = append(cin, in...)
if len(cin) >= len(expected) {
if string(cin) != string(expected) {
panic("mismatch client -> server")
}
return cin, Detach
}
return
}
var done int64
events.Detached = func(c Conn, conn io.ReadWriteCloser) (action Action) {
go func() {
p := make([]byte, len(expected))
defer conn.Close()
_, err := io.ReadFull(conn, p)
must(err)
conn.Write(expected)
}()
return
}
events.Serving = func(srv Server) (action Action) {
go func() {
p := make([]byte, len(expected))
_ = expected
conn, err := net.Dial(network, addr)
must(err)
defer conn.Close()
conn.Write(expected)
_, err = io.ReadFull(conn, p)
must(err)
conn.Write(expected)
_, err = io.ReadFull(conn, p)
must(err)
atomic.StoreInt64(&done, 1)
}()
return
}
events.Tick = func() (delay time.Duration, action Action) {
delay = time.Second / 5
if atomic.LoadInt64(&done) == 1 {
action = Shutdown
}
return
}
if stdlib {
must(Serve(events, network+"-net://"+addr))
} else {
must(Serve(events, network+"://"+addr))
}
}
func TestBadAddresses(t *testing.T) {
var events Events
events.Serving = func(srv Server) (action Action) {
return Shutdown
}
if err := Serve(events, "tulip://howdy"); err == nil {
t.Fatalf("expected error")
}
if err := Serve(events, "howdy"); err == nil {
t.Fatalf("expected error")
}
if err := Serve(events, "tcp://"); err != nil {
t.Fatalf("expected nil, got '%v'", err)
}
}
func TestInputStream(t *testing.T) {
var s InputStream
in := []byte("HELLO")
data := s.Begin(in)
if string(data) != string(in) {
t.Fatalf("expected '%v', got '%v'", in, data)
}
s.End(in[3:])
data = s.Begin([]byte("WLY"))
if string(data) != "LOWLY" {
t.Fatalf("expected '%v', got '%v'", "LOWLY", data)
}
s.End(nil)
data = s.Begin([]byte("PLAYER"))
if string(data) != "PLAYER" {
t.Fatalf("expected '%v', got '%v'", "PLAYER", data)
}
}
func TestReuseInputBuffer(t *testing.T) {
reuses := []bool{true, false}
for _, reuse := range reuses {
var events Events
events.Opened = func(c Conn) (out []byte, opts Options, action Action) {
opts.ReuseInputBuffer = reuse
return
}
var prev []byte
events.Data = func(c Conn, in []byte) (out []byte, action Action) {
if prev == nil {
prev = in
} else {
reused := string(in) == string(prev)
if reused != reuse {
t.Fatalf("expected %v, got %v", reuse, reused)
}
action = Shutdown
}
return
}
events.Serving = func(_ Server) (action Action) {
go func() {
c, err := net.Dial("tcp", ":9991")
must(err)
defer c.Close()
c.Write([]byte("packet1"))
time.Sleep(time.Second / 5)
c.Write([]byte("packet2"))
}()
return
}
must(Serve(events, "tcp://:9991"))
}
}
func TestReuseport(t *testing.T) {
var events Events
events.Serving = func(s Server) (action Action) {
return Shutdown
}
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
var t = "1"
if i%2 == 0 {
t = "true"
}
go func(t string) {
defer wg.Done()
must(Serve(events, "tcp://:9991?reuseport="+t))
}(t)
}
wg.Wait()
}

523
vendor/github.com/tidwall/evio/evio_unix.go generated vendored Normal file
View File

@ -0,0 +1,523 @@
// Copyright 2018 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build darwin netbsd freebsd openbsd dragonfly linux
package evio
import (
"net"
"os"
"runtime"
"sync"
"sync/atomic"
"syscall"
"time"
reuseport "github.com/kavu/go_reuseport"
"github.com/tidwall/evio/internal"
)
type conn struct {
fd int // file descriptor
lnidx int // listener index in the server lns list
out []byte // write buffer
sa syscall.Sockaddr // remote socket address
reuse bool // should reuse input buffer
opened bool // connection opened event fired
action Action // next user action
ctx interface{} // user-defined context
addrIndex int // index of listening address
localAddr net.Addr // local addre
remoteAddr net.Addr // remote addr
loop *loop // connected loop
}
func (c *conn) Context() interface{} { return c.ctx }
func (c *conn) SetContext(ctx interface{}) { c.ctx = ctx }
func (c *conn) AddrIndex() int { return c.addrIndex }
func (c *conn) LocalAddr() net.Addr { return c.localAddr }
func (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }
func (c *conn) Wake() {
if c.loop != nil {
c.loop.poll.Trigger(c)
}
}
type server struct {
events Events // user events
loops []*loop // all the loops
lns []*listener // all the listeners
wg sync.WaitGroup // loop close waitgroup
cond *sync.Cond // shutdown signaler
balance LoadBalance // load balancing method
accepted uintptr // accept counter
tch chan time.Duration // ticker channel
//ticktm time.Time // next tick time
}
type loop struct {
idx int // loop index in the server loops list
poll *internal.Poll // epoll or kqueue
packet []byte // read packet buffer
fdconns map[int]*conn // loop connections fd -> conn
count int32 // connection count
}
// waitForShutdown waits for a signal to shutdown
func (s *server) waitForShutdown() {
s.cond.L.Lock()
s.cond.Wait()
s.cond.L.Unlock()
}
// signalShutdown signals a shutdown an begins server closing
func (s *server) signalShutdown() {
s.cond.L.Lock()
s.cond.Signal()
s.cond.L.Unlock()
}
func serve(events Events, listeners []*listener) error {
// figure out the correct number of loops/goroutines to use.
numLoops := events.NumLoops
if numLoops <= 0 {
if numLoops == 0 {
numLoops = 1
} else {
numLoops = runtime.NumCPU()
}
}
s := &server{}
s.events = events
s.lns = listeners
s.cond = sync.NewCond(&sync.Mutex{})
s.balance = events.LoadBalance
s.tch = make(chan time.Duration)
//println("-- server starting")
if s.events.Serving != nil {
var svr Server
svr.NumLoops = numLoops
svr.Addrs = make([]net.Addr, len(listeners))
for i, ln := range listeners {
svr.Addrs[i] = ln.lnaddr
}
action := s.events.Serving(svr)
switch action {
case None:
case Shutdown:
return nil
}
}
defer func() {
// wait on a signal for shutdown
s.waitForShutdown()
// notify all loops to close by closing all listeners
for _, l := range s.loops {
l.poll.Trigger(errClosing)
}
// wait on all loops to complete reading events
s.wg.Wait()
// close loops and all outstanding connections
for _, l := range s.loops {
for _, c := range l.fdconns {
loopCloseConn(s, l, c, nil)
}
l.poll.Close()
}
//println("-- server stopped")
}()
// create loops locally and bind the listeners.
for i := 0; i < numLoops; i++ {
l := &loop{
idx: i,
poll: internal.OpenPoll(),
packet: make([]byte, 0xFFFF),
fdconns: make(map[int]*conn),
}
for _, ln := range listeners {
l.poll.AddRead(ln.fd)
}
s.loops = append(s.loops, l)
}
// start loops in background
s.wg.Add(len(s.loops))
for _, l := range s.loops {
go loopRun(s, l)
}
return nil
}
func loopCloseConn(s *server, l *loop, c *conn, err error) error {
atomic.AddInt32(&l.count, -1)
delete(l.fdconns, c.fd)
syscall.Close(c.fd)
if s.events.Closed != nil {
switch s.events.Closed(c, err) {
case None:
case Shutdown:
return errClosing
}
}
return nil
}
func loopDetachConn(s *server, l *loop, c *conn, err error) error {
if s.events.Detached == nil {
return loopCloseConn(s, l, c, err)
}
l.poll.ModDetach(c.fd)
atomic.AddInt32(&l.count, -1)
delete(l.fdconns, c.fd)
if err := syscall.SetNonblock(c.fd, false); err != nil {
return err
}
switch s.events.Detached(c, &detachedConn{fd: c.fd}) {
case None:
case Shutdown:
return errClosing
}
return nil
}
func loopNote(s *server, l *loop, note interface{}) error {
var err error
switch v := note.(type) {
case time.Duration:
delay, action := s.events.Tick()
switch action {
case None:
case Shutdown:
err = errClosing
}
s.tch <- delay
case error: // shutdown
err = v
case *conn:
// Wake called for connection
if l.fdconns[v.fd] != v {
return nil // ignore stale wakes
}
return loopWake(s, l, v)
}
return err
}
func loopRun(s *server, l *loop) {
defer func() {
//fmt.Println("-- loop stopped --", l.idx)
s.signalShutdown()
s.wg.Done()
}()
if l.idx == 0 && s.events.Tick != nil {
go loopTicker(s, l)
}
//fmt.Println("-- loop started --", l.idx)
l.poll.Wait(func(fd int, note interface{}) error {
if fd == 0 {
return loopNote(s, l, note)
}
c := l.fdconns[fd]
switch {
case c == nil:
return loopAccept(s, l, fd)
case !c.opened:
return loopOpened(s, l, c)
case len(c.out) > 0:
return loopWrite(s, l, c)
case c.action != None:
return loopAction(s, l, c)
default:
return loopRead(s, l, c)
}
})
}
func loopTicker(s *server, l *loop) {
for {
if err := l.poll.Trigger(time.Duration(0)); err != nil {
break
}
time.Sleep(<-s.tch)
}
}
func loopAccept(s *server, l *loop, fd int) error {
for i, ln := range s.lns {
if ln.fd == fd {
if len(s.loops) > 1 {
switch s.balance {
case LeastConnections:
n := atomic.LoadInt32(&l.count)
for _, lp := range s.loops {
if lp.idx != l.idx {
if atomic.LoadInt32(&lp.count) < n {
return nil // do not accept
}
}
}
case RoundRobin:
idx := int(atomic.LoadUintptr(&s.accepted)) % len(s.loops)
if idx != l.idx {
return nil // do not accept
}
atomic.AddUintptr(&s.accepted, 1)
}
}
if ln.pconn != nil {
return loopUDPRead(s, l, i, fd)
}
nfd, sa, err := syscall.Accept(fd)
if err != nil {
if err == syscall.EAGAIN {
return nil
}
return err
}
if err := syscall.SetNonblock(nfd, true); err != nil {
return err
}
c := &conn{fd: nfd, sa: sa, lnidx: i, loop: l}
l.fdconns[c.fd] = c
l.poll.AddReadWrite(c.fd)
atomic.AddInt32(&l.count, 1)
break
}
}
return nil
}
func loopUDPRead(s *server, l *loop, lnidx, fd int) error {
n, sa, err := syscall.Recvfrom(fd, l.packet, 0)
if err != nil || n == 0 {
return nil
}
if s.events.Data != nil {
var sa6 syscall.SockaddrInet6
switch sa := sa.(type) {
case *syscall.SockaddrInet4:
sa6.ZoneId = 0
sa6.Port = sa.Port
for i := 0; i < 12; i++ {
sa6.Addr[i] = 0
}
sa6.Addr[12] = sa.Addr[0]
sa6.Addr[13] = sa.Addr[1]
sa6.Addr[14] = sa.Addr[2]
sa6.Addr[15] = sa.Addr[3]
case *syscall.SockaddrInet6:
sa6 = *sa
}
c := &conn{}
c.addrIndex = lnidx
c.localAddr = s.lns[lnidx].lnaddr
c.remoteAddr = internal.SockaddrToAddr(&sa6)
in := append([]byte{}, l.packet[:n]...)
out, action := s.events.Data(c, in)
if len(out) > 0 {
if s.events.PreWrite != nil {
s.events.PreWrite()
}
syscall.Sendto(fd, out, 0, sa)
}
switch action {
case Shutdown:
return errClosing
}
}
return nil
}
func loopOpened(s *server, l *loop, c *conn) error {
c.opened = true
c.addrIndex = c.lnidx
c.localAddr = s.lns[c.lnidx].lnaddr
c.remoteAddr = internal.SockaddrToAddr(c.sa)
if s.events.Opened != nil {
out, opts, action := s.events.Opened(c)
if len(out) > 0 {
c.out = append([]byte{}, out...)
}
c.action = action
c.reuse = opts.ReuseInputBuffer
if opts.TCPKeepAlive > 0 {
if _, ok := s.lns[c.lnidx].ln.(*net.TCPListener); ok {
internal.SetKeepAlive(c.fd, int(opts.TCPKeepAlive/time.Second))
}
}
}
if len(c.out) == 0 && c.action == None {
l.poll.ModRead(c.fd)
}
return nil
}
func loopWrite(s *server, l *loop, c *conn) error {
if s.events.PreWrite != nil {
s.events.PreWrite()
}
n, err := syscall.Write(c.fd, c.out)
if err != nil {
if err == syscall.EAGAIN {
return nil
}
return loopCloseConn(s, l, c, err)
}
if n == len(c.out) {
c.out = nil
} else {
c.out = c.out[n:]
}
if len(c.out) == 0 && c.action == None {
l.poll.ModRead(c.fd)
}
return nil
}
func loopAction(s *server, l *loop, c *conn) error {
switch c.action {
default:
c.action = None
case Close:
return loopCloseConn(s, l, c, nil)
case Shutdown:
return errClosing
case Detach:
return loopDetachConn(s, l, c, nil)
}
if len(c.out) == 0 && c.action == None {
l.poll.ModRead(c.fd)
}
return nil
}
func loopWake(s *server, l *loop, c *conn) error {
if s.events.Data == nil {
return nil
}
out, action := s.events.Data(c, nil)
c.action = action
if len(out) > 0 {
c.out = append([]byte{}, out...)
}
if len(c.out) != 0 || c.action != None {
l.poll.ModReadWrite(c.fd)
}
return nil
}
func loopRead(s *server, l *loop, c *conn) error {
var in []byte
n, err := syscall.Read(c.fd, l.packet)
if n == 0 || err != nil {
if err == syscall.EAGAIN {
return nil
}
return loopCloseConn(s, l, c, err)
}
in = l.packet[:n]
if !c.reuse {
in = append([]byte{}, in...)
}
if s.events.Data != nil {
out, action := s.events.Data(c, in)
c.action = action
if len(out) > 0 {
c.out = append([]byte{}, out...)
}
}
if len(c.out) != 0 || c.action != None {
l.poll.ModReadWrite(c.fd)
}
return nil
}
type detachedConn struct {
fd int
}
func (c *detachedConn) Close() error {
err := syscall.Close(c.fd)
if err != nil {
return err
}
c.fd = -1
return nil
}
func (c *detachedConn) Read(p []byte) (n int, err error) {
return syscall.Read(c.fd, p)
}
func (c *detachedConn) Write(p []byte) (n int, err error) {
n = len(p)
for len(p) > 0 {
nn, err := syscall.Write(c.fd, p)
if err != nil {
return n, err
}
p = p[nn:]
}
return n, nil
}
func (ln *listener) close() {
if ln.fd != 0 {
syscall.Close(ln.fd)
}
if ln.f != nil {
ln.f.Close()
}
if ln.ln != nil {
ln.ln.Close()
}
if ln.pconn != nil {
ln.pconn.Close()
}
if ln.network == "unix" {
os.RemoveAll(ln.addr)
}
}
// system takes the net listener and detaches it from it's parent
// event loop, grabs the file descriptor, and makes it non-blocking.
func (ln *listener) system() error {
var err error
switch netln := ln.ln.(type) {
case nil:
switch pconn := ln.pconn.(type) {
case *net.UDPConn:
ln.f, err = pconn.File()
}
case *net.TCPListener:
ln.f, err = netln.File()
case *net.UnixListener:
ln.f, err = netln.File()
}
if err != nil {
ln.close()
return err
}
ln.fd = int(ln.f.Fd())
return syscall.SetNonblock(ln.fd, true)
}
func reuseportListenPacket(proto, addr string) (l net.PacketConn, err error) {
return reuseport.ListenPacket(proto, addr)
}
func reuseportListen(proto, addr string) (l net.Listener, err error) {
return reuseport.Listen(proto, addr)
}

View File

@ -0,0 +1,59 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"strings"
"github.com/tidwall/evio"
)
func main() {
var port int
var loops int
var udp bool
var trace bool
var reuseport bool
var stdlib bool
flag.IntVar(&port, "port", 5000, "server port")
flag.BoolVar(&udp, "udp", false, "listen on udp")
flag.BoolVar(&reuseport, "reuseport", false, "reuseport (SO_REUSEPORT)")
flag.BoolVar(&trace, "trace", false, "print packets to console")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.BoolVar(&stdlib, "stdlib", false, "use stdlib")
flag.Parse()
var events evio.Events
events.NumLoops = loops
events.Serving = func(srv evio.Server) (action evio.Action) {
log.Printf("echo server started on port %d (loops: %d)", port, srv.NumLoops)
if reuseport {
log.Printf("reuseport")
}
if stdlib {
log.Printf("stdlib")
}
return
}
events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
if trace {
log.Printf("%s", strings.TrimSpace(string(in)))
}
out = in
return
}
scheme := "tcp"
if udp {
scheme = "udp"
}
if stdlib {
scheme += "-net"
}
log.Fatal(evio.Serve(events, fmt.Sprintf("%s://:%d?reuseport=%t", scheme, port, reuseport)))
}

View File

@ -0,0 +1,221 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"flag"
"fmt"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/tidwall/evio"
)
var res string
type request struct {
proto, method string
path, query string
head, body string
remoteAddr string
}
func main() {
var port int
var loops int
var aaaa bool
var noparse bool
var unixsocket string
var stdlib bool
flag.StringVar(&unixsocket, "unixsocket", "", "unix socket")
flag.IntVar(&port, "port", 8080, "server port")
flag.BoolVar(&aaaa, "aaaa", false, "aaaaa....")
flag.BoolVar(&noparse, "noparse", true, "do not parse requests")
flag.BoolVar(&stdlib, "stdlib", false, "use stdlib")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.Parse()
if os.Getenv("NOPARSE") == "1" {
noparse = true
}
if aaaa {
res = strings.Repeat("a", 1024)
} else {
res = "Hello World!\r\n"
}
var events evio.Events
events.NumLoops = loops
events.Serving = func(srv evio.Server) (action evio.Action) {
log.Printf("http server started on port %d (loops: %d)", port, srv.NumLoops)
if unixsocket != "" {
log.Printf("http server started at %s", unixsocket)
}
if stdlib {
log.Printf("stdlib")
}
return
}
events.Opened = func(c evio.Conn) (out []byte, opts evio.Options, action evio.Action) {
c.SetContext(&evio.InputStream{})
//log.Printf("opened: laddr: %v: raddr: %v", c.LocalAddr(), c.RemoteAddr())
return
}
events.Closed = func(c evio.Conn, err error) (action evio.Action) {
//log.Printf("closed: %s: %s", c.LocalAddr().String(), c.RemoteAddr().String())
return
}
events.Data = func(c evio.Conn, in []byte) (out []byte, action evio.Action) {
if in == nil {
return
}
is := c.Context().(*evio.InputStream)
data := is.Begin(in)
if noparse && bytes.Contains(data, []byte("\r\n\r\n")) {
// for testing minimal single packet request -> response.
out = appendresp(nil, "200 OK", "", res)
return
}
// process the pipeline
var req request
for {
leftover, err := parsereq(data, &req)
if err != nil {
// bad thing happened
out = appendresp(out, "500 Error", "", err.Error()+"\n")
action = evio.Close
break
} else if len(leftover) == len(data) {
// request not ready, yet
break
}
// handle the request
req.remoteAddr = c.RemoteAddr().String()
out = appendhandle(out, &req)
data = leftover
}
is.End(data)
return
}
var ssuf string
if stdlib {
ssuf = "-net"
}
// We at least want the single http address.
addrs := []string{fmt.Sprintf("tcp"+ssuf+"://:%d", port)}
if unixsocket != "" {
addrs = append(addrs, fmt.Sprintf("unix"+ssuf+"://%s", unixsocket))
}
// Start serving!
log.Fatal(evio.Serve(events, addrs...))
}
// appendhandle handles the incoming request and appends the response to
// the provided bytes, which is then returned to the caller.
func appendhandle(b []byte, req *request) []byte {
return appendresp(b, "200 OK", "", res)
}
// appendresp will append a valid http response to the provide bytes.
// The status param should be the code plus text such as "200 OK".
// The head parameter should be a series of lines ending with "\r\n" or empty.
func appendresp(b []byte, status, head, body string) []byte {
b = append(b, "HTTP/1.1"...)
b = append(b, ' ')
b = append(b, status...)
b = append(b, '\r', '\n')
b = append(b, "Server: evio\r\n"...)
b = append(b, "Date: "...)
b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT")
b = append(b, '\r', '\n')
if len(body) > 0 {
b = append(b, "Content-Length: "...)
b = strconv.AppendInt(b, int64(len(body)), 10)
b = append(b, '\r', '\n')
}
b = append(b, head...)
b = append(b, '\r', '\n')
if len(body) > 0 {
b = append(b, body...)
}
return b
}
// parsereq is a very simple http request parser. This operation
// waits for the entire payload to be buffered before returning a
// valid request.
func parsereq(data []byte, req *request) (leftover []byte, err error) {
sdata := string(data)
var i, s int
var top string
var clen int
var q = -1
// method, path, proto line
for ; i < len(sdata); i++ {
if sdata[i] == ' ' {
req.method = sdata[s:i]
for i, s = i+1, i+1; i < len(sdata); i++ {
if sdata[i] == '?' && q == -1 {
q = i - s
} else if sdata[i] == ' ' {
if q != -1 {
req.path = sdata[s:q]
req.query = req.path[q+1 : i]
} else {
req.path = sdata[s:i]
}
for i, s = i+1, i+1; i < len(sdata); i++ {
if sdata[i] == '\n' && sdata[i-1] == '\r' {
req.proto = sdata[s:i]
i, s = i+1, i+1
break
}
}
break
}
}
break
}
}
if req.proto == "" {
return data, fmt.Errorf("malformed request")
}
top = sdata[:s]
for ; i < len(sdata); i++ {
if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' {
line := sdata[s : i-1]
s = i + 1
if line == "" {
req.head = sdata[len(top)+2 : i+1]
i++
if clen > 0 {
if len(sdata[i:]) < clen {
break
}
req.body = sdata[i : i+clen]
i += clen
}
return data[i:], nil
}
if strings.HasPrefix(line, "Content-Length:") {
n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64)
if err == nil {
clen = int(n)
}
}
}
}
// not enough data
return data, nil
}

View File

@ -0,0 +1,181 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"strings"
"sync"
"github.com/tidwall/evio"
"github.com/tidwall/redcon"
)
type conn struct {
is evio.InputStream
addr string
}
func main() {
var port int
var unixsocket string
var stdlib bool
var loops int
var balance string
flag.IntVar(&port, "port", 6380, "server port")
flag.IntVar(&loops, "loops", 0, "num loops")
flag.StringVar(&unixsocket, "unixsocket", "socket", "unix socket")
flag.StringVar(&balance, "balance", "random", "random, round-robin, least-connections")
flag.BoolVar(&stdlib, "stdlib", false, "use stdlib")
flag.Parse()
var mu sync.RWMutex
var keys = make(map[string]string)
var events evio.Events
switch balance {
default:
log.Fatalf("invalid -balance flag: '%v'", balance)
case "random":
events.LoadBalance = evio.Random
case "round-robin":
events.LoadBalance = evio.RoundRobin
case "least-connections":
events.LoadBalance = evio.LeastConnections
}
events.NumLoops = loops
events.Serving = func(srv evio.Server) (action evio.Action) {
log.Printf("redis server started on port %d (loops: %d)", port, srv.NumLoops)
if unixsocket != "" {
log.Printf("redis server started at %s (loops: %d)", unixsocket, srv.NumLoops)
}
if stdlib {
log.Printf("stdlib")
}
return
}
events.Opened = func(ec evio.Conn) (out []byte, opts evio.Options, action evio.Action) {
//fmt.Printf("opened: %v\n", ec.RemoteAddr())
ec.SetContext(&conn{})
return
}
events.Closed = func(ec evio.Conn, err error) (action evio.Action) {
// fmt.Printf("closed: %v\n", ec.RemoteAddr())
return
}
events.Data = func(ec evio.Conn, in []byte) (out []byte, action evio.Action) {
if in == nil {
log.Printf("wake from %s\n", ec.RemoteAddr())
return nil, evio.Close
}
c := ec.Context().(*conn)
data := c.is.Begin(in)
var n int
var complete bool
var err error
var args [][]byte
for action == evio.None {
complete, args, _, data, err = redcon.ReadNextCommand(data, args[:0])
if err != nil {
action = evio.Close
out = redcon.AppendError(out, err.Error())
break
}
if !complete {
break
}
if len(args) > 0 {
n++
switch strings.ToUpper(string(args[0])) {
default:
out = redcon.AppendError(out, "ERR unknown command '"+string(args[0])+"'")
case "PING":
if len(args) > 2 {
out = redcon.AppendError(out, "ERR wrong number of arguments for '"+string(args[0])+"' command")
} else if len(args) == 2 {
out = redcon.AppendBulk(out, args[1])
} else {
out = redcon.AppendString(out, "PONG")
}
case "WAKE":
go ec.Wake()
out = redcon.AppendString(out, "OK")
case "ECHO":
if len(args) != 2 {
out = redcon.AppendError(out, "ERR wrong number of arguments for '"+string(args[0])+"' command")
} else {
out = redcon.AppendBulk(out, args[1])
}
case "SHUTDOWN":
out = redcon.AppendString(out, "OK")
action = evio.Shutdown
case "QUIT":
out = redcon.AppendString(out, "OK")
action = evio.Close
case "GET":
if len(args) != 2 {
out = redcon.AppendError(out, "ERR wrong number of arguments for '"+string(args[0])+"' command")
} else {
key := string(args[1])
mu.Lock()
val, ok := keys[key]
mu.Unlock()
if !ok {
out = redcon.AppendNull(out)
} else {
out = redcon.AppendBulkString(out, val)
}
}
case "SET":
if len(args) != 3 {
out = redcon.AppendError(out, "ERR wrong number of arguments for '"+string(args[0])+"' command")
} else {
key, val := string(args[1]), string(args[2])
mu.Lock()
keys[key] = val
mu.Unlock()
out = redcon.AppendString(out, "OK")
}
case "DEL":
if len(args) < 2 {
out = redcon.AppendError(out, "ERR wrong number of arguments for '"+string(args[0])+"' command")
} else {
var n int
mu.Lock()
for i := 1; i < len(args); i++ {
if _, ok := keys[string(args[i])]; ok {
n++
delete(keys, string(args[i]))
}
}
mu.Unlock()
out = redcon.AppendInt(out, int64(n))
}
case "FLUSHDB":
mu.Lock()
keys = make(map[string]string)
mu.Unlock()
out = redcon.AppendString(out, "OK")
}
}
}
c.is.End(data)
return
}
var ssuf string
if stdlib {
ssuf = "-net"
}
addrs := []string{fmt.Sprintf("tcp"+ssuf+"://:%d", port)}
if unixsocket != "" {
addrs = append(addrs, fmt.Sprintf("unix"+ssuf+"://%s", unixsocket))
}
err := evio.Serve(events, addrs...)
if err != nil {
log.Fatal(err)
}
}

125
vendor/github.com/tidwall/evio/internal/internal_bsd.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// +build darwin netbsd freebsd openbsd dragonfly
package internal
import (
"syscall"
)
// Poll ...
type Poll struct {
fd int
changes []syscall.Kevent_t
notes noteQueue
}
// OpenPoll ...
func OpenPoll() *Poll {
l := new(Poll)
p, err := syscall.Kqueue()
if err != nil {
panic(err)
}
l.fd = p
_, err = syscall.Kevent(l.fd, []syscall.Kevent_t{{
Ident: 0,
Filter: syscall.EVFILT_USER,
Flags: syscall.EV_ADD | syscall.EV_CLEAR,
}}, nil, nil)
if err != nil {
panic(err)
}
return l
}
// Close ...
func (p *Poll) Close() error {
return syscall.Close(p.fd)
}
// Trigger ...
func (p *Poll) Trigger(note interface{}) error {
p.notes.Add(note)
_, err := syscall.Kevent(p.fd, []syscall.Kevent_t{{
Ident: 0,
Filter: syscall.EVFILT_USER,
Fflags: syscall.NOTE_TRIGGER,
}}, nil, nil)
return err
}
// Wait ...
func (p *Poll) Wait(iter func(fd int, note interface{}) error) error {
events := make([]syscall.Kevent_t, 128)
for {
n, err := syscall.Kevent(p.fd, p.changes, events, nil)
if err != nil && err != syscall.EINTR {
return err
}
p.changes = p.changes[:0]
if err := p.notes.ForEach(func(note interface{}) error {
return iter(0, note)
}); err != nil {
return err
}
for i := 0; i < n; i++ {
if fd := int(events[i].Ident); fd != 0 {
if err := iter(fd, nil); err != nil {
return err
}
}
}
}
}
// AddRead ...
func (p *Poll) AddRead(fd int) {
p.changes = append(p.changes,
syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ,
},
)
}
// AddReadWrite ...
func (p *Poll) AddReadWrite(fd int) {
p.changes = append(p.changes,
syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ,
},
syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE,
},
)
}
// ModRead ...
func (p *Poll) ModRead(fd int) {
p.changes = append(p.changes, syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_WRITE,
})
}
// ModReadWrite ...
func (p *Poll) ModReadWrite(fd int) {
p.changes = append(p.changes, syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE,
})
}
// ModDetach ...
func (p *Poll) ModDetach(fd int) {
p.changes = append(p.changes,
syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_READ,
},
syscall.Kevent_t{
Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_WRITE,
},
)
}

View File

@ -0,0 +1,20 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package internal
import "syscall"
// SetKeepAlive sets the keepalive for the connection
func SetKeepAlive(fd, secs int) error {
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, 0x8, 1); err != nil {
return err
}
switch err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, 0x101, secs); err {
case nil, syscall.ENOPROTOOPT: // OS X 10.7 and earlier don't support this option
default:
return err
}
return syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs)
}

View File

@ -0,0 +1,129 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package internal
import (
"syscall"
)
// Poll ...
type Poll struct {
fd int // epoll fd
wfd int // wake fd
notes noteQueue
}
// OpenPoll ...
func OpenPoll() *Poll {
l := new(Poll)
p, err := syscall.EpollCreate1(0)
if err != nil {
panic(err)
}
l.fd = p
r0, _, e0 := syscall.Syscall(syscall.SYS_EVENTFD, 0, 0, 0)
if e0 != 0 {
syscall.Close(p)
panic(err)
}
l.wfd = int(r0)
l.AddRead(l.wfd)
return l
}
// Close ...
func (p *Poll) Close() error {
if err := syscall.Close(p.wfd); err != nil {
return err
}
return syscall.Close(p.fd)
}
// Trigger ...
func (p *Poll) Trigger(note interface{}) error {
p.notes.Add(note)
_, err := syscall.Write(p.wfd, []byte{0, 0, 0, 0, 0, 0, 0, 1})
return err
}
// Wait ...
func (p *Poll) Wait(iter func(fd int, note interface{}) error) error {
events := make([]syscall.EpollEvent, 64)
for {
n, err := syscall.EpollWait(p.fd, events, -1)
if err != nil && err != syscall.EINTR {
return err
}
if err := p.notes.ForEach(func(note interface{}) error {
return iter(0, note)
}); err != nil {
return err
}
for i := 0; i < n; i++ {
if fd := int(events[i].Fd); fd != p.wfd {
if err := iter(fd, nil); err != nil {
return err
}
} else {
}
}
}
}
// AddReadWrite ...
func (p *Poll) AddReadWrite(fd int) {
if err := syscall.EpollCtl(p.fd, syscall.EPOLL_CTL_ADD, fd,
&syscall.EpollEvent{Fd: int32(fd),
Events: syscall.EPOLLIN | syscall.EPOLLOUT,
},
); err != nil {
panic(err)
}
}
// AddRead ...
func (p *Poll) AddRead(fd int) {
if err := syscall.EpollCtl(p.fd, syscall.EPOLL_CTL_ADD, fd,
&syscall.EpollEvent{Fd: int32(fd),
Events: syscall.EPOLLIN,
},
); err != nil {
panic(err)
}
}
// ModRead ...
func (p *Poll) ModRead(fd int) {
if err := syscall.EpollCtl(p.fd, syscall.EPOLL_CTL_MOD, fd,
&syscall.EpollEvent{Fd: int32(fd),
Events: syscall.EPOLLIN,
},
); err != nil {
panic(err)
}
}
// ModReadWrite ...
func (p *Poll) ModReadWrite(fd int) {
if err := syscall.EpollCtl(p.fd, syscall.EPOLL_CTL_MOD, fd,
&syscall.EpollEvent{Fd: int32(fd),
Events: syscall.EPOLLIN | syscall.EPOLLOUT,
},
); err != nil {
panic(err)
}
}
// ModDetach ...
func (p *Poll) ModDetach(fd int) {
if err := syscall.EpollCtl(p.fd, syscall.EPOLL_CTL_DEL, fd,
&syscall.EpollEvent{Fd: int32(fd),
Events: syscall.EPOLLIN | syscall.EPOLLOUT,
},
); err != nil {
panic(err)
}
}

View File

@ -0,0 +1,11 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package internal
// SetKeepAlive sets the keepalive for the connection
func SetKeepAlive(fd, secs int) error {
// OpenBSD has no user-settable per-socket TCP keepalive options.
return nil
}

View File

@ -0,0 +1,19 @@
// Copyright 2017 Joshua J Baker. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
// +build netbsd freebsd dragonfly linux
package internal
import "syscall"
func SetKeepAlive(fd, secs int) error {
if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil {
return err
}
if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil {
return err
}
return syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs)
}

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