Replaced net package with evio
- Added threads startup flag - Replaced net package with evio - Refactored controller into server
This commit is contained in:
parent
745579b56b
commit
555e47036c
20
Gopkg.lock
generated
20
Gopkg.lock
generated
@ -133,6 +133,14 @@
|
||||
pruneopts = ""
|
||||
revision = "0b12d6b5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:be1d3b7623f11b628068933282015f3dbcd522fa8e6b16d2931edffd42ef2c0b"
|
||||
name = "github.com/kavu/go_reuseport"
|
||||
packages = ["."]
|
||||
pruneopts = ""
|
||||
revision = "ffa96de2479e10ecd06aca8069bf9c55a86701b5"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:75dddee0eb82002b5aff6937fdf6d544b85322d2414524a521768fe4b4e5ed3d"
|
||||
@ -225,6 +233,17 @@
|
||||
pruneopts = ""
|
||||
revision = "b67b1b8c1658cb01502801c14e33c61e6c4cbb95"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8aa59623aefb49c419e5b24179583e41df4b8c2f6a567f2cb8156a78a32e554a"
|
||||
name = "github.com/tidwall/evio"
|
||||
packages = [
|
||||
".",
|
||||
"internal",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "3a190d6d209c66b1fee96ee3db9e70c71e3635d5"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c5ac96e72d3ff6694602f3273dd71ef04a67c9591465aac92dc1aa8c821b8f91"
|
||||
@ -449,6 +468,7 @@
|
||||
"github.com/tidwall/boxtree/d2",
|
||||
"github.com/tidwall/btree",
|
||||
"github.com/tidwall/buntdb",
|
||||
"github.com/tidwall/evio",
|
||||
"github.com/tidwall/geojson",
|
||||
"github.com/tidwall/geojson/geometry",
|
||||
"github.com/tidwall/gjson",
|
||||
|
@ -23,7 +23,8 @@
|
||||
required = [
|
||||
"github.com/tidwall/lotsa",
|
||||
"github.com/mmcloughlin/geohash",
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/geojson",
|
||||
"github.com/tidwall/evio"
|
||||
]
|
||||
|
||||
[[constraint]]
|
||||
|
@ -16,8 +16,8 @@ import (
|
||||
"github.com/peterh/liner"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/cmd/tile38-cli/internal/client"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/client"
|
||||
)
|
||||
|
||||
func userHomeDir() string {
|
||||
|
@ -16,9 +16,9 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/controller"
|
||||
"github.com/tidwall/tile38/internal/hservice"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@ -79,6 +79,7 @@ Advanced Options:
|
||||
--queuefilename path : Event queue path (default:data/queue.db)
|
||||
--http-transport yes/no : HTTP transport (default: yes)
|
||||
--protected-mode yes/no : protected mode (default: yes)
|
||||
--threads num : number of network threads (default: num cores)
|
||||
|
||||
Developer Options:
|
||||
--dev : enable developer mode
|
||||
@ -146,10 +147,11 @@ Developer Options:
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "no":
|
||||
core.ProtectedMode = "no"
|
||||
continue
|
||||
case "yes":
|
||||
core.ProtectedMode = "yes"
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "protected-mode must be 'yes' or 'no'\n")
|
||||
os.Exit(1)
|
||||
@ -162,10 +164,11 @@ Developer Options:
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "no":
|
||||
core.AppendOnly = "no"
|
||||
continue
|
||||
case "yes":
|
||||
core.AppendOnly = "yes"
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "appendonly must be 'yes' or 'no'\n")
|
||||
os.Exit(1)
|
||||
@ -189,9 +192,23 @@ Developer Options:
|
||||
switch strings.ToLower(os.Args[i]) {
|
||||
case "1", "true", "yes":
|
||||
httpTransport = true
|
||||
continue
|
||||
case "0", "false", "no":
|
||||
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
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "http-transport must be 'yes' or 'no'\n")
|
||||
@ -294,7 +311,7 @@ Developer Options:
|
||||
if pidferr != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,6 @@ var AppendFileName string
|
||||
|
||||
// QueueFileName allows for custom queue.db file path
|
||||
var QueueFileName string
|
||||
|
||||
// NumThreads is the number of network threads to use.
|
||||
var NumThreads int
|
||||
|
@ -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
|
||||
}
|
||||
*/
|
@ -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
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -15,11 +15,8 @@ import (
|
||||
"github.com/tidwall/redcon"
|
||||
"github.com/tidwall/resp"
|
||||
"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 {
|
||||
err error
|
||||
}
|
||||
@ -30,8 +27,8 @@ func (err errAOFHook) Error() string {
|
||||
|
||||
var errInvalidAOF = errors.New("invalid aof file")
|
||||
|
||||
func (c *Controller) loadAOF() error {
|
||||
fi, err := c.aof.Stat()
|
||||
func (server *Server) loadAOF() error {
|
||||
fi, err := server.aof.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -56,9 +53,9 @@ func (c *Controller) loadAOF() error {
|
||||
var buf []byte
|
||||
var args [][]byte
|
||||
var packet [0xFFFF]byte
|
||||
var msg server.Message
|
||||
var msg Message
|
||||
for {
|
||||
n, err := c.aof.Read(packet[:])
|
||||
n, err := server.aof.Read(packet[:])
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if len(buf) > 0 {
|
||||
@ -68,7 +65,7 @@ func (c *Controller) loadAOF() error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
c.aofsz += n
|
||||
server.aofsz += n
|
||||
data := packet[:n]
|
||||
if len(buf) > 0 {
|
||||
data = append(buf, data...)
|
||||
@ -83,12 +80,11 @@ func (c *Controller) loadAOF() error {
|
||||
break
|
||||
}
|
||||
if len(args) > 0 {
|
||||
msg.Values = msg.Values[:0]
|
||||
msg.Args = msg.Args[:0]
|
||||
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 := c.command(&msg, nil, nil); err != nil {
|
||||
if _, _, err := server.command(&msg, nil); err != nil {
|
||||
if commandErrIsFatal(err) {
|
||||
return err
|
||||
}
|
||||
@ -104,22 +100,22 @@ func (c *Controller) loadAOF() error {
|
||||
}
|
||||
}
|
||||
|
||||
func qlower(s []byte) string {
|
||||
if len(s) == 3 {
|
||||
if s[0] == 'S' && s[1] == 'E' && s[2] == 'T' {
|
||||
return "set"
|
||||
}
|
||||
if s[0] == 'D' && s[1] == 'E' && s[2] == 'L' {
|
||||
return "del"
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= 'A' || s[i] <= 'Z' {
|
||||
return strings.ToLower(string(s))
|
||||
}
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
// func qlower(s []byte) string {
|
||||
// if len(s) == 3 {
|
||||
// if s[0] == 'S' && s[1] == 'E' && s[2] == 'T' {
|
||||
// return "set"
|
||||
// }
|
||||
// if s[0] == 'D' && s[1] == 'E' && s[2] == 'L' {
|
||||
// return "del"
|
||||
// }
|
||||
// }
|
||||
// for i := 0; i < len(s); i++ {
|
||||
// if s[i] >= 'A' || s[i] <= 'Z' {
|
||||
// return strings.ToLower(string(s))
|
||||
// }
|
||||
// }
|
||||
// return string(s)
|
||||
// }
|
||||
|
||||
func commandErrIsFatal(err error) bool {
|
||||
// FSET (and other writable commands) may return errors that we need
|
||||
@ -132,83 +128,91 @@ func commandErrIsFatal(err error) bool {
|
||||
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 {
|
||||
// just ignore writes if the command did not update
|
||||
return nil
|
||||
}
|
||||
if c.shrinking {
|
||||
var values []string
|
||||
for _, value := range value.Array() {
|
||||
values = append(values, value.String())
|
||||
}
|
||||
c.shrinklog = append(c.shrinklog, values)
|
||||
|
||||
if server.shrinking {
|
||||
nargs := make([]string, len(args))
|
||||
copy(nargs, args)
|
||||
server.shrinklog = append(server.shrinklog, nargs)
|
||||
}
|
||||
data, err := value.MarshalRESP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.aof != nil {
|
||||
n, err := c.aof.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
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)
|
||||
}
|
||||
c.aofsz += n
|
||||
server.aofsz += len(server.aofbuf) - n
|
||||
}
|
||||
|
||||
// notify aof live connections that we have new data
|
||||
c.fcond.L.Lock()
|
||||
c.fcond.Broadcast()
|
||||
c.fcond.L.Unlock()
|
||||
server.fcond.L.Lock()
|
||||
server.fcond.Broadcast()
|
||||
server.fcond.L.Unlock()
|
||||
|
||||
// process geofences
|
||||
if d != nil {
|
||||
// webhook geofences
|
||||
if c.config.followHost() == "" {
|
||||
if server.config.followHost() == "" {
|
||||
// for leader only
|
||||
if d.parent {
|
||||
// queue children
|
||||
for _, d := range d.children {
|
||||
if err := c.queueHooks(d); err != nil {
|
||||
if err := server.queueHooks(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// queue parent
|
||||
if err := c.queueHooks(d); err != nil {
|
||||
if err := server.queueHooks(d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// live geofences
|
||||
c.lcond.L.Lock()
|
||||
server.lcond.L.Lock()
|
||||
if d.parent {
|
||||
// queue children
|
||||
for _, d := range d.children {
|
||||
c.lstack = append(c.lstack, d)
|
||||
server.lstack = append(server.lstack, d)
|
||||
}
|
||||
} else {
|
||||
// queue parent
|
||||
c.lstack = append(c.lstack, d)
|
||||
server.lstack = append(server.lstack, d)
|
||||
}
|
||||
c.lcond.Broadcast()
|
||||
c.lcond.L.Unlock()
|
||||
server.lcond.Broadcast()
|
||||
server.lcond.L.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) queueHooks(d *commandDetailsT) error {
|
||||
func (server *Server) queueHooks(d *commandDetailsT) error {
|
||||
// big list of all of the messages
|
||||
var hmsgs []string
|
||||
var hooks []*Hook
|
||||
// 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 {
|
||||
// match the fence
|
||||
msgs := FenceMatch(hook.Name, hook.ScanWriter, hook.Fence, hook.Metas, d)
|
||||
if len(msgs) > 0 {
|
||||
if hook.channel {
|
||||
c.Publish(hook.Name, msgs...)
|
||||
server.Publish(hook.Name, msgs...)
|
||||
} else {
|
||||
// append each msg to the big list
|
||||
hmsgs = append(hmsgs, msgs...)
|
||||
@ -222,17 +226,17 @@ func (c *Controller) queueHooks(d *commandDetailsT) error {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
c.qidx++ // increment the log id
|
||||
key := hookLogPrefix + uint64ToString(c.qidx)
|
||||
server.qidx++ // increment the log id
|
||||
key := hookLogPrefix + uint64ToString(server.qidx)
|
||||
_, _, err := tx.Set(key, string(msg), hookLogSetDefaults)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -269,86 +273,86 @@ func (s liveAOFSwitches) Error() string {
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
var spos, ssize string
|
||||
|
||||
if vs, spos, ok = tokenval(vs); !ok || spos == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, ssize, ok = tokenval(vs); !ok || ssize == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
pos, err := strconv.ParseInt(spos, 10, 64)
|
||||
if err != nil || pos < 0 {
|
||||
return server.NOMessage, errInvalidArgument(spos)
|
||||
return NOMessage, errInvalidArgument(spos)
|
||||
}
|
||||
size, err := strconv.ParseInt(ssize, 10, 64)
|
||||
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 {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(
|
||||
fmt.Sprintf(`{"ok":true,"md5":"%s","elapsed":"%s"}`, sum, time.Now().Sub(start)))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.SimpleStringValue(sum)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Controller) cmdAOF(msg *server.Message) (res resp.Value, err error) {
|
||||
if c.aof == nil {
|
||||
return server.NOMessage, errors.New("aof disabled")
|
||||
func (server *Server) cmdAOF(msg *Message) (res resp.Value, err error) {
|
||||
if server.aof == nil {
|
||||
return NOMessage, errors.New("aof disabled")
|
||||
}
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var spos string
|
||||
if vs, spos, ok = tokenval(vs); !ok || spos == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
pos, err := strconv.ParseInt(spos, 10, 64)
|
||||
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 {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
defer f.Close()
|
||||
n, err := f.Seek(0, 2)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
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
|
||||
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 {
|
||||
c.mu.Lock()
|
||||
c.aofconnM[conn] = true
|
||||
c.mu.Unlock()
|
||||
func (server *Server) liveAOF(pos int64, conn net.Conn, rd *PipelineReader, msg *Message) error {
|
||||
server.mu.Lock()
|
||||
server.aofconnM[conn] = true
|
||||
server.mu.Unlock()
|
||||
defer func() {
|
||||
c.mu.Lock()
|
||||
delete(c.aofconnM, conn)
|
||||
c.mu.Unlock()
|
||||
server.mu.Lock()
|
||||
delete(server.aofconnM, conn)
|
||||
server.mu.Unlock()
|
||||
conn.Close()
|
||||
}()
|
||||
|
||||
@ -356,9 +360,9 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
|
||||
return err
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
f, err := os.Open(c.aof.Name())
|
||||
c.mu.RUnlock()
|
||||
server.mu.RLock()
|
||||
f, err := os.Open(server.aof.Name())
|
||||
server.mu.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -384,7 +388,7 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
|
||||
return
|
||||
}
|
||||
for _, v := range vs {
|
||||
switch v.Command {
|
||||
switch v.Command() {
|
||||
default:
|
||||
log.Error("received a live command that was not QUIT")
|
||||
return
|
||||
@ -420,9 +424,9 @@ func (c *Controller) liveAOF(pos int64, conn net.Conn, rd *server.PipelineReader
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.fcond.L.Lock()
|
||||
c.fcond.Wait()
|
||||
c.fcond.L.Unlock()
|
||||
server.fcond.L.Lock()
|
||||
server.fcond.Wait()
|
||||
server.fcond.L.Unlock()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -83,7 +83,7 @@ func NewLegacyAOFReader(r io.Reader) *LegacyAOFReader {
|
||||
return rd
|
||||
}
|
||||
|
||||
func (c *Controller) migrateAOF() error {
|
||||
func (c *Server) migrateAOF() error {
|
||||
_, err := os.Stat(path.Join(c.dir, "appendonly.aof"))
|
||||
if err == nil {
|
||||
return nil
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"math"
|
||||
@ -8,9 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/collection"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
)
|
||||
|
||||
@ -18,25 +18,25 @@ const maxkeys = 8
|
||||
const maxids = 32
|
||||
const maxchunk = 4 * 1024 * 1024
|
||||
|
||||
func (c *Controller) aofshrink() {
|
||||
if c.aof == nil {
|
||||
func (server *Server) aofshrink() {
|
||||
if server.aof == nil {
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
c.mu.Lock()
|
||||
if c.shrinking {
|
||||
c.mu.Unlock()
|
||||
server.mu.Lock()
|
||||
if server.shrinking {
|
||||
server.mu.Unlock()
|
||||
return
|
||||
}
|
||||
c.shrinking = true
|
||||
c.shrinklog = nil
|
||||
c.mu.Unlock()
|
||||
server.shrinking = true
|
||||
server.shrinklog = nil
|
||||
server.mu.Unlock()
|
||||
|
||||
defer func() {
|
||||
c.mu.Lock()
|
||||
c.shrinking = false
|
||||
c.shrinklog = nil
|
||||
c.mu.Unlock()
|
||||
server.mu.Lock()
|
||||
server.shrinking = false
|
||||
server.shrinklog = nil
|
||||
server.mu.Unlock()
|
||||
log.Infof("aof shrink ended %v", time.Now().Sub(start))
|
||||
return
|
||||
}()
|
||||
@ -60,9 +60,9 @@ func (c *Controller) aofshrink() {
|
||||
}
|
||||
keysdone = true
|
||||
func() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.scanGreaterOrEqual(nextkey, func(key string, col *collection.Collection) bool {
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
server.scanGreaterOrEqual(nextkey, func(key string, col *collection.Collection) bool {
|
||||
if len(keys) == maxkeys {
|
||||
keysdone = false
|
||||
nextkey = key
|
||||
@ -86,16 +86,16 @@ func (c *Controller) aofshrink() {
|
||||
// load more objects
|
||||
func() {
|
||||
idsdone = true
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
col := c.getCol(keys[0])
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
col := server.getCol(keys[0])
|
||||
if col == nil {
|
||||
return
|
||||
}
|
||||
var fnames = col.FieldArr() // reload an array of field names to match each object
|
||||
var exm = c.expires[keys[0]] // the expiration map
|
||||
var now = time.Now() // used for expiration
|
||||
var count = 0 // the object count
|
||||
var fnames = col.FieldArr() // reload an array of field names to match each object
|
||||
var exm = server.expires[keys[0]] // the expiration map
|
||||
var now = time.Now() // used for expiration
|
||||
var count = 0 // the object count
|
||||
col.ScanGreaterOrEqual(nextid, false,
|
||||
func(id string, obj geojson.Object, fields []float64) bool {
|
||||
if count == maxids {
|
||||
@ -167,9 +167,9 @@ func (c *Controller) aofshrink() {
|
||||
// first load the names of the hooks
|
||||
var hnames []string
|
||||
func() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for name := range c.hooks {
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
for name := range server.hooks {
|
||||
hnames = append(hnames, name)
|
||||
}
|
||||
}()
|
||||
@ -177,9 +177,9 @@ func (c *Controller) aofshrink() {
|
||||
sort.Strings(hnames)
|
||||
for _, name := range hnames {
|
||||
func() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
hook := c.hooks[name]
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
hook := server.hooks[name]
|
||||
if hook == nil {
|
||||
return
|
||||
}
|
||||
@ -203,8 +203,8 @@ func (c *Controller) aofshrink() {
|
||||
values = append(values, "ex",
|
||||
strconv.FormatFloat(ex, 'f', 1, 64))
|
||||
}
|
||||
for _, value := range hook.Message.Values {
|
||||
values = append(values, value.String())
|
||||
for _, value := range hook.Message.Args {
|
||||
values = append(values, value)
|
||||
}
|
||||
// append the values to the aof buffer
|
||||
aofbuf = append(aofbuf, '*')
|
||||
@ -232,10 +232,14 @@ func (c *Controller) aofshrink() {
|
||||
// finally grab any new data that may have been written since
|
||||
// the aofshrink has started and swap out the files.
|
||||
return func() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
|
||||
// flush the aof buffer
|
||||
server.flushAOF()
|
||||
|
||||
aofbuf = aofbuf[:0]
|
||||
for _, values := range c.shrinklog {
|
||||
for _, values := range server.shrinklog {
|
||||
// append the values to the aof buffer
|
||||
aofbuf = append(aofbuf, '*')
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
log.Fatalf("shrink openfile fatal operation: %v", err)
|
||||
}
|
||||
var n int64
|
||||
n, err = c.aof.Seek(0, 2)
|
||||
n, err = server.aof.Seek(0, 2)
|
||||
if err != nil {
|
||||
log.Fatalf("shrink seek end fatal operation: %v", err)
|
||||
}
|
||||
c.aofsz = int(n)
|
||||
server.aofsz = int(n)
|
||||
|
||||
os.Remove(core.AppendFileName + "-bak") // ignore error
|
||||
|
||||
// kill all followers connections
|
||||
for conn := range c.aofconnM {
|
||||
for conn := range server.aofconnM {
|
||||
conn.Close()
|
||||
}
|
||||
return nil
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"sync"
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import "testing"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -74,7 +74,7 @@ func connAOFMD5(conn *Conn, pos, size int64) (sum string, err error) {
|
||||
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)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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 {
|
||||
log.Debug("follow:", addr, ":check some")
|
||||
}
|
236
internal/server/client.go
Normal file
236
internal/server/client.go
Normal 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
|
||||
}
|
||||
*/
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
var name string
|
||||
|
||||
if vs, name, ok = tokenval(vs); !ok {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
m := c.config.getProperties(name)
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
res = resp.StringValue(`{"ok":true,"properties":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
vals := respValuesSimpleMap(m)
|
||||
res = resp.ArrayValue(vals)
|
||||
}
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
var name string
|
||||
|
||||
if vs, name, ok = tokenval(vs); !ok {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
var value string
|
||||
if vs, value, ok = tokenval(vs); !ok {
|
||||
if strings.ToLower(name) != RequirePass {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
c.config.write(true)
|
||||
return server.OKMessage(msg, start), nil
|
||||
return OKMessage(msg, start), nil
|
||||
}
|
||||
|
||||
func (config *Config) followHost() string {
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/tidwall/tile38/internal/collection"
|
||||
"github.com/tidwall/tile38/internal/ds"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
type fvt struct {
|
||||
@ -49,30 +48,30 @@ func orderFields(fmap map[string]int, fields []float64) []fvt {
|
||||
sort.Sort(byField(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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var key string
|
||||
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col == nil {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.NullValue(), nil
|
||||
}
|
||||
return server.NOMessage, errKeyNotFound
|
||||
return NOMessage, errKeyNotFound
|
||||
}
|
||||
|
||||
vals := make([]resp.Value, 0, 2)
|
||||
var buf bytes.Buffer
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`{"ok":true`)
|
||||
}
|
||||
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},
|
||||
Max: geometry.Point{X: maxX, Y: maxY},
|
||||
})
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`,"bounds":`)
|
||||
buf.WriteString(string(bbox.AppendJSON(nil)))
|
||||
} else {
|
||||
@ -97,55 +96,55 @@ func (c *Controller) cmdBounds(msg *server.Message) (resp.Value, error) {
|
||||
}))
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var key string
|
||||
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 msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.SimpleStringValue("none"), nil
|
||||
}
|
||||
return server.NOMessage, errKeyNotFound
|
||||
return NOMessage, errKeyNotFound
|
||||
}
|
||||
|
||||
typ := "hash"
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
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 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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var key, id, typ, sprecision string
|
||||
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, id, ok = tokenval(vs); !ok || id == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
withfields := false
|
||||
@ -154,25 +153,25 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
vs = vs[1:]
|
||||
}
|
||||
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col == nil {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.NullValue(), nil
|
||||
}
|
||||
return server.NOMessage, errKeyNotFound
|
||||
return NOMessage, errKeyNotFound
|
||||
}
|
||||
o, fields, ok := col.Get(id)
|
||||
ok = ok && !c.hasExpired(key, id)
|
||||
ok = ok && !server.hasExpired(key, id)
|
||||
if !ok {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.NullValue(), nil
|
||||
}
|
||||
return server.NOMessage, errIDNotFound
|
||||
return NOMessage, errIDNotFound
|
||||
}
|
||||
|
||||
vals := make([]resp.Value, 0, 2)
|
||||
var buf bytes.Buffer
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`{"ok":true`)
|
||||
}
|
||||
vs, typ, ok = tokenval(vs)
|
||||
@ -182,16 +181,16 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
switch typ {
|
||||
default:
|
||||
return server.NOMessage, errInvalidArgument(typ)
|
||||
return NOMessage, errInvalidArgument(typ)
|
||||
case "object":
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`,"object":`)
|
||||
buf.WriteString(string(o.AppendJSON(nil)))
|
||||
} else {
|
||||
vals = append(vals, resp.StringValue(o.String()))
|
||||
}
|
||||
case "point":
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`,"point":`)
|
||||
buf.Write(appendJSONSimplePoint(nil, o))
|
||||
} else {
|
||||
@ -215,24 +214,24 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
case "hash":
|
||||
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":`)
|
||||
}
|
||||
precision, err := strconv.ParseInt(sprecision, 10, 64)
|
||||
if err != nil || precision < 1 || precision > 64 {
|
||||
return server.NOMessage, errInvalidArgument(sprecision)
|
||||
return NOMessage, errInvalidArgument(sprecision)
|
||||
}
|
||||
center := o.Center()
|
||||
p := geohash.EncodeWithPrecision(center.Y, center.X, uint(precision))
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`"` + p + `"`)
|
||||
} else {
|
||||
vals = append(vals, resp.StringValue(p))
|
||||
}
|
||||
case "bounds":
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`,"bounds":`)
|
||||
buf.Write(appendJSONSimpleBounds(nil, o))
|
||||
} else {
|
||||
@ -251,17 +250,17 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if withfields {
|
||||
fvs := orderFields(col.FieldMap(), fields)
|
||||
if len(fvs) > 0 {
|
||||
fvals := make([]resp.Value, 0, len(fvs)*2)
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`,"fields":{`)
|
||||
}
|
||||
for i, fv := range fvs {
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
if i > 0 {
|
||||
buf.WriteString(`,`)
|
||||
}
|
||||
@ -271,7 +270,7 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
i++
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`}`)
|
||||
} else {
|
||||
vals = append(vals, resp.ArrayValue(fvals))
|
||||
@ -279,10 +278,10 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
var oval resp.Value
|
||||
if withfields {
|
||||
oval = resp.ArrayValue(vals)
|
||||
@ -291,12 +290,12 @@ func (c *Controller) cmdGet(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
@ -311,24 +310,24 @@ func (c *Controller) cmdDel(msg *server.Message) (res resp.Value, d commandDetai
|
||||
return
|
||||
}
|
||||
found := false
|
||||
col := c.getCol(d.key)
|
||||
col := server.getCol(d.key)
|
||||
if col != nil {
|
||||
d.obj, d.fields, ok = col.Delete(d.id)
|
||||
if ok {
|
||||
if col.Count() == 0 {
|
||||
c.deleteCol(d.key)
|
||||
server.deleteCol(d.key)
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
c.clearIDExpires(d.key, d.id)
|
||||
server.clearIDExpires(d.key, d.id)
|
||||
d.command = "del"
|
||||
d.updated = found
|
||||
d.timestamp = time.Now()
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if d.updated {
|
||||
res = resp.IntegerValue(1)
|
||||
} else {
|
||||
@ -338,9 +337,9 @@ func (c *Controller) cmdDel(msg *server.Message) (res resp.Value, d commandDetai
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
@ -369,7 +368,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
|
||||
}
|
||||
|
||||
var expired int
|
||||
col := c.getCol(d.key)
|
||||
col := server.getCol(d.key)
|
||||
if col != nil {
|
||||
g := glob.Parse(d.pattern, false)
|
||||
if g.Limits[0] == "" && g.Limits[1] == "" {
|
||||
@ -386,7 +385,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
|
||||
} else {
|
||||
d.children[i] = dc
|
||||
}
|
||||
c.clearIDExpires(d.key, dc.id)
|
||||
server.clearIDExpires(d.key, dc.id)
|
||||
}
|
||||
if atLeastOneNotDeleted {
|
||||
var nchildren []*commandDetailsT
|
||||
@ -398,7 +397,7 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
|
||||
d.children = nchildren
|
||||
}
|
||||
if col.Count() == 0 {
|
||||
c.deleteCol(d.key)
|
||||
server.deleteCol(d.key)
|
||||
}
|
||||
}
|
||||
d.command = "pdel"
|
||||
@ -406,9 +405,9 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
|
||||
d.timestamp = now
|
||||
d.parent = true
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
total := len(d.children) - expired
|
||||
if total < 0 {
|
||||
total = 0
|
||||
@ -418,9 +417,9 @@ func (c *Controller) cmdPdel(msg *server.Message) (res resp.Value, d commandDeta
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
if vs, d.key, ok = tokenval(vs); !ok || d.key == "" {
|
||||
err = errInvalidNumberOfArguments
|
||||
@ -430,9 +429,9 @@ func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDeta
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
col := c.getCol(d.key)
|
||||
col := server.getCol(d.key)
|
||||
if col != nil {
|
||||
c.deleteCol(d.key)
|
||||
server.deleteCol(d.key)
|
||||
d.updated = true
|
||||
} else {
|
||||
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.timestamp = time.Now()
|
||||
c.clearKeyExpires(d.key)
|
||||
server.clearKeyExpires(d.key)
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if d.updated {
|
||||
res = resp.IntegerValue(1)
|
||||
} else {
|
||||
@ -454,36 +453,36 @@ func (c *Controller) cmdDrop(msg *server.Message) (res resp.Value, d commandDeta
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
if len(vs) != 0 {
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
c.cols = ds.BTree{}
|
||||
c.exlistmu.Lock()
|
||||
c.exlist = nil
|
||||
c.exlistmu.Unlock()
|
||||
c.expires = make(map[string]map[string]time.Time)
|
||||
c.hooks = make(map[string]*Hook)
|
||||
c.hookcols = make(map[string]map[string]*Hook)
|
||||
server.cols = ds.BTree{}
|
||||
server.exlistmu.Lock()
|
||||
server.exlist = nil
|
||||
server.exlistmu.Unlock()
|
||||
server.expires = make(map[string]map[string]time.Time)
|
||||
server.hooks = make(map[string]*Hook)
|
||||
server.hookcols = make(map[string]map[string]*Hook)
|
||||
d.command = "flushdb"
|
||||
d.updated = true
|
||||
d.timestamp = time.Now()
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.SimpleStringValue("OK")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) parseSetArgs(vs []resp.Value) (
|
||||
func (server *Server) parseSetArgs(vs []string) (
|
||||
d commandDetailsT, fields []string, values []float64,
|
||||
xx, nx bool,
|
||||
expires *float64, etype []byte, evs []resp.Value, err error,
|
||||
expires *float64, etype []byte, evs []string, err error,
|
||||
) {
|
||||
var ok bool
|
||||
var typ []byte
|
||||
@ -496,7 +495,7 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
|
||||
return
|
||||
}
|
||||
var arg []byte
|
||||
var nvs []resp.Value
|
||||
var nvs []string
|
||||
for {
|
||||
if nvs, arg, ok = tokenvalbytes(vs); !ok || len(arg) == 0 {
|
||||
err = errInvalidNumberOfArguments
|
||||
@ -689,7 +688,7 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
d.obj, err = geojson.Parse(object, &c.geomParseOpts)
|
||||
d.obj, err = geojson.Parse(object, &server.geomParseOpts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -700,29 +699,29 @@ func (c *Controller) parseSetArgs(vs []resp.Value) (
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) cmdSet(msg *server.Message) (res resp.Value, d commandDetailsT, err error) {
|
||||
if c.config.maxMemory() > 0 && c.outOfMemory.on() {
|
||||
func (server *Server) cmdSet(msg *Message) (res resp.Value, d commandDetailsT, err error) {
|
||||
if server.config.maxMemory() > 0 && server.outOfMemory.on() {
|
||||
err = errOOM
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var fmap map[string]int
|
||||
var fields []string
|
||||
var values []float64
|
||||
var xx, nx bool
|
||||
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 {
|
||||
return
|
||||
}
|
||||
col := c.getCol(d.key)
|
||||
col := server.getCol(d.key)
|
||||
if col == nil {
|
||||
if xx {
|
||||
goto notok
|
||||
}
|
||||
col = collection.New()
|
||||
c.setCol(d.key, col)
|
||||
server.setCol(d.key, col)
|
||||
}
|
||||
if xx || nx {
|
||||
_, _, ok := col.Get(d.id)
|
||||
@ -730,12 +729,12 @@ func (c *Controller) cmdSet(msg *server.Message) (res resp.Value, d commandDetai
|
||||
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.command = "set"
|
||||
d.updated = true // perhaps we should do a diff on the previous object?
|
||||
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.
|
||||
fmap = col.FieldMap()
|
||||
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 {
|
||||
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 {
|
||||
default:
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.SimpleStringValue("OK")
|
||||
}
|
||||
return
|
||||
notok:
|
||||
switch msg.OutputType {
|
||||
default:
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
if nx {
|
||||
err = errIDAlreadyExists
|
||||
} else {
|
||||
err = errIDNotFound
|
||||
}
|
||||
return
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.NullValue()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) parseFSetArgs(vs []resp.Value) (
|
||||
func (server *Server) parseFSetArgs(vs []string) (
|
||||
d commandDetailsT, fields []string, values []float64, xx bool, err error,
|
||||
) {
|
||||
var ok bool
|
||||
@ -813,20 +812,20 @@ func (c *Controller) parseFSetArgs(vs []resp.Value) (
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) cmdFset(msg *server.Message) (res resp.Value, d commandDetailsT, err error) {
|
||||
if c.config.maxMemory() > 0 && c.outOfMemory.on() {
|
||||
func (server *Server) cmdFset(msg *Message) (res resp.Value, d commandDetailsT, err error) {
|
||||
if server.config.maxMemory() > 0 && server.outOfMemory.on() {
|
||||
err = errOOM
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var fields []string
|
||||
var values []float64
|
||||
var xx bool
|
||||
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 {
|
||||
err = errKeyNotFound
|
||||
return
|
||||
@ -849,17 +848,17 @@ func (c *Controller) cmdFset(msg *server.Message) (res resp.Value, d commandDeta
|
||||
}
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.IntegerValue(updateCount)
|
||||
}
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var key, id, svalue string
|
||||
var ok bool
|
||||
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
|
||||
}
|
||||
ok = false
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col != nil {
|
||||
_, _, ok = col.Get(id)
|
||||
ok = ok && !c.hasExpired(key, id)
|
||||
ok = ok && !server.hasExpired(key, id)
|
||||
}
|
||||
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
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
if ok {
|
||||
res = resp.StringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
} else {
|
||||
return resp.SimpleStringValue(""), d, errIDNotFound
|
||||
}
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if ok {
|
||||
res = resp.IntegerValue(1)
|
||||
} else {
|
||||
@ -911,9 +910,9 @@ func (c *Controller) cmdExpire(msg *server.Message) (res resp.Value, d commandDe
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var key, id string
|
||||
var ok bool
|
||||
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
|
||||
ok = false
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col != nil {
|
||||
_, _, ok = col.Get(id)
|
||||
ok = ok && !c.hasExpired(key, id)
|
||||
ok = ok && !server.hasExpired(key, id)
|
||||
if ok {
|
||||
cleared = c.clearIDExpires(key, id)
|
||||
cleared = server.clearIDExpires(key, id)
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.IntegerValue(0), d, nil
|
||||
}
|
||||
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.timestamp = time.Now()
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.SimpleStringValue(`{"ok":true,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if cleared {
|
||||
res = resp.IntegerValue(1)
|
||||
} else {
|
||||
@ -960,9 +959,9 @@ func (c *Controller) cmdPersist(msg *server.Message) (res resp.Value, d commandD
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var key, id string
|
||||
var ok bool
|
||||
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
|
||||
ok = false
|
||||
var ok2 bool
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col != nil {
|
||||
_, _, ok = col.Get(id)
|
||||
ok = ok && !c.hasExpired(key, id)
|
||||
ok = ok && !server.hasExpired(key, id)
|
||||
if ok {
|
||||
var at time.Time
|
||||
at, ok2 = c.getExpires(key, id)
|
||||
at, ok2 = server.getExpires(key, id)
|
||||
if ok2 {
|
||||
if time.Now().After(at) {
|
||||
ok2 = false
|
||||
@ -1000,7 +999,7 @@ func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) {
|
||||
}
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
if ok {
|
||||
var ttl string
|
||||
if ok2 {
|
||||
@ -1013,7 +1012,7 @@ func (c *Controller) cmdTTL(msg *server.Message) (res resp.Value, err error) {
|
||||
} else {
|
||||
return resp.SimpleStringValue(""), errIDNotFound
|
||||
}
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if ok {
|
||||
if ok2 {
|
||||
res = resp.IntegerValue(int(v))
|
@ -1,17 +1,15 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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()
|
||||
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
|
||||
|
||||
@ -33,62 +31,63 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
|
||||
var cols, objs int
|
||||
var ok bool
|
||||
if vs, snumCols, ok = tokenval(vs); !ok || snumCols == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, snumPoints, ok = tokenval(vs); !ok || snumPoints == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
var sminLat, sminLon, smaxLat, smaxLon string
|
||||
if vs, sminLat, ok = tokenval(vs); !ok || sminLat == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, sminLon, ok = tokenval(vs); !ok || sminLon == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, smaxLat, ok = tokenval(vs); !ok || smaxLat == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, smaxLon, ok = tokenval(vs); !ok || smaxLon == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
var err error
|
||||
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 {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
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 {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return server.NOMessage, errInvalidArgument(snumCols)
|
||||
return NOMessage, errInvalidArgument(snumCols)
|
||||
}
|
||||
cols = int(n)
|
||||
n, err = strconv.ParseUint(snumPoints, 10, 64)
|
||||
if err != nil {
|
||||
return server.NOMessage, errInvalidArgument(snumPoints)
|
||||
return NOMessage, errInvalidArgument(snumPoints)
|
||||
}
|
||||
docmd := func(values []resp.Value) error {
|
||||
nmsg := &server.Message{}
|
||||
docmd := func(args []string) error {
|
||||
nmsg := &Message{}
|
||||
*nmsg = *msg
|
||||
nmsg.Values = values
|
||||
nmsg.Command = strings.ToLower(values[0].String())
|
||||
nmsg.Args = args
|
||||
var d commandDetailsT
|
||||
_, d, err = c.command(nmsg, nil, nil)
|
||||
_, d, err = c.command(nmsg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.writeAOF(resp.ArrayValue(nmsg.Values), &d)
|
||||
|
||||
return c.writeAOF(nmsg.Args, &d)
|
||||
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
objs = int(n)
|
||||
@ -99,19 +98,21 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
|
||||
// lock cycle
|
||||
for j := 0; j < objs; j++ {
|
||||
id := strconv.FormatInt(int64(j), 10)
|
||||
var values []resp.Value
|
||||
var values []string
|
||||
if j%8 == 0 {
|
||||
values = append(values, resp.StringValue("set"),
|
||||
resp.StringValue(key), resp.StringValue(id),
|
||||
resp.StringValue("STRING"), resp.StringValue(fmt.Sprintf("str%v", j)))
|
||||
values = append(values, "set", key, id, "STRING", fmt.Sprintf("str%v", j))
|
||||
} else {
|
||||
lat, lon := randMassInsertPosition(minLat, minLon, maxLat, maxLon)
|
||||
values = make([]resp.Value, 0, 16)
|
||||
values = append(values, resp.StringValue("set"), resp.StringValue(key), resp.StringValue(id))
|
||||
values = make([]string, 0, 16)
|
||||
values = append(values, "set", key, id)
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
@ -125,15 +126,15 @@ func (c *Controller) cmdMassInsert(msg *server.Message) (res resp.Value, err err
|
||||
}(key)
|
||||
}
|
||||
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()
|
||||
if len(msg.Values) != 2 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
if len(msg.Args) != 2 {
|
||||
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))
|
||||
return server.OKMessage(msg, start), nil
|
||||
return OKMessage(msg, start), nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
@ -6,8 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/btree"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
type exitem struct {
|
||||
@ -33,7 +31,7 @@ func (a *exitem) Less(v btree.Item, ctx interface{}) bool {
|
||||
}
|
||||
|
||||
// fillExpiresList occurs once at startup
|
||||
func (c *Controller) fillExpiresList() {
|
||||
func (c *Server) fillExpiresList() {
|
||||
c.exlistmu.Lock()
|
||||
c.exlist = c.exlist[:0]
|
||||
for key, m := range c.expires {
|
||||
@ -45,7 +43,7 @@ func (c *Controller) fillExpiresList() {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.
|
||||
func (c *Controller) clearKeyExpires(key string) {
|
||||
func (c *Server) clearKeyExpires(key string) {
|
||||
delete(c.expires, key)
|
||||
}
|
||||
|
||||
// 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]
|
||||
if m == nil {
|
||||
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.
|
||||
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 {
|
||||
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.
|
||||
func (c *Controller) hasExpired(key, id string) bool {
|
||||
func (c *Server) hasExpired(key, id string) bool {
|
||||
at, ok := c.getExpires(key, id)
|
||||
if !ok {
|
||||
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
|
||||
// from the database. It's executes 10 times a seconds.
|
||||
func (c *Controller) backgroundExpiring() {
|
||||
func (c *Server) backgroundExpiring() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var purgelist []exitem
|
||||
for {
|
||||
@ -128,16 +126,15 @@ func (c *Controller) backgroundExpiring() {
|
||||
for _, item := range purgelist {
|
||||
if c.hasExpired(item.key, item.id) {
|
||||
// purge from database
|
||||
msg := &server.Message{}
|
||||
msg.Values = resp.MultiBulkValue("del", item.key, item.id).Array()
|
||||
msg.Command = "del"
|
||||
msg := &Message{}
|
||||
msg.Args = []string{"del", item.key, item.id}
|
||||
_, d, err := c.cmdDel(msg)
|
||||
if err != nil {
|
||||
c.mu.Unlock()
|
||||
log.Fatal(err)
|
||||
continue
|
||||
}
|
||||
if err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
|
||||
if err := c.writeAOF(msg.Args, &d); err != nil {
|
||||
c.mu.Unlock()
|
||||
log.Fatal(err)
|
||||
continue
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"math"
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/tidwall/geojson/geometry"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
// FenceMatch executes a fence match returns back json messages for fence detection.
|
||||
@ -175,7 +174,7 @@ func fenceMatch(
|
||||
}
|
||||
sw.fmap = details.fmap
|
||||
sw.fullFields = true
|
||||
sw.msg.OutputType = server.JSON
|
||||
sw.msg.OutputType = JSON
|
||||
sw.writeObject(ScanWriterParams{
|
||||
id: details.id,
|
||||
o: details.obj,
|
||||
@ -354,7 +353,7 @@ func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
|
||||
}
|
||||
|
||||
func fenceMatchRoam(
|
||||
c *Controller, fence *liveFenceSwitches,
|
||||
c *Server, fence *liveFenceSwitches,
|
||||
tkey, tid string, obj geojson.Object,
|
||||
) (nearbys, faraways []roamMatch) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -12,27 +12,26 @@ import (
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
var errNoLongerFollowing = errors.New("no longer following")
|
||||
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ok bool
|
||||
var host, sport string
|
||||
|
||||
if vs, host, ok = tokenval(vs); !ok || host == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if vs, sport, ok = tokenval(vs); !ok || sport == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
host = strings.ToLower(host)
|
||||
sport = strings.ToLower(sport)
|
||||
@ -44,7 +43,7 @@ func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error)
|
||||
} else {
|
||||
n, err := strconv.ParseUint(sport, 10, 64)
|
||||
if err != nil {
|
||||
return server.NOMessage, errInvalidArgument(sport)
|
||||
return NOMessage, errInvalidArgument(sport)
|
||||
}
|
||||
port := int(n)
|
||||
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)
|
||||
if err != nil {
|
||||
c.mu.Lock()
|
||||
return server.NOMessage, fmt.Errorf("cannot follow: %v", err)
|
||||
return NOMessage, fmt.Errorf("cannot follow: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if auth != "" {
|
||||
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)
|
||||
if err != nil {
|
||||
c.mu.Lock()
|
||||
return server.NOMessage, fmt.Errorf("cannot follow: %v", err)
|
||||
return NOMessage, fmt.Errorf("cannot follow: %v", err)
|
||||
}
|
||||
if m["id"] == "" {
|
||||
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() {
|
||||
c.mu.Lock()
|
||||
return server.NOMessage, fmt.Errorf("cannot follow self")
|
||||
return NOMessage, fmt.Errorf("cannot follow self")
|
||||
}
|
||||
if m["following"] != "" {
|
||||
c.mu.Lock()
|
||||
return server.NOMessage, fmt.Errorf("cannot follow a follower")
|
||||
return NOMessage, fmt.Errorf("cannot follow a follower")
|
||||
}
|
||||
c.mu.Lock()
|
||||
}
|
||||
@ -94,10 +93,10 @@ func (c *Controller) cmdFollow(msg *server.Message) (res resp.Value, err error)
|
||||
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")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -113,29 +112,27 @@ func doServer(conn *Conn) (map[string]string, error) {
|
||||
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()
|
||||
defer c.mu.Unlock()
|
||||
if c.followc.get() != followc {
|
||||
return c.aofsz, errNoLongerFollowing
|
||||
}
|
||||
msg := &server.Message{
|
||||
Command: strings.ToLower(values[0].String()),
|
||||
Values: values,
|
||||
}
|
||||
_, d, err := c.command(msg, nil, nil)
|
||||
msg := &Message{Args: args}
|
||||
|
||||
_, d, err := c.command(msg, nil)
|
||||
if err != nil {
|
||||
if commandErrIsFatal(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, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -149,7 +146,7 @@ func (c *Controller) followDoLeaderAuth(conn *Conn, auth string) error {
|
||||
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 {
|
||||
return errNoLongerFollowing
|
||||
}
|
||||
@ -228,8 +225,12 @@ func (c *Controller) followStep(host string, port int, followc int) error {
|
||||
if telnet || v.Type() != resp.Array {
|
||||
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 {
|
||||
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 {
|
||||
err := c.followStep(host, port, followc)
|
||||
if err == errNoLongerFollowing {
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/tidwall/tile38/internal/endpoint"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
var hookLogSetDefaults = &buntdb.SetOptions{
|
||||
@ -36,22 +35,22 @@ func (a hooksByName) Swap(i, j int) {
|
||||
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,
|
||||
) {
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var name, urls, cmd string
|
||||
var ok bool
|
||||
if vs, name, ok = tokenval(vs); !ok || name == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
var endpoints []string
|
||||
if chanCmd {
|
||||
endpoints = []string{"local://" + name}
|
||||
} else {
|
||||
if vs, urls, ok = tokenval(vs); !ok || urls == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
for _, url := range strings.Split(urls, ",") {
|
||||
url = strings.TrimSpace(url)
|
||||
@ -63,7 +62,7 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||
endpoints = append(endpoints, url)
|
||||
}
|
||||
}
|
||||
var commandvs []resp.Value
|
||||
var commandvs []string
|
||||
var cmdlc string
|
||||
var types []string
|
||||
var expires float64
|
||||
@ -72,31 +71,31 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||
for {
|
||||
commandvs = vs
|
||||
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
cmdlc = strings.ToLower(cmd)
|
||||
switch cmdlc {
|
||||
default:
|
||||
return server.NOMessage, d, errInvalidArgument(cmd)
|
||||
return NOMessage, d, errInvalidArgument(cmd)
|
||||
case "meta":
|
||||
var metakey string
|
||||
var metaval string
|
||||
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 == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
metaMap[metakey] = metaval
|
||||
continue
|
||||
case "ex":
|
||||
var s string
|
||||
if vs, s, ok = tokenval(vs); !ok || s == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return server.NOMessage, d, errInvalidArgument(s)
|
||||
return NOMessage, d, errInvalidArgument(s)
|
||||
}
|
||||
expires = v
|
||||
expiresSet = true
|
||||
@ -111,20 +110,18 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||
s, err := c.cmdSearchArgs(true, cmdlc, vs, types)
|
||||
defer s.Close()
|
||||
if err != nil {
|
||||
return server.NOMessage, d, err
|
||||
return NOMessage, d, err
|
||||
}
|
||||
if !s.fence {
|
||||
return server.NOMessage, d, errors.New("missing FENCE argument")
|
||||
return NOMessage, d, errors.New("missing FENCE argument")
|
||||
}
|
||||
s.cmd = cmdlc
|
||||
cmsg := &server.Message{}
|
||||
cmsg := &Message{}
|
||||
*cmsg = *msg
|
||||
cmsg.Values = make([]resp.Value, len(commandvs))
|
||||
cmsg.Args = make([]string, len(commandvs))
|
||||
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))
|
||||
for key, val := range metaMap {
|
||||
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)
|
||||
if err != nil {
|
||||
|
||||
return server.NOMessage, d, err
|
||||
return NOMessage, d, err
|
||||
}
|
||||
if h, ok := c.hooks[name]; ok {
|
||||
if h.channel != chanCmd {
|
||||
return server.NOMessage, d,
|
||||
return NOMessage, d,
|
||||
errors.New("hooks and channels cannot share the same name")
|
||||
}
|
||||
if h.Equals(hook) {
|
||||
@ -170,9 +167,9 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||
c.hookex.Push(hook)
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
return server.OKMessage(msg, start), d, nil
|
||||
case server.RESP:
|
||||
case JSON:
|
||||
return OKMessage(msg, start), d, nil
|
||||
case RESP:
|
||||
return resp.IntegerValue(0), d, nil
|
||||
}
|
||||
}
|
||||
@ -198,27 +195,27 @@ func (c *Controller) cmdSetHook(msg *server.Message, chanCmd bool) (
|
||||
c.hookex.Push(hook)
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
return server.OKMessage(msg, start), d, nil
|
||||
case server.RESP:
|
||||
case JSON:
|
||||
return OKMessage(msg, start), d, nil
|
||||
case RESP:
|
||||
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,
|
||||
) {
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var name string
|
||||
var ok bool
|
||||
if vs, name, ok = tokenval(vs); !ok || name == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
if h, ok := c.hooks[name]; ok && h.channel == chanCmd {
|
||||
h.Close()
|
||||
@ -231,9 +228,9 @@ func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) (
|
||||
d.timestamp = time.Now()
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
return server.OKMessage(msg, start), d, nil
|
||||
case server.RESP:
|
||||
case JSON:
|
||||
return OKMessage(msg, start), d, nil
|
||||
case RESP:
|
||||
if d.updated {
|
||||
return resp.IntegerValue(1), d, nil
|
||||
}
|
||||
@ -242,19 +239,19 @@ func (c *Controller) cmdDelHook(msg *server.Message, chanCmd bool) (
|
||||
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,
|
||||
) {
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var pattern string
|
||||
var ok bool
|
||||
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
count := 0
|
||||
@ -277,9 +274,9 @@ func (c *Controller) cmdPDelHook(msg *server.Message, channel bool) (
|
||||
d.timestamp = time.Now()
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
return server.OKMessage(msg, start), d, nil
|
||||
case server.RESP:
|
||||
case JSON:
|
||||
return OKMessage(msg, start), d, nil
|
||||
case RESP:
|
||||
return resp.IntegerValue(count), d, nil
|
||||
}
|
||||
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
|
||||
// purge it from the database if needed. This operation is called from an
|
||||
// independent goroutine
|
||||
func (c *Controller) possiblyExpireHook(name string) {
|
||||
func (c *Server) possiblyExpireHook(name string) {
|
||||
c.mu.Lock()
|
||||
if h, ok := c.hooks[name]; ok {
|
||||
if !h.expires.IsZero() && time.Now().After(h.expires) {
|
||||
// purge from database
|
||||
msg := &server.Message{}
|
||||
msg := &Message{}
|
||||
if h.channel {
|
||||
msg.Values = resp.MultiBulkValue("delchan", h.Name).Array()
|
||||
msg.Command = "delchan"
|
||||
msg.Args = []string{"delchan", h.Name}
|
||||
} else {
|
||||
msg.Values = resp.MultiBulkValue("delhook", h.Name).Array()
|
||||
msg.Command = "delhook"
|
||||
msg.Args = []string{"delhook", h.Name}
|
||||
}
|
||||
_, d, err := c.cmdDelHook(msg, h.channel)
|
||||
if err != nil {
|
||||
c.mu.Unlock()
|
||||
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()
|
||||
panic(err)
|
||||
}
|
||||
@ -316,20 +311,20 @@ func (c *Controller) possiblyExpireHook(name string) {
|
||||
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,
|
||||
) {
|
||||
start := time.Now()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var pattern string
|
||||
var ok bool
|
||||
|
||||
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
var hooks []*Hook
|
||||
@ -345,7 +340,7 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
|
||||
sort.Sort(hooksByName(hooks))
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteString(`{"ok":true,`)
|
||||
if channel {
|
||||
@ -370,11 +365,11 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
|
||||
}
|
||||
}
|
||||
buf.WriteString(`],"command":[`)
|
||||
for i, v := range hook.Message.Values {
|
||||
for i, v := range hook.Message.Args {
|
||||
if i > 0 {
|
||||
buf.WriteString(`,`)
|
||||
}
|
||||
buf.WriteString(jsonString(v.String()))
|
||||
buf.WriteString(jsonString(v))
|
||||
}
|
||||
buf.WriteString(`],"meta":{`)
|
||||
for i, meta := range hook.Metas {
|
||||
@ -390,7 +385,7 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
|
||||
buf.WriteString(`],"elapsed":"` +
|
||||
time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
var vals []resp.Value
|
||||
for _, hook := range hooks {
|
||||
var hvals []resp.Value
|
||||
@ -401,7 +396,11 @@ func (c *Controller) cmdHooks(msg *server.Message, channel bool) (
|
||||
evals = append(evals, resp.StringValue(endpoint))
|
||||
}
|
||||
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
|
||||
for _, meta := range hook.Metas {
|
||||
metas = append(metas, resp.StringValue(meta.Name))
|
||||
@ -421,7 +420,7 @@ type Hook struct {
|
||||
Key string
|
||||
Name string
|
||||
Endpoints []string
|
||||
Message *server.Message
|
||||
Message *Message
|
||||
Fence *liveFenceSwitches
|
||||
ScanWriter *scanWriter
|
||||
Metas []FenceMeta
|
||||
@ -461,9 +460,15 @@ func (h *Hook) Equals(hook *Hook) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return resp.ArrayValue(h.Message.Values).Equals(
|
||||
resp.ArrayValue(hook.Message.Values))
|
||||
if len(h.Message.Args) != len(hook.Message.Args) {
|
||||
return false
|
||||
}
|
||||
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
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/sjson"
|
||||
"github.com/tidwall/tile38/internal/collection"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
func appendJSONString(b []byte, s string) []byte {
|
||||
@ -86,44 +85,44 @@ func jsonTimeFormat(t time.Time) string {
|
||||
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()
|
||||
|
||||
if len(msg.Values) < 3 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
if len(msg.Args) < 3 {
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(msg.Values) > 5 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
if len(msg.Args) > 5 {
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
key := msg.Values[1].String()
|
||||
id := msg.Values[2].String()
|
||||
key := msg.Args[1]
|
||||
id := msg.Args[2]
|
||||
var doget bool
|
||||
var path string
|
||||
var raw bool
|
||||
if len(msg.Values) > 3 {
|
||||
if len(msg.Args) > 3 {
|
||||
doget = true
|
||||
path = msg.Values[3].String()
|
||||
if len(msg.Values) == 5 {
|
||||
if strings.ToLower(msg.Values[4].String()) == "raw" {
|
||||
path = msg.Args[3]
|
||||
if len(msg.Args) == 5 {
|
||||
if strings.ToLower(msg.Args[4]) == "raw" {
|
||||
raw = true
|
||||
} else {
|
||||
return server.NOMessage, errInvalidArgument(msg.Values[4].String())
|
||||
return NOMessage, errInvalidArgument(msg.Args[4])
|
||||
}
|
||||
}
|
||||
}
|
||||
col := c.getCol(key)
|
||||
if col == nil {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.NullValue(), nil
|
||||
}
|
||||
return server.NOMessage, errKeyNotFound
|
||||
return NOMessage, errKeyNotFound
|
||||
}
|
||||
o, _, ok := col.Get(id)
|
||||
if !ok {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.NullValue(), nil
|
||||
}
|
||||
return server.NOMessage, errIDNotFound
|
||||
return NOMessage, errIDNotFound
|
||||
}
|
||||
var res gjson.Result
|
||||
if doget {
|
||||
@ -138,38 +137,38 @@ func (c *Controller) cmdJget(msg *server.Message) (resp.Value, error) {
|
||||
val = res.String()
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
buf.WriteString(`{"ok":true`)
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
if res.Exists() {
|
||||
buf.WriteString(`,"value":` + jsonString(val))
|
||||
}
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if !res.Exists() {
|
||||
return resp.NullValue(), 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]
|
||||
start := time.Now()
|
||||
|
||||
var raw, str bool
|
||||
switch len(msg.Values) {
|
||||
switch len(msg.Args) {
|
||||
default:
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
case 5:
|
||||
case 6:
|
||||
switch strings.ToLower(msg.Values[5].String()) {
|
||||
switch strings.ToLower(msg.Args[5]) {
|
||||
default:
|
||||
return server.NOMessage, d, errInvalidArgument(msg.Values[5].String())
|
||||
return NOMessage, d, errInvalidArgument(msg.Args[5])
|
||||
case "raw":
|
||||
raw = true
|
||||
case "str":
|
||||
@ -177,10 +176,10 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
|
||||
}
|
||||
}
|
||||
|
||||
key := msg.Values[1].String()
|
||||
id := msg.Values[2].String()
|
||||
path := msg.Values[3].String()
|
||||
val := msg.Values[4].String()
|
||||
key := msg.Args[1]
|
||||
id := msg.Args[2]
|
||||
path := msg.Args[3]
|
||||
val := msg.Args[4]
|
||||
if !str && !raw {
|
||||
switch val {
|
||||
default:
|
||||
@ -216,18 +215,12 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
|
||||
json, err = sjson.Set(json, path, val)
|
||||
}
|
||||
if err != nil {
|
||||
return server.NOMessage, d, err
|
||||
return NOMessage, d, err
|
||||
}
|
||||
|
||||
if geoobj {
|
||||
nmsg := *msg
|
||||
nmsg.Values = []resp.Value{
|
||||
resp.StringValue("SET"),
|
||||
resp.StringValue(key),
|
||||
resp.StringValue(id),
|
||||
resp.StringValue("OBJECT"),
|
||||
resp.StringValue(json),
|
||||
}
|
||||
nmsg.Args = []string{"SET", key, id, "OBJECT", json}
|
||||
// SET key id OBJECT json
|
||||
return c.cmdSet(&nmsg)
|
||||
}
|
||||
@ -244,33 +237,33 @@ func (c *Controller) cmdJset(msg *server.Message) (res resp.Value, d commandDeta
|
||||
c.clearIDExpires(key, id)
|
||||
col.Set(d.id, d.obj, nil, nil)
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), d, nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
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()
|
||||
|
||||
if len(msg.Values) != 4 {
|
||||
return server.NOMessage, d, errInvalidNumberOfArguments
|
||||
if len(msg.Args) != 4 {
|
||||
return NOMessage, d, errInvalidNumberOfArguments
|
||||
}
|
||||
key := msg.Values[1].String()
|
||||
id := msg.Values[2].String()
|
||||
path := msg.Values[3].String()
|
||||
key := msg.Args[1]
|
||||
id := msg.Args[2]
|
||||
path := msg.Args[3]
|
||||
|
||||
col := c.getCol(key)
|
||||
if col == nil {
|
||||
if msg.OutputType == server.RESP {
|
||||
if msg.OutputType == RESP {
|
||||
return resp.IntegerValue(0), d, nil
|
||||
}
|
||||
return server.NOMessage, d, errKeyNotFound
|
||||
return NOMessage, d, errKeyNotFound
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return server.NOMessage, d, err
|
||||
return NOMessage, d, err
|
||||
}
|
||||
if njson == json {
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
return server.NOMessage, d, errPathNotFound
|
||||
case server.RESP:
|
||||
case JSON:
|
||||
return NOMessage, d, errPathNotFound
|
||||
case RESP:
|
||||
return resp.IntegerValue(0), d, nil
|
||||
}
|
||||
return server.NOMessage, d, nil
|
||||
return NOMessage, d, nil
|
||||
}
|
||||
json = njson
|
||||
if geoobj {
|
||||
nmsg := *msg
|
||||
nmsg.Values = []resp.Value{
|
||||
resp.StringValue("SET"),
|
||||
resp.StringValue(key),
|
||||
resp.StringValue(id),
|
||||
resp.StringValue("OBJECT"),
|
||||
resp.StringValue(json),
|
||||
}
|
||||
nmsg.Args = []string{"SET", key, id, "OBJECT", json}
|
||||
// SET key id OBJECT json
|
||||
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)
|
||||
col.Set(d.id, d.obj, nil, nil)
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), d, nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
return resp.IntegerValue(1), d, nil
|
||||
}
|
||||
return server.NOMessage, d, nil
|
||||
return NOMessage, d, nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -7,25 +7,24 @@ import (
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var pattern string
|
||||
var ok bool
|
||||
if vs, pattern, ok = tokenval(vs); !ok || pattern == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
var wr = &bytes.Buffer{}
|
||||
var once bool
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`{"ok":true,"keys":[`)
|
||||
}
|
||||
var everything bool
|
||||
@ -47,16 +46,16 @@ func (c *Controller) cmdKeys(msg *server.Message) (res resp.Value, err error) {
|
||||
}
|
||||
if match {
|
||||
if once {
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteByte(',')
|
||||
}
|
||||
} else {
|
||||
once = true
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
wr.WriteString(jsonString(key))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
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)
|
||||
}
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`],"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(wr.String()), nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
type liveBuffer struct {
|
||||
@ -20,7 +19,7 @@ type liveBuffer struct {
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func (c *Controller) processLives() {
|
||||
func (c *Server) processLives() {
|
||||
for {
|
||||
c.lcond.L.Lock()
|
||||
for len(c.lstack) > 0 {
|
||||
@ -47,30 +46,31 @@ func writeLiveMessage(
|
||||
conn net.Conn,
|
||||
message []byte,
|
||||
wrapRESP bool,
|
||||
connType server.Type,
|
||||
websocket bool,
|
||||
connType Type, websocket bool,
|
||||
) error {
|
||||
if len(message) == 0 {
|
||||
return nil
|
||||
}
|
||||
if websocket {
|
||||
return server.WriteWebSocketMessage(conn, message)
|
||||
return WriteWebSocketMessage(conn, message)
|
||||
}
|
||||
var err error
|
||||
switch connType {
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if wrapRESP {
|
||||
_, err = fmt.Fprintf(conn, "$%d\r\n%s\r\n", len(message), string(message))
|
||||
} else {
|
||||
_, err = conn.Write(message)
|
||||
}
|
||||
case server.Native:
|
||||
case Native:
|
||||
_, err = fmt.Fprintf(conn, "$%d %s\r\n", len(message), string(message))
|
||||
}
|
||||
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()
|
||||
log.Info("live " + addr)
|
||||
defer func() {
|
||||
@ -139,7 +139,7 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
switch v.Command {
|
||||
switch v.Command() {
|
||||
default:
|
||||
log.Error("received a live command that was not QUIT")
|
||||
return
|
||||
@ -152,13 +152,13 @@ func (c *Controller) goLive(inerr error, conn net.Conn, rd *server.PipelineReade
|
||||
outputType := msg.OutputType
|
||||
connType := msg.ConnType
|
||||
if websocket {
|
||||
outputType = server.JSON
|
||||
outputType = JSON
|
||||
}
|
||||
var livemsg []byte
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
livemsg = []byte(`{"ok":true,"live":true}`)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
livemsg = []byte("+OK\r\n")
|
||||
}
|
||||
if err := writeLiveMessage(conn, livemsg, false, connType, websocket); err != nil {
|
@ -1,42 +1,41 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var arg string
|
||||
var ok bool
|
||||
|
||||
if len(vs) != 0 {
|
||||
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
|
||||
// server prior to the next command being executed.
|
||||
switch strings.ToLower(arg) {
|
||||
default:
|
||||
return server.NOMessage, errInvalidArgument(arg)
|
||||
return NOMessage, errInvalidArgument(arg)
|
||||
case "json":
|
||||
msg.OutputType = server.JSON
|
||||
msg.OutputType = JSON
|
||||
case "resp":
|
||||
msg.OutputType = server.RESP
|
||||
msg.OutputType = RESP
|
||||
}
|
||||
return server.OKMessage(msg, start), nil
|
||||
return OKMessage(msg, start), nil
|
||||
}
|
||||
// return the output
|
||||
switch msg.OutputType {
|
||||
default:
|
||||
return server.NOMessage, nil
|
||||
case server.JSON:
|
||||
return NOMessage, nil
|
||||
case JSON:
|
||||
return resp.StringValue(`{"ok":true,"output":"json","elapsed":` + time.Now().Sub(start).String() + `}`), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
return resp.StringValue("resp"), nil
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"github.com/tidwall/redcon"
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -35,7 +34,7 @@ func newPubsub() *pubsub {
|
||||
}
|
||||
|
||||
// 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
|
||||
c.pubsub.mu.RLock()
|
||||
if hub := c.pubsub.hubs[pubsubChannel][channel]; hub != nil {
|
||||
@ -131,53 +130,53 @@ func newSubhub() *subhub {
|
||||
}
|
||||
|
||||
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 {
|
||||
return goingLive
|
||||
}
|
||||
|
||||
func (c *Controller) cmdSubscribe(msg *server.Message) (resp.Value, error) {
|
||||
if len(msg.Values) < 2 {
|
||||
func (c *Server) cmdSubscribe(msg *Message) (resp.Value, error) {
|
||||
if len(msg.Args) < 2 {
|
||||
return resp.Value{}, errInvalidNumberOfArguments
|
||||
}
|
||||
return server.NOMessage, liveSubscriptionSwitches{}
|
||||
return NOMessage, liveSubscriptionSwitches{}
|
||||
}
|
||||
|
||||
func (c *Controller) cmdPsubscribe(msg *server.Message) (resp.Value, error) {
|
||||
if len(msg.Values) < 2 {
|
||||
func (c *Server) cmdPsubscribe(msg *Message) (resp.Value, error) {
|
||||
if len(msg.Args) < 2 {
|
||||
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()
|
||||
if len(msg.Values) != 3 {
|
||||
if len(msg.Args) != 3 {
|
||||
return resp.Value{}, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
channel := msg.Values[1].String()
|
||||
message := msg.Values[2].String()
|
||||
channel := msg.Args[1]
|
||||
message := msg.Args[2]
|
||||
//geofence := gjson.Valid(message) && gjson.Get(message, "fence").Bool()
|
||||
n := c.Publish(channel, message) //, geofence)
|
||||
var res resp.Value
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
res = resp.StringValue(`{"ok":true` +
|
||||
`,"published":` + strconv.FormatInt(int64(n), 10) +
|
||||
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.IntegerValue(n)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Controller) liveSubscription(
|
||||
func (c *Server) liveSubscription(
|
||||
conn net.Conn,
|
||||
rd *server.PipelineReader,
|
||||
msg *server.Message,
|
||||
rd *PipelineReader,
|
||||
msg *Message,
|
||||
websocket bool,
|
||||
) error {
|
||||
defer conn.Close() // close connection when we are done
|
||||
@ -185,7 +184,7 @@ func (c *Controller) liveSubscription(
|
||||
outputType := msg.OutputType
|
||||
connType := msg.ConnType
|
||||
if websocket {
|
||||
outputType = server.JSON
|
||||
outputType = JSON
|
||||
}
|
||||
|
||||
var start time.Time
|
||||
@ -199,44 +198,44 @@ func (c *Controller) liveSubscription(
|
||||
}
|
||||
writeOK := func() {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
write([]byte(`{"ok":true` +
|
||||
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
write([]byte(`+OK\r\n`))
|
||||
}
|
||||
}
|
||||
writeWrongNumberOfArgsErr := func(command string) {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
write([]byte(`{"ok":false,"err":"invalid number of arguments"` +
|
||||
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
write([]byte(`-ERR wrong number of arguments ` +
|
||||
`for '` + command + `' command\r\n`))
|
||||
}
|
||||
}
|
||||
writeOnlyPubsubErr := func() {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
write([]byte(`{"ok":false` +
|
||||
`,"err":"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / ` +
|
||||
`PING / QUIT allowed in this context"` +
|
||||
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
write([]byte("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / " +
|
||||
"PING / QUIT allowed in this context\r\n"))
|
||||
}
|
||||
}
|
||||
writeSubscribe := func(command, channel string, num int) {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
write([]byte(`{"ok":true` +
|
||||
`,"command":` + jsonString(command) +
|
||||
`,"channel":` + jsonString(channel) +
|
||||
`,"num":` + strconv.FormatInt(int64(num), 10) +
|
||||
`,"elapsed":"` + time.Now().Sub(start).String() + `"}`))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
b := redcon.AppendArray(nil, 3)
|
||||
b = redcon.AppendBulkString(b, command)
|
||||
b = redcon.AppendBulkString(b, channel)
|
||||
@ -247,7 +246,7 @@ func (c *Controller) liveSubscription(
|
||||
writeMessage := func(msg submsg) {
|
||||
if msg.kind == pubsubChannel {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var data []byte
|
||||
if !gjson.Valid(msg.message) {
|
||||
data = appendJSONString(nil, msg.message)
|
||||
@ -255,7 +254,7 @@ func (c *Controller) liveSubscription(
|
||||
data = []byte(msg.message)
|
||||
}
|
||||
write(data)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
b := redcon.AppendArray(nil, 3)
|
||||
b = redcon.AppendBulkString(b, "message")
|
||||
b = redcon.AppendBulkString(b, msg.channel)
|
||||
@ -264,7 +263,7 @@ func (c *Controller) liveSubscription(
|
||||
}
|
||||
} else {
|
||||
switch outputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var data []byte
|
||||
if !gjson.Valid(msg.message) {
|
||||
data = appendJSONString(nil, msg.message)
|
||||
@ -272,7 +271,7 @@ func (c *Controller) liveSubscription(
|
||||
data = []byte(msg.message)
|
||||
}
|
||||
write(data)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
b := redcon.AppendArray(nil, 4)
|
||||
b = redcon.AppendBulkString(b, "pmessage")
|
||||
b = redcon.AppendBulkString(b, msg.pattern)
|
||||
@ -325,12 +324,12 @@ func (c *Controller) liveSubscription(
|
||||
}
|
||||
}()
|
||||
|
||||
msgs := []*server.Message{msg}
|
||||
msgs := []*Message{msg}
|
||||
for {
|
||||
for _, msg := range msgs {
|
||||
start = time.Now()
|
||||
var kind int
|
||||
switch msg.Command {
|
||||
switch msg.Command() {
|
||||
case "quit":
|
||||
writeOK()
|
||||
return nil
|
||||
@ -341,14 +340,14 @@ func (c *Controller) liveSubscription(
|
||||
default:
|
||||
writeOnlyPubsubErr()
|
||||
}
|
||||
if len(msg.Values) < 2 {
|
||||
writeWrongNumberOfArgsErr(msg.Command)
|
||||
if len(msg.Args) < 2 {
|
||||
writeWrongNumberOfArgsErr(msg.Command())
|
||||
}
|
||||
for i := 1; i < len(msg.Values); i++ {
|
||||
channel := msg.Values[i].String()
|
||||
for i := 1; i < len(msg.Args); i++ {
|
||||
channel := msg.Args[i]
|
||||
m[kind][channel] = true
|
||||
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
|
@ -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())
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@ -6,25 +6,24 @@ import (
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var arg string
|
||||
var ok bool
|
||||
|
||||
if vs, arg, ok = tokenval(vs); !ok || arg == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
if len(vs) != 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
update := false
|
||||
switch strings.ToLower(arg) {
|
||||
default:
|
||||
return server.NOMessage, errInvalidArgument(arg)
|
||||
return NOMessage, errInvalidArgument(arg)
|
||||
case "yes":
|
||||
if !c.config.readOnly() {
|
||||
update = true
|
||||
@ -41,5 +40,5 @@ func (c *Controller) cmdReadOnly(msg *server.Message) (res resp.Value, err error
|
||||
if update {
|
||||
c.config.write(false)
|
||||
}
|
||||
return server.OKMessage(msg, start), nil
|
||||
return OKMessage(msg, start), nil
|
||||
}
|
46
internal/server/respconn.go
Normal file
46
internal/server/respconn.go
Normal 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
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/tidwall/resp"
|
||||
"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,
|
||||
) {
|
||||
var t searchScanBaseTokens
|
||||
@ -27,32 +26,32 @@ func (c *Controller) cmdScanArgs(vs []resp.Value) (
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
s, err := c.cmdScanArgs(vs)
|
||||
if s.usingLua() {
|
||||
defer s.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
res = server.NOMessage
|
||||
res = NOMessage
|
||||
err = errors.New(r.(string))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
wr := &bytes.Buffer{}
|
||||
sw, err := c.newScanWriter(
|
||||
wr, msg, s.key, s.output, s.precision, s.glob, false,
|
||||
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`{"ok":true`)
|
||||
}
|
||||
sw.writeHead()
|
||||
@ -90,7 +89,7 @@ func (c *Controller) cmdScan(msg *server.Message) (res resp.Value, err error) {
|
||||
}
|
||||
}
|
||||
sw.writeFoot()
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.BytesValue(wr.Bytes()), nil
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"github.com/tidwall/tile38/internal/clip"
|
||||
"github.com/tidwall/tile38/internal/collection"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
const limitItems = 100
|
||||
@ -32,9 +31,9 @@ const (
|
||||
|
||||
type scanWriter struct {
|
||||
mu sync.Mutex
|
||||
c *Controller
|
||||
c *Server
|
||||
wr *bytes.Buffer
|
||||
msg *server.Message
|
||||
msg *Message
|
||||
col *collection.Collection
|
||||
fmap map[string]int
|
||||
farr []string
|
||||
@ -71,8 +70,8 @@ type ScanWriterParams struct {
|
||||
clip geojson.Object
|
||||
}
|
||||
|
||||
func (c *Controller) newScanWriter(
|
||||
wr *bytes.Buffer, msg *server.Message, key string, output outputT,
|
||||
func (c *Server) newScanWriter(
|
||||
wr *bytes.Buffer, msg *Message, key string, output outputT,
|
||||
precision uint64, globPattern string, matchValues bool,
|
||||
cursor, limit uint64, wheres []whereT, whereins []whereinT, whereevals []whereevalT, nofields bool,
|
||||
) (
|
||||
@ -134,7 +133,7 @@ func (sw *scanWriter) writeHead() {
|
||||
sw.mu.Lock()
|
||||
defer sw.mu.Unlock()
|
||||
switch sw.msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
if len(sw.farr) > 0 && sw.hasFieldsOutput() {
|
||||
sw.wr.WriteString(`,"fields":[`)
|
||||
for i, field := range sw.farr {
|
||||
@ -159,7 +158,7 @@ func (sw *scanWriter) writeHead() {
|
||||
case outputCount:
|
||||
|
||||
}
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +170,7 @@ func (sw *scanWriter) writeFoot() {
|
||||
cursor = 0
|
||||
}
|
||||
switch sw.msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
switch sw.output {
|
||||
default:
|
||||
sw.wr.WriteByte(']')
|
||||
@ -180,7 +179,7 @@ func (sw *scanWriter) writeFoot() {
|
||||
}
|
||||
sw.wr.WriteString(`,"count":` + strconv.FormatUint(sw.count, 10))
|
||||
sw.wr.WriteString(`,"cursor":` + strconv.FormatUint(cursor, 10))
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
if sw.output == outputCount {
|
||||
sw.respOut = resp.IntegerValue(int(sw.count))
|
||||
} else {
|
||||
@ -354,7 +353,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||
opts.o = clip.Clip(opts.o, opts.clip)
|
||||
}
|
||||
switch sw.msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var wr bytes.Buffer
|
||||
var jsfields string
|
||||
if sw.once {
|
||||
@ -418,7 +417,7 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||
wr.WriteString(`}`)
|
||||
}
|
||||
sw.wr.Write(wr.Bytes())
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
vals := make([]resp.Value, 1, 3)
|
||||
vals[0] = resp.StringValue(opts.id)
|
||||
if sw.output == outputIDs {
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
"github.com/tidwall/tile38/internal/log"
|
||||
"github.com/yuin/gopher-lua"
|
||||
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
|
||||
type lStatePool struct {
|
||||
m sync.Mutex
|
||||
c *Controller
|
||||
c *Server
|
||||
saved []*lua.LState
|
||||
total int
|
||||
}
|
||||
|
||||
// newPool returns a new pool of lua states
|
||||
func (c *Controller) newPool() *lStatePool {
|
||||
func (c *Server) newPool() *lStatePool {
|
||||
pl := &lStatePool{
|
||||
saved: make([]*lua.LState, iniLuaPoolSize),
|
||||
c: c,
|
||||
@ -206,7 +206,7 @@ func (sm *lScriptMap) Flush() {
|
||||
}
|
||||
|
||||
// NewScriptMap returns a new map with lua scripts
|
||||
func (c *Controller) newScriptMap() *lScriptMap {
|
||||
func (c *Server) newScriptMap() *lScriptMap {
|
||||
return &lScriptMap{
|
||||
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
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var script, numkeysStr, key, arg string
|
||||
if vs, script, ok = tokenval(vs); !ok || script == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
if vs, numkeysStr, ok = tokenval(vs); !ok || numkeysStr == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
var i, numkeys uint64
|
||||
@ -411,7 +411,7 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
|
||||
luaState, map[string]lua.LValue{
|
||||
"KEYS": keysTbl,
|
||||
"ARGV": argsTbl,
|
||||
"EVAL_CMD": lua.LString(msg.Command),
|
||||
"EVAL_CMD": lua.LString(msg.Command()),
|
||||
})
|
||||
|
||||
compiled, ok := c.luascripts.Get(shaSum)
|
||||
@ -431,7 +431,7 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
|
||||
} else {
|
||||
fn, err = luaState.Load(strings.NewReader(script), "f_"+shaSum)
|
||||
if err != nil {
|
||||
return server.NOMessage, makeSafeErr(err)
|
||||
return NOMessage, makeSafeErr(err)
|
||||
}
|
||||
c.luascripts.Put(shaSum, fn.Proto)
|
||||
}
|
||||
@ -442,66 +442,66 @@ func (c *Controller) cmdEvalUnified(scriptIsSha bool, msg *server.Message) (res
|
||||
"ARGV": lua.LNil,
|
||||
"EVAL_CMD": lua.LNil,
|
||||
})
|
||||
|
||||
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
|
||||
luaState.Pop(1)
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(`,"result":` + ConvertToJSON(ret))
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var script string
|
||||
if vs, script, ok = tokenval(vs); !ok || script == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
|
||||
shaSum := Sha1Sum(script)
|
||||
|
||||
luaState, err := c.luapool.Get()
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
defer c.luapool.Put(luaState)
|
||||
|
||||
fn, err := luaState.Load(strings.NewReader(script), "f_"+shaSum)
|
||||
if err != nil {
|
||||
return server.NOMessage, makeSafeErr(err)
|
||||
return NOMessage, makeSafeErr(err)
|
||||
}
|
||||
c.luascripts.Put(shaSum, fn.Proto)
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(`,"result":"` + shaSum + `"`)
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
var ok bool
|
||||
var shaSum string
|
||||
@ -509,7 +509,7 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
|
||||
var ires int
|
||||
for len(vs) > 0 {
|
||||
if vs, shaSum, ok = tokenval(vs); !ok || shaSum == "" {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
_, ok = c.luascripts.Get(shaSum)
|
||||
if ok {
|
||||
@ -521,7 +521,7 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
|
||||
}
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
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(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
var resArray []resp.Value
|
||||
for _, ires := range results {
|
||||
resArray = append(resArray, resp.IntegerValue(ires))
|
||||
@ -541,28 +541,28 @@ func (c *Controller) cmdScriptExists(msg *server.Message) (resp.Value, error) {
|
||||
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()
|
||||
c.luascripts.Flush()
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`{"ok":true`)
|
||||
buf.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.StringValue(buf.String()), nil
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
return resp.StringValue("OK"), 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,
|
||||
) {
|
||||
switch msg.Command {
|
||||
switch msg.Command() {
|
||||
default:
|
||||
err = fmt.Errorf("unknown command '%s'", msg.Values[0])
|
||||
err = fmt.Errorf("unknown command '%s'", msg.Args[0])
|
||||
case "set":
|
||||
res, d, err = c.cmdSet(msg)
|
||||
case "fset":
|
||||
@ -609,16 +609,11 @@ func (c *Controller) commandInScript(msg *server.Message) (
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Controller) luaTile38Call(evalcmd string, cmd string, args ...string) (resp.Value, error) {
|
||||
msg := &server.Message{}
|
||||
msg.OutputType = server.RESP
|
||||
msg.Command = strings.ToLower(cmd)
|
||||
msg.Values = append(msg.Values, resp.StringValue(msg.Command))
|
||||
for _, arg := range args {
|
||||
msg.Values = append(msg.Values, resp.StringValue(arg))
|
||||
}
|
||||
|
||||
switch msg.Command {
|
||||
func (c *Server) luaTile38Call(evalcmd string, cmd string, args ...string) (resp.Value, error) {
|
||||
msg := &Message{}
|
||||
msg.OutputType = RESP
|
||||
msg.Args = append([]string{cmd}, args...)
|
||||
switch msg.Command() {
|
||||
case "ping", "echo", "auth", "massinsert", "shutdown", "gc",
|
||||
"sethook", "pdelhook", "delhook",
|
||||
"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.
|
||||
func (c *Controller) luaTile38AtomicRW(msg *server.Message) (resp.Value, error) {
|
||||
func (c *Server) luaTile38AtomicRW(msg *Message) (resp.Value, error) {
|
||||
var write bool
|
||||
|
||||
switch msg.Command {
|
||||
switch msg.Command() {
|
||||
default:
|
||||
return resp.NullValue(), errCmdNotSupported
|
||||
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 err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
|
||||
if err := c.writeAOF(msg.Args, &d); err != nil {
|
||||
return resp.NullValue(), err
|
||||
}
|
||||
}
|
||||
@ -678,8 +673,8 @@ func (c *Controller) luaTile38AtomicRW(msg *server.Message) (resp.Value, error)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Controller) luaTile38AtomicRO(msg *server.Message) (resp.Value, error) {
|
||||
switch msg.Command {
|
||||
func (c *Server) luaTile38AtomicRO(msg *Message) (resp.Value, error) {
|
||||
switch msg.Command() {
|
||||
default:
|
||||
return resp.NullValue(), errCmdNotSupported
|
||||
|
||||
@ -702,11 +697,11 @@ func (c *Controller) luaTile38AtomicRO(msg *server.Message) (resp.Value, error)
|
||||
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
|
||||
|
||||
// choose the locking strategy
|
||||
switch msg.Command {
|
||||
switch msg.Command() {
|
||||
default:
|
||||
return resp.NullValue(), errCmdNotSupported
|
||||
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 err := c.writeAOF(resp.ArrayValue(msg.Values), &d); err != nil {
|
||||
if err := c.writeAOF(msg.Args, &d); err != nil {
|
||||
return resp.NullValue(), err
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/internal/bing"
|
||||
"github.com/tidwall/tile38/internal/glob"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
const defaultCircleSteps = 64
|
||||
@ -57,14 +56,14 @@ func (s liveFenceSwitches) usingLua() bool {
|
||||
return len(s.whereevals) > 0
|
||||
}
|
||||
|
||||
func (c *Controller) cmdSearchArgs(
|
||||
fromFenceCmd bool, cmd string, vs []resp.Value, types []string,
|
||||
func (server *Server) cmdSearchArgs(
|
||||
fromFenceCmd bool, cmd string, vs []string, types []string,
|
||||
) (s liveFenceSwitches, err error) {
|
||||
var t searchScanBaseTokens
|
||||
if fromFenceCmd {
|
||||
t.fence = true
|
||||
}
|
||||
vs, t, err = c.parseSearchScanBaseTokens(cmd, t, vs)
|
||||
vs, t, err = server.parseSearchScanBaseTokens(cmd, t, vs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -80,7 +79,7 @@ func (c *Controller) cmdSearchArgs(
|
||||
if _, err := strconv.ParseFloat(typ, 64); err == nil {
|
||||
// It's likely that the output was not specified, but rather the search bounds.
|
||||
s.searchScanBaseTokens.output = defaultSearchOutput
|
||||
vs = append([]resp.Value{resp.StringValue(typ)}, vs...)
|
||||
vs = append([]string{typ}, vs...)
|
||||
typ = "BOUNDS"
|
||||
}
|
||||
}
|
||||
@ -283,7 +282,7 @@ func (c *Controller) cmdSearchArgs(
|
||||
err = errInvalidNumberOfArguments
|
||||
return
|
||||
}
|
||||
col := c.getCol(key)
|
||||
col := server.getCol(key)
|
||||
if col == nil {
|
||||
err = errKeyNotFound
|
||||
return
|
||||
@ -337,35 +336,35 @@ var nearbyTypes = []string{"point"}
|
||||
var withinOrIntersectsTypes = []string{
|
||||
"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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
wr := &bytes.Buffer{}
|
||||
s, err := c.cmdSearchArgs(false, "nearby", vs, nearbyTypes)
|
||||
s, err := server.cmdSearchArgs(false, "nearby", vs, nearbyTypes)
|
||||
if s.usingLua() {
|
||||
defer s.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
res = server.NOMessage
|
||||
res = NOMessage
|
||||
err = errors.New(r.(string))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
s.cmd = "nearby"
|
||||
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,
|
||||
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`{"ok":true`)
|
||||
}
|
||||
sw.writeHead()
|
||||
@ -390,10 +389,10 @@ func (c *Controller) cmdNearby(msg *server.Message) (res resp.Value, err error)
|
||||
ignoreGlobMatch: true,
|
||||
})
|
||||
}
|
||||
c.nearestNeighbors(&s, sw, s.obj.(*geojson.Circle), &matched, iter)
|
||||
server.nearestNeighbors(&s, sw, s.obj.(*geojson.Circle), &matched, iter)
|
||||
}
|
||||
sw.writeFoot()
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.BytesValue(wr.Bytes()), nil
|
||||
}
|
||||
@ -407,14 +406,14 @@ type iterItem struct {
|
||||
dist float64
|
||||
}
|
||||
|
||||
func (c *Controller) nearestNeighbors(
|
||||
func (server *Server) nearestNeighbors(
|
||||
s *liveFenceSwitches, sw *scanWriter, target *geojson.Circle, matched *uint32,
|
||||
iter func(id string, o geojson.Object, fields []float64, dist *float64,
|
||||
) bool) {
|
||||
limit := int(sw.cursor + sw.limit)
|
||||
var items []iterItem
|
||||
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
|
||||
}
|
||||
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) {
|
||||
return c.cmdWithinOrIntersects("within", msg)
|
||||
func (server *Server) cmdWithin(msg *Message) (res resp.Value, err error) {
|
||||
return server.cmdWithinOrIntersects("within", msg)
|
||||
}
|
||||
|
||||
func (c *Controller) cmdIntersects(msg *server.Message) (res resp.Value, err error) {
|
||||
return c.cmdWithinOrIntersects("intersects", msg)
|
||||
func (server *Server) cmdIntersects(msg *Message) (res resp.Value, err error) {
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
wr := &bytes.Buffer{}
|
||||
s, err := c.cmdSearchArgs(false, cmd, vs, withinOrIntersectsTypes)
|
||||
s, err := server.cmdSearchArgs(false, cmd, vs, withinOrIntersectsTypes)
|
||||
if s.usingLua() {
|
||||
defer s.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
res = server.NOMessage
|
||||
res = NOMessage
|
||||
err = errors.New(r.(string))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
s.cmd = cmd
|
||||
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,
|
||||
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`{"ok":true`)
|
||||
}
|
||||
sw.writeHead()
|
||||
@ -490,7 +489,7 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||
sw.col.Within(s.obj, s.sparse, func(
|
||||
id string, o geojson.Object, fields []float64,
|
||||
) bool {
|
||||
if c.hasExpired(s.key, id) {
|
||||
if server.hasExpired(s.key, id) {
|
||||
return true
|
||||
}
|
||||
return sw.writeObject(ScanWriterParams{
|
||||
@ -506,7 +505,7 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||
o geojson.Object,
|
||||
fields []float64,
|
||||
) bool {
|
||||
if c.hasExpired(s.key, id) {
|
||||
if server.hasExpired(s.key, id) {
|
||||
return true
|
||||
}
|
||||
params := ScanWriterParams{
|
||||
@ -523,18 +522,18 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||
}
|
||||
}
|
||||
sw.writeFoot()
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.BytesValue(wr.Bytes()), nil
|
||||
}
|
||||
return sw.respOut, nil
|
||||
}
|
||||
|
||||
func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) (
|
||||
func (server *Server) cmdSeachValuesArgs(vs []string) (
|
||||
s liveFenceSwitches, err error,
|
||||
) {
|
||||
var t searchScanBaseTokens
|
||||
vs, t, err = c.parseSearchScanBaseTokens("search", t, vs)
|
||||
vs, t, err = server.parseSearchScanBaseTokens("search", t, vs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -546,32 +545,32 @@ func (c *Controller) cmdSeachValuesArgs(vs []resp.Value) (
|
||||
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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
|
||||
wr := &bytes.Buffer{}
|
||||
s, err := c.cmdSeachValuesArgs(vs)
|
||||
s, err := server.cmdSeachValuesArgs(vs)
|
||||
if s.usingLua() {
|
||||
defer s.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
res = server.NOMessage
|
||||
res = NOMessage
|
||||
err = errors.New(r.(string))
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
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,
|
||||
s.cursor, s.limit, s.wheres, s.whereins, s.whereevals, s.nofields)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`{"ok":true`)
|
||||
}
|
||||
sw.writeHead()
|
||||
@ -613,7 +612,7 @@ func (c *Controller) cmdSearch(msg *server.Message) (res resp.Value, err error)
|
||||
}
|
||||
}
|
||||
sw.writeFoot()
|
||||
if msg.OutputType == server.JSON {
|
||||
if msg.OutputType == JSON {
|
||||
wr.WriteString(`,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
return resp.BytesValue(wr.Bytes()), nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -13,16 +13,15 @@ import (
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"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()
|
||||
vs := msg.Values[1:]
|
||||
vs := msg.Args[1:]
|
||||
var ms = []map[string]interface{}{}
|
||||
|
||||
if len(vs) == 0 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
var vals []resp.Value
|
||||
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_strings"] = col.StringCount()
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
ms = append(ms, m)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
vals = append(vals, resp.ArrayValue(respValuesSimpleMap(m)))
|
||||
}
|
||||
} else {
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
ms = append(ms, nil)
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
vals = append(vals, resp.NullValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
|
||||
data, err := json.Marshal(ms)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.ArrayValue(vals)
|
||||
}
|
||||
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()
|
||||
|
||||
if len(msg.Values) != 1 {
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
if len(msg.Args) != 1 {
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
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)
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
data, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
res = resp.StringValue(`{"ok":true,"stats":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
vals := respValuesSimpleMap(m)
|
||||
res = resp.ArrayValue(vals)
|
||||
}
|
||||
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, "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
|
||||
}
|
||||
func (c *Controller) writeInfoClients(w *bytes.Buffer) {
|
||||
func (c *Server) writeInfoClients(w *bytes.Buffer) {
|
||||
c.connsmu.RLock()
|
||||
fmt.Fprintf(w, "connected_clients:%d\r\n", len(c.conns)) // Number of client connections (excluding connections from slaves)
|
||||
c.connsmu.RUnlock()
|
||||
}
|
||||
func (c *Controller) writeInfoMemory(w *bytes.Buffer) {
|
||||
func (c *Server) writeInfoMemory(w *bytes.Buffer) {
|
||||
var mem runtime.MemStats
|
||||
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
|
||||
@ -156,7 +155,7 @@ func boolInt(t bool) int {
|
||||
}
|
||||
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_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
|
||||
@ -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_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
|
||||
}
|
||||
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
|
||||
}
|
||||
func (c *Controller) writeInfoCluster(w *bytes.Buffer) {
|
||||
func (c *Server) writeInfoCluster(w *bytes.Buffer) {
|
||||
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()
|
||||
|
||||
sections := []string{"server", "clients", "memory", "persistence", "stats", "replication", "cpu", "cluster", "keyspace"}
|
||||
switch len(msg.Values) {
|
||||
switch len(msg.Args) {
|
||||
default:
|
||||
return server.NOMessage, errInvalidNumberOfArguments
|
||||
return NOMessage, errInvalidNumberOfArguments
|
||||
case 1:
|
||||
case 2:
|
||||
section := strings.ToLower(msg.Values[1].String())
|
||||
section := strings.ToLower(msg.Args[1])
|
||||
switch section {
|
||||
default:
|
||||
sections = []string{section}
|
||||
@ -235,13 +234,13 @@ func (c *Controller) cmdInfo(msg *server.Message) (res resp.Value, err error) {
|
||||
}
|
||||
|
||||
switch msg.OutputType {
|
||||
case server.JSON:
|
||||
case JSON:
|
||||
data, err := json.Marshal(w.String())
|
||||
if err != nil {
|
||||
return server.NOMessage, err
|
||||
return NOMessage, err
|
||||
}
|
||||
res = resp.StringValue(`{"ok":true,"info":` + string(data) + `,"elapsed":"` + time.Now().Sub(start).String() + "\"}")
|
||||
case server.RESP:
|
||||
case RESP:
|
||||
res = resp.BytesValue(w.Bytes())
|
||||
}
|
||||
|
||||
@ -262,7 +261,7 @@ func respValuesSimpleMap(m map[string]interface{}) []resp.Value {
|
||||
return vals
|
||||
}
|
||||
|
||||
func (c *Controller) statsCollections(line string) (string, error) {
|
||||
func (c *Server) statsCollections(line string) (string, error) {
|
||||
start := time.Now()
|
||||
var key string
|
||||
var ms = []map[string]interface{}{}
|
@ -1,6 +1,6 @@
|
||||
// +build !linux,!darwin
|
||||
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
@ -1,6 +1,6 @@
|
||||
// +build linux darwin
|
||||
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (c *Controller) writeInfoCPU(w *bytes.Buffer) {
|
||||
func (c *Server) writeInfoCPU(w *bytes.Buffer) {
|
||||
var selfRu syscall.Rusage
|
||||
var cRu syscall.Rusage
|
||||
syscall.Getrusage(syscall.RUSAGE_SELF, &selfRu)
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/resp"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
@ -34,18 +33,18 @@ func token(line string) (newLine, token string) {
|
||||
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 {
|
||||
token = vs[0].String()
|
||||
token = vs[0]
|
||||
nvs = vs[1:]
|
||||
ok = true
|
||||
}
|
||||
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 {
|
||||
token = vs[0].Bytes()
|
||||
token = []byte(vs[0])
|
||||
nvs = vs[1:]
|
||||
ok = true
|
||||
}
|
||||
@ -168,7 +167,7 @@ func (wherein whereinT) match(value float64) bool {
|
||||
}
|
||||
|
||||
type whereevalT struct {
|
||||
c *Controller
|
||||
c *Server
|
||||
luaState *lua.LState
|
||||
fn *lua.LFunction
|
||||
}
|
||||
@ -249,10 +248,10 @@ type searchScanBaseTokens struct {
|
||||
clip bool
|
||||
}
|
||||
|
||||
func (c *Controller) parseSearchScanBaseTokens(
|
||||
cmd string, t searchScanBaseTokens, vs []resp.Value,
|
||||
func (c *Server) parseSearchScanBaseTokens(
|
||||
cmd string, t searchScanBaseTokens, vs []string,
|
||||
) (
|
||||
vsout []resp.Value, tout searchScanBaseTokens, err error,
|
||||
vsout []string, tout searchScanBaseTokens, err error,
|
||||
) {
|
||||
var ok bool
|
||||
if vs, t.key, ok = tokenval(vs); !ok || t.key == "" {
|
||||
@ -622,7 +621,7 @@ func (c *Controller) parseSearchScanBaseTokens(
|
||||
}
|
||||
|
||||
t.output = defaultSearchOutput
|
||||
var nvs []resp.Value
|
||||
var nvs []string
|
||||
var sprecision string
|
||||
var which string
|
||||
if nvs, which, ok = tokenval(vs); ok && which != "" {
|
@ -1,4 +1,4 @@
|
||||
package controller
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/tidwall/tile38/core"
|
||||
"github.com/tidwall/tile38/internal/controller"
|
||||
tlog "github.com/tidwall/tile38/internal/log"
|
||||
"github.com/tidwall/tile38/internal/server"
|
||||
)
|
||||
|
||||
var errTimeout = errors.New("timeout")
|
||||
@ -51,7 +51,7 @@ func mockOpenServer() (*mockServer, error) {
|
||||
s := &mockServer{port: port}
|
||||
tlog.SetOutput(logOutput)
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
14
vendor/github.com/kavu/go_reuseport/.circleci/config.yml
generated
vendored
Normal file
14
vendor/github.com/kavu/go_reuseport/.circleci/config.yml
generated
vendored
Normal 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
23
vendor/github.com/kavu/go_reuseport/.gitignore
generated
vendored
Normal 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
30
vendor/github.com/kavu/go_reuseport/.travis.yml
generated
vendored
Normal 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
21
vendor/github.com/kavu/go_reuseport/LICENSE
generated
vendored
Normal 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
9
vendor/github.com/kavu/go_reuseport/Makefile
generated
vendored
Normal 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
48
vendor/github.com/kavu/go_reuseport/README.md
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
# GO_REUSEPORT
|
||||
|
||||
[](https://travis-ci.org/kavu/go_reuseport)
|
||||
[](https://codecov.io/gh/kavu/go_reuseport)
|
||||
[](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
1
vendor/github.com/kavu/go_reuseport/go.mod
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
module github.com/kavu/go_reuseport
|
50
vendor/github.com/kavu/go_reuseport/reuseport.go
generated
vendored
Normal file
50
vendor/github.com/kavu/go_reuseport/reuseport.go
generated
vendored
Normal 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
44
vendor/github.com/kavu/go_reuseport/reuseport_bsd.go
generated
vendored
Normal 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
52
vendor/github.com/kavu/go_reuseport/reuseport_linux.go
generated
vendored
Normal 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
|
||||
}
|
19
vendor/github.com/kavu/go_reuseport/reuseport_windows.go
generated
vendored
Normal file
19
vendor/github.com/kavu/go_reuseport/reuseport_windows.go
generated
vendored
Normal 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
143
vendor/github.com/kavu/go_reuseport/tcp.go
generated
vendored
Normal 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
218
vendor/github.com/kavu/go_reuseport/tcp_test.go
generated
vendored
Normal 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
22
vendor/github.com/kavu/go_reuseport/test.bash
generated
vendored
Executable 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
143
vendor/github.com/kavu/go_reuseport/udp.go
generated
vendored
Normal 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
99
vendor/github.com/kavu/go_reuseport/udp_test.go
generated
vendored
Normal 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
2
vendor/github.com/tidwall/evio/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
language: go
|
||||
script: go test -run none
|
20
vendor/github.com/tidwall/evio/LICENSE
generated
vendored
Normal file
20
vendor/github.com/tidwall/evio/LICENSE
generated
vendored
Normal 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
176
vendor/github.com/tidwall/evio/README.md
generated
vendored
Normal 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
2
vendor/github.com/tidwall/evio/benchmarks/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
bin/
|
||||
socket
|
27
vendor/github.com/tidwall/evio/benchmarks/README.md
generated
vendored
Normal file
27
vendor/github.com/tidwall/evio/benchmarks/README.md
generated
vendored
Normal 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
308
vendor/github.com/tidwall/evio/benchmarks/analyze.go
generated
vendored
Normal 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
36
vendor/github.com/tidwall/evio/benchmarks/bench-echo.sh
generated
vendored
Executable 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
37
vendor/github.com/tidwall/evio/benchmarks/bench-http.sh
generated
vendored
Executable 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
43
vendor/github.com/tidwall/evio/benchmarks/bench-redis.sh
generated
vendored
Executable 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
15
vendor/github.com/tidwall/evio/benchmarks/bench.sh
generated
vendored
Executable 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
|
32
vendor/github.com/tidwall/evio/benchmarks/fasthttp-server/main.go
generated
vendored
Normal file
32
vendor/github.com/tidwall/evio/benchmarks/fasthttp-server/main.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
47
vendor/github.com/tidwall/evio/benchmarks/net-echo-server/main.go
generated
vendored
Normal file
47
vendor/github.com/tidwall/evio/benchmarks/net-echo-server/main.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
36
vendor/github.com/tidwall/evio/benchmarks/net-http-server/main.go
generated
vendored
Normal file
36
vendor/github.com/tidwall/evio/benchmarks/net-http-server/main.go
generated
vendored
Normal 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
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
34
vendor/github.com/tidwall/evio/benchmarks/out/echo.txt
generated
vendored
Normal 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
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
49
vendor/github.com/tidwall/evio/benchmarks/out/http.txt
generated
vendored
Normal 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 ---
|
28
vendor/github.com/tidwall/evio/benchmarks/out/redis1.txt
generated
vendored
Normal file
28
vendor/github.com/tidwall/evio/benchmarks/out/redis1.txt
generated
vendored
Normal 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 ---
|
28
vendor/github.com/tidwall/evio/benchmarks/out/redis16.txt
generated
vendored
Normal file
28
vendor/github.com/tidwall/evio/benchmarks/out/redis16.txt
generated
vendored
Normal 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 ---
|
28
vendor/github.com/tidwall/evio/benchmarks/out/redis8.txt
generated
vendored
Normal file
28
vendor/github.com/tidwall/evio/benchmarks/out/redis8.txt
generated
vendored
Normal 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 ---
|
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_1.png
generated
vendored
Normal file
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_1.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_16.png
generated
vendored
Normal file
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_16.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_8.png
generated
vendored
Normal file
BIN
vendor/github.com/tidwall/evio/benchmarks/out/redis_pipeline_8.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
268
vendor/github.com/tidwall/evio/evio.go
generated
vendored
Normal file
268
vendor/github.com/tidwall/evio/evio.go
generated
vendored
Normal 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
41
vendor/github.com/tidwall/evio/evio_other.go
generated
vendored
Normal 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
459
vendor/github.com/tidwall/evio/evio_std.go
generated
vendored
Normal 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
478
vendor/github.com/tidwall/evio/evio_test.go
generated
vendored
Normal 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
523
vendor/github.com/tidwall/evio/evio_unix.go
generated
vendored
Normal 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)
|
||||
}
|
59
vendor/github.com/tidwall/evio/examples/echo-server/main.go
generated
vendored
Normal file
59
vendor/github.com/tidwall/evio/examples/echo-server/main.go
generated
vendored
Normal 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)))
|
||||
}
|
221
vendor/github.com/tidwall/evio/examples/http-server/main.go
generated
vendored
Normal file
221
vendor/github.com/tidwall/evio/examples/http-server/main.go
generated
vendored
Normal 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
|
||||
}
|
181
vendor/github.com/tidwall/evio/examples/redis-server/main.go
generated
vendored
Normal file
181
vendor/github.com/tidwall/evio/examples/redis-server/main.go
generated
vendored
Normal 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
125
vendor/github.com/tidwall/evio/internal/internal_bsd.go
generated
vendored
Normal 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,
|
||||
},
|
||||
)
|
||||
}
|
20
vendor/github.com/tidwall/evio/internal/internal_darwin.go
generated
vendored
Normal file
20
vendor/github.com/tidwall/evio/internal/internal_darwin.go
generated
vendored
Normal 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)
|
||||
}
|
129
vendor/github.com/tidwall/evio/internal/internal_linux.go
generated
vendored
Normal file
129
vendor/github.com/tidwall/evio/internal/internal_linux.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
11
vendor/github.com/tidwall/evio/internal/internal_openbsd.go
generated
vendored
Normal file
11
vendor/github.com/tidwall/evio/internal/internal_openbsd.go
generated
vendored
Normal 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
|
||||
}
|
19
vendor/github.com/tidwall/evio/internal/internal_unix.go
generated
vendored
Normal file
19
vendor/github.com/tidwall/evio/internal/internal_unix.go
generated
vendored
Normal 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
Loading…
x
Reference in New Issue
Block a user