2018-10-29 01:49:45 +03:00
|
|
|
package server
|
2016-03-05 02:08:16 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2016-04-01 02:26:36 +03:00
|
|
|
"github.com/tidwall/resp"
|
2018-10-11 00:25:40 +03:00
|
|
|
"github.com/tidwall/tile38/internal/log"
|
2016-03-05 02:08:16 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var errNoLongerFollowing = errors.New("no longer following")
|
|
|
|
|
|
|
|
const checksumsz = 512 * 1024
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) cmdFollow(msg *Message) (res resp.Value, err error) {
|
2016-04-01 02:26:36 +03:00
|
|
|
start := time.Now()
|
2018-10-29 01:49:45 +03:00
|
|
|
vs := msg.Args[1:]
|
2016-04-01 02:26:36 +03:00
|
|
|
var ok bool
|
2016-03-05 02:08:16 +03:00
|
|
|
var host, sport string
|
Lua scripting feature. (#224)
* Start on lua scripting
* Implement evalsha, script load, script exists, and script flush
* Type conversions from lua to resp/json.
Refactor to make luastate and luascripts persistent in the controller.
* Change controller.command and all underlying commands to return resp.Value.
Serialize only during the ouput.
* First stab at tile38 call from lua
* Change tile38 into tile38.call in Lua
* Property return errors from scripts
* Minor refactoring. No locking on script run
* Cleanup/refactoring
* Create a pool of 5 lua states, allow for more as needed. Refactor.
* Use safe map for scripts. Add a limit for max number of lua states. Refactor.
* Refactor
* Refactor script commands into atomic, read-only, and non-atomic classes.
Proper locking for all three classes.
Add tests for scripts
* More tests for scripts
* Properly escape newlines in lua-produced errors
* Better test for readonly failure
* Correctly convert ok/err messages between lua and resp.
Add pcall, sha1hex, error_reply, status_reply functions to tile38 namespace in lua.
* Add pcall test. Change writeErr to work with string argument
* Make sure eval/evalsha never attempt to write AOF
* Add eval-set and eval-get to benchmarks
* Fix eval benchmark tests, add more
* Improve benchmarks
* Optimizations and refactoring.
* Add lua memtest
* Typo
* Add dependency
* golint fixes
* gofmt fixes
* Add scripting commands to the core/commands.json
* Use ARGV for args inside lua
2017-10-05 18:20:40 +03:00
|
|
|
|
2016-04-01 02:26:36 +03:00
|
|
|
if vs, host, ok = tokenval(vs); !ok || host == "" {
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, errInvalidNumberOfArguments
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if vs, sport, ok = tokenval(vs); !ok || sport == "" {
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, errInvalidNumberOfArguments
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if len(vs) != 0 {
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, errInvalidNumberOfArguments
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
host = strings.ToLower(host)
|
|
|
|
sport = strings.ToLower(sport)
|
|
|
|
var update bool
|
|
|
|
if host == "no" && sport == "one" {
|
2019-10-30 20:17:59 +03:00
|
|
|
update = s.config.followHost() != "" || s.config.followPort() != 0
|
|
|
|
s.config.setFollowHost("")
|
|
|
|
s.config.setFollowPort(0)
|
2016-03-05 02:08:16 +03:00
|
|
|
} else {
|
|
|
|
n, err := strconv.ParseUint(sport, 10, 64)
|
|
|
|
if err != nil {
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, errInvalidArgument(sport)
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
port := int(n)
|
2019-10-30 20:17:59 +03:00
|
|
|
update = s.config.followHost() != host || s.config.followPort() != port
|
|
|
|
auth := s.config.leaderAuth()
|
2016-03-05 02:08:16 +03:00
|
|
|
if update {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Unlock()
|
2016-04-01 02:26:36 +03:00
|
|
|
conn, err := DialTimeout(fmt.Sprintf("%s:%d", host, port), time.Second*2)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow: %v", err)
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
defer conn.Close()
|
2016-03-08 18:35:43 +03:00
|
|
|
if auth != "" {
|
2019-10-30 20:17:59 +03:00
|
|
|
if err := s.followDoLeaderAuth(conn, auth); err != nil {
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow: %v", err)
|
2016-03-08 18:35:43 +03:00
|
|
|
}
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
m, err := doServer(conn)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow: %v", err)
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if m["id"] == "" {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow: invalid id")
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
if m["id"] == s.config.serverID() {
|
|
|
|
s.mu.Lock()
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow self")
|
2016-04-01 02:26:36 +03:00
|
|
|
}
|
|
|
|
if m["following"] != "" {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2018-10-29 01:49:45 +03:00
|
|
|
return NOMessage, fmt.Errorf("cannot follow a follower")
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
s.config.setFollowHost(host)
|
|
|
|
s.config.setFollowPort(port)
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
s.config.write(false)
|
2016-03-05 02:08:16 +03:00
|
|
|
if update {
|
2022-09-27 20:15:31 +03:00
|
|
|
s.followc.Add(1)
|
2019-10-30 20:17:59 +03:00
|
|
|
if s.config.followHost() != "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
log.Infof("following new host '%s' '%s'.", host, sport)
|
2022-09-27 20:15:31 +03:00
|
|
|
go s.follow(s.config.followHost(), s.config.followPort(),
|
|
|
|
int(s.followc.Load()))
|
2016-03-05 02:08:16 +03:00
|
|
|
} else {
|
|
|
|
log.Infof("following no one")
|
|
|
|
}
|
|
|
|
}
|
2018-10-29 01:49:45 +03:00
|
|
|
return OKMessage(msg, start), nil
|
2016-04-01 02:26:36 +03:00
|
|
|
}
|
|
|
|
|
2019-01-19 00:51:20 +03:00
|
|
|
// cmdReplConf is a command handler that sets replication configuration info
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) cmdReplConf(msg *Message, client *Client) (res resp.Value, err error) {
|
2019-01-19 00:51:20 +03:00
|
|
|
start := time.Now()
|
|
|
|
vs := msg.Args[1:]
|
|
|
|
var ok bool
|
|
|
|
var cmd, val string
|
|
|
|
|
|
|
|
// Parse the message
|
|
|
|
if vs, cmd, ok = tokenval(vs); !ok || cmd == "" {
|
|
|
|
return NOMessage, errInvalidNumberOfArguments
|
|
|
|
}
|
2021-03-31 18:13:44 +03:00
|
|
|
if _, val, ok = tokenval(vs); !ok || val == "" {
|
2019-01-19 00:51:20 +03:00
|
|
|
return NOMessage, errInvalidNumberOfArguments
|
|
|
|
}
|
|
|
|
|
|
|
|
// Switch on the command received
|
|
|
|
switch cmd {
|
|
|
|
case "listening-port":
|
|
|
|
// Parse the port as an integer
|
|
|
|
port, err := strconv.Atoi(val)
|
|
|
|
if err != nil {
|
|
|
|
return NOMessage, errInvalidArgument(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the replication port to the client and return
|
2020-03-21 02:47:13 +03:00
|
|
|
s.connsmu.RLock()
|
|
|
|
defer s.connsmu.RUnlock()
|
2019-10-30 20:17:59 +03:00
|
|
|
for _, c := range s.conns {
|
2019-01-19 00:51:20 +03:00
|
|
|
if c.remoteAddr == client.remoteAddr {
|
2020-03-21 02:47:13 +03:00
|
|
|
c.mu.Lock()
|
2019-01-19 00:51:20 +03:00
|
|
|
c.replPort = port
|
2020-03-21 02:47:13 +03:00
|
|
|
c.mu.Unlock()
|
2019-01-19 00:51:20 +03:00
|
|
|
return OKMessage(msg, start), nil
|
|
|
|
}
|
|
|
|
}
|
2023-06-29 00:59:28 +03:00
|
|
|
case "ip-address":
|
|
|
|
// Apply the replication ip to the client and return
|
|
|
|
s.connsmu.RLock()
|
|
|
|
defer s.connsmu.RUnlock()
|
|
|
|
for _, c := range s.conns {
|
|
|
|
if c.remoteAddr == client.remoteAddr {
|
|
|
|
c.mu.Lock()
|
|
|
|
c.replAddr = val
|
|
|
|
c.mu.Unlock()
|
|
|
|
return OKMessage(msg, start), nil
|
|
|
|
}
|
|
|
|
}
|
2019-01-19 00:51:20 +03:00
|
|
|
}
|
|
|
|
return NOMessage, fmt.Errorf("cannot find follower")
|
|
|
|
}
|
|
|
|
|
2018-10-29 01:49:45 +03:00
|
|
|
func doServer(conn *RESPConn) (map[string]string, error) {
|
2016-04-01 02:26:36 +03:00
|
|
|
v, err := conn.Do("server")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if v.Error() != nil {
|
|
|
|
return nil, v.Error()
|
|
|
|
}
|
|
|
|
arr := v.Array()
|
|
|
|
m := make(map[string]string)
|
|
|
|
for i := 0; i < len(arr)/2; i++ {
|
|
|
|
m[arr[i*2+0].String()] = arr[i*2+1].String()
|
|
|
|
}
|
|
|
|
return m, err
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) followHandleCommand(args []string, followc int, w io.Writer) (int, error) {
|
|
|
|
s.mu.Lock()
|
|
|
|
defer s.mu.Unlock()
|
2022-09-27 20:15:31 +03:00
|
|
|
if int(s.followc.Load()) != followc {
|
2019-10-30 20:17:59 +03:00
|
|
|
return s.aofsz, errNoLongerFollowing
|
2016-04-01 02:26:36 +03:00
|
|
|
}
|
2018-10-29 01:49:45 +03:00
|
|
|
msg := &Message{Args: args}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
_, d, err := s.command(msg, nil)
|
2016-04-01 02:26:36 +03:00
|
|
|
if err != nil {
|
|
|
|
if commandErrIsFatal(err) {
|
2019-10-30 20:17:59 +03:00
|
|
|
return s.aofsz, err
|
2016-04-01 02:26:36 +03:00
|
|
|
}
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
if err := s.writeAOF(args, &d); err != nil {
|
|
|
|
return s.aofsz, err
|
2016-04-01 02:26:36 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
if len(s.aofbuf) > 10240 {
|
|
|
|
s.flushAOF(false)
|
2018-11-13 22:04:16 +03:00
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
return s.aofsz, nil
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) followDoLeaderAuth(conn *RESPConn, auth string) error {
|
2016-04-01 02:26:36 +03:00
|
|
|
v, err := conn.Do("auth", auth)
|
2016-03-08 18:35:43 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if v.Error() != nil {
|
|
|
|
return v.Error()
|
2016-03-08 18:35:43 +03:00
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if v.String() != "OK" {
|
|
|
|
return errors.New("cannot follow: auth no ok")
|
2016-03-08 18:35:43 +03:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) followStep(host string, port int, followc int) error {
|
2022-09-27 20:15:31 +03:00
|
|
|
if int(s.followc.Load()) != followc {
|
2016-03-05 02:08:16 +03:00
|
|
|
return errNoLongerFollowing
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
2023-05-21 18:22:27 +03:00
|
|
|
s.faofsz = 0
|
2019-10-30 20:17:59 +03:00
|
|
|
s.fcup = false
|
|
|
|
auth := s.config.leaderAuth()
|
|
|
|
s.mu.Unlock()
|
2016-03-05 02:08:16 +03:00
|
|
|
addr := fmt.Sprintf("%s:%d", host, port)
|
2016-04-01 02:26:36 +03:00
|
|
|
|
2016-03-05 02:08:16 +03:00
|
|
|
// check if we are following self
|
2016-04-01 02:26:36 +03:00
|
|
|
conn, err := DialTimeout(addr, time.Second*2)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot follow: %v", err)
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
2016-03-08 18:35:43 +03:00
|
|
|
if auth != "" {
|
2019-10-30 20:17:59 +03:00
|
|
|
if err := s.followDoLeaderAuth(conn, auth); err != nil {
|
2016-03-08 18:35:43 +03:00
|
|
|
return fmt.Errorf("cannot follow: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
m, err := doServer(conn)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot follow: %v", err)
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
|
|
|
|
if m["id"] == "" {
|
|
|
|
return fmt.Errorf("cannot follow: invalid id")
|
|
|
|
}
|
2019-10-30 20:17:59 +03:00
|
|
|
if m["id"] == s.config.serverID() {
|
2016-03-05 02:08:16 +03:00
|
|
|
return fmt.Errorf("cannot follow self")
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if m["following"] != "" {
|
2016-03-05 02:08:16 +03:00
|
|
|
return fmt.Errorf("cannot follow a follower")
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
|
2016-03-05 02:08:16 +03:00
|
|
|
// verify checksum
|
2022-09-22 04:44:09 +03:00
|
|
|
pos, err := s.followCheckSome(addr, followc, auth)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-01-19 00:51:20 +03:00
|
|
|
// Send the replication port to the leader
|
2023-06-29 00:59:28 +03:00
|
|
|
p := s.config.announcePort()
|
|
|
|
if p == 0 {
|
|
|
|
p = s.port
|
|
|
|
}
|
|
|
|
v, err := conn.Do("replconf", "listening-port", p)
|
2019-01-19 00:51:20 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if v.Error() != nil {
|
|
|
|
return v.Error()
|
|
|
|
}
|
|
|
|
if v.String() != "OK" {
|
|
|
|
return errors.New("invalid response to replconf request")
|
|
|
|
}
|
2023-06-29 00:59:28 +03:00
|
|
|
|
|
|
|
// Send the replication ip to the leader
|
|
|
|
ip := s.config.announceIP()
|
|
|
|
if ip != "" {
|
|
|
|
v, err := conn.Do("replconf", "ip-address", ip)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if v.Error() != nil {
|
|
|
|
return v.Error()
|
|
|
|
}
|
|
|
|
if v.String() != "OK" {
|
|
|
|
return errors.New("invalid response to replconf request")
|
|
|
|
}
|
|
|
|
}
|
2022-09-25 01:42:07 +03:00
|
|
|
if s.opts.ShowDebugMessages {
|
2019-01-19 00:51:20 +03:00
|
|
|
log.Debug("follow:", addr, ":replconf")
|
|
|
|
}
|
|
|
|
|
|
|
|
v, err = conn.Do("aof", pos)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
if v.Error() != nil {
|
|
|
|
return v.Error()
|
|
|
|
}
|
|
|
|
if v.String() != "OK" {
|
2016-03-05 02:08:16 +03:00
|
|
|
return errors.New("invalid response to aof live request")
|
|
|
|
}
|
2022-09-25 01:42:07 +03:00
|
|
|
if s.opts.ShowDebugMessages {
|
2016-03-05 02:08:16 +03:00
|
|
|
log.Debug("follow:", addr, ":read aof")
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
|
|
|
|
aofSize, err := strconv.ParseInt(m["aof_size"], 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-21 18:22:27 +03:00
|
|
|
s.mu.Lock()
|
|
|
|
s.faofsz = int(aofSize)
|
|
|
|
s.mu.Unlock()
|
|
|
|
|
2016-04-01 02:26:36 +03:00
|
|
|
caughtUp := pos >= aofSize
|
2016-03-05 02:08:16 +03:00
|
|
|
if caughtUp {
|
2019-10-30 20:17:59 +03:00
|
|
|
s.mu.Lock()
|
|
|
|
s.fcup = true
|
|
|
|
s.fcuponce = true
|
|
|
|
s.mu.Unlock()
|
2016-03-05 02:08:16 +03:00
|
|
|
log.Info("caught up")
|
|
|
|
}
|
2023-05-21 18:22:27 +03:00
|
|
|
|
2022-09-13 03:06:27 +03:00
|
|
|
nullw := io.Discard
|
2016-03-05 02:08:16 +03:00
|
|
|
for {
|
2016-04-01 02:26:36 +03:00
|
|
|
v, telnet, _, err := conn.rd.ReadMultiBulk()
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-01 02:26:36 +03:00
|
|
|
vals := v.Array()
|
|
|
|
if telnet || v.Type() != resp.Array {
|
|
|
|
return errors.New("invalid multibulk")
|
|
|
|
}
|
2018-10-29 01:49:45 +03:00
|
|
|
svals := make([]string, len(vals))
|
|
|
|
for i := 0; i < len(vals); i++ {
|
|
|
|
svals[i] = vals[i].String()
|
|
|
|
}
|
2016-04-01 03:58:02 +03:00
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
aofsz, err := s.followHandleCommand(svals, followc, nullw)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-21 18:22:27 +03:00
|
|
|
s.mu.Lock()
|
|
|
|
s.faofsz = aofsz
|
|
|
|
s.mu.Unlock()
|
2016-03-05 02:08:16 +03:00
|
|
|
if !caughtUp {
|
2016-04-01 02:26:36 +03:00
|
|
|
if aofsz >= int(aofSize) {
|
2016-03-05 02:08:16 +03:00
|
|
|
caughtUp = true
|
2023-05-21 18:29:30 +03:00
|
|
|
s.mu.Lock()
|
2019-10-30 20:17:59 +03:00
|
|
|
s.flushAOF(false)
|
|
|
|
s.fcup = true
|
|
|
|
s.fcuponce = true
|
|
|
|
s.mu.Unlock()
|
2016-03-05 02:08:16 +03:00
|
|
|
log.Info("caught up")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-30 20:17:59 +03:00
|
|
|
func (s *Server) follow(host string, port int, followc int) {
|
2016-03-05 02:08:16 +03:00
|
|
|
for {
|
2019-10-30 20:17:59 +03:00
|
|
|
err := s.followStep(host, port, followc)
|
2016-03-05 02:08:16 +03:00
|
|
|
if err == errNoLongerFollowing {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil && err != io.EOF {
|
2016-03-08 18:35:43 +03:00
|
|
|
log.Error("follow: " + err.Error())
|
2016-03-05 02:08:16 +03:00
|
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
}
|