From 16700083f91948258c88cd3978cd41b06dcf61b8 Mon Sep 17 00:00:00 2001 From: Xinran Xu Date: Fri, 2 Dec 2016 18:00:54 +0800 Subject: [PATCH 1/3] Bugfix: fmt.Sscanf need indicate '\r\n' explicitly The older version think '*2\r\n' as invalid input when parsing multiline command. SScanf must indicate '\r\n' explicitly now. --- parser.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index caaf49c..2ebfeb7 100644 --- a/parser.go +++ b/parser.go @@ -21,7 +21,7 @@ func parseRequest(conn io.ReadCloser) (*Request, error) { // Multiline request: if line[0] == '*' { - if _, err := fmt.Sscanf(line, "*%d\r", &argsCount); err != nil { + if _, err := fmt.Sscanf(line, "*%d\r\n", &argsCount); err != nil { return nil, malformed("*", line) } // All next lines are pairs of: @@ -71,7 +71,7 @@ func readArgument(r *bufio.Reader) ([]byte, error) { return nil, malformed("$", line) } var argSize int - if _, err := fmt.Sscanf(line, "$%d\r", &argSize); err != nil { + if _, err := fmt.Sscanf(line, "$%d\r\n", &argSize); err != nil { return nil, malformed("$", line) } From a8b718fe7a05ccf67a655a1f2ae03541148f0882 Mon Sep 17 00:00:00 2001 From: Xu Xinran Date: Sat, 3 Dec 2016 22:51:26 +0800 Subject: [PATCH 2/3] bugfix: use same bufio.Reader for all requests in one connection. Old code create bufio.Reader for each request. When using pipeline mode, bufio.Reader may eat some input data which belong to next request. --- parser.go | 5 +---- request.go | 6 +----- server.go | 4 +++- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/parser.go b/parser.go index 2ebfeb7..3e590bf 100644 --- a/parser.go +++ b/parser.go @@ -8,8 +8,7 @@ import ( "strings" ) -func parseRequest(conn io.ReadCloser) (*Request, error) { - r := bufio.NewReader(conn) +func parseRequest(r *bufio.Reader) (*Request, error) { // first line of redis request should be: // *CRLF line, err := r.ReadString('\n') @@ -43,7 +42,6 @@ func parseRequest(conn io.ReadCloser) (*Request, error) { return &Request{ Name: strings.ToLower(string(firstArg)), Args: args, - Body: conn, }, nil } @@ -59,7 +57,6 @@ func parseRequest(conn io.ReadCloser) (*Request, error) { return &Request{ Name: strings.ToLower(string(fields[0])), Args: args, - Body: conn, }, nil } diff --git a/request.go b/request.go index 4888e69..dd16048 100644 --- a/request.go +++ b/request.go @@ -1,16 +1,12 @@ package redis -import ( - "io" - "strconv" -) +import "strconv" type Request struct { Name string Args [][]byte Host string ClientChan chan struct{} - Body io.ReadCloser } func (r *Request) HasArgument(index int) bool { diff --git a/server.go b/server.go index c4a4131..14a1f4c 100644 --- a/server.go +++ b/server.go @@ -5,6 +5,7 @@ package redis import ( + "bufio" "fmt" "io" "io/ioutil" @@ -88,8 +89,9 @@ func (srv *Server) ServeClient(conn net.Conn) (err error) { clientAddr = co.RemoteAddr().String() } + br := bufio.NewReader(conn) for { - request, err := parseRequest(conn) + request, err := parseRequest(br) if err != nil { return err } From 6ead2866efbbf50fbc1cd75eaadb948b9e1b9cfe Mon Sep 17 00:00:00 2001 From: Xu Xinran Date: Mon, 5 Dec 2016 11:27:59 +0800 Subject: [PATCH 3/3] add maxConnection limit, try to avoid OOM by limit connection number --- server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index 14a1f4c..e7d0c90 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,7 @@ package redis import ( "bufio" "fmt" + "golang.org/x/net/netutil" "io" "io/ioutil" "net" @@ -20,7 +21,8 @@ type Server struct { methods map[string]HandlerFn } -func (srv *Server) ListenAndServe() error { +// ListenAndServe receives an argument maxConnection, which limit max connection it can accept simultaneous, passing maxConnection <= 0 means no limit. +func (srv *Server) ListenAndServe(maxConnection int) error { addr := srv.Addr if srv.Proto == "" { srv.Proto = "tcp" @@ -34,6 +36,9 @@ func (srv *Server) ListenAndServe() error { if e != nil { return e } + if maxConnection > 0 { + l = netutil.LimitListener(l, maxConnection) + } return srv.Serve(l) }