redis TCP concurrency

This commit is contained in:
Meutel 2017-07-09 18:46:32 +02:00
parent d9437be5a2
commit 72c34f3568
2 changed files with 83 additions and 45 deletions

View File

@ -10,6 +10,17 @@ import (
"strings"
)
type RedisCmd struct {
cmd string
key string
val string
}
type CommandExec struct {
*RedisCmd
Result chan string
}
func getCommand(reader io.Reader) (string, error) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
@ -32,8 +43,8 @@ func isCommand(str string) bool {
}
}
func parseCmd(line string) (string, string, string, error) {
cmd, key, val := "", "", ""
func parseCmd(line string) (*RedisCmd, error) {
redisCmd := new(RedisCmd)
var err error
parts := strings.Split(line, " ")
if len(parts) != 2 && len(parts) != 3 {
@ -43,23 +54,23 @@ func parseCmd(line string) (string, string, string, error) {
switch i {
case 0:
if isCommand(part) {
cmd = part
redisCmd.cmd = part
} else {
err = errors.New("Unknown command")
break
}
case 1:
key = part
redisCmd.key = part
case 2:
if cmd == "SET" {
val = part
if redisCmd.cmd == "SET" {
redisCmd.val = part
} else {
err = errors.New("Invalid number of arguments")
}
}
}
}
return cmd, key, val, err
return redisCmd, err
}
func respond(writer io.Writer, ret string) {
@ -78,39 +89,68 @@ func main() {
}
defer ln.Close()
commands := make(chan CommandExec)
go func() {
for exec := range commands {
switch exec.cmd {
case "DEL":
log.Println("Deleting", exec.key)
delete(db, exec.key)
close(exec.Result)
case "GET":
log.Println("Getting", exec.key)
ret, ok := db[exec.key]
if ok {
log.Println("Value found", ret)
go func() {
exec.Result <- ret
}()
} else {
log.Println("Unknown key", exec.key)
close(exec.Result)
}
case "SET":
log.Println("Setting", exec.key)
db[exec.key] = exec.val
close(exec.Result)
}
}
}()
for {
conn, err := ln.Accept()
if err != nil {
panic(err)
}
results := make(chan string)
go func() {
line, err := getCommand(conn)
if err != nil {
respond(conn, fmt.Sprint(err))
}
cmd, key, val, err := parseCmd(line)
if err != nil {
respond(conn, fmt.Sprint(err))
}
switch cmd {
case "DEL":
log.Println("Deleting", key)
delete(db, key)
case "GET":
log.Println("Getting", key)
ret, ok := db[key]
ret, ok := <-results
if ok {
log.Println("Value found", ret)
respond(conn, ret)
} else {
log.Println("Unknown key", key)
}
case "SET":
log.Println("Setting", key)
db[key] = val
}
defer conn.Close()
}()
go func() {
exec := new(CommandExec)
exec.Result = results
line, err := getCommand(conn)
if err != nil {
go func() {
exec.Result <- fmt.Sprint(err)
}()
}
cmd, err := parseCmd(line)
if err != nil {
go func() {
exec.Result <- fmt.Sprint(err)
}()
}
exec.RedisCmd = cmd
commands <- *exec
}()
}
}

View File

@ -47,20 +47,18 @@ func TestRespond(t *testing.T) {
type ParseCmdTest struct {
line string
fails bool
cmd string
key string
val string
RedisCmd
}
func TestParseCommand(t *testing.T) {
tests := []ParseCmdTest{
{"test", true, "", "", ""},
{"test test", true, "", "", ""},
{"SET 1 2 3", true, "", "", ""},
{"GET 1 2", true, "", "", ""},
{"test", true, RedisCmd{"", "", ""}},
{"test test", true, RedisCmd{"", "", ""}},
{"SET 1 2 3", true, RedisCmd{"", "", ""}},
{"GET 1 2", true, RedisCmd{"", "", ""}},
}
for _, test := range tests {
c, k, v, e := parseCmd(test.line)
c, e := parseCmd(test.line)
if test.fails {
if e == nil {
t.Log("should fail (" + test.line + ")")
@ -71,16 +69,16 @@ func TestParseCommand(t *testing.T) {
t.Log("should no fail", e)
t.Fail()
}
if c != test.cmd {
t.Log("Error command", c, test.cmd)
if c.cmd != test.cmd {
t.Log("Error command", c.cmd, test.cmd)
t.Fail()
}
if k != test.key {
t.Log("Error key", k, test.key)
if c.key != test.key {
t.Log("Error key", c.key, test.key)
t.Fail()
}
if v != test.val {
t.Log("Error value", v, test.val)
if c.val != test.val {
t.Log("Error value", c.val, test.val)
t.Fail()
}
}