diff --git a/cmd/derod/rpc/nbio_websocket_test.go b/cmd/derod/rpc/nbio_websocket_test.go new file mode 100644 index 00000000..527d0dd1 --- /dev/null +++ b/cmd/derod/rpc/nbio_websocket_test.go @@ -0,0 +1,99 @@ +package rpc + +import ( + "crypto/tls" + "net" + "net/http" + "sync/atomic" + "testing" + "time" + + gws "github.com/gorilla/websocket" + ltls "github.com/lesismal/llib/std/crypto/tls" + "github.com/lesismal/nbio/nbhttp" + "github.com/lesismal/nbio/nbhttp/websocket" +) + +// TestNbioWebsocketRoundTrip validates that the bumped nbio (v1.6.11) websocket +// server still upgrades HTTP connections, delivers messages, and fires the +// connection-close callback. This exercises the exact library the getwork +// mining server relies on (websocket_getwork_server.go), which the simulator +// integration suite does not cover. +func TestNbioWebsocketRoundTrip(t *testing.T) { + // Reserve an ephemeral port. + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("listen: %v", err) + } + addr := l.Addr().String() + l.Close() + + // Self-signed cert (same generator the production getwork server uses, + // backed by llib's crypto/tls which nbio v1.6.11 requires). + tlsCert := generate_random_tls_cert() + tlsConfig := <ls.Config{Certificates: []ltls.Certificate{tlsCert}} + + upgrader := websocket.NewUpgrader() + var closed int32 + upgrader.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { + // Echo the payload back to the client. + _ = c.WriteMessage(messageType, data) + }) + upgrader.OnClose(func(c *websocket.Conn, err error) { + atomic.AddInt32(&closed, 1) + }) + + mux := &http.ServeMux{} + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if _, err := upgrader.Upgrade(w, r, nil); err != nil { + t.Errorf("upgrade failed: %v", err) + } + }) + + svr := nbhttp.NewServer(nbhttp.Config{ + Name: "TEST", + Network: "tcp", + AddrsTLS: []string{addr}, + TLSConfig: tlsConfig, + Handler: mux, + NPoller: 1, + }) + if err := svr.Start(); err != nil { + t.Fatalf("server start failed: %v", err) + } + defer svr.Stop() + + // Allow the listener to come up. + time.Sleep(200 * time.Millisecond) + + dialer := gws.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + c, _, err := dialer.Dial("wss://"+addr+"/", nil) + if err != nil { + t.Fatalf("dial failed: %v", err) + } + defer c.Close() + + // Send a frame and expect it echoed back. + msg := []byte("hello-nbio-v1.6.11") + if err := c.WriteMessage(gws.TextMessage, msg); err != nil { + t.Fatalf("write failed: %v", err) + } + c.SetReadDeadline(time.Now().Add(2 * time.Second)) + mt, got, err := c.ReadMessage() + if err != nil { + t.Fatalf("read failed: %v", err) + } + if mt != gws.TextMessage { + t.Fatalf("message type = %d, want TextMessage", mt) + } + if string(got) != string(msg) { + t.Fatalf("echo = %q, want %q", got, msg) + } + + // Closing the client must trigger the server-side OnClose callback. + c.Close() + time.Sleep(200 * time.Millisecond) + if atomic.LoadInt32(&closed) == 0 { + t.Fatalf("expected OnClose to fire after client disconnect") + } +} diff --git a/cmd/derod/rpc/websocket_getwork_server.go b/cmd/derod/rpc/websocket_getwork_server.go index 56c86f88..b4d91eed 100644 --- a/cmd/derod/rpc/websocket_getwork_server.go +++ b/cmd/derod/rpc/websocket_getwork_server.go @@ -291,7 +291,7 @@ func onWebsocket(w http.ResponseWriter, r *http.Request) { //panic(err) return } - wsConn := conn.(*websocket.Conn) + wsConn := conn session := user_session{address: *addr, address_sum: graviton.Sum(addr_raw)} wsConn.SetSession(&session) @@ -350,11 +350,12 @@ func Getwork_server() { NPoller: runtime.NumCPU(), }) - svr.OnReadBufferAlloc(func(c *nbio.Conn) []byte { - return memPool.Get().([]byte) + svr.OnReadBufferAlloc(func(c *nbio.Conn) *[]byte { + b := memPool.Get().([]byte) + return &b }) - svr.OnReadBufferFree(func(c *nbio.Conn, b []byte) { - memPool.Put(b) + svr.OnReadBufferFree(func(c *nbio.Conn, b *[]byte) { + memPool.Put(*b) }) //globals.Cron.AddFunc("@every 2s", SendJob) // if daemon restart automaticaly send job diff --git a/tests/normal/nbio_getwork_test/run_test.sh b/tests/normal/nbio_getwork_test/run_test.sh new file mode 100755 index 00000000..916619f2 --- /dev/null +++ b/tests/normal/nbio_getwork_test/run_test.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# +# Integration test for the nbio getwork (mining) websocket server. +# +# This exercises the REAL daemon getwork path (cmd/derod/rpc/websocket_getwork_server.go, +# which is built on nbio) end-to-end with a real miner client (cmd/dero-miner, which uses +# gorilla/websocket). The simulator used by run_integration_test.sh does NOT start the +# getwork server, so this standalone test is the only one that covers the bumped nbio +# code path in production conditions. +# +# When run under run_integration_test.sh, this test starts its OWN derod +# (the harness simulator does not start the getwork server) and uses dedicated, +# non-default ports so it never collides with the harness simulator (20000/8080) +# or the persistent dev daemon (10100/10102). +# +# Usage: bash tests/normal/nbio_getwork_test/run_test.sh + +set -u + +ABSDIR=$(cd "$(dirname "$0")" && pwd) +REPO_ROOT=$(cd "$ABSDIR/../../.." && pwd) +cd "$REPO_ROOT" + +export GOPROXY=https://proxy.golang.org,direct +export GOSUMDB=sum.golang.org +export PATH="$HOME/go/bin:$PATH" + +ADDRESS="deto1qy0ehnqjpr0wxqnknyc66du2fsxyktppkr8m8e6jvplp954klfjz2qqdzcd8p" +GETWORK_PORT=18092 +DATADIR=/tmp/nbio_getwork_test +DEROD_LOG=/tmp/nbio_getwork_derod.log +MINER_LOG=/tmp/nbio_getwork_miner.log + +cleanup() { + pkill -f "/tmp/nbio_derod" 2>/dev/null + pkill -f "/tmp/nbio_miner" 2>/dev/null + rm -rf "$DATADIR" "$DEROD_LOG" "$MINER_LOG" +} +trap cleanup EXIT + +echo "==> Building derod + dero-miner" +go build -o /tmp/nbio_derod ./cmd/derod || { echo "FAIL: derod build"; exit 1; } +go build -o /tmp/nbio_miner ./cmd/dero-miner || { echo "FAIL: dero-miner build"; exit 1; } + +echo "==> Starting derod --testnet (getwork on 127.0.0.1:$GETWORK_PORT, rpc 18093, p2p disabled)" +rm -rf "$DATADIR" +mkdir -p "$DATADIR" +/tmp/nbio_derod --testnet --data-dir="$DATADIR" --rpc-bind="127.0.0.1:18093" --p2p-bind=":0" --getwork-bind="127.0.0.1:$GETWORK_PORT" --clog-level=2 >"$DEROD_LOG" 2>&1 & +disown + +echo "==> Waiting for getwork server to start" +started=0 +for i in $(seq 1 60); do + if grep -q "GETWORK/Websocket server started" "$DEROD_LOG" 2>/dev/null; then + started=1 + break + fi + sleep 1 +done +if [ "$started" -ne 1 ]; then + echo "FAIL: getwork server did not start" + tail -30 "$DEROD_LOG" + exit 1 +fi +echo " getwork server started" + +echo "==> Starting dero-miner (gorilla/websocket client -> nbio server)" +/tmp/nbio_miner --testnet --daemon-rpc-address="127.0.0.1:$GETWORK_PORT" --wallet-address="$ADDRESS" >"$MINER_LOG" 2>&1 & +disown + +echo "==> Waiting for miner to connect and receive a job" +sleep 12 + +if ! grep -q "wss://127.0.0.1:$GETWORK_PORT" "$MINER_LOG"; then + echo "FAIL: miner did not attempt to connect" + cat "$MINER_LOG" + exit 1 +fi + +if grep -q "Error connecting to server" "$MINER_LOG"; then + echo "FAIL: miner failed to connect to the bumped nbio getwork server" + echo "---- miner log ----" + cat "$MINER_LOG" + echo "---- derod log (tail) ----" + tail -20 "$DEROD_LOG" + exit 1 +fi + +echo "PASS: miner connected to the bumped nbio getwork server and received work" +exit 0 diff --git a/vendor/github.com/lesismal/llib/README.md b/vendor/github.com/lesismal/llib/README.md index 61f364e0..e5a94530 100644 --- a/vendor/github.com/lesismal/llib/README.md +++ b/vendor/github.com/lesismal/llib/README.md @@ -1,4 +1,4 @@ -# llib - [lesismal](https://github.com/lesismal)'s lib +# llib - [nbio](https://github.com/lesismal/nbio)'s dependency lib. [![GoDoc][1]][2] [![MIT licensed][3]][4] [![Go Version][5]][6] @@ -9,4 +9,11 @@ [5]: https://img.shields.io/badge/go-%3E%3D1.16-30dff3?style=flat-square&logo=go [6]: https://github.com/lesismal/llib -Less Is More :smile: + +## Features +- [x] Blocking/NonBlocking TLS interface(rewritten from a copy of golang 1.6 std's tls). + + +## Why this lib? +- [nbio](https://github.com/lesismal/nbio) itself depends on the golang std and llib only and keeps the dependencies clean. +- If new features of [nbio](https://github.com/lesismal/nbio) needs to depend on 3rd libs, it will be implemented in llib too. diff --git a/vendor/github.com/lesismal/llib/bytes/buffer.go b/vendor/github.com/lesismal/llib/bytes/buffer.go deleted file mode 100644 index d1dc3a94..00000000 --- a/vendor/github.com/lesismal/llib/bytes/buffer.go +++ /dev/null @@ -1,225 +0,0 @@ -package bytes - -import ( - "errors" -) - -var ( - ErrInvalidLength = errors.New("invalid length") - ErrInvalidPosition = errors.New("invalid position") - ErrNotEnougth = errors.New("bytes not enougth") -) - -// Buffer . -type Buffer struct { - total int - buffers [][]byte - onRelease func(b []byte) -} - -// Len . -func (bb *Buffer) Len() int { - return bb.total -} - -// Push . -func (bb *Buffer) Push(b []byte) { - if len(b) == 0 { - return - } - bb.buffers = append(bb.buffers, b) - bb.total += len(b) -} - -// Pop . -func (bb *Buffer) Pop(n int) ([]byte, error) { - if n < 0 { - return nil, ErrInvalidLength - } - if bb.total < n { - return nil, ErrNotEnougth - } - - bb.total -= n - - var buf = bb.buffers[0] - if len(buf) >= n { - ret := buf[:n] - bb.buffers[0] = bb.buffers[0][n:] - if len(bb.buffers[0]) == 0 { - bb.releaseHead() - } - return ret, nil - } - - var ret = make([]byte, n)[0:0] - for n > 0 { - if len(buf) >= n { - ret = append(ret, buf[:n]...) - bb.buffers[0] = bb.buffers[0][n:] - if len(bb.buffers[0]) == 0 { - bb.releaseHead() - } - return ret, nil - } - ret = append(ret, buf...) - bb.releaseHead() - n -= len(buf) - buf = bb.buffers[0] - } - return ret, nil -} - -// Append . -func (bb *Buffer) Append(b []byte) { - if len(b) == 0 { - return - } - - n := len(bb.buffers) - - if n == 0 { - bb.buffers = append(bb.buffers, b) - return - } - bb.buffers[n-1] = append(bb.buffers[n-1], b...) - bb.total += len(b) -} - -// Head . -func (bb *Buffer) Head(n int) ([]byte, error) { - if n < 0 { - return nil, ErrInvalidLength - } - if bb.total < n { - return nil, ErrNotEnougth - } - - if len(bb.buffers[0]) >= n { - return bb.buffers[0][:n], nil - } - - ret := make([]byte, n) - - copied := 0 - for i := 0; n > 0; i++ { - buf := bb.buffers[i] - if len(buf) >= n { - copy(ret[copied:], buf[:n]) - return ret, nil - } else { - copy(ret[copied:], buf) - n -= len(buf) - copied += len(buf) - } - } - - return ret, nil -} - -// Sub . -func (bb *Buffer) Sub(from, to int) ([]byte, error) { - if from < 0 || to < 0 || to < from { - return nil, ErrInvalidPosition - } - if bb.total < to { - return nil, ErrNotEnougth - } - - if len(bb.buffers[0]) >= to { - return bb.buffers[0][from:to], nil - } - - n := to - from - ret := make([]byte, n) - copied := 0 - for i := 0; n > 0; i++ { - buf := bb.buffers[i] - if len(buf) >= from+n { - copy(ret[copied:], buf[from:from+n]) - return ret, nil - } else { - if len(buf) > from { - if from > 0 { - buf = buf[from:] - from = 0 - } - copy(ret[copied:], buf) - copied += len(buf) - n -= len(buf) - } else { - from -= len(buf) - } - } - } - - return ret, nil -} - -// Write . -func (bb *Buffer) Write(b []byte) { - bb.Push(b) -} - -// Read . -func (bb *Buffer) Read(n int) ([]byte, error) { - return bb.Pop(n) -} - -// ReadAll . -func (bb *Buffer) ReadAll() ([]byte, error) { - if len(bb.buffers) == 0 { - return nil, nil - } - - ret := append([]byte{}, bb.buffers[0]...) - if bb.onRelease != nil { - bb.onRelease(bb.buffers[0]) - for i := 1; i < len(bb.buffers); i++ { - ret = append(ret, bb.buffers[i]...) - bb.onRelease(bb.buffers[i]) - - } - } else { - for i := 1; i < len(bb.buffers); i++ { - ret = append(ret, bb.buffers[i]...) - } - } - bb.buffers = nil - bb.total = 0 - - return ret, nil -} - -// Reset . -func (bb *Buffer) Reset() { - if bb.onRelease != nil { - for i := 0; i < len(bb.buffers); i++ { - bb.onRelease(bb.buffers[i]) - - } - } - bb.buffers = nil - bb.total = 0 -} - -func (bb *Buffer) OnRelease(onRelease func(b []byte)) { - bb.onRelease = onRelease -} - -func (bb *Buffer) releaseHead() { - if bb.onRelease != nil { - bb.onRelease(bb.buffers[0]) - } - switch len(bb.buffers) { - case 1: - bb.buffers = nil - default: - bb.buffers = bb.buffers[1:] - } -} - -// NewBuffer . -func NewBuffer() *Buffer { - return &Buffer{} -} diff --git a/vendor/github.com/lesismal/llib/bytes/buffer_test.go b/vendor/github.com/lesismal/llib/bytes/buffer_test.go deleted file mode 100644 index 982a3c87..00000000 --- a/vendor/github.com/lesismal/llib/bytes/buffer_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package bytes - -import ( - "testing" -) - -func TestBuffer(t *testing.T) { - str := "hello world" - - buffer := NewBuffer() - buffer.Write([]byte("hel")) - buffer.Write([]byte("lo world")) - b, err := buffer.ReadAll() - if err != nil { - t.Fatal(err) - } - if string(b) != str { - t.Fatal(string(b)) - } - - buffer.Write([]byte("hel")) - buffer.Write([]byte("lo ")) - buffer.Write([]byte("wor")) - buffer.Write([]byte("ld")) - for i := 0; i < len(str); i++ { - for j := i; j < len(str); j++ { - sub, err := buffer.Sub(i, j) - if err != nil { - t.Fatal(err) - } - if string(sub) != string([]byte(str)[i:j]) { - t.Fatalf("[%v:%v] %v != %v", i, j, string(sub), string([]byte(str)[i:j])) - } - } - } - - for i := 0; i < len(str); i++ { - for j := i; j < len(str); j++ { - buffer.Write([]byte("hel")) - buffer.Write([]byte("lo ")) - buffer.Write([]byte("wor")) - buffer.Write([]byte("ld")) - - b, err = buffer.Read(j) - if err != nil { - t.Fatal(err) - } - if string(b) != string([]byte(str)[:j]) { - t.Fatalf("[%v:%v] %v != %v", i, j, string(b), string([]byte(str)[:j])) - } - - buffer.Reset() - } - } - - for i := 0; i < len(str); i++ { - for j := i; j < len(str); j++ { - buffer.Write([]byte("hel")) - buffer.Write([]byte("lo ")) - buffer.Write([]byte("wor")) - buffer.Write([]byte("ld")) - - buffer.Read(i) - b, err = buffer.Read(j - i) - if err != nil { - t.Fatal(err) - } - if string(b) != string([]byte(str)[i:j]) { - t.Fatalf("[%v:%v] %v != %v", i, j, string(b), string([]byte(str)[i:j])) - } - - buffer.Reset() - } - } - - buffer.Append([]byte("hello")) - buffer.Append([]byte(" world")) - if string(buffer.buffers[0]) != "hello world" { - t.Fatal(string(buffer.buffers[0])) - } - b, err = buffer.ReadAll() - if err != nil { - t.Fatal(err) - } - if string(b) != "hello world" { - t.Fatal(string(b)) - } - - buffer.Reset() - - buffer.Push([]byte("hello ")) - buffer.Push([]byte("world")) - if string(buffer.buffers[0]) != "hello " { - t.Fatal(string(buffer.buffers[0])) - } - buffer.Pop(1) - if string(buffer.buffers[0]) != "ello " { - t.Fatal(string(buffer.buffers[0])) - } - buffer.Pop(5) - if string(buffer.buffers[0]) != "world" { - t.Fatal(string(buffer.buffers[0])) - } - buffer.ReadAll() - if len(buffer.buffers) != 0 { - t.Fatal(string(buffer.buffers[0])) - } -} diff --git a/vendor/github.com/lesismal/llib/bytes/pool.go b/vendor/github.com/lesismal/llib/bytes/pool.go deleted file mode 100644 index 3a6cb6de..00000000 --- a/vendor/github.com/lesismal/llib/bytes/pool.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package bytes - -import ( - "sync" -) - -// maxAppendSize represents the max size to append to a slice. -const maxAppendSize = 1024 * 1024 * 4 - -// Pool is the default instance of []byte pool. -// User can customize a Pool implementation and reset this instance if needed. -var Pool interface { - Get() []byte - GetN(size int) []byte - Put(b []byte) -} = NewPool(64) - -// bufferPool is a default implementatiion of []byte Pool. -type bufferPool struct { - sync.Pool - MinSize int -} - -// NewPool creates and returns a bufferPool instance. -// All slice created by this instance has an initial cap of minSize. -func NewPool(minSize int) *bufferPool { - if minSize <= 0 { - minSize = 64 - } - bp := &bufferPool{ - MinSize: minSize, - } - bp.Pool.New = func() interface{} { - buf := make([]byte, bp.MinSize) - return &buf - } - return bp -} - -// Get gets a slice from the pool and returns it with length 0. -// User can append the slice and should Put it back to the pool after being used over. -func (bp *bufferPool) Get() []byte { - pbuf := bp.Pool.Get().(*[]byte) - return (*pbuf)[0:0] -} - -// GetN returns a slice with length size. -// To reuse slices as possible, -// if the cap of the slice got from the pool is not enough, -// It will append the slice, -// or put the slice back to the pool and create a new slice with cap of size. -// -// User can use the slice both by the size or append it, -// and should Put it back to the pool after being used over. -func (bp *bufferPool) GetN(size int) []byte { - pbuf := bp.Pool.Get().(*[]byte) - need := size - cap(*pbuf) - if need > 0 { - if need <= maxAppendSize { - *pbuf = (*pbuf)[:cap(*pbuf)] - *pbuf = append(*pbuf, make([]byte, need)...) - } else { - bp.Pool.Put(pbuf) - newBuf := make([]byte, size) - pbuf = &newBuf - } - } - - return (*pbuf)[:size] -} - -// Put puts a slice back to the pool. -// If the slice's cap is smaller than MinSize, -// it will not be put back to the pool but dropped. -func (bp *bufferPool) Put(b []byte) { - if cap(b) < bp.MinSize { - return - } - bp.Pool.Put(&b) -} diff --git a/vendor/github.com/lesismal/llib/bytes/pool_test.go b/vendor/github.com/lesismal/llib/bytes/pool_test.go deleted file mode 100644 index bf4e9877..00000000 --- a/vendor/github.com/lesismal/llib/bytes/pool_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package bytes - -import "testing" - -func TestMemPool(t *testing.T) { - const minMemSize = 64 - pool := NewPool(minMemSize) - for i := 0; i < 1024*1024; i++ { - buf := pool.GetN(i) - if len(buf) != i { - t.Fatalf("invalid length: %v != %v", len(buf), i) - } - pool.Put(buf) - } - for i := 1024 * 1024; i < 1024*1024*1024; i += 1024 * 1024 { - buf := pool.GetN(i) - if len(buf) != i { - t.Fatalf("invalid length: %v != %v", len(buf), i) - } - pool.Put(buf) - } -} diff --git a/vendor/github.com/lesismal/llib/concurrent/batch.go b/vendor/github.com/lesismal/llib/concurrent/batch.go deleted file mode 100644 index 5ec0244b..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/batch.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "sync" -) - -var ( - _defaultBatch = NewBatch() -) - -type call struct { - mux sync.RWMutex - ret interface{} - err error -} - -// Batch . -type Batch struct { - _mux sync.Mutex - _callings map[interface{}]*call -} - -// Do . -func (o *Batch) Do(key interface{}, f func() (interface{}, error)) (interface{}, error) { - o._mux.Lock() - c, ok := o._callings[key] - if ok { - o._mux.Unlock() - c.mux.RLock() - c.mux.RUnlock() - return c.ret, c.err - } - - c = &call{} - c.mux.Lock() - o._callings[key] = c - o._mux.Unlock() - c.ret, c.err = f() - c.mux.Unlock() - - o._mux.Lock() - delete(o._callings, key) - o._mux.Unlock() - - return c.ret, c.err -} - -// NewBatch . -func NewBatch() *Batch { - return &Batch{_callings: map[interface{}]*call{}} -} - -// Do . -func Do(key interface{}, f func() (interface{}, error)) (interface{}, error) { - return _defaultBatch.Do(key, f) -} diff --git a/vendor/github.com/lesismal/llib/concurrent/batch_test.go b/vendor/github.com/lesismal/llib/concurrent/batch_test.go deleted file mode 100644 index a803d4fc..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/batch_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "log" - "testing" - "time" -) - -func TestBatch(t *testing.T) { - batchCall := func() (interface{}, error) { - time.Sleep(time.Second) - return time.Now().Format("2006/01/02 15:04:05.000"), nil - } - for i := 0; i < 10; i++ { - go func(id int) { - ret, err := Do(3, batchCall) - log.Println("Batch().Do():", id, ret, err) - }(2) - } - func(id int) { - ret, err := Do(3, batchCall) - log.Println("Batch().Do():", id, ret, err) - }(1) - - func(id int) { - ret, err := Do(3, batchCall) - log.Println("Batch().Do():", id, ret, err) - }(3) - time.Sleep(time.Second) -} diff --git a/vendor/github.com/lesismal/llib/concurrent/map.go b/vendor/github.com/lesismal/llib/concurrent/map.go deleted file mode 100644 index cf4aa0ba..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/map.go +++ /dev/null @@ -1,100 +0,0 @@ -package concurrent - -import ( - "sync" - "sync/atomic" - - "github.com/cespare/xxhash" -) - -type bucket struct { - mux sync.RWMutex - values map[string]interface{} -} - -func (b *bucket) Get(k string) (interface{}, bool) { - b.mux.RLock() - v, ok := b.values[k] - b.mux.RUnlock() - return v, ok -} - -func (b *bucket) Set(k string, v interface{}) bool { - b.mux.Lock() - _, exsist := b.values[k] - b.values[k] = v - b.mux.Unlock() - return !exsist -} - -func (b *bucket) Delete(k string) bool { - b.mux.Lock() - _, exsist := b.values[k] - delete(b.values, k) - b.mux.Unlock() - return exsist -} - -func (b *bucket) forEach(f func(k string, v interface{}) bool) bool { - success := false - b.mux.RLock() - for k, v := range b.values { - success = f(k, v) - if !success { - break - } - } - b.mux.RUnlock() - return success -} - -type Map struct { - size int64 - buckets []*bucket -} - -func (m *Map) Get(k string) (interface{}, bool) { - i := hash(k) % uint64(len(m.buckets)) - return m.buckets[i].Get(k) -} - -func (m *Map) Set(k string, v interface{}) { - i := hash(k) % uint64(len(m.buckets)) - if m.buckets[i].Set(k, v) { - atomic.AddInt64(&m.size, 1) - } -} - -func (m *Map) Delete(k string) { - i := hash(k) % uint64(len(m.buckets)) - if m.buckets[i].Delete(k) { - atomic.AddInt64(&m.size, -1) - } -} - -func (m *Map) Size() int64 { - return atomic.LoadInt64(&m.size) -} - -func (m *Map) ForEach(f func(k string, v interface{}) bool) { - for _, b := range m.buckets { - if !b.forEach(f) { - return - } - } -} - -func NewMap(bucketNum int) *Map { - if bucketNum <= 0 { - bucketNum = 64 - } - m := &Map{buckets: make([]*bucket, bucketNum)} - for i := 0; i < bucketNum; i++ { - m.buckets[i] = &bucket{values: map[string]interface{}{}} - } - return m -} - -func hash(k string) uint64 { - return xxhash.Sum64String(k) -} diff --git a/vendor/github.com/lesismal/llib/concurrent/map_test.go b/vendor/github.com/lesismal/llib/concurrent/map_test.go deleted file mode 100644 index c3d67c15..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/map_test.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "fmt" - "log" - "testing" -) - -func TestMap(t *testing.T) { - m := NewMap(64) - size := 100000 - for i := 0; i < size; i++ { - k := fmt.Sprintf("key_%d", i) - v := fmt.Sprintf("value_%d", i) - vv, ok := m.Get(k) - if ok { - log.Fatalf("[%v] exists: '%v'", k, vv) - } - m.Set(k, v) - vv, ok = m.Get(k) - if !ok { - log.Fatalf("[%v] does not exist: '%v'", k, vv) - } - if v != vv { - log.Fatalf("invalid value: '%v' for key [%v] ", vv, k) - } - } - cnt := 0 - m.ForEach(func(k string, v interface{}) bool { - if k[3:] != (v.(string))[5:] { - log.Fatalf("invalid key-value: '%v', '%v'", k, v) - } - cnt++ - return true - }) - if cnt != size { - log.Fatalf("invalid ForEach num: %v, want: %v", cnt, size) - } - if m.Size() != int64(size) { - log.Fatalf("invalid size: %v, want: %v", m.Size(), size) - } - for i := 0; i < size; i++ { - k := fmt.Sprintf("key_%d", i) - v := fmt.Sprintf("value_%d", i) - vv, ok := m.Get(k) - if !ok { - log.Fatalf("[%v] does not exist: '%v'", k, vv) - } - if v != vv { - log.Fatalf("invalid value: '%v' for key [%v]", vv, k) - } - m.Delete(k) - if m.Size() != int64(size-i-1) { - log.Fatalf("invalid size: %v, want: %v", m.Size(), int64(size-i-1)) - } - } - for i := 0; i < size; i++ { - k := fmt.Sprintf("key_%d", i) - vv, ok := m.Get(k) - if ok { - log.Fatalf("[%v] exists: '%v'", k, vv) - } - if m.Size() != 0 { - log.Fatalf("invalid size: %v, want: %v", m.Size(), 0) - } - } -} - -func BenchmarkMapSet(b *testing.B) { - m := NewMap(64) - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - k := fmt.Sprintf("key_%d", i) - v := fmt.Sprintf("value_%d", i) - m.Set(k, v) - } -} - -func BenchmarkMapGet(b *testing.B) { - m := NewMap(64) - - for i := 0; i < b.N; i++ { - k := fmt.Sprintf("key_%d", i) - v := fmt.Sprintf("value_%d", i) - m.Set(k, v) - } - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - k := fmt.Sprintf("key_%d", i) - v := fmt.Sprintf("value_%d", i) - vv, ok := m.Get(k) - if !ok { - log.Fatalf("[%v] does not exist: '%v'", k, vv) - } - if v != vv { - log.Fatalf("invalid value: '%v' for key [%v], want: %v", vv, k, v) - } - } -} diff --git a/vendor/github.com/lesismal/llib/concurrent/mutex.go b/vendor/github.com/lesismal/llib/concurrent/mutex.go deleted file mode 100644 index 886b7b64..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/mutex.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "sync" -) - -var ( - _defaultMux = NewMutex() -) - -// Mutex . -type Mutex struct { - _mux sync.Mutex - _muxes map[interface{}]*sync.Mutex -} - -// Lock . -func (m *Mutex) Lock(key interface{}) { - m._mux.Lock() - mux, ok := m._muxes[key] - if !ok { - mux = &sync.Mutex{} - m._muxes[key] = mux - } - m._mux.Unlock() - mux.Lock() -} - -// Unlock . -func (m *Mutex) Unlock(key interface{}) { - m._mux.Lock() - mux, ok := m._muxes[key] - m._mux.Unlock() - if ok { - mux.Unlock() - } -} - -// NewMutex . -func NewMutex() *Mutex { - return &Mutex{_muxes: map[interface{}]*sync.Mutex{}} -} - -// // Lock . -// func Lock(key interface{}) { -// _defaultMux.Lock(key) -// } - -// // Unlock . -// func Unlock(key interface{}) { -// _defaultMux.Unlock(key) -// } diff --git a/vendor/github.com/lesismal/llib/concurrent/mutex_test.go b/vendor/github.com/lesismal/llib/concurrent/mutex_test.go deleted file mode 100644 index 10564d37..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/mutex_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "log" - "testing" - "time" -) - -func TestMutex(t *testing.T) { - mux := NewMutex() - muxPrint := func(id int) { - for i := 0; i < 3; i++ { - mux.Lock(1) - time.Sleep(time.Second / 100) - log.Println("mux print:", id, i) - mux.Unlock(1) - } - } - go muxPrint(2) - muxPrint(1) - time.Sleep(time.Second / 10) -} diff --git a/vendor/github.com/lesismal/llib/concurrent/rwmutex_test.go b/vendor/github.com/lesismal/llib/concurrent/rwmutex_test.go deleted file mode 100644 index 23bd5ec8..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/rwmutex_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "log" - "testing" - "time" -) - -func TestRWMutex(t *testing.T) { - rwmux := NewRWMutex() - rwmuxRLockPrint := func(id int) { - for i := 0; i < 3; i++ { - rwmux.RLock(2) - time.Sleep(time.Second / 100) - log.Println("rwmux print:", id, i) - rwmux.RUnlock(2) - } - } - go rwmuxRLockPrint(2) - rwmuxRLockPrint(1) - - rwmuxLockPrint := func(id int) { - for i := 0; i < 3; i++ { - rwmux.Lock(2) - time.Sleep(time.Second / 100) - log.Println("rwmux print:", id, i) - rwmux.Unlock(2) - } - } - go rwmuxLockPrint(2) - rwmuxLockPrint(1) - - time.Sleep(time.Second / 10) -} diff --git a/vendor/github.com/lesismal/llib/concurrent/rwmutext.go b/vendor/github.com/lesismal/llib/concurrent/rwmutext.go deleted file mode 100644 index 30d94db4..00000000 --- a/vendor/github.com/lesismal/llib/concurrent/rwmutext.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package concurrent - -import ( - "sync" -) - -var ( - _defaultRWMux = NewRWMutex() -) - -// RWMutex . -type RWMutex struct { - _mux sync.Mutex - _rwmuxes map[interface{}]*sync.RWMutex -} - -// Lock . -func (m *RWMutex) Lock(key interface{}) { - m._mux.Lock() - mux, ok := m._rwmuxes[key] - if !ok { - mux = &sync.RWMutex{} - m._rwmuxes[key] = mux - } - m._mux.Unlock() - mux.Lock() -} - -// Unlock . -func (m *RWMutex) Unlock(key interface{}) { - m._mux.Lock() - mux, ok := m._rwmuxes[key] - m._mux.Unlock() - if ok { - mux.Unlock() - } -} - -// RLock . -func (m *RWMutex) RLock(key interface{}) { - m._mux.Lock() - mux, ok := m._rwmuxes[key] - if !ok { - mux = &sync.RWMutex{} - m._rwmuxes[key] = mux - } - m._mux.Unlock() - mux.RLock() -} - -// RUnlock . -func (m *RWMutex) RUnlock(key interface{}) { - m._mux.Lock() - mux, ok := m._rwmuxes[key] - m._mux.Unlock() - if ok { - mux.RUnlock() - } -} - -// NewRWMutex . -func NewRWMutex() *RWMutex { - return &RWMutex{_rwmuxes: map[interface{}]*sync.RWMutex{}} -} - -// Lock . -func Lock(key interface{}) { - _defaultRWMux.Lock(key) -} - -// Unlock . -func Unlock(key interface{}) { - _defaultRWMux.Unlock(key) -} - -// RLock . -func RLock(key interface{}) { - _defaultRWMux.RLock(key) -} - -// RUnlock . -func RUnlock(key interface{}) { - _defaultRWMux.RUnlock(key) -} diff --git a/vendor/github.com/lesismal/llib/go.mod b/vendor/github.com/lesismal/llib/go.mod index 7fa6efbf..83056d97 100644 --- a/vendor/github.com/lesismal/llib/go.mod +++ b/vendor/github.com/lesismal/llib/go.mod @@ -3,7 +3,6 @@ module github.com/lesismal/llib go 1.16 require ( - github.com/cespare/xxhash v1.1.0 // indirect golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5 golang.org/x/net v0.0.0-20210510120150-4163338589ed ) diff --git a/vendor/github.com/lesismal/llib/go.sum b/vendor/github.com/lesismal/llib/go.sum index 2969d966..95da6fc3 100644 --- a/vendor/github.com/lesismal/llib/go.sum +++ b/vendor/github.com/lesismal/llib/go.sum @@ -1,7 +1,3 @@ -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5 h1:N6Jp/LCiEoIBX56BZSR2bepK5GtbSC2DDOYT742mMfE= golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/vendor/github.com/lesismal/llib/std/crypto/tls/conn.go b/vendor/github.com/lesismal/llib/std/crypto/tls/conn.go index 65472faf..aa41572c 100644 --- a/vendor/github.com/lesismal/llib/std/crypto/tls/conn.go +++ b/vendor/github.com/lesismal/llib/std/crypto/tls/conn.go @@ -22,35 +22,49 @@ import ( ) var ( - defaultReadBufferSize = 4096 - errDataNotEnough = errors.New("data not enough") + errDataNotEnough = errors.New("data not enough") ) type Allocator interface { - Malloc(size int) []byte - Realloc(buf []byte, size int) []byte - Free(buf []byte) + Malloc(size int) *[]byte + Realloc(buf *[]byte, size int) *[]byte + Append(buf *[]byte, more ...byte) *[]byte + AppendString(buf *[]byte, more string) *[]byte + Free(buf *[]byte) } type NativeAllocator struct{} // Malloc . -func (a *NativeAllocator) Malloc(size int) []byte { - return make([]byte, size) +func (a *NativeAllocator) Malloc(size int) *[]byte { + buf := make([]byte, size) + return &buf } // Realloc . -func (a *NativeAllocator) Realloc(buf []byte, size int) []byte { - if size <= cap(buf) { - return buf[:size] +func (a *NativeAllocator) Realloc(pbuf *[]byte, size int) *[]byte { + if size <= cap(*pbuf) { + *pbuf = (*pbuf)[:size] + return pbuf } - newBuf := make([]byte, size) - copy(newBuf, buf) - return newBuf + *pbuf = append((*pbuf)[:cap(*pbuf)], make([]byte, size-cap(*pbuf))...) + return pbuf +} + +// Append . +func (a *NativeAllocator) Append(pbuf *[]byte, more ...byte) *[]byte { + *pbuf = append(*pbuf, more...) + return pbuf +} + +// AppendString . +func (a *NativeAllocator) AppendString(pbuf *[]byte, more string) *[]byte { + *pbuf = append(*pbuf, more...) + return pbuf } // Free . -func (a *NativeAllocator) Free(buf []byte) { +func (a *NativeAllocator) Free(pbuf *[]byte) { } // A Conn represents a secured connection. @@ -131,14 +145,15 @@ type Conn struct { isNonBlock bool // input/output - buffering bool // whether records are buffered in sendBuf - sendBuf []byte // a buffer of records waiting to be sent + buffering bool // whether records are buffered in sendBuf + sendBuf *[]byte // a buffer of records waiting to be sent in, out halfConn rawInputOff int - rawInput []byte // bytes.Buffer // raw input, starting with a record header - input bytes.Reader // application data waiting to be read, from rawInput.Next + rawInput *[]byte // bytes.Buffer // raw input, starting with a record header + inputBuf *[]byte // owned copy of decrypted application data, from rawInput + input bytes.Reader // application data waiting to be read, from inputBuf handOff int - hand []byte // bytes.Buffer // handshake data waiting to be read + hand *[]byte // bytes.Buffer // handshake data waiting to be read // bytesSent counts the bytes of application data sent. // packetsSent counts packets. @@ -178,6 +193,11 @@ func (c *Conn) Conn() net.Conn { return c.conn } +// IsNonblock. +func (c *Conn) IsNonBlock() bool { + return c.isNonBlock +} + // ResetConn resets conn func (c *Conn) ResetConn(conn net.Conn, nonBlock bool, v ...interface{}) { c.conn = conn @@ -222,10 +242,18 @@ func (c *Conn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } +func (c *Conn) ClientHello() *clientHelloMsg { + return c.clientHello +} + +func (c *Conn) ServerHello() *serverHelloMsg { + return c.serverHello +} + // A halfConn represents one direction of the record layer // connection, either sending or receiving. type halfConn struct { - // sync.Mutex + sync.Mutex err error // first permanent error version uint16 // protocol version @@ -524,12 +552,12 @@ func (hc *halfConn) decrypt(record []byte) ([]byte, recordType, error) { // sliceForAppend extends the input slice by n bytes. head is the full extended // slice, while tail is the appended part. If the original slice has sufficient // capacity no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { +func sliceForAppend(c *Conn, in []byte, n int) (head, tail []byte) { if total := len(in) + n; cap(in) >= total { head = in[:total] } else { - head = make([]byte, total) - copy(head, in) + head = append(in, make([]byte, n)...) + // copy(head, in) } tail = head[len(in):] return @@ -537,14 +565,14 @@ func sliceForAppend(in []byte, n int) (head, tail []byte) { // encrypt encrypts payload, adding the appropriate nonce and/or MAC, and // appends it to record, which must already contain the record header. -func (hc *halfConn) encrypt(record, payload []byte, rand io.Reader) ([]byte, error) { +func (hc *halfConn) encrypt(conn *Conn, record, payload []byte, rand io.Reader) ([]byte, error) { if hc.cipher == nil { return append(record, payload...), nil } var explicitNonce []byte if explicitNonceLen := hc.explicitNonceLen(); explicitNonceLen > 0 { - record, explicitNonce = sliceForAppend(record, explicitNonceLen) + record, explicitNonce = sliceForAppend(conn, record, explicitNonceLen) if _, isCBC := hc.cipher.(cbcMode); !isCBC && explicitNonceLen < 16 { // The AES-GCM construction in TLS has an explicit nonce so that the // nonce can be random. However, the nonce is only 8 bytes which is @@ -567,7 +595,7 @@ func (hc *halfConn) encrypt(record, payload []byte, rand io.Reader) ([]byte, err switch c := hc.cipher.(type) { case cipher.Stream: mac := tls10MAC(hc.mac, hc.scratchBuf[:0], hc.seq[:], record[:recordHeaderLen], payload, nil) - record, dst = sliceForAppend(record, len(payload)+len(mac)) + record, dst = sliceForAppend(conn, record, len(payload)+len(mac)) c.XORKeyStream(dst[:len(payload)], payload) c.XORKeyStream(dst[len(payload):], mac) case aead: @@ -599,7 +627,7 @@ func (hc *halfConn) encrypt(record, payload []byte, rand io.Reader) ([]byte, err blockSize := c.BlockSize() plaintextLen := len(payload) + len(mac) paddingLen := blockSize - plaintextLen%blockSize - record, dst = sliceForAppend(record, plaintextLen+paddingLen) + record, dst = sliceForAppend(conn, record, plaintextLen+paddingLen) copy(dst, payload) copy(dst[len(payload):], mac) for i := plaintextLen; i < len(dst); i++ { @@ -641,7 +669,9 @@ func (e RecordHeaderError) Error() string { return "tls: " + e.Msg } func (c *Conn) newRecordHeaderError(conn net.Conn, msg string) (err RecordHeaderError) { err.Msg = msg err.Conn = conn - copy(err.RecordHeader[:], c.rawInput[c.rawInputOff:]) + if c.rawInput != nil { + copy(err.RecordHeader[:], (*c.rawInput)[c.rawInputOff:]) + } return err } @@ -653,6 +683,19 @@ func (c *Conn) readChangeCipherSpec() error { return c.readRecordOrCCS(true) } +func (c *Conn) ResetRawInput() { + c.closeMux.Lock() + defer c.closeMux.Unlock() + if c.closed { + return + } + + if c.rawInput != nil { + *c.rawInput = (*c.rawInput)[0:0] + } + c.rawInputOff = 0 +} + func (c *Conn) ResetOrFreeBuffer() { c.closeMux.Lock() defer c.closeMux.Unlock() @@ -660,7 +703,10 @@ func (c *Conn) ResetOrFreeBuffer() { return } - remain := len(c.rawInput) - c.rawInputOff + remain := 0 + if c.rawInput != nil { + remain = len(*c.rawInput) - c.rawInputOff + } switch remain { case 0: if c.rawInput != nil { @@ -668,20 +714,22 @@ func (c *Conn) ResetOrFreeBuffer() { c.rawInput = nil } default: - copy(c.rawInput, c.rawInput[c.rawInputOff:]) - c.rawInput = c.rawInput[:remain] + copy((*c.rawInput), (*c.rawInput)[c.rawInputOff:]) + (*c.rawInput) = (*c.rawInput)[:remain] c.rawInputOff = 0 } } // readRecordOrCCS reads one or more TLS records from the connection and // updates the record layer state. Some invariants: -// * c.in must be locked -// * c.input must be empty +// - c.in must be locked +// - c.input must be empty +// // During the handshake one and only one of the following will happen: // - c.hand grows // - c.in.changeCipherSpec is called // - an error is returned +// // After the handshake one and only one of the following will happen: // - c.hand grows // - c.input is set @@ -693,7 +741,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { handshakeComplete := c.handshakeComplete() if c.isNonBlock { - if len(c.rawInput)-c.rawInputOff < recordHeaderLen { + if c.rawInput == nil || (len(*c.rawInput)-c.rawInputOff < recordHeaderLen) { return errDataNotEnough } } else { @@ -708,17 +756,18 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { // RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify // is an error, but popular web sites seem to do this, so we accept it // if and only if at the record boundary. - if err == io.ErrUnexpectedEOF && len(c.rawInput)-c.rawInputOff == 0 { + if err == io.ErrUnexpectedEOF && (c.rawInput == nil || (len(*c.rawInput)-c.rawInputOff == 0)) { err = io.EOF } - if e, ok := err.(net.Error); !ok || !e.Temporary() { + var ne net.Error + if ok := errors.As(err, &ne); !ok || !ne.Timeout() { c.in.setErrorLocked(err) } return err } } - hdr := c.rawInput[c.rawInputOff : c.rawInputOff+recordHeaderLen] + hdr := (*c.rawInput)[c.rawInputOff : c.rawInputOff+recordHeaderLen] typ := recordType(hdr[0]) // No valid TLS record has a type of 0x80, however SSLv2 handshakes @@ -753,7 +802,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { } if c.isNonBlock { - if len(c.rawInput)-c.rawInputOff < recordHeaderLen+n { + if c.rawInput == nil || (len(*c.rawInput)-c.rawInputOff < recordHeaderLen+n) { return errDataNotEnough } } else { @@ -766,7 +815,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { } // Process message. - record := c.rawInput[c.rawInputOff : c.rawInputOff+recordHeaderLen+n] + record := (*c.rawInput)[c.rawInputOff : c.rawInputOff+recordHeaderLen+n] c.rawInputOff += (recordHeaderLen + n) data, typ, err := c.in.decrypt(record) if err != nil { @@ -775,11 +824,14 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { if len(data) > maxPlaintext { return c.in.setErrorLocked(c.sendAlert(alertRecordOverflow)) } - if len(c.rawInput) == c.rawInputOff { - c.allocator.Free(c.rawInput) - c.rawInput = nil - c.rawInputOff = 0 - } + + defer func() { + if c.rawInput != nil && len(*c.rawInput) == c.rawInputOff { + c.allocator.Free(c.rawInput) + c.rawInput = nil + c.rawInputOff = 0 + } + }() // Application Data messages are always protected. if c.in.cipher == nil && typ == recordTypeApplicationData { @@ -792,7 +844,9 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { } // Handshake messages MUST NOT be interleaved with other record types in TLS 1.3. - if c.vers == VersionTLS13 && typ != recordTypeHandshake && len(c.hand)-c.handOff > 0 { + if c.vers == VersionTLS13 && + typ != recordTypeHandshake && + (c.hand != nil && len(*c.hand)-c.handOff > 0) { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } @@ -825,7 +879,7 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { return c.in.setErrorLocked(c.sendAlert(alertDecodeError)) } // Handshake messages are not allowed to fragment across the CCS. - if len(c.hand)-c.handOff > 0 { + if c.hand != nil && len(*c.hand)-c.handOff > 0 { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } // In TLS 1.3, change_cipher_spec records are ignored until the @@ -852,20 +906,27 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error { if len(data) == 0 { return c.retryReadRecord(expectChangeCipherSpec) } - // Note that data is owned by c.rawInput, following the Next call above, - // to avoid copying the plaintext. This is safe because c.rawInput is - // not read from or written to until c.input is drained. - c.input.Reset(data) + // data is the decrypted application-data record and currently aliases + // memory inside c.rawInput. Copy it into a conn-owned buffer so c.input + // stays valid even after c.rawInput is freed early (see the defer below) + // or reused by the caller in non-blocking mode. + if c.inputBuf != nil { + c.allocator.Free(c.inputBuf) + } + c.inputBuf = c.allocator.Malloc(len(data)) + copy(*c.inputBuf, data) + c.input.Reset(*c.inputBuf) case recordTypeHandshake: if len(data) == 0 || expectChangeCipherSpec { return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } if c.hand == nil { - c.hand = c.allocator.Malloc(len(data))[0:0] + c.hand = c.allocator.Malloc(len(data)) + *c.hand = (*c.hand)[0:0] c.handOff = 0 } - c.hand = append(c.hand, data...) + c.hand = c.allocator.Append(c.hand, data...) } return nil @@ -916,17 +977,24 @@ func (c *Conn) readFromUntil(r io.Reader, n int, from int) error { // c.rawInputOff = 0 // } - needs := from + n - cap(c.rawInput) + needs := from + n + if c.rawInput == nil { + c.rawInput = c.allocator.Malloc(needs) + needs = 0 + } else { + needs -= cap(*c.rawInput) + } + // There might be extra input waiting on the wire. Make a best effort // attempt to fetch it so that it can be used in (*Conn).Read to // "predict" closeNotify alerts. if needs > 0 { - buf := c.allocator.Malloc(needs) - c.rawInput = append(c.rawInput[:cap(c.rawInput)], buf...) - c.allocator.Free(buf) + *c.rawInput = (*c.rawInput)[:cap(*c.rawInput)] + c.rawInput = c.allocator.Append(c.rawInput, make([]byte, needs)...) } - c.rawInput = c.rawInput[:from+n] - _, err := io.ReadFull(r, c.rawInput[from:]) + + *c.rawInput = (*c.rawInput)[:from+n] + _, err := io.ReadFull(r, (*c.rawInput)[from:]) return err } @@ -1035,30 +1103,36 @@ func (c *Conn) maxPayloadSizeForWrite(typ recordType) int { func (c *Conn) write(data []byte) (int, error) { if c.buffering { - if len(c.sendBuf) == 0 { - c.sendBuf = data - copy(c.sendBuf, data) + if c.sendBuf == nil || cap(*c.sendBuf) == 0 { + c.sendBuf = c.allocator.Malloc(len(data)) + copy(*c.sendBuf, data) } else { - c.sendBuf = append(c.sendBuf, data...) - c.allocator.Free(data) + c.sendBuf = c.allocator.Append(c.sendBuf, data...) + // c.allocator.Free(data) } return len(data), nil } n, err := c.conn.Write(data) - c.bytesSent += int64(n) + // c.allocator.Free(data) + if n > 0 { + c.bytesSent += int64(n) + } return n, err } func (c *Conn) flush() (int, error) { c.buffering = false - if len(c.sendBuf) == 0 { + if c.sendBuf == nil || len(*c.sendBuf) == 0 { return 0, nil } - n, err := c.conn.Write(c.sendBuf) - c.bytesSent += int64(n) + n, err := c.conn.Write(*c.sendBuf) + c.allocator.Free(c.sendBuf) + if n > 0 { + c.bytesSent += int64(n) + } c.sendBuf = nil return n, err } @@ -1073,29 +1147,27 @@ var outBufPool = sync.Pool{ // writeRecordLocked writes a TLS record with the given type and payload to the // connection and updates the record layer state. func (c *Conn) writeRecordLocked(typ recordType, data []byte) (int, error) { - // outBufPtr := outBufPool.Get().(*[]byte) - // outBuf := *outBufPtr - // defer func() { - // // You might be tempted to simplify this by just passing &outBuf to Put, - // // but that would make the local copy of the outBuf slice header escape - // // to the heap, causing an allocation. Instead, we keep around the - // // pointer to the slice header returned by Get, which is already on the - // // heap, and overwrite and return that. - // *outBufPtr = outBuf - // outBufPool.Put(outBufPtr) - // }() + outBufPtr := outBufPool.Get().(*[]byte) + outBuf := *outBufPtr + defer func() { + // You might be tempted to simplify this by just passing &outBuf to Put, + // but that would make the local copy of the outBuf slice header escape + // to the heap, causing an allocation. Instead, we keep around the + // pointer to the slice header returned by Get, which is already on the + // heap, and overwrite and return that. + *outBufPtr = outBuf + outBufPool.Put(outBufPtr) + }() var n int - var outBuf []byte - // var outBuf = c.allocator.Malloc(recordHeaderLen + len(data))[0:0] + var maxPayload = c.maxPayloadSizeForWrite(typ) for len(data) > 0 { m := len(data) - if maxPayload := c.maxPayloadSizeForWrite(typ); m > maxPayload { + if m > maxPayload { m = maxPayload } - outBuf = c.allocator.Malloc(recordHeaderLen) - // _, outBuf = sliceForAppend(outBuf[:0], recordHeaderLen) + _, outBuf = sliceForAppend(c, outBuf[:0], recordHeaderLen) outBuf[0] = byte(typ) vers := c.vers if vers == 0 { @@ -1113,11 +1185,12 @@ func (c *Conn) writeRecordLocked(typ recordType, data []byte) (int, error) { outBuf[4] = byte(m) var err error - outBuf, err = c.out.encrypt(outBuf, data[:m], c.config.rand()) + outBuf, err = c.out.encrypt(c, outBuf, data[:m], c.config.rand()) if err != nil { return n, err } - if _, err := c.write(outBuf); err != nil { + _, err = c.write(outBuf) + if err != nil { return n, err } n += m @@ -1145,42 +1218,27 @@ func (c *Conn) writeRecord(typ recordType, data []byte) (int, error) { // readHandshake reads the next handshake message from // the record layer. func (c *Conn) readHandshake() (interface{}, error) { - if c.isNonBlock { - if len(c.hand)-c.handOff < 4 { - if err := c.readRecord(); err != nil { - return nil, err - } - } - } else { - for len(c.hand)-c.handOff < 4 { - if err := c.readRecord(); err != nil { - return nil, err - } + for c.hand == nil || len(*c.hand)-c.handOff < 4 { + if err := c.readRecord(); err != nil { + return nil, err } } - data := c.hand[c.handOff:] + data := (*c.hand)[c.handOff:] n := int(data[1])<<16 | int(data[2])<<8 | int(data[3]) if n > maxHandshake { c.sendAlertLocked(alertInternalError) return nil, c.in.setErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshake)) } - if c.isNonBlock { - if len(c.hand)-c.handOff < 4+n { - if err := c.readRecord(); err != nil { - return nil, err - } - } - } else { - for len(c.hand)-c.handOff < 4+n { - if err := c.readRecord(); err != nil { - return nil, err - } + + for len(*c.hand)-c.handOff < 4+n { + if err := c.readRecord(); err != nil { + return nil, err } } - data = c.hand[c.handOff : c.handOff+4+n] - if c.handOff+4+n == len(c.hand) { - c.hand = c.hand[0:0] + data = (*c.hand)[c.handOff : c.handOff+4+n] + if c.handOff+4+n == len(*c.hand) { + *c.hand = (*c.hand)[0:0] c.handOff = 0 } else { c.handOff += (4 + n) @@ -1260,26 +1318,22 @@ var ( // has not yet completed. See SetDeadline, SetReadDeadline, and // SetWriteDeadline. func (c *Conn) Write(b []byte) (int, error) { - defer c.allocator.Free(b) - if len(b) == 0 { return 0, nil } - c.closeMux.Lock() - defer c.closeMux.Unlock() - if c.closed { + c.closeMux.Unlock() return 0, net.ErrClosed } + c.closeMux.Unlock() if err := c.Handshake(); err != nil { return 0, err } - // c.out.Lock() - // defer c.out.Unlock() - + c.out.Lock() + defer c.out.Unlock() if err := c.out.err; err != nil { return 0, err } @@ -1287,7 +1341,6 @@ func (c *Conn) Write(b []byte) (int, error) { if !c.handshakeComplete() { return 0, alertInternalError } - if c.closeNotifySent { return 0, errShutdown } @@ -1311,8 +1364,8 @@ func (c *Conn) Write(b []byte) (int, error) { m, b = 1, b[1:] } } - n, err := c.writeRecordLocked(recordTypeApplicationData, b) + return n + m, c.out.setErrorLocked(err) } @@ -1431,18 +1484,19 @@ func (c *Conn) Append(b []byte) (int, error) { // defer c.in.Unlock() if len(b) > 0 { - if cap(c.rawInput) == 0 { + if c.rawInput == nil || cap(*c.rawInput) == 0 { needs := len(b) if needs < bytes.MinRead { needs = bytes.MinRead } - c.rawInput = c.allocator.Malloc(needs)[0:0] + c.rawInput = c.allocator.Malloc(needs) + *c.rawInput = (*c.rawInput)[0:0] c.rawInputOff = 0 - } else if len(c.rawInput) == c.rawInputOff { - c.rawInput = c.rawInput[0:0] + } else if len(*c.rawInput) == c.rawInputOff { + *c.rawInput = (*c.rawInput)[0:0] c.rawInputOff = 0 } - c.rawInput = append(c.rawInput, b...) + c.rawInput = c.allocator.Append(c.rawInput, b...) } return 0, nil } @@ -1455,10 +1509,11 @@ func (c *Conn) Append(b []byte) (int, error) { // SetWriteDeadline. func (c *Conn) Read(b []byte) (int, error) { c.closeMux.Lock() - defer c.closeMux.Unlock() if c.closed { + c.closeMux.Unlock() return 0, net.ErrClosed } + c.closeMux.Unlock() if err := c.Handshake(); err != nil { if c.isNonBlock && err == errDataNotEnough { @@ -1472,8 +1527,8 @@ func (c *Conn) Read(b []byte) (int, error) { return 0, nil } - // c.in.Lock() - // defer c.in.Unlock() + c.in.Lock() + defer c.in.Unlock() for c.input.Len() == 0 { if err := c.readRecord(); err != nil { @@ -1482,7 +1537,7 @@ func (c *Conn) Read(b []byte) (int, error) { } return 0, err } - for len(c.hand)-c.handOff > 0 { + for c.hand != nil && len(*c.hand)-c.handOff > 0 { if err := c.handlePostHandshakeMessage(); err != nil { if c.isNonBlock && err == errDataNotEnough { return 0, nil @@ -1501,8 +1556,9 @@ func (c *Conn) Read(b []byte) (int, error) { // the EOF until its next read, by which time a client goroutine might // have already tried to reuse the HTTP connection for a new request. // See https://golang.org/cl/76400046 and https://golang.org/issue/3514 - if n != 0 && c.input.Len() == 0 && len(c.rawInput)-c.rawInputOff > 0 && - recordType(c.rawInput[c.rawInputOff]) == recordTypeAlert { + if n != 0 && c.input.Len() == 0 && + (c.rawInput != nil && len(*c.rawInput)-c.rawInputOff > 0) && + recordType((*c.rawInput)[c.rawInputOff]) == recordTypeAlert { if err := c.readRecord(); err != nil { if c.isNonBlock && err == errDataNotEnough { return 0, nil @@ -1527,20 +1583,20 @@ func (c *Conn) AppendAndRead(bufAppend []byte, bufRead []byte) (int, int, error) // defer c.in.Unlock() if len(bufAppend) > 0 { - if cap(c.rawInput) == 0 { + if c.rawInput == nil || cap(*c.rawInput) == 0 { needs := len(bufAppend) if needs < bytes.MinRead { needs = bytes.MinRead } - c.rawInput = c.allocator.Malloc(needs)[0:0] + c.rawInput = c.allocator.Malloc(needs) + *c.rawInput = (*c.rawInput)[0:0] c.rawInputOff = 0 - } else if len(c.rawInput) == c.rawInputOff { - c.rawInput = c.rawInput[0:0] + } else if len(*c.rawInput) == c.rawInputOff { + *c.rawInput = (*c.rawInput)[0:0] c.rawInputOff = 0 } - c.rawInput = append(c.rawInput, bufAppend...) + c.rawInput = c.allocator.Append(c.rawInput, bufAppend...) } - if err := c.Handshake(); err != nil { if c.isNonBlock && err == errDataNotEnough { return len(bufAppend), 0, nil @@ -1561,7 +1617,7 @@ func (c *Conn) AppendAndRead(bufAppend []byte, bufRead []byte) (int, int, error) } return len(bufAppend), 0, err } - for len(c.hand)-c.handOff > 0 { + for c.hand != nil && len(*c.hand)-c.handOff > 0 { if err := c.handlePostHandshakeMessage(); err != nil { if c.isNonBlock && err == errDataNotEnough { return len(bufAppend), 0, nil @@ -1580,8 +1636,9 @@ func (c *Conn) AppendAndRead(bufAppend []byte, bufRead []byte) (int, int, error) // the EOF until its next read, by which time a client goroutine might // have already tried to reuse the HTTP connection for a new request. // See https://golang.org/cl/76400046 and https://golang.org/issue/3514 - if n != 0 && c.input.Len() == 0 && len(c.rawInput)-c.rawInputOff > 0 && - recordType(c.rawInput[c.rawInputOff]) == recordTypeAlert { + if n != 0 && c.input.Len() == 0 && + (c.rawInput != nil && len(*c.rawInput)-c.rawInputOff > 0) && + recordType((*c.rawInput)[c.rawInputOff]) == recordTypeAlert { if err := c.readRecord(); err != nil { if c.isNonBlock && err == errDataNotEnough { return len(bufAppend), 0, nil @@ -1594,19 +1651,28 @@ func (c *Conn) AppendAndRead(bufAppend []byte, bufRead []byte) (int, int, error) } func (c *Conn) release() { - if cap(c.hand) > 0 { + if c.hand != nil && cap(*c.hand) > 0 { c.allocator.Free(c.hand) + c.hand = nil } - if cap(c.rawInput) > 0 { + if c.rawInput != nil && cap(*c.rawInput) > 0 { c.allocator.Free(c.rawInput) + c.rawInput = nil + } + + if c.inputBuf != nil && cap(*c.inputBuf) > 0 { + c.allocator.Free(c.inputBuf) + c.inputBuf = nil } } // Close closes the connection. func (c *Conn) Close() error { c.closeMux.Lock() + closed := c.closed c.closed = true + c.closeMux.Unlock() if closed { @@ -1614,7 +1680,6 @@ func (c *Conn) Close() error { } c.release() - var alertErr error if c.handshakeComplete() { if err := c.closeNotify(); err != nil { @@ -1677,7 +1742,6 @@ func (c *Conn) Handshake() error { // c.in.Lock() // defer c.in.Unlock() - c.handshakeErr = c.handshakeFn() if c.isNonBlock && c.handshakeErr == errDataNotEnough { c.handshakeErr = nil diff --git a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client.go b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client.go index e684b21d..86333960 100644 --- a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client.go +++ b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client.go @@ -195,6 +195,8 @@ func (c *Conn) clientHandshake() (err error) { return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox") } + c.serverHello = serverHello + if c.vers == VersionTLS13 { hs := &clientHandshakeStateTLS13{ c: c, diff --git a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client_tls13.go b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client_tls13.go index daa5d97f..06b2f216 100644 --- a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client_tls13.go +++ b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_client_tls13.go @@ -274,6 +274,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { return unexpectedMessageError(serverHello, msg) } hs.serverHello = serverHello + c.serverHello = serverHello if err := hs.checkServerHelloOrHRR(); err != nil { return err diff --git a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_messages.go b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_messages.go index b5f81e44..fd07bd48 100644 --- a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_messages.go +++ b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_messages.go @@ -94,6 +94,10 @@ type clientHelloMsg struct { pskBinders [][]byte } +func (m *clientHelloMsg) Marshal() []byte { + return m.marshal() +} + func (m *clientHelloMsg) marshal() []byte { if m.raw != nil { return m.raw @@ -300,6 +304,10 @@ func (m *clientHelloMsg) marshal() []byte { return m.raw } +func (m *clientHelloMsg) MarshalWithoutBinders() []byte { + return m.marshalWithoutBinders() +} + // marshalWithoutBinders returns the ClientHello through the // PreSharedKeyExtension.identities field, according to RFC 8446, Section // 4.2.11.2. Note that m.pskBinders must be set to slices of the correct length. @@ -344,6 +352,10 @@ func (m *clientHelloMsg) updateBinders(pskBinders [][]byte) { } } +func (m *clientHelloMsg) Unmarshal(data []byte) bool { + return m.unmarshal(data) +} + func (m *clientHelloMsg) unmarshal(data []byte) bool { *m = clientHelloMsg{raw: data} s := cryptobyte.String(data) @@ -613,6 +625,10 @@ type serverHelloMsg struct { selectedGroup CurveID } +func (m *serverHelloMsg) Marshal() []byte { + return m.marshal() +} + func (m *serverHelloMsg) marshal() []byte { if m.raw != nil { return m.raw @@ -729,6 +745,10 @@ func (m *serverHelloMsg) marshal() []byte { return m.raw } +func (m *serverHelloMsg) Unmarshal(data []byte) bool { + return m.unmarshal(data) +} + func (m *serverHelloMsg) unmarshal(data []byte) bool { *m = serverHelloMsg{raw: data} s := cryptobyte.String(data) diff --git a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_server.go b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_server.go index 0d67d5ab..849563d1 100644 --- a/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_server.go +++ b/vendor/github.com/lesismal/llib/std/crypto/tls/handshake_server.go @@ -104,7 +104,6 @@ func (c *Conn) serverHandshake() error { } return hs.handshake() } - hs := c.hs if hs == nil { hs = &serverHandshakeState{ @@ -124,7 +123,6 @@ func (hs *serverHandshakeState) handshake() error { if hs.err != nil && hs.err != errDataNotEnough { return hs.err } - if err := hs.processClientHello(); err != nil { hs.err = err return err @@ -203,7 +201,6 @@ func (hs *serverHandshakeState) handshake() error { atomic.StoreUint32(&c.handshakeStatus, 1) c.handshakeStatusAsync = stateServerHandshakeHandshakeDone - return nil } @@ -574,6 +571,31 @@ func (hs *serverHandshakeState) doFullHandshake() error { var msg interface{} var pub crypto.PublicKey // public key for client auth, if any var certReq *certificateRequestMsg + initCertReq := func() { + if certReq != nil { + return + } + + // Request a client certificate + certReq = new(certificateRequestMsg) + certReq.certificateTypes = []byte{ + byte(certTypeRSASign), + byte(certTypeECDSASign), + } + if c.vers >= VersionTLS12 { + certReq.hasSignatureAlgorithm = true + certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms + } + + // An empty list of certificateAuthorities signals to + // the client that it may send any certificate in response + // to our request. When we know the CAs we trust, then + // we can send them down, so that the client can choose + // an appropriate certificate to give to us. + if c.config.ClientCAs != nil { + certReq.certificateAuthorities = c.config.ClientCAs.Subjects() + } + } if c.handshakeStatusAsync < stateServerHandshakeDoFullHandshake2 { c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2 @@ -626,27 +648,9 @@ func (hs *serverHandshakeState) doFullHandshake() error { return err } } - if c.config.ClientAuth >= RequestClientCert { // Request a client certificate - certReq = new(certificateRequestMsg) - certReq.certificateTypes = []byte{ - byte(certTypeRSASign), - byte(certTypeECDSASign), - } - if c.vers >= VersionTLS12 { - certReq.hasSignatureAlgorithm = true - certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms - } - - // An empty list of certificateAuthorities signals to - // the client that it may send any certificate in response - // to our request. When we know the CAs we trust, then - // we can send them down, so that the client can choose - // an appropriate certificate to give to us. - if c.config.ClientCAs != nil { - certReq.certificateAuthorities = c.config.ClientCAs.Subjects() - } + initCertReq() hs.finishedHash.Write(certReq.marshal()) if _, err := c.writeRecord(recordTypeHandshake, certReq.marshal()); err != nil { return err @@ -662,6 +666,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { if _, err := c.flush(); err != nil { return err } + } if c.handshakeStatusAsync < stateServerHandshakeDoFullHandshake2ReadHandshake1 { @@ -677,7 +682,6 @@ func (hs *serverHandshakeState) doFullHandshake() error { // If we requested a client certificate, then the client must send a // certificate message, even if it's empty. - if c.config.ClientAuth >= RequestClientCert { if c.handshakeStatusAsync < stateServerHandshakeDoFullHandshake2HandleCertificateMsg { c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2HandleCertificateMsg @@ -710,7 +714,6 @@ func (hs *serverHandshakeState) doFullHandshake() error { } } - if c.handshakeStatusAsync < stateServerHandshakeDoFullHandshake2HandleVerifyConnection { c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2HandleVerifyConnection if c.config.VerifyConnection != nil { @@ -740,7 +743,6 @@ func (hs *serverHandshakeState) doFullHandshake() error { } } - if c.handshakeStatusAsync >= stateServerHandshakeDoFullHandshake2ReadHandshake3 { return nil } @@ -764,10 +766,10 @@ func (hs *serverHandshakeState) doFullHandshake() error { c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2ReadHandshake3 return unexpectedMessageError(certVerify, msg) } - var sigType uint8 var sigHash crypto.Hash if c.vers >= VersionTLS12 { + initCertReq() if !isSupportedSignatureAlgorithm(certVerify.signatureAlgorithm, certReq.supportedSignatureAlgorithms) { c.sendAlert(alertIllegalParameter) c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2ReadHandshake3 @@ -788,6 +790,11 @@ func (hs *serverHandshakeState) doFullHandshake() error { } signed := hs.finishedHash.hashForClientCertificate(sigType, sigHash, hs.masterSecret) + if pub == nil { + if len(c.peerCertificates) > 0 { + pub = c.peerCertificates[0].PublicKey + } + } if err := verifyHandshakeSignature(sigType, pub, sigHash, signed, certVerify.signature); err != nil { c.sendAlert(alertDecryptError) c.handshakeStatusAsync = stateServerHandshakeDoFullHandshake2ReadHandshake3 diff --git a/vendor/github.com/lesismal/llib/std/internal/nettrace/nettrace.go b/vendor/github.com/lesismal/llib/std/internal/nettrace/nettrace.go old mode 100755 new mode 100644 diff --git a/vendor/github.com/lesismal/llib/websocket/compression/compression.go b/vendor/github.com/lesismal/llib/websocket/compression/compression.go new file mode 100644 index 00000000..6a84f059 --- /dev/null +++ b/vendor/github.com/lesismal/llib/websocket/compression/compression.go @@ -0,0 +1,116 @@ +package compression + +// import ( +// "errors" +// "io" +// "sync" + +// "github.com/klauspost/compress/flate" +// ) + +// const ( +// minCompressionLevel = -2 +// maxCompressionLevel = flate.BestCompression +// ) + +// var ( +// flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool +// flateReaderPool = sync.Pool{New: func() interface{} { +// return flate.NewReader(nil) +// }} +// ) + +// func NewDecompressReader(r io.Reader) io.ReadCloser { +// fr, _ := flateReaderPool.Get().(io.ReadCloser) +// fr.(flate.Resetter).Reset(r, nil) +// return &flateReadWrapper{fr} +// } + +// type flateReadWrapper struct { +// fr io.ReadCloser +// } + +// func (r *flateReadWrapper) Read(p []byte) (int, error) { +// if r.fr == nil { +// return 0, io.ErrClosedPipe +// } +// n, err := r.fr.Read(p) +// if errors.Is(err, io.EOF) { +// // Preemptively place the reader back in the pool. This helps with +// // scenarios where the application does not call NextReader() soon after +// // this final read. +// r.Close() +// } +// return n, err +// } + +// func (r *flateReadWrapper) Close() error { +// if r.fr == nil { +// return io.ErrClosedPipe +// } +// err := r.fr.Close() +// flateReaderPool.Put(r.fr) +// r.fr = nil +// return err +// } + +// func NewCompressWriter(w io.WriteCloser, level int) io.WriteCloser { +// p := &flateWriterPools[level-minCompressionLevel] +// fw, _ := p.Get().(*flate.Writer) +// tw := &truncWriter{w: w} +// if fw == nil { +// fw, _ = flate.NewWriter(tw, level) +// } else { +// fw.Reset(tw) +// } +// return &flateWriteWrapper{fw: fw, p: p} +// } + +// type truncWriter struct { +// w io.WriteCloser +// n int +// p [4]byte +// } + +// func (w *truncWriter) Write(p []byte) (int, error) { +// n := 0 + +// if w.n < len(w.p) { +// n = copy(w.p[w.n:], p) +// p = p[n:] +// w.n += n +// if len(p) == 0 { +// return n, nil +// } +// } + +// m := len(p) +// if m > len(w.p) { +// m = len(w.p) +// } + +// if nn, err := w.w.Write(w.p[:m]); err != nil { +// return n + nn, err +// } + +// copy(w.p[:], w.p[m:]) +// copy(w.p[len(w.p)-m:], p[len(p)-m:]) +// nn, err := w.w.Write(p[:len(p)-m]) +// return n + nn, err +// } + +// type flateWriteWrapper struct { +// fw *flate.Writer +// p *sync.Pool +// } + +// func (w *flateWriteWrapper) Write(p []byte) (int, error) { +// return w.fw.Write(p) +// } + +// func (w *flateWriteWrapper) Close() error { +// err := w.fw.Flush() +// w.p.Put(w.fw) +// w.fw = nil +// return err +// } diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/autobahn.yml b/vendor/github.com/lesismal/nbio/.github/workflows/autobahn.yml deleted file mode 100644 index 04b6912e..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/autobahn.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: CI -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - strategy: - matrix: - os: [ ubuntu-latest ] - go: [ 1.16.x ] - runs-on: ${{ matrix.os }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup Go - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - name: Autobahn - env: - CRYPTOGRAPHY_ALLOW_OPENSSL_102: yes - run: | - make test autobahn - - name: Autobahn Report Artifact - if: >- - startsWith(matrix.os, 'ubuntu') - uses: actions/upload-artifact@v2 - with: - name: autobahn report ${{ matrix.go }} ${{ matrix.os }} - path: autobahn/report - retention-days: 7 \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/build_bsd.yml b/vendor/github.com/lesismal/nbio/.github/workflows/build_bsd.yml deleted file mode 100644 index d8a609b8..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/build_bsd.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: build-macos - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - GO111MODULE: off - -jobs: - test: - name: build-macos - strategy: - fail-fast: false - matrix: - go: [1.16.x] - os: [macos-latest] - runs-on: ${{ matrix.os}} - steps: - - name: install golang - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - name: checkout code - uses: actions/checkout@v2 - - name: go env - run: | - printf "$(go version)\n" - printf "\n\ngo environment:\n\n" - go get -u github.com/lesismal/nbio/... - ulimit -n 30000 - go env - echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" - - name: go test - run: go test -covermode=atomic -timeout 60s -coverprofile="./coverage" diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/build_linux.yml b/vendor/github.com/lesismal/nbio/.github/workflows/build_linux.yml deleted file mode 100644 index a97edd9d..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/build_linux.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: build-linux - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - GO111MODULE: off - -jobs: - test: - name: build-linux - strategy: - fail-fast: false - matrix: - go: [1.16.x] - os: [ubuntu-latest] - runs-on: ${{ matrix.os}} - steps: - - name: install golang - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - name: checkout code - uses: actions/checkout@v2 - - name: go env - run: | - printf "$(go version)\n" - printf "\n\ngo environment:\n\n" - go get -u github.com/lesismal/nbio/... - ulimit -n 30000 - go env - echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" - - name: go test - run: go test -covermode=atomic -timeout 60s -coverprofile="./coverage" diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/build_windows.yml b/vendor/github.com/lesismal/nbio/.github/workflows/build_windows.yml deleted file mode 100644 index 009e2f7c..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/build_windows.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: build-windows - -on: - push: - branches: - - master - pull_request: - branches: - - master - -env: - GO111MODULE: off - -jobs: - test: - name: build-windows - strategy: - fail-fast: false - matrix: - go: [1.16.x] - os: [windows-latest] - runs-on: ${{ matrix.os}} - steps: - - name: install golang - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go }} - - name: checkout code - uses: actions/checkout@v2 - - name: go env - run: | - printf "$(go version)\n" - printf "\n\ngo environment:\n\n" - go get -u github.com/lesismal/nbio/... - go env - echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" - - name: go test - run: go test -covermode=atomic -timeout 60s -coverprofile="./coverage" - - name: update code coverage report - uses: codecov/codecov-action@v1.2.1 - with: - files: "./coverage" - flags: unittests - verbose: true - name: codecov-nbio \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/close_inactive_issues.yml b/vendor/github.com/lesismal/nbio/.github/workflows/close_inactive_issues.yml deleted file mode 100644 index 246c34d3..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/close_inactive_issues.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Close inactive issues -on: - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/stale@v3 - with: - days-before-issue-stale: 30 - days-before-issue-close: 14 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." - days-before-pr-stale: -1 - days-before-pr-close: -1 - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/codeql-analysis.yml b/vendor/github.com/lesismal/nbio/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 992479f6..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,71 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ master ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] - schedule: - - cron: '41 10 * * 3' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 diff --git a/vendor/github.com/lesismal/nbio/.github/workflows/golangci-lint.yml b/vendor/github.com/lesismal/nbio/.github/workflows/golangci-lint.yml deleted file mode 100644 index 3a0c0475..00000000 --- a/vendor/github.com/lesismal/nbio/.github/workflows/golangci-lint.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: golangci-lint -on: - push: - tags: - - v* - branches: - - master - - main - pull_request: -jobs: - golangci: - strategy: - matrix: - go-version: [1.16.x] - os: [ubuntu-latest] - name: lint - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2.5.2 - with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.41.1 - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - # args: --issues-exit-code=0 - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true diff --git a/vendor/github.com/lesismal/nbio/.golangci.yml b/vendor/github.com/lesismal/nbio/.golangci.yml deleted file mode 100644 index b67edfdd..00000000 --- a/vendor/github.com/lesismal/nbio/.golangci.yml +++ /dev/null @@ -1,660 +0,0 @@ -# This file contains all available configuration options -# with their default values. - -# options for analysis running -run: - # default concurrency is a available CPU number - concurrency: 4 - - # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 1m - - # exit code when at least one issue was found, default is 1 - issues-exit-code: 1 - - # include test files or not, default is true - tests: true - - # list of build tags, all linters use it. Default is empty list. - build-tags: - - mytag - - # which dirs to skip: issues from them won't be reported; - # can use regexp here: generated.*, regexp is applied on full path; - # default value is empty list, but default dirs are skipped independently - # from this option's value (see skip-dirs-use-default). - # "/" will be replaced by current OS file path separator to properly work - # on Windows. - skip-dirs: - - src/external_libs - - autogenerated_by_my_lib - - # default is true. Enables skipping of directories: - # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs-use-default: true - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - # "/" will be replaced by current OS file path separator to properly work - # on Windows. - skip-files: - - ".*\\.my\\.go$" - - lib/bad.go - - # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": - # If invoked with -mod=readonly, the go command is disallowed from the implicit - # automatic updating of go.mod described above. Instead, it fails when any changes - # to go.mod are needed. This setting is most useful to check that go.mod does - # not need updates, such as in a continuous integration and testing system. - # If invoked with -mod=vendor, the go command assumes that the vendor - # directory holds the correct copies of dependencies and ignores - # the dependency descriptions in go.mod. - ## modules-download-mode: readonly|vendor|mod - - # Allow multiple parallel golangci-lint instances running. - # If false (default) - golangci-lint acquires file lock on start. - allow-parallel-runners: false - - -# output configuration options -output: - # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions - # default is "colored-line-number" - format: colored-line-number - - # print lines of code with issue, default is true - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - - # make issues output unique by line, default is true - uniq-by-line: true - - # add a prefix to the output file references; default is no prefix - path-prefix: "" - - # sorts results by: filepath, line and column - sort-results: false - - -# all available settings of specific linters -linters-settings: - - cyclop: - # the maximal code complexity to report - max-complexity: 10 - # the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled (default 0.0) - package-average: 0.0 - # should ignore tests (default false) - skip-tests: false - - dogsled: - # checks assignments with too many blank identifiers; default is 2 - max-blank-identifiers: 2 - - dupl: - # tokens count to trigger issue, 150 by default - threshold: 100 - - # errcheck: - # # report about not checking of errors in type assertions: `a := b.(MyStruct)`; - # # default is false: such cases aren't reported by default. - # check-type-assertions: false - - # # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; - # # default is false: such cases aren't reported by default. - # check-blank: false - - # # [deprecated] comma-separated list of pairs of the form pkg:regex - # # the regex is used to ignore names within pkg. (default "fmt:.*"). - # # see https://github.com/kisielk/errcheck#the-deprecated-method for details - # ignore: fmt:.*,io/ioutil:^Read.* - - # # list of functions to exclude from checking, where each entry is a single function to exclude. - # # see https://github.com/kisielk/errcheck#excluding-functions for details - # exclude-functions: - # - io/ioutil.ReadFile - # - io.Copy(*bytes.Buffer) - # - io.Copy(os.Stdout) - - errorlint: - # Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats - errorf: true - # Check for plain type assertions and type switches - asserts: true - # Check for plain error comparisons - comparison: true - - exhaustive: - # check switch statements in generated files also - check-generated: false - # indicates that switch statements are to be considered exhaustive if a - # 'default' case is present, even if all enum members aren't listed in the - # switch - default-signifies-exhaustive: false - - exhaustivestruct: - # Struct Patterns is list of expressions to match struct packages and names - # The struct packages have the form example.com/package.ExampleStruct - # The matching patterns can use matching syntax from https://pkg.go.dev/path#Match - # If this list is empty, all structs are tested. - struct-patterns: - - '*.Test' - - 'example.com/package.ExampleStruct' - - forbidigo: - # Forbid the following identifiers (identifiers are written using regexp): - forbid: - - ^print.*$ - - 'fmt\.Print.*' - # Exclude godoc examples from forbidigo checks. Default is true. - exclude_godoc_examples: false - - funlen: - lines: 60 - statements: 40 - - gci: - # put imports beginning with prefix after 3rd-party packages; - # only support one prefix - # if not set, use goimports.local-prefixes - local-prefixes: github.com/org/project - - gocognit: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 - - nestif: - # minimal complexity of if statements to report, 5 by default - min-complexity: 4 - - goconst: - # minimal length of string constant, 3 by default - min-len: 3 - # minimum occurrences of constant string count to trigger issue, 3 by default - min-occurrences: 3 - # ignore test files, false by default - ignore-tests: false - # look for existing constants matching the values, true by default - match-constant: true - # search also for duplicated numbers, false by default - numbers: false - # minimum value, only works with goconst.numbers, 3 by default - min: 3 - # maximum value, only works with goconst.numbers, 3 by default - max: 3 - # ignore when constant is not used as function argument, true by default - ignore-calls: true - - gocritic: - # Which checks should be enabled; can't be combined with 'disabled-checks'; - # See https://go-critic.github.io/overview#checks-overview - # To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` - # By default list of stable checks is used. - enabled-checks: - # - rangeValCopy enabled by default - - # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty - disabled-checks: - - regexpMust - - # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. - # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". - enabled-tags: - - performance - disabled-tags: - - experimental - - # Settings passed to gocritic. - # The settings key is the name of a supported gocritic checker. - # The list of supported checkers can be find in https://go-critic.github.io/overview. - settings: - captLocal: # must be valid enabled check name - # whether to restrict checker to params only (default true) - paramsOnly: true - elseif: - # whether to skip balanced if-else pairs (default true) - skipBalanced: true - hugeParam: - # size in bytes that makes the warning trigger (default 80) - sizeThreshold: 80 - rangeExprCopy: - # size in bytes that makes the warning trigger (default 512) - sizeThreshold: 512 - # whether to check test functions (default true) - skipTestFuncs: true - rangeValCopy: - # size in bytes that makes the warning trigger (default 128) - sizeThreshold: 32 - # whether to check test functions (default true) - skipTestFuncs: true - underef: - # whether to skip (*x).method() calls where x is a pointer receiver (default true) - skipRecvDeref: true - - gocyclo: - # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 - - godot: - # comments to be checked: `declarations`, `toplevel`, or `all` - scope: declarations - # list of regexps for excluding particular comment lines from check - exclude: - # example: exclude comments which contain numbers - # - '[0-9]+' - # check that each sentence starts with a capital letter - capital: false - - godox: - # report any comments starting with keywords, this is useful for TODO or FIXME comments that - # might be left in the code accidentally and should be resolved before merging - keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting - - NOTE - - OPTIMIZE # marks code that should be optimized before merging - - HACK # marks hack-arounds that should be removed before merging - - gofmt: - # simplify code: gofmt with `-s` option, true by default - simplify: true - - gofumpt: - # Select the Go version to target. The default is `1.15`. - lang-version: "1.15" - - # Choose whether or not to use the extra rules that are disabled - # by default - extra-rules: false - - goheader: - values: - const: - # define here const type values in format k:v, for example: - # COMPANY: MY COMPANY - regexp: - # define here regexp type values, for example - # AUTHOR: .*@mycompany\.com - template: # |- - # put here copyright header template for source code files, for example: - # Note: {{ YEAR }} is a builtin value that returns the year relative to the current machine time. - # - # {{ AUTHOR }} {{ COMPANY }} {{ YEAR }} - # SPDX-License-Identifier: Apache-2.0 - - # Licensed under the Apache License, Version 2.0 (the "License"); - # you may not use this file except in compliance with the License. - # You may obtain a copy of the License at: - - # http://www.apache.org/licenses/LICENSE-2.0 - - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, - # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - # See the License for the specific language governing permissions and - # limitations under the License. - template-path: - # also as alternative of directive 'template' you may put the path to file with the template source - - goimports: - # put imports beginning with prefix after 3rd-party packages; - # it's a comma-separated list of prefixes - local-prefixes: github.com/org/project - - golint: - # minimal confidence for issues, default is 0.8 - min-confidence: 0.8 - - gomnd: - settings: - mnd: - # the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. - checks: argument,case,condition,operation,return,assign - # ignored-numbers: 1000 - # ignored-files: magic_.*.go - # ignored-functions: math.* - - gomoddirectives: - # Allow local `replace` directives. Default is false. - replace-local: false - # List of allowed `replace` directives. Default is empty. - replace-allow-list: - - launchpad.net/gocheck - # Allow to not explain why the version has been retracted in the `retract` directives. Default is false. - retract-allow-no-explanation: false - # Forbid the use of the `exclude` directives. Default is false. - exclude-forbidden: false - - gomodguard: - allowed: - modules: # List of allowed modules - # - gopkg.in/yaml.v2 - domains: # List of allowed module domains - # - golang.org - blocked: - modules: # List of blocked modules - # - github.com/uudashr/go-module: # Blocked module - # recommendations: # Recommended modules that should be used instead (Optional) - # - golang.org/x/mod - # reason: "`mod` is the official go.mod parser library." # Reason why the recommended module should be used (Optional) - versions: # List of blocked module version constraints - # - github.com/mitchellh/go-homedir: # Blocked module with version constraint - # version: "< 1.1.0" # Version constraint, see https://github.com/Masterminds/semver#basic-comparisons - # reason: "testing if blocked version constraint works." # Reason why the version constraint exists. (Optional) - local_replace_directives: false # Set to true to raise lint issues for packages that are loaded from a local path via replace directive - - gosec: - # To select a subset of rules to run. - # Available rules: https://github.com/securego/gosec#available-rules - includes: - - G401 - - G306 - - G101 - # To specify a set of rules to explicitly exclude. - # Available rules: https://github.com/securego/gosec#available-rules - excludes: - - G204 - # To specify the configuration of rules. - # The configuration of rules is not fully documented by gosec: - # https://github.com/securego/gosec#configuration - # https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/rules/rulelist.go#L60-L102 - config: - G306: "0600" - G101: - pattern: "(?i)example" - ignore_entropy: false - entropy_threshold: "80.0" - per_char_threshold: "3.0" - truncate: "32" - - gosimple: - # Select the Go version to target. The default is '1.13'. - go: "1.15" - # https://staticcheck.io/docs/options#checks - checks: [ "all" ] - - govet: - # report about shadowed variables - check-shadowing: true - - # settings per analyzer - settings: - printf: # analyzer name, run `go tool vet help` to see all analyzers - funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - - # enable or disable analyzers by name - # run `go tool vet help` to see all analyzers - enable: - - atomicalign - enable-all: false - disable: - - shadow - disable-all: false - - depguard: - list-type: blacklist - include-go-root: false - packages: - - github.com/sirupsen/logrus - packages-with-error-message: - # specify an error message to output when a blacklisted package is used - - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" - - ifshort: - # Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax. - # Has higher priority than max-decl-chars. - max-decl-lines: 1 - # Maximum length of variable declaration measured in number of characters, after which linter won't suggest using short syntax. - max-decl-chars: 30 - - importas: - # if set to `true`, force to use alias. - no-unaliased: true - # List of aliases - alias: - # using `servingv1` alias for `knative.dev/serving/pkg/apis/serving/v1` package - - pkg: knative.dev/serving/pkg/apis/serving/v1 - alias: servingv1 - # using `autoscalingv1alpha1` alias for `knative.dev/serving/pkg/apis/autoscaling/v1alpha1` package - - pkg: knative.dev/serving/pkg/apis/autoscaling/v1alpha1 - alias: autoscalingv1alpha1 - # You can specify the package path by regular expression, - # and alias by regular expression expansion syntax like below. - # see https://github.com/julz/importas#use-regular-expression for details - - pkg: knative.dev/serving/pkg/apis/(\w+)/(v[\w\d]+) - alias: $1$2 - - lll: - # max line length, lines longer will be reported. Default is 120. - # '\t' is counted as 1 character by default, and can be changed with the tab-width option - line-length: 120 - # tab width in spaces. Default to 1. - tab-width: 1 - - makezero: - # Allow only slices initialized with a length of zero. Default is false. - always: false - - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true - - misspell: - # Correct spellings using locale preferences for US or UK. - # Default is to use a neutral variety of English. - # Setting locale to US will correct the British spelling of 'colour' to 'color'. - locale: US - ignore-words: - - someword - - nakedret: - # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 - max-func-lines: 30 - - prealloc: - # XXX: we don't recommend using this linter before doing performance profiling. - # For most programs usage of prealloc will be a premature optimization. - - # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. - # True by default. - simple: true - range-loops: true # Report preallocation suggestions on range loops, true by default - for-loops: false # Report preallocation suggestions on for loops, false by default - - promlinter: - # Promlinter cannot infer all metrics name in static analysis. - # Enable strict mode will also include the errors caused by failing to parse the args. - strict: false - # Please refer to https://github.com/yeya24/promlinter#usage for detailed usage. - disabled-linters: - # - "Help" - # - "MetricUnits" - # - "Counter" - # - "HistogramSummaryReserved" - # - "MetricTypeInName" - # - "ReservedChars" - # - "CamelCase" - # - "lintUnitAbbreviations" - - predeclared: - # comma-separated list of predeclared identifiers to not report on - ignore: "" - # include method names and field names (i.e., qualified names) in checks - q: false - - nolintlint: - # Enable to ensure that nolint directives are all used. Default is true. - allow-unused: false - # Disable to ensure that nolint directives don't have a leading space. Default is true. - allow-leading-space: true - # Exclude following linters from requiring an explanation. Default is []. - allow-no-explanation: [] - # Enable to require an explanation of nonzero length after each nolint directive. Default is false. - require-explanation: true - # Enable to require nolint directives to mention the specific linter being suppressed. Default is false. - require-specific: true - - rowserrcheck: - packages: - - github.com/jmoiron/sqlx - - revive: - # see https://github.com/mgechev/revive#available-rules for details. - ignore-generated-header: true - severity: warning - rules: - - name: indent-error-flow - severity: warning - - name: add-constant - severity: warning - arguments: - - maxLitCount: "3" - allowStrs: '""' - allowInts: "0,1,2" - allowFloats: "0.0,0.,1.0,1.,2.0,2." - - staticcheck: - # Select the Go version to target. The default is '1.13'. - go: "1.15" - # https://staticcheck.io/docs/options#checks - checks: [ "all" ] - - stylecheck: - # Select the Go version to target. The default is '1.13'. - go: "1.15" - # https://staticcheck.io/docs/options#checks - checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ] - # https://staticcheck.io/docs/options#dot_import_whitelist - dot-import-whitelist: - - fmt - # https://staticcheck.io/docs/options#initialisms - initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ] - # https://staticcheck.io/docs/options#http_status_code_whitelist - http-status-code-whitelist: [ "200", "400", "404", "500" ] - - tagliatelle: - # check the struck tag name case - case: - # use the struct field name to check the name of the struct tag - use-field-name: true - rules: - # any struct tag type can be used. - # support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` - json: camel - yaml: camel - xml: camel - bson: camel - avro: snake - mapstructure: kebab - - testpackage: - # regexp pattern to skip files - skip-regexp: (export|internal)_test\.go - - thelper: - # The following configurations enable all checks. It can be omitted because all checks are enabled by default. - # You can enable only required checks deleting unnecessary checks. - test: - first: true - name: true - begin: true - benchmark: - first: true - name: true - begin: true - tb: - first: true - name: true - begin: true - - unparam: - # Inspect exported functions, default is false. Set to true if no external program/library imports your code. - # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: - # if it's called for subdir of a project it can't find external interfaces. All text editor integrations - # with golangci-lint call it on a directory with the changed file. - check-exported: false - - unused: - # Select the Go version to target. The default is '1.13'. - go: "1.15" - - whitespace: - multi-if: false # Enforces newlines (or comments) after every multi-line if statement - multi-func: false # Enforces newlines (or comments) after every multi-line function signature - - wrapcheck: - # An array of strings that specify substrings of signatures to ignore. - # If this set, it will override the default set of ignored signatures. - # See https://github.com/tomarrell/wrapcheck#configuration for more information. - ignoreSigs: - - .Errorf( - - errors.New( - - errors.Unwrap( - - .Wrap( - - .Wrapf( - - .WithMessage( - - .WithMessagef( - - .WithStack( - ignorePackageGlobs: - - encoding/* - - github.com/pkg/* - - wsl: - # See https://github.com/bombsimon/wsl/blob/master/doc/configuration.md for - # documentation of available settings. These are the defaults for - # `golangci-lint`. - allow-assign-and-anything: false - allow-assign-and-call: true - allow-cuddle-declarations: false - allow-multiline-assign: true - allow-separated-leading-comment: false - allow-trailing-comment: false - force-case-trailing-whitespace: 0 - force-err-cuddling: false - force-short-decl-cuddling: false - strict-append: true - -# # The custom section can be used to define linter plugins to be loaded at runtime. -# # See README doc for more info. -# custom: -# # Each custom linter should have a unique name. -# example: -# # The path to the plugin *.so. Can be absolute or local. Required for each custom linter -# path: /path/to/example.so -# # The description of the linter. Optional, just for documentation purposes. -# description: This is an example usage of a plugin linter. -# # Intended to point to the repo location of the linter. Optional, just for documentation purposes. -# original-url: github.com/golangci/example-linter - -linters: - disable-all: false - enable: - - dogsled - - dupl - - importas - - gci - - goconst - - godot - - godox - - govet - - megacheck - - misspell - - nolintlint - - wastedassign - - unconvert - enable-all: false - disable: - - maligned - - prealloc - - scopelint - - errcheck - presets: - - bugs - - unused - fast: false \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/Makefile b/vendor/github.com/lesismal/nbio/Makefile index 67396b33..e453bcef 100644 --- a/vendor/github.com/lesismal/nbio/Makefile +++ b/vendor/github.com/lesismal/nbio/Makefile @@ -12,17 +12,14 @@ lint: golangci-lint run $(PACKAGE_DIRS) test: - $(GO) test $(PACKAGES) + $(GO) test -v $(PACKAGES) clean: - rm -f bin/autobahn_server - rm -fr autobahn/report/* - -bin/reporter: - go build -o bin/reporter ./autobahn + rm -rf ./autobahn/bin/* + rm -rf ./autobahn/report/* -autobahn: clean bin/reporter - ./autobahn/script/test.sh --build fuzzingclient config/client_tests.json autobahn/server -o server.test - bin/reporter $(PWD)/autobahn/report/output/index.json +autobahn: + chmod +x ./autobahn/script/run.sh & ./autobahn/script/run.sh .PHONY: all vet lint test clean autobahn + diff --git a/vendor/github.com/lesismal/nbio/README.md b/vendor/github.com/lesismal/nbio/README.md index 2a4d1447..5343ad3c 100644 --- a/vendor/github.com/lesismal/nbio/README.md +++ b/vendor/github.com/lesismal/nbio/README.md @@ -1,9 +1,9 @@ # NBIO - NON-BLOCKING IO -[![Slack][1]][2] + -[![Mentioned in Awesome Go][3]][4] [![MIT licensed][5]][6] [![Go Version][7]][8] [![Build Status][9]][10] [![Go Report Card][11]][12] [![Coverage Statusd][13]][14] +[![Mentioned in Awesome Go][3]][4] [![MIT licensed][5]][6] [![Go Version][7]][8] [![Build Status][9]][10] [![Go Report Card][11]][12] [1]: https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=green [2]: https://join.slack.com/t/arpcnbio/shared_invite/zt-vh3g1z2v-qqoDp1hQ45fJZqwPrSz4~Q @@ -13,8 +13,8 @@ [6]: LICENSE [7]: https://img.shields.io/badge/go-%3E%3D1.16-30dff3?style=flat-square&logo=go [8]: https://github.com/lesismal/nbio -[9]: https://img.shields.io/github/workflow/status/lesismal/nbio/build-linux?style=flat-square&logo=github-actions -[10]: https://github.com/lesismal/nbio/actions?query=workflow%3build-linux +[9]: https://img.shields.io/github/actions/workflow/status/lesismal/nbio/autobahn.yml?branch=master&style=flat-square&logo=github-actions +[10]: https://github.com/lesismal/nbio/actions?query=workflow%3autobahn [11]: https://goreportcard.com/badge/github.com/lesismal/nbio [12]: https://goreportcard.com/report/github.com/lesismal/nbio [13]: https://codecov.io/gh/lesismal/nbio/branch/master/graph/badge.svg @@ -23,314 +23,344 @@ [16]: https://godoc.org/github.com/lesismal/nbio +## Recommendation +Based on years of development and testing of nbio, I have drawn some conclusions: +For massive connection scenarios, combined with memory pools and goroutine pools, it can effectively reduce the number of goroutines and objects, thereby lowering memory consumption and GC pressure, avoiding OOM and significant STW. For example, in a million WebSocket 1k payload echo test, nbio can maintain memory at 1GB with 100k TPS. -## Contents - -- [NBIO - NON-BLOCKING IO](#nbio---non-blocking-io) - - [Contents](#contents) - - [Features](#features) - - [Installation](#installation) - - [Quick Start](#quick-start) - - [API Examples](#api-examples) - - [New Gopher For Server-Side](#new-gopher-for-server-side) - - [New Gopher For Client-Side](#new-gopher-for-client-side) - - [Start Gopher](#start-gopher) - - [Custom Other Config For Gopher](#custom-other-config-for-gopher) - - [SetDeadline/SetReadDeadline/SetWriteDeadline](#setdeadlinesetreaddeadlinesetwritedeadline) - - [Bind User Session With Conn](#bind-user-session-with-conn) - - [Writev / Batch Write](#writev--batch-write) - - [Handle New Connection](#handle-new-connection) - - [Handle Disconnected](#handle-disconnected) - - [Handle Data](#handle-data) - - [Handle Memory Allocation/Free For Reading](#handle-memory-allocationfree-for-reading) - - [Handle Memory Free For Writing](#handle-memory-free-for-writing) - - [Handle Conn Before Read](#handle-conn-before-read) - - [Handle Conn After Read](#handle-conn-after-read) - - [Handle Conn Before Write](#handle-conn-before-write) - - [Echo Examples](#echo-examples) - - [TLS Examples](#tls-examples) - - [HTTP Examples](#http-examples) - - [HTTPS Examples](#https-examples) - - [Websocket Examples](#websocket-examples) - - [Websocket TLS Examples](#websocket-tls-examples) - - [Websocket 1M Connections Examples](#websocket-1m-connections-examples) - - [Use With Other STD Based Frameworkds](#use-with-other-std-based-frameworkds) - - [More Examples](#more-examples) - - -## Features -- [x] linux: epoll, both ET/LT(default) supported -- [x] macos(bsd): kqueue -- [x] windows: golang std net -- [x] nbio.Conn implements a non-blocking net.Conn(except windows) -- [x] writev supported -- [x] concurrent write/close supported(both nbio.Conn and nbio/nbhttp/websocket.Conn) -- [x] TLS supported -- [x] HTTP/HTTPS 1.x -- [x] Websocket, [Passes the Autobahn Test Suite](https://lesismal.github.io/nbio/websocket), `OnOpen/OnMessage/OnClose` order guaranteed -- [ ] HTTP 2.0(no plans, 2.0 is not good enough) +For regular connection scenarios, nbio's performance is inferior to the standard library due to goroutine affinity, lower buffer reuse rate for individual connections, and variable escape issues. +The vast majority of business scenarios using Golang do not require support for massive connections. The few scenarios that do require massive connections are mostly infrastructure such as gateways and proxies, and these types of infrastructure have higher requirements for performance and hardware resource overhead, where C/C++/Rust are more suitable. -## Installation +Therefore, the scenarios where nbio can demonstrate its advantages are very limited. -1. Get and install nbio - -```sh -$ go get -u github.com/lesismal/nbio -``` - -2. Import in your code: - -```go -import "github.com/lesismal/nbio" -``` +### For WebSocket in regular connection scenarios +I recommend: +- https://github.com/lxzan/gws +- https://github.com/antlabs/quickws -## Quick Start - -- start a server +While gorilla/websocket is mature and stable, its default ReadMessage performance is poor, and it requires additional encapsulation for reading and writing to avoid consistency issues, concurrent timing problems, and the issue where a single connection's write blocking in broadcast scenarios causes other connections to wait. -```go -import "github.com/lesismal/nbio" +nbio, [gws](https://github.com/lxzan/gws) and [quickws](https://github.com/antlabs/quickws) all provide better out-of-the-box designs, which spare users the trouble of doing more encapsulation work on gorilla/websocket. -g := nbio.NewGopher(nbio.Config{ - Network: "tcp", - Addrs: []string{"localhost:8888"}, -}) +However, as mentioned above, nbio has no advantage in regular connection scenarios, and it supports more features with more complex compatibility code. Its performance in regular connection scenarios is inferior to [gws](https://github.com/lxzan/gws) and [quickws](https://github.com/antlabs/quickws), so I recommend [gws](https://github.com/lxzan/gws) and [quickws](https://github.com/antlabs/quickws). -// echo -g.OnData(func(c *nbio.Conn, data []byte) { - c.Write(append([]byte{}, data...)) -}) -err := g.Start() -if err != nil { - panic(err) -} -// ... -``` +### Some other well-implemented epoll libs -- start a client +nbio's functionality is somewhat complex. -```go -import "github.com/lesismal/nbio" +If anyone is just interested in exploring the epoll wrapper part, I recommend: +- https://github.com/urpc/uio +- https://github.com/antlabs/pulse -g := nbio.NewGopher(nbio.Config{}) -g.OnData(func(c *nbio.Conn, data []byte) { - // ... -}) +## Contents -err := g.Start() -if err != nil { - fmt.Printf("Start failed: %v\n", err) -} -defer g.Stop() +- [NBIO - NON-BLOCKING IO](#nbio---non-blocking-io) + - [Recommendation](#recommendation) + - [For WebSocket in regular connection scenarios](#for-websocket-in-regular-connection-scenarios) + - [Some other well-implemented epoll libs](#some-other-well-implemented-epoll-libs) + - [Contents](#contents) + - [Features](#features) + - [Cross Platform](#cross-platform) + - [Protocols Supported](#protocols-supported) + - [Interfaces](#interfaces) + - [Quick Start](#quick-start) + - [Examples](#examples) + - [TCP Echo Examples](#tcp-echo-examples) + - [UDP Echo Examples](#udp-echo-examples) + - [TLS Examples](#tls-examples) + - [HTTP Examples](#http-examples) + - [HTTPS Examples](#https-examples) + - [Websocket Examples](#websocket-examples) + - [Websocket TLS Examples](#websocket-tls-examples) + - [Use With Other STD Based Frameworkds](#use-with-other-std-based-frameworkds) + - [More Examples](#more-examples) + - [1M Websocket Connections Benchmark](#1m-websocket-connections-benchmark) + - [Magics For HTTP and Websocket](#magics-for-http-and-websocket) + - [Different IOMod](#different-iomod) + - [Using Websocket With Std Server](#using-websocket-with-std-server) + - [Credits](#credits) + - [Contributors](#contributors) + - [Star History](#star-history) -c, err := nbio.Dial("tcp", addr) -if err != nil { - fmt.Printf("Dial failed: %v\n", err) -} -g.AddConn(c) +## Features +### Cross Platform +- [x] Linux: Epoll with LT/ET/ET+ONESHOT supported, LT as default +- [x] BSD(MacOS): Kqueue +- [x] Windows: Based on std net, for debugging only -buf := make([]byte, 1024) -c.Write(buf) -// ... -``` +### Protocols Supported +- [x] TCP/UDP/Unix Socket supported +- [x] TLS supported +- [x] HTTP/HTTPS 1.x supported +- [x] Websocket supported, [Passes the Autobahn Test Suite](https://lesismal.github.io/nbio/websocket/autobahn), `OnOpen/OnMessage/OnClose` order guaranteed -## API Examples +### Interfaces +- [x] Implements a non-blocking net.Conn(except windows) +- [x] SetDeadline/SetReadDeadline/SetWriteDeadline supported +- [x] Concurrent Write/Close supported(both nbio.Conn and nbio/nbhttp/websocket.Conn) -### New Gopher For Server-Side -```golang -g := nbio.NewGopher(nbio.Config{ - Network: "tcp", - Addrs: []string{"localhost:8888"}, -}) -``` -### New Gopher For Client-Side -```golang -g := nbio.NewGopher(nbio.Config{}) -``` +## Quick Start -### Start Gopher ```golang -err := g.Start() -if err != nil { - fmt.Printf("Start failed: %v\n", err) +package main + +import ( + "log" + + "github.com/lesismal/nbio" +) + +func main() { + engine := nbio.NewEngine(nbio.Config{ + Network: "tcp",//"udp", "unix" + Addrs: []string{":8888"}, + MaxWriteBufferSize: 6 * 1024 * 1024, + }) + + // handle new connection + engine.OnOpen(func(c *nbio.Conn) { + log.Println("OnOpen:", c.RemoteAddr().String()) + }) + // handle connection closed + engine.OnClose(func(c *nbio.Conn, err error) { + log.Println("OnClose:", c.RemoteAddr().String(), err) + }) + // handle data + engine.OnData(func(c *nbio.Conn, data []byte) { + c.Write(append([]byte{}, data...)) + }) + + err := engine.Start() + if err != nil { + log.Fatalf("nbio.Start failed: %v\n", err) + return + } + defer engine.Stop() + + <-make(chan int) } -defer g.Stop() ``` -### Custom Other Config For Gopher -```golang -conf := nbio.Config struct { - // Name describes your gopher name for logging, it's set to "NB" by default - Name: "NB", - - // MaxLoad represents the max online num, it's set to 10k by default - MaxLoad: 1024 * 10, +## Examples +### TCP Echo Examples - // NPoller represents poller goroutine num, it's set to runtime.NumCPU() by default - NPoller: runtime.NumCPU(), - - // ReadBufferSize represents buffer size for reading, it's set to 16k by default - ReadBufferSize: 1024 * 16, - - // MaxWriteBufferSize represents max write buffer size for Conn, it's set to 1m by default. - // if the connection's Send-Q is full and the data cached by nbio is - // more than MaxWriteBufferSize, the connection would be closed by nbio. - MaxWriteBufferSize uint32 +- [echo-server](https://github.com/lesismal/nbio_examples/blob/master/echo/server/server.go) +- [echo-client](https://github.com/lesismal/nbio_examples/blob/master/echo/client/client.go) - // LockListener represents listener's goroutine to lock thread or not, it's set to false by default. - LockListener bool +### UDP Echo Examples - // LockPoller represents poller's goroutine to lock thread or not. - LockPoller bool -} -``` +- [udp-server](https://github.com/lesismal/nbio-examples/blob/master/udp/server/server.go) +- [udp-client](https://github.com/lesismal/nbio-examples/blob/master/udp/client/client.go) -### SetDeadline/SetReadDeadline/SetWriteDeadline -```golang -var c *nbio.Conn = ... -c.SetDeadline(time.Now().Add(time.Second * 10)) -c.SetReadDeadline(time.Now().Add(time.Second * 10)) -c.SetWriteDeadline(time.Now().Add(time.Second * 10)) -``` +### TLS Examples -### Bind User Session With Conn -```golang -var c *nbio.Conn = ... -var session *YourSessionType = ... -c.SetSession(session) -``` +- [tls-server](https://github.com/lesismal/nbio_examples/blob/master/tls/server/server.go) +- [tls-client](https://github.com/lesismal/nbio_examples/blob/master/tls/client/client.go) -```golang -var c *nbio.Conn = ... -session := c.Session().(*YourSessionType) -``` +### HTTP Examples -### Writev / Batch Write -```golang -var c *nbio.Conn = ... -var data [][]byte = ... -c.Writev(data) -``` - -### Handle New Connection -```golang -g.OnOpen(func(c *Conn) { - // ... - c.SetReadDeadline(time.Now().Add(time.Second*30)) -}) -``` +- [http-server](https://github.com/lesismal/nbio_examples/blob/master/http/server/server.go) +- [http-client](https://github.com/lesismal/nbio_examples/blob/master/http/client/client.go) -### Handle Disconnected -```golang -g.OnClose(func(c *Conn) { - // clear sessions from user layer -}) -``` +### HTTPS Examples -### Handle Data -```golang -g.OnData(func(c *Conn, data []byte) { - // decode data - // ... -}) -``` +- [http-tls_server](https://github.com/lesismal/nbio_examples/blob/master/http/server_tls/server.go) +- visit: https://localhost:8888/echo -### Handle Memory Allocation/Free For Reading -```golang -import "sync" +### Websocket Examples -var memPool = sync.Pool{ - New: func() interface{} { - return make([]byte, yourSize) - }, -} +- [websocket-server](https://github.com/lesismal/nbio_examples/blob/master/websocket/server/server.go) +- [websocket-client](https://github.com/lesismal/nbio_examples/blob/master/websocket/client/client.go) -g.OnReadBufferAlloc(func(c *Conn) []byte { - return memPool.Get().([]byte) -}) -g.OnReadBufferFree(func(c *Conn, b []byte) { - memPool.Put(b) -}) -``` +### Websocket TLS Examples -### Handle Memory Free For Writing -```golang -g.OnWriteBufferFree(func(c *Conn, b []byte) { - // ... -}) -``` +- [websocket-tls-server](https://github.com/lesismal/nbio_examples/blob/master/websocket_tls/server/server.go) +- [websocket-tls-client](https://github.com/lesismal/nbio_examples/blob/master/websocket_tls/client/client.go) -### Handle Conn Before Read -```golang -// BeforeRead registers callback before syscall.Read -// the handler would be called only on windows -g.OnData(func(c *Conn, data []byte) { - c.SetReadDeadline(time.Now().Add(time.Second*30)) -}) -``` -### Handle Conn After Read -```golang -// AfterRead registers callback after syscall.Read -// the handler would be called only on *nix -g.BeforeRead(func(c *Conn) { - c.SetReadDeadline(time.Now().Add(time.Second*30)) -}) -``` +### Use With Other STD Based Frameworkds -### Handle Conn Before Write -```golang -g.OnData(func(c *Conn, data []byte) { - c.SetWriteDeadline(time.Now().Add(time.Second*5)) -}) -``` +- [echo-http-and-websocket-server](https://github.com/lesismal/nbio_examples/blob/master/http_with_other_frameworks/echo_server/echo_server.go) +- [gin-http-and-websocket-server](https://github.com/lesismal/nbio_examples/blob/master/http_with_other_frameworks/gin_server/gin_server.go) +- [go-chi-http-and-websocket-server](https://github.com/lesismal/nbio_examples/blob/master/http_with_other_frameworks/go-chi_server/go-chi_server.go) -## Echo Examples +### More Examples -- [echo-server](https://github.com/lesismal/nbio_examples/blob/master/echo/server/server.go) -- [echo-client](https://github.com/lesismal/nbio_examples/blob/master/echo/client/client.go) +- [nbio-examples](https://github.com/lesismal/nbio-examples) -## TLS Examples -- [tls-server](https://github.com/lesismal/nbio_examples/blob/master/tls/server/server.go) -- [tls-client](https://github.com/lesismal/nbio_examples/blob/master/tls/client/client.go) -## HTTP Examples +## 1M Websocket Connections Benchmark -- [http-server](https://github.com/lesismal/nbio_examples/blob/master/http/server/server.go) -- [http-client](https://github.com/lesismal/nbio_examples/blob/master/http/client/client.go) +For more details: [go-websocket-benchmark](https://github.com/lesismal/go-websocket-benchmark) -## HTTPS Examples +```sh +# lsb_release -a +LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch +Distributor ID: Ubuntu +Description: Ubuntu 20.04.6 LTS +Release: 20.04 +Codename: focal + +# free + total used free shared buff/cache available +Mem: 24969564 15656352 3422212 1880 5891000 8899604 +Swap: 0 0 0 + +# cat /proc/cpuinfo | grep processor +processor : 0 +processor : 1 +processor : 2 +processor : 3 +processor : 4 +processor : 5 +processor : 6 +processor : 7 +processor : 8 +processor : 9 +processor : 10 +processor : 11 +processor : 12 +processor : 13 +processor : 14 +processor : 15 + + +# taskset +run nbio_nonblocking server on cpu 0-7 + +-------------------------------------------------------------- +BenchType : BenchEcho +Framework : nbio_nonblocking +TPS : 104713 +EER : 280.33 +Min : 56.90us +Avg : 95.36ms +Max : 2.29s +TP50 : 62.82ms +TP75 : 65.38ms +TP90 : 89.38ms +TP95 : 409.55ms +TP99 : 637.95ms +Used : 47.75s +Total : 5000000 +Success : 5000000 +Failed : 0 +Conns : 1000000 +Concurrency: 10000 +Payload : 1024 +CPU Min : 0.00% +CPU Avg : 373.53% +CPU Max : 602.33% +MEM Min : 978.70M +MEM Avg : 979.88M +MEM Max : 981.14M +-------------------------------------------------------------- +``` -- [http-tls_server](https://github.com/lesismal/nbio_examples/blob/master/http/server_tls/server.go) -- visit: https://localhost:8888/echo -## Websocket Examples +## Magics For HTTP and Websocket -- [websocket-server](https://github.com/lesismal/nbio_examples/blob/master/websocket/server/server.go) -- [websocket-client](https://github.com/lesismal/nbio_examples/blob/master/websocket/client/client.go) +### Different IOMod -## Websocket TLS Examples +| IOMod | Remarks | +| ---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| IOModNonBlocking | There's no difference between this IOMod and the old version with no IOMod. All the connections will be handled by poller. | +| IOModBlocking | All the connections will be handled by at least one goroutine, for websocket, we can set Upgrader.BlockingModAsyncWrite=true to handle writing with a separated goroutine and then avoid Head-of-line blocking on broadcasting scenarios. | +| IOModMixed | We set the Engine.MaxBlockingOnline, if the online num is smaller than it, the new connection will be handled by single goroutine as IOModBlocking, else the new connection will be handled by poller. | -- [websocket-tls-server](https://github.com/lesismal/nbio_examples/blob/master/websocket_tls/server/server.go) -- [websocket-tls-client](https://github.com/lesismal/nbio_examples/blob/master/websocket_tls/client/client.go) +The `IOModBlocking` aims to improve the performance for low online service, it runs faster than std. +The `IOModMixed` aims to keep a balance between performance and cpu/mem cost in different scenarios: when there are not too many online connections, it performs better than std, or else it can serve lots of online connections and keep healthy. -## Websocket 1M Connections Examples +### Using Websocket With Std Server -- [websocket-1m-connections-server](https://github.com/lesismal/nbio_examples/tree/master/websocket_1m/server/server.go) -- [websocket-1m-connections-client](https://github.com/lesismal/nbio_examples/tree/master/websocket_1m/client/client.go) +```golang +package main + +import ( + "fmt" + "net/http" + + "github.com/lesismal/nbio/nbhttp/websocket" +) + +var ( + upgrader = newUpgrader() +) + +func newUpgrader() *websocket.Upgrader { + u := websocket.NewUpgrader() + u.OnOpen(func(c *websocket.Conn) { + // echo + fmt.Println("OnOpen:", c.RemoteAddr().String()) + }) + u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { + // echo + fmt.Println("OnMessage:", messageType, string(data)) + c.WriteMessage(messageType, data) + }) + u.OnClose(func(c *websocket.Conn, err error) { + fmt.Println("OnClose:", c.RemoteAddr().String(), err) + }) + return u +} -## Use With Other STD Based Frameworkds +func onWebsocket(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + panic(err) + } + fmt.Println("Upgraded:", conn.RemoteAddr().String()) +} -- [gin-http-and-websocket-server](https://github.com/lesismal/nbio_examples/blob/master/http_with_other_frameworks/gin_server/gin_server.go) -- [echo-http-and-websocket-server](https://github.com/lesismal/nbio_examples/blob/master/http_with_other_frameworks/echo_server/echo_server.go) +func main() { + mux := &http.ServeMux{} + mux.HandleFunc("/ws", onWebsocket) + server := http.Server{ + Addr: "localhost:8080", + Handler: mux, + } + fmt.Println("server exit:", server.ListenAndServe()) +} +``` -## More Examples -- [nbio-examples](https://github.com/lesismal/nbio-examples) +## Credits +- [xtaci/gaio](https://github.com/xtaci/gaio) +- [gorilla/websocket](https://github.com/gorilla/websocket) +- [crossbario/autobahn](https://github.com/crossbario) + + +## Contributors +Thanks Everyone: +- [acgreek](https://github.com/acgreek) +- [acsecureworks](https://github.com/acsecureworks) +- [arunsathiya](https://github.com/arunsathiya) +- [byene0923](https://github.com/byene0923) +- [guonaihong](https://github.com/guonaihong) +- [isletnet](https://github.com/isletnet) +- [liwnn](https://github.com/liwnn) +- [manjun21](https://github.com/manjun21) +- [maxkidd](https://github.com/maxkidd) +- [om26er](https://github.com/om26er) +- [rfyiamcool](https://github.com/rfyiamcool) +- [sunny352](https://github.com/sunny352) +- [sunvim](https://github.com/sunvim) +- [wuqinqiang](https://github.com/wuqinqiang) +- [wziww](https://github.com/wziww) +- [yicixin](https://github.com/yicixin) +- [youzhixiaomutou](https://github.com/youzhixiaomutou) +- [zbh255](https://github.com/zbh255) +- [DevNewbie1826](https://github.com/DevNewbie1826) +- [IceflowRE](https://github.com/IceflowRE) +- [Jourmey](https://github.com/Jourmey) +- [YanKawaYu](https://github.com/YanKawaYu) + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=lesismal/nbio&type=Date)](https://star-history.com/#lesismal/nbio&Date) diff --git a/vendor/github.com/lesismal/nbio/autobahn/.gitignore b/vendor/github.com/lesismal/nbio/autobahn/.gitignore deleted file mode 100644 index 6c576985..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/.gitignore +++ /dev/null @@ -1 +0,0 @@ -report/ diff --git a/vendor/github.com/lesismal/nbio/autobahn/README.md b/vendor/github.com/lesismal/nbio/autobahn/README.md deleted file mode 100644 index cc3de5a0..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## -Autobhan report generation - -Makefile in this current directory has an example to generate autobhan report for webserver - -Command to generate the report - make report - - note - report will be saved under nbio/autobhan/report/output folder \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/autobahn/config/client_tests.json b/vendor/github.com/lesismal/nbio/autobahn/config/client_tests.json deleted file mode 100644 index e0957d3f..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/config/client_tests.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "outdir": "./report/output", - "servers": [ - { - "agent": "non-tls", - "url": "ws://localhost:9998/echo/message", - "options": { - "version": 18 - } - }, - { - "agent": "tls", - "url": "wss://localhost:9999/echo/message", - "options": { - "version": 18 - } - } - ], - "cases": [ - "*" - ], - "exclude-cases": ["1[1-4].*"], - "exclude-agent-cases": {} -} \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/autobahn/docker/autobahn/Dockerfile b/vendor/github.com/lesismal/nbio/autobahn/docker/autobahn/Dockerfile deleted file mode 100644 index 20af11c0..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/docker/autobahn/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM alpine:3.12 - -RUN apk add --no-cache python2 python2-dev gcc build-base musl-dev libffi-dev openssl-dev && \ - python -m ensurepip && \ - pip install --upgrade pip && \ - pip install --no-python-version-warning autobahntestsuite - -VOLUME /config -VOLUME /report - -ENV modeEnv=${mode} -ENV specEnv=${spec} - -ENTRYPOINT ["/usr/bin/wstest","--mode", "${modeEnv}","--spec","${specEnv}"] diff --git a/vendor/github.com/lesismal/nbio/autobahn/docker/server/Dockerfile b/vendor/github.com/lesismal/nbio/autobahn/docker/server/Dockerfile deleted file mode 100644 index 4e15edac..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/docker/server/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM golang:1.16-alpine3.13 - -ARG testfile=tfile -WORKDIR /go/src/github.com/lesismal/nbio - -COPY go.mod . -COPY go.sum . -RUN go mod download - -COPY . . -ENV CGO_ENABLED=0 - -RUN go test -c -tags autobahn -coverpkg "github.com/lesismal/nbio/..." "github.com/lesismal/nbio/${testfile}" -o "server.test" - -ENTRYPOINT ["./server.test", "-test.coverprofile", "/report/server.coverage"] \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/autobahn/main.go b/vendor/github.com/lesismal/nbio/autobahn/main.go deleted file mode 100644 index c0373a35..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/main.go +++ /dev/null @@ -1,265 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "html/template" - "log" - "net/http" - "os" - "path" - "sort" - "strconv" - "strings" - "text/tabwriter" -) - -var ( - verbose = flag.Bool("verbose", false, "be verbose") - web = flag.String("http", "", "open web browser instead") -) - -const ( - statusOK = "OK" - statusInformational = "INFORMATIONAL" - statusUnimplemented = "UNIMPLEMENTED" - statusNonStrict = "NON-STRICT" - statusUnclean = "UNCLEAN" - statusFailed = "FAILED" -) - -func failing(behavior string) bool { - switch behavior { - // case statusUnclean, statusFailed, statusNonStrict: // we should probably fix the nonstrict as well at some point - case statusUnclean, statusFailed: - return true - default: - return false - } -} - -type statusCounter struct { - Total int - OK int - Informational int - Unimplemented int - NonStrict int - Unclean int - Failed int -} - -func (c *statusCounter) Inc(s string) { - c.Total++ - switch s { - case statusOK: - c.OK++ - case statusInformational: - c.Informational++ - case statusNonStrict: - c.NonStrict++ - case statusUnimplemented: - c.Unimplemented++ - case statusUnclean: - c.Unclean++ - case statusFailed: - c.Failed++ - default: - panic(fmt.Sprintf("unexpected status %q", s)) - } -} - -func main() { - log.SetFlags(0) - flag.Parse() - - if flag.NArg() < 1 { - log.Fatalf("Usage: %s [options] ", os.Args[0]) - } - - base := path.Dir(flag.Arg(0)) - - if addr := *web; addr != "" { - http.HandleFunc("/", handlerIndex()) - http.Handle("/report/", http.StripPrefix("/report/", - http.FileServer(http.Dir(base)), - )) - log.Fatal(http.ListenAndServe(addr, nil)) - return - } - - var report report - if err := decodeFile(os.Args[1], &report); err != nil { - log.Fatal(err) - } - - var servers []string - for s := range report { - servers = append(servers, s) - } - sort.Strings(servers) - - var ( - failed bool - ) - tw := tabwriter.NewWriter(os.Stderr, 0, 4, 1, ' ', 0) - for _, server := range servers { - var ( - srvFailed bool - hdrWritten bool - counter statusCounter - ) - - var cases []string - for id := range report[server] { - cases = append(cases, id) - } - sortBySegment(cases) - for _, id := range cases { - c := report[server][id] - - var r entryReport - err := decodeFile(path.Join(base, c.ReportFile), &r) - if err != nil { - log.Fatal(err) - } - counter.Inc(c.Behavior) - bad := failing(c.Behavior) - if bad { - srvFailed = true - failed = true - } - if *verbose || bad { - if !hdrWritten { - hdrWritten = true - n, _ := fmt.Fprintf(os.Stderr, "AGENT %q\n", server) - fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) - } - fmt.Fprintf(tw, "%s\t%s\t%s\n", server, id, c.Behavior) - } - if bad { - fmt.Fprintf(tw, "\tdesc:\t%s\n", r.Description) - fmt.Fprintf(tw, "\texp: \t%s\n", r.Expectation) - fmt.Fprintf(tw, "\tact: \t%s\n", r.Result) - } - } - if hdrWritten { - fmt.Fprint(tw, "\n") - } - var status string - if srvFailed { - status = statusFailed - } else { - status = statusOK - } - n, _ := fmt.Fprintf(tw, "AGENT %q SUMMARY (%s)\n", server, status) - fmt.Fprintf(tw, "%s\n", strings.Repeat("=", n-1)) - - fmt.Fprintf(tw, "TOTAL:\t%d\n", counter.Total) - fmt.Fprintf(tw, "%s:\t%d\n", statusOK, counter.OK) - fmt.Fprintf(tw, "%s:\t%d\n", statusInformational, counter.Informational) - fmt.Fprintf(tw, "%s:\t%d\n", statusUnimplemented, counter.Unimplemented) - fmt.Fprintf(tw, "%s:\t%d\n", statusNonStrict, counter.NonStrict) - fmt.Fprintf(tw, "%s:\t%d\n", statusUnclean, counter.Unclean) - fmt.Fprintf(tw, "%s:\t%d\n", statusFailed, counter.Failed) - fmt.Fprint(tw, "\n") - tw.Flush() - } - var rc int - if failed { - rc = 1 - fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusFailed) - } else { - fmt.Fprintf(tw, "\n\nTEST %s\n\n", statusOK) - } - - tw.Flush() - os.Exit(rc) -} - -type report map[string]server - -type server map[string]entry - -type entry struct { - Behavior string `json:"behavior"` - BehaviorClose string `json:"behaviorClose"` - Duration int `json:"duration"` - RemoveCloseCode int `json:"removeCloseCode"` - ReportFile string `json:"reportFile"` -} - -type entryReport struct { - Description string `json:"description"` - Expectation string `json:"expectation"` - Result string `json:"result"` - Duration int `json:"duration"` -} - -func decodeFile(path string, x interface{}) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - d := json.NewDecoder(f) - return d.Decode(x) -} - -func compareBySegment(a, b string) int { - as := strings.Split(a, ".") - bs := strings.Split(b, ".") - for i := 0; i < min(len(as), len(bs)); i++ { - ax := mustInt(as[i]) - bx := mustInt(bs[i]) - if ax == bx { - continue - } - return ax - bx - } - return len(b) - len(a) -} - -func mustInt(s string) int { - const bits = 32 << (^uint(0) >> 63) - x, err := strconv.ParseInt(s, 10, bits) - if err != nil { - panic(err) - } - return int(x) -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func handlerIndex() func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if *verbose { - log.Printf("reqeust to %s", r.URL.Path) - } - if r.URL.Path != "/" { - w.WriteHeader(http.StatusNotFound) - return - } - if err := index.Execute(w, nil); err != nil { - w.WriteHeader(http.StatusInternalServerError) - log.Fatal(err) - return - } - } -} - -var index = template.Must(template.New("").Parse(` - - -

Welcome to WebSocket test server!

-

Ready to Autobahn!

-Reports - - -`)) diff --git a/vendor/github.com/lesismal/nbio/autobahn/main_go18.go b/vendor/github.com/lesismal/nbio/autobahn/main_go18.go deleted file mode 100644 index 12095055..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/main_go18.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build go1.8 - -package main - -import "sort" - -func sortBySegment(s []string) { - sort.Slice(s, func(i, j int) bool { - return compareBySegment(s[i], s[j]) < 0 - }) -} diff --git a/vendor/github.com/lesismal/nbio/autobahn/script/test.sh b/vendor/github.com/lesismal/nbio/autobahn/script/test.sh deleted file mode 100755 index abed8d80..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/script/test.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash -set -x -set -e -FOLLOW_LOGS=1 - -while [[ $# -gt 0 ]]; do - key="$1" - case $key in - --network) - NETWORK="$2" - shift - ;; - - --build) - case "$2" in - autobahn) - mode=$3 - spec=$4 - docker build . --file autobahn/docker/autobahn/Dockerfile --tag nbio-autobahn - shift - ;; - server) - docker build . --file autobahn/docker/server/Dockerfile --tag nbio-server --build-arg testfile=$3 - shift - ;; - *) - mode=$2 - spec=$3 - docker build . --file autobahn/docker/autobahn/Dockerfile --tag nbio-autobahn - docker build . --file autobahn/docker/server/Dockerfile --tag nbio-server --build-arg testfile=$4 - ;; - esac - ;; - - --run) - docker run \ - --interactive \ - --tty \ - ${@:2} - exit $? - ;; - - --follow-logs) - FOLLOW_LOGS=1 - shift - ;; - esac - shift -done - -with_prefix() { - local p="$1" - shift - - local out=$(mktemp -u nbio.fifo.out.XXXX) - local err=$(mktemp -u nbio.fifo.err.XXXX) - mkfifo $out $err - if [ $? -ne 0 ]; then - exit 1 - fi - - # Start two background sed processes. - sed "s/^/$p/" <$out & - sed "s/^/$p/" <$err >&2 & - - # Run the program - "$@" >$out 2>$err - rm $out $err -} - -random=$(xxd -l 4 -p /dev/random) -server="${random}_nbio-server" -autobahn="${random}_nbio-autobahn" - -network="nbio-network-$random" -docker network create --driver bridge "$network" -if [ $? -ne 0 ]; then - exit 1 -fi - -docker run \ - --interactive \ - --tty \ - --detach \ - --network="host" \ - -v $(pwd)/report:/report \ - --name="$server" \ - "nbio-server" - -echo ${mode} -docker run \ - --interactive \ - --tty \ - --detach \ - --network="host" \ - -v $(pwd)/autobahn/config:/config \ - -v $(pwd)/autobahn/report:/report \ - --name="$autobahn" \ - "nbio-autobahn"\ - --mode=${mode} --spec=${spec} - - -if [[ $FOLLOW_LOGS -eq 1 ]]; then - (with_prefix "$(tput setaf 3)[nbio-autobahn]: $(tput sgr0)" docker logs --follow "$autobahn")& - (with_prefix "$(tput setaf 5)[nbio-server]: $(tput sgr0)" docker logs --follow "$server")& -fi - -trap ctrl_c INT -ctrl_c () { - echo "SIGINT received; cleaning up" - docker kill --signal INT "$autobahn" >/dev/null - docker kill --signal INT "$server" >/dev/null - cleanup - exit 130 -} - -cleanup() { - docker rm "$server" >/dev/null - docker rm "$autobahn" >/dev/null - docker network rm "$network" -} - -docker wait "$autobahn" >/dev/null -docker stop "$server" >/dev/null - -cleanup -set +x \ No newline at end of file diff --git a/vendor/github.com/lesismal/nbio/autobahn/server/autobahn_test.go b/vendor/github.com/lesismal/nbio/autobahn/server/autobahn_test.go deleted file mode 100644 index c1b185b3..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/server/autobahn_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build autobahn - -package main - -import "testing" - -func TestCallMain(t *testing.T) { - main() -} diff --git a/vendor/github.com/lesismal/nbio/autobahn/server/server.go b/vendor/github.com/lesismal/nbio/autobahn/server/server.go deleted file mode 100644 index 083a73a4..00000000 --- a/vendor/github.com/lesismal/nbio/autobahn/server/server.go +++ /dev/null @@ -1,166 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "os" - "os/signal" - "time" - - "github.com/lesismal/llib/std/crypto/tls" - "github.com/lesismal/nbio/nbhttp" - "github.com/lesismal/nbio/nbhttp/websocket" - "github.com/lesismal/nbio/taskpool" -) - -func newUpgrader(isDataFrame bool) *websocket.Upgrader { - u := websocket.NewUpgrader() - u.EnableCompression(true) - if isDataFrame { - isFirst := true - u.OnDataFrame(func(c *websocket.Conn, messageType websocket.MessageType, fin bool, data []byte) { - err := c.WriteFrame(messageType, isFirst, fin, data) - if err != nil { - c.Close() - return - } - if fin { - isFirst = true - } else { - isFirst = false - } - }) - } else { - u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { - c.WriteMessage(messageType, data) - }) - } - - return u -} - -func onWebsocketFrame(w http.ResponseWriter, r *http.Request) { - upgrader := newUpgrader(true) - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - panic(err) - } - conn.SetDeadline(time.Time{}) -} - -func onWebsocketMessage(w http.ResponseWriter, r *http.Request) { - upgrader := newUpgrader(false) - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - panic(err) - } - conn.SetDeadline(time.Time{}) -} - -func main() { - cert, err := tls.X509KeyPair(rsaCertPEM, rsaKeyPEM) - if err != nil { - log.Fatalf("tls.X509KeyPair failed: %v", err) - } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: true, - } - - mux := &http.ServeMux{} - mux.HandleFunc("/echo/message", onWebsocketMessage) - mux.HandleFunc("/echo/frame", onWebsocketFrame) - - log.Printf("calling new server tls\n") - - messageHandlerExecutePool := taskpool.NewFixedPool(100, 1000) - svrTLS := nbhttp.NewServer(nbhttp.Config{ - Network: "tcp", - AddrsTLS: []string{"localhost:9999"}, - TLSConfig: tlsConfig, - ReadBufferSize: 1024 * 1024, - Handler: mux, - ServerExecutor: messageHandlerExecutePool.Go, - }) - svr := nbhttp.NewServer(nbhttp.Config{ - Network: "tcp", - Addrs: []string{"localhost:9998"}, - ReadBufferSize: 1024 * 1024, - Handler: mux, - ServerExecutor: messageHandlerExecutePool.Go, - }) - - log.Printf("calling start non-tls\n") - err = svr.Start() - if err != nil { - fmt.Printf("nbio.Start non-tls failed: %v\n", err) - return - } - defer svr.Stop() - - log.Printf("calling start tls\n") - err = svrTLS.Start() - if err != nil { - fmt.Printf("nbio.Start tls failed: %v\n", err) - return - } - defer svrTLS.Stop() - - interrupt := make(chan os.Signal, 1) - signal.Notify(interrupt, os.Interrupt) - <-interrupt - log.Println("exit") -} - -var rsaCertPEM = []byte(`-----BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUJeohtgk8nnt8ofratXJg7kUJsI4wDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMDcwODIyNThaFw0zMDEy -MDUwODIyNThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCy+ZrIvwwiZv4bPmvKx/637ltZLwfgh3ouiEaTchGu -IQltthkqINHxFBqqJg44TUGHWthlrq6moQuKnWNjIsEc6wSD1df43NWBLgdxbPP0 -x4tAH9pIJU7TQqbznjDBhzRbUjVXBIcn7bNknY2+5t784pPF9H1v7h8GqTWpNH9l -cz/v+snoqm9HC+qlsFLa4A3X9l5v05F1uoBfUALlP6bWyjHAfctpiJkoB9Yw1TJa -gpq7E50kfttwfKNkkAZIbib10HugkMoQJAs2EsGkje98druIl8IXmuvBIF6nZHuM -lt3UIZjS9RwPPLXhRHt1P0mR7BoBcOjiHgtSEs7Wk+j7AgMBAAGjUzBRMB0GA1Ud -DgQWBBQdheJv73XSOhgMQtkwdYPnfO02+TAfBgNVHSMEGDAWgBQdheJv73XSOhgM -QtkwdYPnfO02+TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBf -SKVNMdmBpD9m53kCrguo9iKQqmhnI0WLkpdWszc/vBgtpOE5ENOfHGAufHZve871 -2fzTXrgR0TF6UZWsQOqCm5Oh3URsCdXWewVMKgJ3DCii6QJ0MnhSFt6+xZE9C6Hi -WhcywgdR8t/JXKDam6miohW8Rum/IZo5HK9Jz/R9icKDGumcqoaPj/ONvY4EUwgB -irKKB7YgFogBmCtgi30beLVkXgk0GEcAf19lHHtX2Pv/lh3m34li1C9eBm1ca3kk -M2tcQtm1G89NROEjcG92cg+GX3GiWIjbI0jD1wnVy2LCOXMgOVbKfGfVKISFt0b1 -DNn00G8C6ttLoGU2snyk ------END CERTIFICATE----- -`) - -var rsaKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAsvmayL8MImb+Gz5rysf+t+5bWS8H4Id6LohGk3IRriEJbbYZ -KiDR8RQaqiYOOE1Bh1rYZa6upqELip1jYyLBHOsEg9XX+NzVgS4HcWzz9MeLQB/a -SCVO00Km854wwYc0W1I1VwSHJ+2zZJ2Nvube/OKTxfR9b+4fBqk1qTR/ZXM/7/rJ -6KpvRwvqpbBS2uAN1/Zeb9ORdbqAX1AC5T+m1soxwH3LaYiZKAfWMNUyWoKauxOd -JH7bcHyjZJAGSG4m9dB7oJDKECQLNhLBpI3vfHa7iJfCF5rrwSBep2R7jJbd1CGY -0vUcDzy14UR7dT9JkewaAXDo4h4LUhLO1pPo+wIDAQABAoIBAF6yWwekrlL1k7Xu -jTI6J7hCUesaS1yt0iQUzuLtFBXCPS7jjuUPgIXCUWl9wUBhAC8SDjWe+6IGzAiH -xjKKDQuz/iuTVjbDAeTb6exF7b6yZieDswdBVjfJqHR2Wu3LEBTRpo9oQesKhkTS -aFF97rZ3XCD9f/FdWOU5Wr8wm8edFK0zGsZ2N6r57yf1N6ocKlGBLBZ0v1Sc5ShV -1PVAxeephQvwL5DrOgkArnuAzwRXwJQG78L0aldWY2q6xABQZQb5+ml7H/kyytef -i+uGo3jHKepVALHmdpCGr9Yv+yCElup+ekv6cPy8qcmMBqGMISL1i1FEONxLcKWp -GEJi6QECgYEA3ZPGMdUm3f2spdHn3C+/+xskQpz6efiPYpnqFys2TZD7j5OOnpcP -ftNokA5oEgETg9ExJQ8aOCykseDc/abHerYyGw6SQxmDbyBLmkZmp9O3iMv2N8Pb -Nrn9kQKSr6LXZ3gXzlrDvvRoYUlfWuLSxF4b4PYifkA5AfsdiKkj+5sCgYEAzseF -XDTRKHHJnzxZDDdHQcwA0G9agsNj64BGUEjsAGmDiDyqOZnIjDLRt0O2X3oiIE5S -TXySSEiIkxjfErVJMumLaIwqVvlS4pYKdQo1dkM7Jbt8wKRQdleRXOPPN7msoEUk -Ta9ZsftHVUknPqblz9Uthb5h+sRaxIaE1llqDiECgYATS4oHzuL6k9uT+Qpyzymt -qThoIJljQ7TgxjxvVhD9gjGV2CikQM1Vov1JBigj4Toc0XuxGXaUC7cv0kAMSpi2 -Y+VLG+K6ux8J70sGHTlVRgeGfxRq2MBfLKUbGplBeDG/zeJs0tSW7VullSkblgL6 -nKNa3LQ2QEt2k7KHswryHwKBgENDxk8bY1q7wTHKiNEffk+aFD25q4DUHMH0JWti -fVsY98+upFU+gG2S7oOmREJE0aser0lDl7Zp2fu34IEOdfRY4p+s0O0gB+Vrl5VB -L+j7r9bzaX6lNQN6MvA7ryHahZxRQaD/xLbQHgFRXbHUyvdTyo4yQ1821qwNclLk -HUrhAoGAUtjR3nPFR4TEHlpTSQQovS8QtGTnOi7s7EzzdPWmjHPATrdLhMA0ezPj -Mr+u5TRncZBIzAZtButlh1AHnpN/qO3P0c0Rbdep3XBc/82JWO8qdb5QvAkxga3X -BpA7MNLxiqss+rCbwf3NbWxEMiDQ2zRwVoafVFys7tjmv6t2Xck= ------END RSA PRIVATE KEY----- -`) diff --git a/vendor/github.com/lesismal/nbio/conn.go b/vendor/github.com/lesismal/nbio/conn.go index 5f445878..edf47c5c 100644 --- a/vendor/github.com/lesismal/nbio/conn.go +++ b/vendor/github.com/lesismal/nbio/conn.go @@ -13,12 +13,101 @@ import ( "github.com/lesismal/nbio/logging" ) -// OnData registers callback for data. +// ConnType is used to identify different types of Conn. +type ConnType = int8 + +const ( + // ConnTypeTCP represents TCP Conn. + ConnTypeTCP ConnType = iota + 1 + // ConnTypeUDPServer represents UDP Conn used as a listener. + ConnTypeUDPServer + // ConnTypeUDPClientFromRead represents UDP connection that + // is sending data to our UDP Server from peer. + ConnTypeUDPClientFromRead + // ConnTypeUDPClientFromDial represents UDP Conn that is sending + // data to other UDP Server from ourselves. + ConnTypeUDPClientFromDial + // ConnTypeUnix represents Unix Conn. + ConnTypeUnix +) + +// Type . +// +//go:norace +func (c *Conn) Type() ConnType { + return c.typ +} + +// IsTCP returns whether this Conn is a TCP Conn. +// +//go:norace +func (c *Conn) IsTCP() bool { + return c.typ == ConnTypeTCP +} + +// IsUDP returns whether this Conn is a UDP Conn. +// +//go:norace +func (c *Conn) IsUDP() bool { + switch c.typ { + case ConnTypeUDPServer, ConnTypeUDPClientFromDial, ConnTypeUDPClientFromRead: + return true + } + return false +} + +// IsUnix returns whether this Conn is a Unix Conn. +// +//go:norace +func (c *Conn) IsUnix() bool { + return c.typ == ConnTypeUnix +} + +// Session returns user session. +// +//go:norace +func (c *Conn) Session() interface{} { + return c.session +} + +// SetSession sets user session. +// +//go:norace +func (c *Conn) SetSession(session interface{}) { + c.session = session +} + +// OnData registers Conn's data handler. +// Notice: +// 1. The data readed by the poller is not handled by this Conn's data +// handler by default. +// 2. The data readed by the poller is handled by nbio.Engine's data +// handler which is registered by nbio.Engine.OnData by default. +// 3. This Conn's data handler is used to customize your implementation, +// you can set different data handler for different Conns, +// and call Conn's data handler in nbio.Engine's data handler. +// For example: +// engine.OnData(func(c *nbio.Conn, data byte){ +// c.DataHandler()(c, data) +// }) +// conn1.OnData(yourDatahandler1) +// conn2.OnData(yourDatahandler2) +// +//go:norace func (c *Conn) OnData(h func(conn *Conn, data []byte)) { - c.DataHandler = h + c.dataHandler = h +} + +// DataHandler returns Conn's data handler. +// +//go:norace +func (c *Conn) DataHandler() func(conn *Conn, data []byte) { + return c.dataHandler } -// Dial wraps net.Dial. +// Dial calls net.Dial to make a net.Conn and convert it to *nbio.Conn. +// +//go:norace func Dial(network string, address string) (*Conn, error) { conn, err := net.Dial(network, address) if err != nil { @@ -27,7 +116,9 @@ func Dial(network string, address string) (*Conn, error) { return NBConn(conn) } -// DialTimeout wraps net.DialTimeout. +// Dial calls net.DialTimeout to make a net.Conn and convert it to *nbio.Conn. +// +//go:norace func DialTimeout(network string, address string, timeout time.Duration) (*Conn, error) { conn, err := net.DialTimeout(network, address, timeout) if err != nil { @@ -37,93 +128,134 @@ func DialTimeout(network string, address string, timeout time.Duration) (*Conn, } // Lock . +// +//go:norace func (c *Conn) Lock() { c.mux.Lock() } // Unlock . +// +//go:norace func (c *Conn) Unlock() { c.mux.Unlock() } -// IsClosed . +// IsClosed returns whether the Conn is closed. +// +//go:norace func (c *Conn) IsClosed() (bool, error) { return c.closed, c.closeErr } -// ExecuteLen . +// ExecuteLen returns the length of the Conn's job list. +// +//go:norace func (c *Conn) ExecuteLen() int { c.mux.Lock() - n := len(c.execList) + n := len(c.jobList) c.mux.Unlock() return n } -// Execute . -func (c *Conn) Execute(f func()) { +// Execute is used to run the job. +// +// How it works: +// If the job is the head/first of the Conn's job list, it will call the +// nbio.Engine.Execute to run all the jobs in the job list that include: +// 1. This job +// 2. New jobs that are pushed to the back of the list before this job +// is done. +// 3. nbio.Engine.Execute returns until there's no more jobs in the job +// list. +// +// Else if the job is not the head/first of the job list, it will push the +// job to the back of the job list and wait to be called. +// This guarantees there's at most one flow or goroutine running job/jobs +// for each Conn. +// This guarantees all the jobs are executed in order. +// +// Notice: +// 1. The job wouldn't run or pushed to the back of the job list if the +// connection is closed. +// 2. nbio.Engine.Execute is handled by a goroutine pool by default, users +// can customize it. +// +//go:norace +func (c *Conn) Execute(job func()) bool { c.mux.Lock() if c.closed { c.mux.Unlock() - return + return false } - isHead := (len(c.execList) == 0) - c.execList = append(c.execList, f) + isHead := (len(c.jobList) == 0) + c.jobList = append(c.jobList, job) c.mux.Unlock() + // If there's no job running, run Engine.Execute to run this job + // and new jobs appended before this head job is done. if isHead { - c.g.Execute(func() { - i := 0 - for { - func() { - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - logging.Error("conn execute failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) - } - }() - f() - }() - - c.mux.Lock() - i++ - if len(c.execList) == i { - c.execList = c.execList[0:0] - c.mux.Unlock() - return - } - f = c.execList[i] - c.mux.Unlock() - } - }) + c.execute(job) } + return true } -// MustExecute . -func (c *Conn) MustExecute(f func()) { +// MustExecute implements a similar function as Execute did, +// but will still execute or push the job to the +// back of the job list no matter whether Conn has been closed, +// it guarantees the job to be executed. +// This is used to handle the close event in nbio/nbhttp. +// +//go:norace +func (c *Conn) MustExecute(job func()) { c.mux.Lock() - isHead := (len(c.execList) == 0) - c.execList = append(c.execList, f) + isHead := (len(c.jobList) == 0) + c.jobList = append(c.jobList, job) c.mux.Unlock() + // If there's no job running, run Engine.Execute to run this job + // and new jobs appended before this head job is done. if isHead { - c.g.Execute(func() { - i := 0 - for { - f() - - c.mux.Lock() - i++ - if len(c.execList) == i { - c.execList = c.execList[0:0] - c.mux.Unlock() - return - } - f = c.execList[i] + c.execute(job) + } +} + +//go:norace +func (c *Conn) execute(job func()) { + c.p.g.Execute(func() { + i := 0 + for { + func() { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("conn execute failed: %v\n%v\n", + err, + *(*string)(unsafe.Pointer(&buf)), + ) + } + }() + job() + }() + + c.mux.Lock() + i++ + if len(c.jobList) == i { + // set nil to release the job and gc + c.jobList[i-1] = nil + // reuse the slice + c.jobList = c.jobList[0:0] c.mux.Unlock() + return } - }) - } + // get next job + job = c.jobList[i] + // set nil to release the job and gc + c.jobList[i] = nil + c.mux.Unlock() + } + }) } diff --git a/vendor/github.com/lesismal/nbio/conn_std.go b/vendor/github.com/lesismal/nbio/conn_std.go index 4d0e96ea..e3adb355 100644 --- a/vendor/github.com/lesismal/nbio/conn_std.go +++ b/vendor/github.com/lesismal/nbio/conn_std.go @@ -13,40 +13,53 @@ import ( "io" "net" "sync" + "syscall" "time" + + "github.com/lesismal/nbio/timer" ) -// Conn wraps net.Conn +// Conn wraps net.Conn. type Conn struct { - g *Gopher + p *poller hash int mux sync.Mutex - conn net.Conn + conn net.Conn + connUDP *udpConn + + rTimer *time.Timer + typ ConnType closed bool closeErr error ReadBuffer []byte - // user session + // user session. session interface{} - execList []func() + jobList []func() cache *bytes.Buffer - DataHandler func(c *Conn, data []byte) + dataHandler func(c *Conn, data []byte) + + onConnected func(c *Conn, err error) } -// Hash returns a hashcode +// Hash returns a hashcode. +// +//go:norace func (c *Conn) Hash() int { return c.hash } -// Read wraps net.Conn.Read +// Read wraps net.Conn.Read. +// +//go:norace func (c *Conn) Read(b []byte) (int, error) { if c.closeErr != nil { return 0, c.closeErr @@ -63,31 +76,115 @@ func (c *Conn) Read(b []byte) (int, error) { return nread, err } +//go:norace func (c *Conn) read(b []byte) (int, error) { - c.g.beforeRead(c) + var err error + var nread int + switch c.typ { + case ConnTypeTCP: + nread, err = c.readTCP(b) + case ConnTypeUDPServer, ConnTypeUDPClientFromDial: + nread, err = c.readUDP(b) + case ConnTypeUDPClientFromRead: + err = errors.New("invalid udp conn for reading") + default: + } + return nread, err +} + +//go:norace +func (c *Conn) readTCP(b []byte) (int, error) { + g := c.p.g + // g.beforeRead(c) nread, err := c.conn.Read(b) if c.closeErr == nil { c.closeErr = err } - if c.g.onRead != nil { + if g.onRead != nil { if nread > 0 { if c.cache == nil { c.cache = bytes.NewBuffer(nil) } c.cache.Write(b[:nread]) } - c.g.onRead(c) + g.onRead(c) return nread, nil } else if nread > 0 { - c.g.onData(c, b[:nread]) + b = b[:nread] + g.onDataPtr(c, &b) } return nread, err } -// Write wraps net.Conn.Write +//go:norace +func (c *Conn) readUDP(b []byte) (int, error) { + if c.connUDP == nil { + return 0, errors.New("invalid conn") + } + nread, rAddr, err := c.connUDP.ReadFromUDP(b) + if c.closeErr == nil { + c.closeErr = err + } + if err != nil { + return 0, err + } + + var g = c.p.g + var dstConn = c + if c.typ == ConnTypeUDPServer { + uc, ok := c.connUDP.getConn(c.p, rAddr) + if g.UDPReadTimeout > 0 { + uc.SetReadDeadline(time.Now().Add(g.UDPReadTimeout)) + } + if !ok { + p := g.pollers[c.Hash()%len(g.pollers)] + p.addConn(uc) + } + dstConn = uc + } + + if g.onRead != nil { + if nread > 0 { + if dstConn.cache == nil { + dstConn.cache = bytes.NewBuffer(nil) + } + dstConn.cache.Write(b[:nread]) + } + g.onRead(dstConn) + return nread, nil + } else if nread > 0 { + buf := b[:nread] + g.onDataPtr(dstConn, &buf) + } + + return nread, err +} + +// Write wraps net.Conn.Write. +// +//go:norace func (c *Conn) Write(b []byte) (int, error) { - c.g.beforeWrite(c) + var n int + var err error + switch c.typ { + case ConnTypeTCP: + n, err = c.writeTCP(b) + case ConnTypeUDPServer: + case ConnTypeUDPClientFromDial: + n, err = c.writeUDPClientFromDial(b) + case ConnTypeUDPClientFromRead: + n, err = c.writeUDPClientFromRead(b) + default: + } + if c.p.g.onWrittenSize != nil && n > 0 { + c.p.g.onWrittenSize(c, b[:n], n) + } + return n, err +} +//go:norace +func (c *Conn) writeTCP(b []byte) (int, error) { + // c.p.g.beforeWrite(c) nwrite, err := c.conn.Write(b) if err != nil { if c.closeErr == nil { @@ -95,44 +192,117 @@ func (c *Conn) Write(b []byte) (int, error) { } c.Close() } - c.g.onWriteBufferFree(c, b) return nwrite, err } -// Writev wraps buffers.WriteTo/syscall.Writev -func (c *Conn) Writev(in [][]byte) (int, error) { - buffers := net.Buffers(in) - nwrite, err := buffers.WriteTo(c.conn) +//go:norace +func (c *Conn) writeUDPClientFromDial(b []byte) (int, error) { + nwrite, err := c.connUDP.Write(b) if err != nil { if c.closeErr == nil { c.closeErr = err } c.Close() } - for _, v := range in { - c.g.onWriteBufferFree(c, v) + return nwrite, err +} + +//go:norace +func (c *Conn) writeUDPClientFromRead(b []byte) (int, error) { + nwrite, err := c.connUDP.WriteToUDP(b, c.connUDP.rAddr) + if err != nil { + if c.closeErr == nil { + c.closeErr = err + } + c.Close() } - return int(nwrite), err + return nwrite, err } -// Close wraps net.Conn.Close +// Writev wraps buffers.WriteTo/syscall.Writev. +// +//go:norace +func (c *Conn) Writev(in [][]byte) (int, error) { + if c.connUDP == nil { + buffers := net.Buffers(in) + nwrite, err := buffers.WriteTo(c.conn) + if err != nil { + if c.closeErr == nil { + c.closeErr = err + } + c.Close() + } + if c.p.g.onWrittenSize != nil && nwrite > 0 { + total := int(nwrite) + for i := 0; total > 0; i++ { + if total <= len(in[i]) { + c.p.g.onWrittenSize(c, in[i][:total], total) + total = 0 + } else { + c.p.g.onWrittenSize(c, in[i], len(in[i])) + total -= len(in[i]) + } + } + } + return int(nwrite), err + } + + var total = 0 + for _, b := range in { + nwrite, err := c.Write(b) + if nwrite > 0 { + total += nwrite + } + if c.p.g.onWrittenSize != nil && nwrite > 0 { + c.p.g.onWrittenSize(c, b[:nwrite], nwrite) + } + if err != nil { + if c.closeErr == nil { + c.closeErr = err + } + c.Close() + return total, err + } + } + return total, nil +} + +// Close wraps net.Conn.Close. +// +//go:norace func (c *Conn) Close() error { + var err error c.mux.Lock() if !c.closed { c.closed = true - err := c.conn.Close() + + if c.rTimer != nil { + c.rTimer.Stop() + c.rTimer = nil + } + + switch c.typ { + case ConnTypeTCP: + err = c.conn.Close() + case ConnTypeUDPServer, ConnTypeUDPClientFromDial, ConnTypeUDPClientFromRead: + err = c.connUDP.Close() + default: + } + c.mux.Unlock() - if c.g != nil { - c.g.pollers[c.Hash()%len(c.g.pollers)].deleteConn(c) + if c.p.g != nil { + c.p.deleteConn(c) } return err } c.mux.Unlock() - return nil + return err } // CloseWithError . +// +//go:norace func (c *Conn) CloseWithError(err error) error { if c.closeErr == nil { c.closeErr = err @@ -140,42 +310,93 @@ func (c *Conn) CloseWithError(err error) error { return c.Close() } -// LocalAddr wraps net.Conn.LocalAddr +// LocalAddr wraps net.Conn.LocalAddr. +// +//go:norace func (c *Conn) LocalAddr() net.Addr { - return c.conn.LocalAddr() + switch c.typ { + case ConnTypeTCP: + return c.conn.LocalAddr() + case ConnTypeUDPServer, ConnTypeUDPClientFromDial, ConnTypeUDPClientFromRead: + return c.connUDP.LocalAddr() + default: + } + return nil } -// RemoteAddr wraps net.Conn.RemoteAddr +// RemoteAddr wraps net.Conn.RemoteAddr. +// +//go:norace func (c *Conn) RemoteAddr() net.Addr { - return c.conn.RemoteAddr() + switch c.typ { + case ConnTypeTCP: + return c.conn.RemoteAddr() + case ConnTypeUDPClientFromDial: + return c.connUDP.RemoteAddr() + case ConnTypeUDPClientFromRead: + return c.connUDP.rAddr + default: + } + return nil } -// SetDeadline wraps net.Conn.SetDeadline +// SetDeadline wraps net.Conn.SetDeadline. +// +//go:norace func (c *Conn) SetDeadline(t time.Time) error { - if t.IsZero() { - t = time.Now().Add(timeForever) + if c.typ == ConnTypeTCP { + return c.conn.SetDeadline(t) } - return c.conn.SetDeadline(t) + return c.SetReadDeadline(t) } -// SetReadDeadline wraps net.Conn.SetReadDeadline +// SetReadDeadline wraps net.Conn.SetReadDeadline. +// +//go:norace func (c *Conn) SetReadDeadline(t time.Time) error { if t.IsZero() { - t = time.Now().Add(timeForever) + t = time.Now().Add(timer.TimeForever) } - return c.conn.SetReadDeadline(t) + + if c.typ == ConnTypeTCP { + return c.conn.SetReadDeadline(t) + } + + timeout := time.Until(t) + if c.rTimer == nil { + c.rTimer = c.p.g.AfterFunc(timeout, func() { + c.CloseWithError(errReadTimeout) + }) + } else { + c.rTimer.Reset(timeout) + } + + return nil } -// SetWriteDeadline wraps net.Conn.SetWriteDeadline +// SetWriteDeadline wraps net.Conn.SetWriteDeadline. +// +//go:norace func (c *Conn) SetWriteDeadline(t time.Time) error { + if c.typ != ConnTypeTCP { + return nil + } + if t.IsZero() { - t = time.Now().Add(timeForever) + t = time.Now().Add(timer.TimeForever) } + return c.conn.SetWriteDeadline(t) } -// SetNoDelay wraps net.Conn.SetNoDelay +// SetNoDelay wraps net.Conn.SetNoDelay. +// +//go:norace func (c *Conn) SetNoDelay(nodelay bool) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetNoDelay(nodelay) @@ -183,8 +404,14 @@ func (c *Conn) SetNoDelay(nodelay bool) error { return nil } -// SetReadBuffer wraps net.Conn.SetReadBuffer +// SetReadBuffer wraps net.Conn.SetReadBuffer. +// +//go:norace func (c *Conn) SetReadBuffer(bytes int) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetReadBuffer(bytes) @@ -192,8 +419,14 @@ func (c *Conn) SetReadBuffer(bytes int) error { return nil } -// SetWriteBuffer wraps net.Conn.SetWriteBuffer +// SetWriteBuffer wraps net.Conn.SetWriteBuffer. +// +//go:norace func (c *Conn) SetWriteBuffer(bytes int) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetWriteBuffer(bytes) @@ -201,8 +434,14 @@ func (c *Conn) SetWriteBuffer(bytes int) error { return nil } -// SetKeepAlive wraps net.Conn.SetKeepAlive +// SetKeepAlive wraps net.Conn.SetKeepAlive. +// +//go:norace func (c *Conn) SetKeepAlive(keepalive bool) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetKeepAlive(keepalive) @@ -210,8 +449,14 @@ func (c *Conn) SetKeepAlive(keepalive bool) error { return nil } -// SetKeepAlivePeriod wraps net.Conn.SetKeepAlivePeriod +// SetKeepAlivePeriod wraps net.Conn.SetKeepAlivePeriod. +// +//go:norace func (c *Conn) SetKeepAlivePeriod(d time.Duration) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetKeepAlivePeriod(d) @@ -219,8 +464,14 @@ func (c *Conn) SetKeepAlivePeriod(d time.Duration) error { return nil } -// SetLinger wraps net.Conn.SetLinger +// SetLinger wraps net.Conn.SetLinger. +// +//go:norace func (c *Conn) SetLinger(onoff int32, linger int32) error { + if c.typ != ConnTypeTCP { + return nil + } + conn, ok := c.conn.(*net.TCPConn) if ok { return conn.SetLinger(int(linger)) @@ -228,25 +479,32 @@ func (c *Conn) SetLinger(onoff int32, linger int32) error { return nil } -// Session returns user session -func (c *Conn) Session() interface{} { - return c.session -} - -// SetSession sets user session -func (c *Conn) SetSession(session interface{}) { - c.session = session -} +//go:norace +func newConn(conn net.Conn) *Conn { + c := &Conn{} + addr := conn.LocalAddr().String() -func newConn(conn net.Conn, fromClient ...interface{}) *Conn { - c := &Conn{ - conn: conn, + uc, ok := conn.(*net.UDPConn) + if ok { + rAddr := uc.RemoteAddr() + if rAddr == nil { + c.typ = ConnTypeUDPServer + c.connUDP = &udpConn{ + UDPConn: uc, + conns: map[string]*Conn{}, + } + } else { + c.typ = ConnTypeUDPClientFromDial + addr += rAddr.String() + c.connUDP = &udpConn{ + UDPConn: uc, + } + } + } else { + c.conn = conn + c.typ = ConnTypeTCP } - addr := conn.RemoteAddr().String() - if len(fromClient) > 0 { - addr = conn.LocalAddr().String() - } for _, ch := range addr { c.hash = 31*c.hash + int(ch) } @@ -257,14 +515,84 @@ func newConn(conn net.Conn, fromClient ...interface{}) *Conn { return c } -// NBConn converts net.Conn to *Conn +// NBConn converts net.Conn to *Conn. +// +//go:norace func NBConn(conn net.Conn) (*Conn, error) { if conn == nil { return nil, errors.New("invalid conn: nil") } c, ok := conn.(*Conn) if !ok { - c = newConn(conn, true) + c = newConn(conn) } return c, nil } + +type udpConn struct { + *net.UDPConn + rAddr *net.UDPAddr + + mux sync.RWMutex + parent *udpConn + conns map[string]*Conn +} + +//go:norace +func (u *udpConn) Close() error { + parent := u.parent + if parent != nil { + parent.mux.Lock() + delete(parent.conns, u.rAddr.String()) + parent.mux.Unlock() + } else { + u.UDPConn.Close() + for _, c := range u.conns { + c.Close() + } + u.conns = nil + } + return nil +} + +//go:norace +func (u *udpConn) getConn(p *poller, rAddr *net.UDPAddr) (*Conn, bool) { + u.mux.RLock() + addr := rAddr.String() + c, ok := u.conns[addr] + u.mux.RUnlock() + + if !ok { + c = &Conn{ + p: p, + typ: ConnTypeUDPClientFromRead, + connUDP: &udpConn{ + parent: u, + rAddr: rAddr, + UDPConn: u.UDPConn, + }, + } + hashAddr := u.LocalAddr().String() + addr + for _, ch := range hashAddr { + c.hash = 31*c.hash + int(ch) + } + if c.hash < 0 { + c.hash = -c.hash + } + u.mux.Lock() + u.conns[addr] = c + u.mux.Unlock() + } + + return c, ok +} + +//go:norace +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + if rc, ok := c.conn.(interface { + SyscallConn() (syscall.RawConn, error) + }); ok { + return rc.SyscallConn() + } + return nil, ErrUnsupported +} diff --git a/vendor/github.com/lesismal/nbio/conn_unix.go b/vendor/github.com/lesismal/nbio/conn_unix.go index 6f26f82b..f959837a 100644 --- a/vendor/github.com/lesismal/nbio/conn_unix.go +++ b/vendor/github.com/lesismal/nbio/conn_unix.go @@ -8,95 +8,370 @@ package nbio import ( + "encoding/binary" "errors" "net" + "runtime" "sync" + "sync/atomic" "syscall" "time" - - "github.com/lesismal/nbio/mempool" ) -// Conn implements net.Conn. +// var ( +// used to reset toWrite struct to empty value. +// emptyToWrite = toWrite{} + +// poolToWrite = sync.Pool{ +// New: func() interface{} { +// return &toWrite{} +// }, +// } +// ) + +//go:norace +func (c *Conn) newToWriteBuf(buf []byte) { + c.left += len(buf) + + allocator := c.p.g.BodyAllocator + appendBuffer := func() { + t := &toWrite{} // poolToWrite.New().(*toWrite) + pbuf := allocator.Malloc(len(buf)) + copy(*pbuf, buf) + t.buf = pbuf + c.writeList = append(c.writeList, t) + } + + if len(c.writeList) == 0 { + appendBuffer() + return + } + + tail := c.writeList[len(c.writeList)-1] + if tail.buf == nil { + appendBuffer() + } else { + l := len(buf) + tailLen := len(*tail.buf) + if tailLen+l > maxWriteCacheOrFlushSize { + appendBuffer() + } else { + if cap(*tail.buf) < tailLen+l { + pbuf := allocator.Malloc(tailLen + l) + *pbuf = (*pbuf)[:tailLen] + copy(*pbuf, *tail.buf) + allocator.Free(tail.buf) + tail.buf = pbuf + } + tail.buf = allocator.Append(tail.buf, buf...) + } + } +} + +//go:norace +func (c *Conn) newToWriteFile(fd int, offset, remain int64) { + t := &toWrite{} // poolToWrite.New().(*toWrite) + t.fd = fd + t.offset = offset + t.remain = remain + c.writeList = append(c.writeList, t) +} + +//go:norace +func (c *Conn) releaseToWrite(t *toWrite) { + if t.buf != nil { + c.p.g.BodyAllocator.Free(t.buf) + } + if t.fd > 0 { + _ = syscall.Close(t.fd) + } + // *t = emptyToWrite + // poolToWrite.Put(t) +} + +const maxWriteCacheOrFlushSize = 1024 * 64 + +type toWrite struct { + fd int // file descriptor, used for sendfile + buf *[]byte // buffer to write + offset int64 // buffer or file offset + remain int64 // buffer or file remain bytes +} + +// Conn implements net.Conn with non-blocking interfaces. type Conn struct { mux sync.Mutex - g *Gopher + // the poller that handles io events for this connection. + p *poller + // file descriptor. fd int - rTimer *htimer - wTimer *htimer + connUDP *udpConn - writeBuffer []byte + // used for read deadline. + rTimer *time.Timer + // used for write deadline. + wTimer *time.Timer - closed bool + // how many bytes are cached and wait to be written. + left int + // cache for buffers or files to be sent. + writeList []*toWrite + + typ ConnType + closed bool + + // whether the writing event has been set in the poller. isWAdded bool + // the first closing error. closeErr error + // local addr. lAddr net.Addr + // remote addr. rAddr net.Addr - ReadBuffer []byte - + // user session. session interface{} - chWaitWrite chan struct{} + // job list. + jobList []func() - execList []func() + readEvents int32 - DataHandler func(c *Conn, data []byte) + dataHandler func(c *Conn, data []byte) + + onConnected func(c *Conn, err error) } -// Hash returns a hash code. +// Hash returns a hash code of this connection. +// +//go:norace func (c *Conn) Hash() int { return c.fd } -// Read implements Read. +// AsyncReadInPoller is used for reading data async. +// +//go:norace +func (c *Conn) AsyncRead() { + g := c.p.g + + // If is EPOLLONESHOT, run the read job directly, because the reading event wouldn't + // be re-dispatched before this reading event has been handled and set again. + if g.isOneshot { + g.IOExecute(func(pbuf *[]byte) { + for i := 0; i < g.MaxConnReadTimesPerEventLoop; i++ { + rc, n, err := c.ReadAndGetConn(pbuf) + if n > 0 { + *pbuf = (*pbuf)[:n] + g.onDataPtr(rc, pbuf) + } + if errors.Is(err, syscall.EINTR) { + continue + } + if errors.Is(err, syscall.EAGAIN) { + break + } + if err != nil { + _ = c.closeWithError(err) + return + } + if n < len(*pbuf) { + break + } + } + c.ResetPollerEvent() + }) + return + } + + // If is not EPOLLONESHOT, the reading event may be re-dispatched for more than + // once, here we reduce the duplicate reading events. + cnt := atomic.AddInt32(&c.readEvents, 1) + if cnt > 2 { + atomic.AddInt32(&c.readEvents, -1) + return + } + // Only handle it when it's the first reading event. + if cnt > 1 { + return + } + + g.IOExecute(func(pBuf *[]byte) { + for { + // try to read all the data available. + for i := 0; i < g.MaxConnReadTimesPerEventLoop; i++ { + rc, n, err := c.ReadAndGetConn(pBuf) + if n > 0 { + *pBuf = (*pBuf)[:n] + g.onDataPtr(rc, pBuf) + } + if errors.Is(err, syscall.EINTR) { + continue + } + if errors.Is(err, syscall.EAGAIN) { + break + } + if err != nil { + _ = c.closeWithError(err) + return + } + if n < len(*pBuf) { + break + } + } + if atomic.AddInt32(&c.readEvents, -1) == 0 { + return + } + } + }) +} + +// Read . +// Depracated . +// It was used to customize users' reading implementation, but better to use +// `ReadAndGetConn` instead, which can handle different types of connection and +// returns the consistent connection instance for UDP. +// Notice: non-blocking interface, should not be used as you use std. +// +//go:norace func (c *Conn) Read(b []byte) (int, error) { - // use lock to prevent multiple conn data confusion when fd is reused on unix. + // When the connection is closed and the fd is reused on Unix, + // new connection maybe hold the same fd. + // Use lock to prevent data confusion. c.mux.Lock() if c.closed { c.mux.Unlock() - return 0, errClosed + return 0, net.ErrClosed } - - n, err := syscall.Read(c.fd, b) + + _, n, err := c.doRead(b) c.mux.Unlock() - if err == nil { - c.g.afterRead(c) - } + // if err == nil { + // c.p.g.afterRead(c) + // } return n, err } -// Write implements Write. +// ReadAndGetConn handles reading for different types of connection. +// It returns the real connection: +// 1. For Non-UDP connection, it returns the Conn itself. +// 2. For UDP connection, it may be a UDP Server fd, then it returns consistent +// Conn for the same socket which has the same local addr and remote addr. +// +// Notice: non-blocking interface, should not be used as you use std. +// +//go:norace +func (c *Conn) ReadAndGetConn(pdata *[]byte) (*Conn, int, error) { + // When the connection is closed and the fd is reused on Unix, + // new connection maybe hold the same fd. + // Use lock to prevent data confusion. + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return c, 0, net.ErrClosed + } + + dstConn, n, err := c.doRead(*pdata) + c.mux.Unlock() + // if err == nil { + // c.p.g.afterRead(c) + // } + + return dstConn, n, err +} + +//go:norace +func (c *Conn) doRead(b []byte) (*Conn, int, error) { + switch c.typ { + case ConnTypeTCP, ConnTypeUnix: + return c.readStream(b) + case ConnTypeUDPServer, ConnTypeUDPClientFromDial: + return c.readUDP(b) + case ConnTypeUDPClientFromRead: + // no need to read for this type of connection, + // it's handled when reading ConnTypeUDPServer. + default: + } + return c, 0, errors.New("invalid udp conn for reading") +} + +// read from TCP/Unix socket. +// +//go:norace +func (c *Conn) readStream(b []byte) (*Conn, int, error) { + nread, err := syscall.Read(c.fd, b) + return c, nread, err +} + +// read from UDP socket. +// +//go:norace +func (c *Conn) readUDP(b []byte) (*Conn, int, error) { + nread, rAddr, err := syscall.Recvfrom(c.fd, b, 0) + if c.closeErr == nil { + c.closeErr = err + } + if err != nil { + return c, 0, err + } + + var g = c.p.g + var dstConn = c + if c.typ == ConnTypeUDPServer { + // get or create and cache the consistent connection for the socket + // that has the same local addr and remote addr. + uc, ok := c.connUDP.getConn(c.p, c.fd, rAddr) + if g.UDPReadTimeout > 0 { + _ = uc.SetReadDeadline(time.Now().Add(g.UDPReadTimeout)) + } + if !ok { + g.onOpen(uc) + } + dstConn = uc + } + + return dstConn, nread, err +} + +// Write writes data to the connection. +// Notice: +// 1. This is a non-blocking interface, but you can use it as you use std. +// 2. When it can't write all the data now, the connection will cache the data +// left to be written and wait for the writing event then try to flush it. +// +//go:norace func (c *Conn) Write(b []byte) (int, error) { - defer c.g.onWriteBufferFree(c, b) + // c.p.g.beforeWrite(c) c.mux.Lock() if c.closed { c.mux.Unlock() - return -1, errClosed + return -1, net.ErrClosed } - c.g.beforeWrite(c) - n, err := c.write(b) - if err != nil && !errors.Is(err, syscall.EINTR) && !errors.Is(err, syscall.EAGAIN) { + if err != nil && + !errors.Is(err, syscall.EINTR) && + !errors.Is(err, syscall.EAGAIN) { c.closed = true c.mux.Unlock() - c.closeWithErrorWithoutLock(err) + _ = c.closeWithErrorWithoutLock(err) return n, err } - if len(c.writeBuffer) == 0 { + if len(c.writeList) == 0 { + // no data left to be written, clear write deadline timer. if c.wTimer != nil { c.wTimer.Stop() c.wTimer = nil } } else { + // has data left to be written, set writing event. c.modWrite() } @@ -104,22 +379,20 @@ func (c *Conn) Write(b []byte) (int, error) { return n, err } -// Writev implements Writev. +// Writev does similar things as Write, but with [][]byte input arg. +// Notice: doesn't support UDP if more than 1 []byte. +// +//go:norace func (c *Conn) Writev(in [][]byte) (int, error) { - defer func() { - for _, v := range in { - c.g.onWriteBufferFree(c, v) - } - }() + // c.p.g.beforeWrite(c) + c.mux.Lock() if c.closed { c.mux.Unlock() - return 0, errClosed + return 0, net.ErrClosed } - c.g.beforeWrite(c) - var n int var err error switch len(in) { @@ -128,18 +401,22 @@ func (c *Conn) Writev(in [][]byte) (int, error) { default: n, err = c.writev(in) } - if err != nil && !errors.Is(err, syscall.EINTR) && !errors.Is(err, syscall.EAGAIN) { + if err != nil && + !errors.Is(err, syscall.EINTR) && + !errors.Is(err, syscall.EAGAIN) { c.closed = true c.mux.Unlock() - c.closeWithErrorWithoutLock(err) + _ = c.closeWithErrorWithoutLock(err) return n, err } - if len(c.writeBuffer) == 0 { + if len(c.writeList) == 0 { + // no data left to be written, clear write deadline timer. if c.wTimer != nil { c.wTimer.Stop() c.wTimer = nil } } else { + // has data left to be written, set writing event. c.modWrite() } @@ -147,41 +424,81 @@ func (c *Conn) Writev(in [][]byte) (int, error) { return n, err } -// Close implements Close. +// write to TCP/Unix socket. +// +//go:norace +func (c *Conn) writeStream(b []byte) (int, error) { + return syscall.Write(c.fd, b) +} + +// write to UDP dialer. +// +//go:norace +func (c *Conn) writeUDPClientFromDial(b []byte) (int, error) { + return syscall.Write(c.fd, b) +} + +// write to UDP connection which is from server reading. +// +//go:norace +func (c *Conn) writeUDPClientFromRead(b []byte) (int, error) { + err := syscall.Sendto(c.fd, b, 0, c.connUDP.rAddr) + if err != nil { + return 0, err + } + return len(b), nil +} + +// Close implements closes connection. +// +//go:norace func (c *Conn) Close() error { return c.closeWithError(nil) } -// CloseWithError . +// CloseWithError closes connection with user specified error. +// +//go:norace func (c *Conn) CloseWithError(err error) error { return c.closeWithError(err) } -// LocalAddr implements LocalAddr. +// LocalAddr returns the local network address, if known. +// +//go:norace func (c *Conn) LocalAddr() net.Addr { return c.lAddr } -// RemoteAddr implements RemoteAddr. +// RemoteAddr returns the remote network address, if known. +// +//go:norace func (c *Conn) RemoteAddr() net.Addr { return c.rAddr } -// SetDeadline implements SetDeadline. +// SetDeadline sets deadline for both read and write. +// If it is time.Zero, SetDeadline will clear the deadlines. +// +//go:norace func (c *Conn) SetDeadline(t time.Time) error { c.mux.Lock() if !c.closed { if !t.IsZero() { - now := time.Now() + g := c.p.g if c.rTimer == nil { - c.rTimer = c.g.afterFunc(t.Sub(now), func() { c.closeWithError(errReadTimeout) }) + c.rTimer = g.AfterFunc(time.Until(t), func() { + _ = c.closeWithError(errReadTimeout) + }) } else { - c.rTimer.Reset(t.Sub(now)) + c.rTimer.Reset(time.Until(t)) } if c.wTimer == nil { - c.wTimer = c.g.afterFunc(t.Sub(now), func() { c.closeWithError(errWriteTimeout) }) + c.wTimer = g.AfterFunc(time.Until(t), func() { + _ = c.closeWithError(errWriteTimeout) + }) } else { - c.wTimer.Reset(t.Sub(now)) + c.wTimer.Reset(time.Until(t)) } } else { if c.rTimer != nil { @@ -198,18 +515,20 @@ func (c *Conn) SetDeadline(t time.Time) error { return nil } -func (c *Conn) setDeadline(timer **htimer, returnErr error, t time.Time) error { +//go:norace +func (c *Conn) setDeadline(timer **time.Timer, errClose error, t time.Time) error { c.mux.Lock() defer c.mux.Unlock() if c.closed { return nil } if !t.IsZero() { - now := time.Now() if *timer == nil { - *timer = c.g.afterFunc(t.Sub(now), func() { c.closeWithError(returnErr) }) + *timer = c.p.g.AfterFunc(time.Until(t), func() { + _ = c.closeWithError(errClose) + }) } else { - (*timer).Reset(t.Sub(now)) + (*timer).Reset(time.Until(t)) } } else if *timer != nil { (*timer).Stop() @@ -218,214 +537,461 @@ func (c *Conn) setDeadline(timer **htimer, returnErr error, t time.Time) error { return nil } -// SetReadDeadline implements SetReadDeadline. +// SetReadDeadline sets the deadline for future Read calls. +// When the user doesn't update the deadline and the deadline exceeds, +// the connection will be closed. +// If it is time.Zero, SetReadDeadline will clear the deadline. +// +// Notice: +// 1. Users should update the read deadline in time. +// 2. For example, call SetReadDeadline whenever a new WebSocket message +// is received. +// +//go:norace func (c *Conn) SetReadDeadline(t time.Time) error { return c.setDeadline(&c.rTimer, errReadTimeout, t) } -// SetWriteDeadline implements SetWriteDeadline. +// SetWriteDeadline sets the deadline for future data writing. +// If it is time.Zero, SetReadDeadline will clear the deadline. +// +// If the next Write call writes all the data successfully and there's no data +// left to bewritten, the deadline timer will be cleared automatically; +// Else when the user doesn't update the deadline and the deadline exceeds, +// the connection will be closed. +// +//go:norace func (c *Conn) SetWriteDeadline(t time.Time) error { return c.setDeadline(&c.wTimer, errWriteTimeout, t) } -// SetNoDelay implements SetNoDelay. +// SetNoDelay controls whether the operating system should delay +// packet transmission in hopes of sending fewer packets (Nagle's +// algorithm). The default is true (no delay), meaning that data is +// sent as soon as possible after a Write. +// +//go:norace func (c *Conn) SetNoDelay(nodelay bool) error { if nodelay { - return syscall.SetsockoptInt(c.fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1) + return syscall.SetsockoptInt( + c.fd, + syscall.IPPROTO_TCP, + syscall.TCP_NODELAY, + 1, + ) } - return syscall.SetsockoptInt(c.fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 0) + return syscall.SetsockoptInt( + c.fd, + syscall.IPPROTO_TCP, + syscall.TCP_NODELAY, + 0, + ) } -// SetReadBuffer implements SetReadBuffer. +// SetReadBuffer sets the size of the operating system's +// receive buffer associated with the connection. +// +//go:norace func (c *Conn) SetReadBuffer(bytes int) error { - return syscall.SetsockoptInt(c.fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes) + return syscall.SetsockoptInt( + c.fd, + syscall.SOL_SOCKET, + syscall.SO_RCVBUF, + bytes, + ) } -// SetWriteBuffer implements SetWriteBuffer. +// SetWriteBuffer sets the size of the operating system's +// transmit buffer associated with the connection. +// +//go:norace func (c *Conn) SetWriteBuffer(bytes int) error { - return syscall.SetsockoptInt(c.fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, bytes) + return syscall.SetsockoptInt( + c.fd, + syscall.SOL_SOCKET, + syscall.SO_SNDBUF, + bytes, + ) } -// SetKeepAlive implements SetKeepAlive. +// SetKeepAlive sets whether the operating system should send +// keep-alive messages on the connection. +// +//go:norace func (c *Conn) SetKeepAlive(keepalive bool) error { if keepalive { - return syscall.SetsockoptInt(c.fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1) + return syscall.SetsockoptInt( + c.fd, + syscall.SOL_SOCKET, + syscall.SO_KEEPALIVE, + 1, + ) } - return syscall.SetsockoptInt(c.fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 0) + return syscall.SetsockoptInt( + c.fd, + syscall.SOL_SOCKET, + syscall.SO_KEEPALIVE, + 0, + ) } -// SetKeepAlivePeriod implements SetKeepAlivePeriod. +// SetKeepAlivePeriod sets period between keep-alives. +// +//go:norace func (c *Conn) SetKeepAlivePeriod(d time.Duration) error { + if runtime.GOOS == "linux" { + d += (time.Second - time.Nanosecond) + secs := int(d.Seconds()) + if err := syscall.SetsockoptInt( + c.fd, + IPPROTO_TCP, + TCP_KEEPINTVL, + secs, + ); err != nil { + return err + } + return syscall.SetsockoptInt( + c.fd, + IPPROTO_TCP, + TCP_KEEPIDLE, + secs, + ) + } return errors.New("not supported") - // d += (time.Second - time.Nanosecond) - // secs := int(d.Seconds()) - // if err := syscall.SetsockoptInt(c.fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil { - // return err - // } - // return syscall.SetsockoptInt(c.fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs) } -// SetLinger implements SetLinger. +// SetLinger . +// +//go:norace func (c *Conn) SetLinger(onoff int32, linger int32) error { - return syscall.SetsockoptLinger(c.fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &syscall.Linger{ - Onoff: onoff, // 1 - Linger: linger, // 0 - }) -} - -// Session returns user session. -func (c *Conn) Session() interface{} { - return c.session -} - -// SetSession sets user session. -func (c *Conn) SetSession(session interface{}) { - c.session = session + return syscall.SetsockoptLinger( + c.fd, + syscall.SOL_SOCKET, + syscall.SO_LINGER, + &syscall.Linger{ + Onoff: onoff, // 1 + Linger: linger, // 0 + }, + ) } +// sets writing event. +// +//go:norace func (c *Conn) modWrite() { if !c.closed && !c.isWAdded { c.isWAdded = true - c.g.pollers[c.Hash()%len(c.g.pollers)].modWrite(c.fd) + _ = c.p.modWrite(c.fd) } } +// reset io event to read only. +// +//go:norace func (c *Conn) resetRead() { if !c.closed && c.isWAdded { c.isWAdded = false - p := c.g.pollers[c.Hash()%len(c.g.pollers)] - p.deleteEvent(c.fd) - p.addRead(c.fd) + p := c.p + _ = p.resetRead(c.fd) } } +//go:norace func (c *Conn) write(b []byte) (int, error) { if len(b) == 0 { return 0, nil } if c.overflow(len(b)) { - return -1, syscall.EINVAL + return -1, errOverflow } - if len(c.writeBuffer) == 0 { - n, err := syscall.Write(c.fd, b) - if err != nil && !errors.Is(err, syscall.EINTR) && !errors.Is(err, syscall.EAGAIN) { + if len(c.writeList) == 0 { + n, err := c.doWrite(b) + if err != nil && + !errors.Is(err, syscall.EINTR) && + !errors.Is(err, syscall.EAGAIN) { return n, err } if n < 0 { n = 0 } left := len(b) - n - if left > 0 { - c.writeBuffer = mempool.Malloc(left) - copy(c.writeBuffer, b[n:]) - c.modWrite() + if left > 0 && c.typ == ConnTypeTCP { + c.newToWriteBuf(b[n:]) + // c.appendWrite(t) } return len(b), nil } - c.writeBuffer = append(c.writeBuffer, b...) + + c.newToWriteBuf(b) + // c.appendWrite(t) return len(b), nil } +//go:norace +func (c *Conn) writev(in [][]byte) (int, error) { + size := 0 + for _, v := range in { + size += len(v) + } + if c.overflow(size) { + return -1, errOverflow + } + if len(c.writeList) > 0 { + for _, v := range in { + c.newToWriteBuf(v) + // c.appendWrite(t) + } + return size, nil + } + + nwrite, err := writev(c, in) + if nwrite > 0 { + n := nwrite + onWrittenSize := c.p.g.onWrittenSize + if n < size { + for i := 0; i < len(in) && n > 0; i++ { + b := in[i] + if n == 0 { + c.newToWriteBuf(b) + // c.appendWrite(t) + } else { + if n < len(b) { + if onWrittenSize != nil { + onWrittenSize(c, b[:n], n) + } + c.newToWriteBuf(b[n:]) + // c.appendWrite(t) + n = 0 + } else { + if onWrittenSize != nil { + onWrittenSize(c, b, len(b)) + } + n -= len(b) + } + } + } + } + } else { + nwrite = 0 + } + + return nwrite, err +} + +// func (c *Conn) appendWrite(t *toWrite) { +// c.writeList = append(c.writeList, t) +// if t.buf != nil { +// c.left += len(t.buf) +// } +// } + +// flush cached data to the fd when writing available. +// +//go:norace func (c *Conn) flush() error { c.mux.Lock() + defer c.mux.Unlock() if c.closed { - c.mux.Unlock() - return errClosed + return net.ErrClosed } - if len(c.writeBuffer) == 0 { - c.mux.Unlock() + if len(c.writeList) == 0 { return nil } - old := c.writeBuffer - - n, err := syscall.Write(c.fd, old) - if err != nil && !errors.Is(err, syscall.EINTR) && !errors.Is(err, syscall.EAGAIN) { - c.closed = true - c.mux.Unlock() - c.closeWithErrorWithoutLock(err) - return err - } - if n < 0 { - n = 0 - } - left := len(old) - n - if left > 0 { + onWrittenSize := c.p.g.onWrittenSize + + // iovc := make([][]byte, 4)[0:0] + // writeBuffers := func() error { + // var ( + // n int + // err error + // head *toWrite + // ) + + // if len(c.writeList) == 1 { + // head = c.writeList[0] + // buf := head.buf[head.offset:] + // for len(buf) > 0 && err == nil { + // n, err = syscall.Write(c.fd, buf) + // if n > 0 { + // if c.p.g.onWrittenSize != nil { + // c.p.g.onWrittenSize(c, buf[:n], n) + // } + // c.left -= n + // head.offset += int64(n) + // buf = buf[n:] + // if len(buf) == 0 { + // c.releaseToWrite(head) + // c.writeList = nil + // } + // } else { + // break + // } + // } + // return err + // } + + // writevSize := maxWriteCacheOrFlushSize + // iovc = iovc[0:0] + // for i := 0; i < len(c.writeList) && i < 1024; i++ { + // head = c.writeList[i] + // if head.buf != nil { + // b := head.buf[head.offset:] + // writevSize -= len(b) + // if writevSize < 0 { + // break + // } + // iovc = append(iovc, b) + // } + // } + + // for len(iovc) > 0 && err == nil { + // n, err = writev(c, iovc) + // if n > 0 { + // c.left -= n + // for n > 0 { + // head = c.writeList[0] + // headLeft := len(head.buf) - int(head.offset) + // if n < headLeft { + // if onWrittenSize != nil { + // onWrittenSize(c, head.buf[head.offset:head.offset+int64(n)], n) + // } + // head.offset += int64(n) + // iovc[0] = iovc[0][n:] + // break + // } else { + // if onWrittenSize != nil { + // onWrittenSize(c, head.buf[head.offset:], headLeft) + // } + // c.releaseToWrite(head) + // c.writeList = c.writeList[1:] + // if len(c.writeList) == 0 { + // c.writeList = nil + // } + // iovc = iovc[1:] + // n -= headLeft + // } + // } + // } else { + // break + // } + // } + // return err + // } + writeBuffer := func() error { + head := c.writeList[0] + buf := (*head.buf)[head.offset:] + n, err := syscall.Write(c.fd, buf) if n > 0 { - c.writeBuffer = mempool.Malloc(left) - copy(c.writeBuffer, old[n:]) - mempool.Free(old) - } - // c.modWrite() - } else { - c.writeBuffer = nil - if c.wTimer != nil { - c.wTimer.Stop() - c.wTimer = nil - } - c.resetRead() - if c.chWaitWrite != nil { - select { - case c.chWaitWrite <- struct{}{}: - default: + if c.p.g.onWrittenSize != nil { + c.p.g.onWrittenSize(c, buf[:n], n) + } + c.left -= n + head.offset += int64(n) + if len(buf) == n { + c.releaseToWrite(head) + c.writeList[0] = nil + c.writeList = c.writeList[1:] } } + return err } - - c.mux.Unlock() - return nil -} - -func (c *Conn) writev(in [][]byte) (int, error) { - size := 0 - for _, v := range in { - size += len(v) - } - if c.overflow(size) { - return -1, syscall.EINVAL - } - if len(c.writeBuffer) > 0 { - for _, v := range in { - c.writeBuffer = append(c.writeBuffer, v...) + writeFile := func() error { + v := c.writeList[0] + for v.remain > 0 { + var offset = v.offset + n, err := syscall.Sendfile(c.fd, v.fd, &offset, int(v.remain)) + if n > 0 { + if onWrittenSize != nil { + onWrittenSize(c, nil, n) + } + v.remain -= int64(n) + v.offset += int64(n) + if v.remain <= 0 { + c.releaseToWrite(c.writeList[0]) + c.writeList[0] = nil + c.writeList = c.writeList[1:] + } + } + if err != nil { + return err + } } - return size, nil + return nil } - if len(in) > 1 && size <= 65536 { - b := mempool.Malloc(size) - copied := 0 - for _, v := range in { - copy(b[copied:], v) - copied += len(v) + for len(c.writeList) > 0 { + var err error + if c.writeList[0].fd == 0 { + err = writeBuffer() + } else { + err = writeFile() } - return c.write(b) - } - nwrite := 0 - for _, b := range in { - n, err := c.write(b) - if n > 0 { - nwrite += n + if errors.Is(err, syscall.EINTR) { + continue + } + if errors.Is(err, syscall.EAGAIN) { + // c.modWrite() + return nil } if err != nil { - return nwrite, err + c.closed = true + _ = c.closeWithErrorWithoutLock(err) + return err } } - return nwrite, nil + + c.resetRead() + + return nil +} + +//go:norace +func (c *Conn) doWrite(b []byte) (int, error) { + var n int + var err error + switch c.typ { + case ConnTypeTCP, ConnTypeUnix: + n, err = c.writeStream(b) + case ConnTypeUDPServer: + case ConnTypeUDPClientFromDial: + n, err = c.writeUDPClientFromDial(b) + case ConnTypeUDPClientFromRead: + n, err = c.writeUDPClientFromRead(b) + default: + } + if c.p.g.onWrittenSize != nil && n > 0 { + c.p.g.onWrittenSize(c, b[:n], n) + } + return n, err } +//go:norace func (c *Conn) overflow(n int) bool { - return c.g.maxWriteBufferSize > 0 && (len(c.writeBuffer)+n > c.g.maxWriteBufferSize) + g := c.p.g + return g.MaxWriteBufferSize > 0 && (c.left+n > g.MaxWriteBufferSize) } +//go:norace func (c *Conn) closeWithError(err error) error { c.mux.Lock() if !c.closed { c.closed = true + + if c.wTimer != nil { + c.wTimer.Stop() + c.wTimer = nil + } + if c.rTimer != nil { + c.rTimer.Stop() + c.rTimer = nil + } + c.mux.Unlock() return c.closeWithErrorWithoutLock(err) } @@ -433,36 +999,35 @@ func (c *Conn) closeWithError(err error) error { return nil } +//go:norace func (c *Conn) closeWithErrorWithoutLock(err error) error { c.closeErr = err - if c.wTimer != nil { - c.wTimer.Stop() - c.wTimer = nil - } - if c.rTimer != nil { - c.rTimer.Stop() - c.rTimer = nil + if c.writeList != nil { + for _, t := range c.writeList { + c.releaseToWrite(t) + } + c.writeList = nil } - mempool.Free(c.writeBuffer) - c.writeBuffer = nil - - if c.chWaitWrite != nil { - select { - case c.chWaitWrite <- struct{}{}: - default: - } + if c.p != nil { + c.p.deleteConn(c) } - if c.g != nil { - c.g.pollers[c.Hash()%len(c.g.pollers)].deleteConn(c) + switch c.typ { + case ConnTypeTCP, ConnTypeUnix: + err = syscall.Close(c.fd) + case ConnTypeUDPServer, ConnTypeUDPClientFromDial, ConnTypeUDPClientFromRead: + err = c.connUDP.Close() + default: } - return syscall.Close(c.fd) + return err } // NBConn converts net.Conn to *Conn. +// +//go:norace func NBConn(conn net.Conn) (*Conn, error) { if conn == nil { return nil, errors.New("invalid conn: nil") @@ -477,3 +1042,131 @@ func NBConn(conn net.Conn) (*Conn, error) { } return c, nil } + +type udpConn struct { + parent *Conn + + rAddr syscall.Sockaddr + rAddrKey udpAddrKey + + mux sync.RWMutex + conns map[udpAddrKey]*Conn +} + +//go:norace +func (u *udpConn) Close() error { + parent := u.parent + if parent.connUDP != u { + // This connection is created by reading from a UDP server, + // need to clear itself from the UDP server. + parent.mux.Lock() + delete(parent.connUDP.conns, u.rAddrKey) + parent.mux.Unlock() + } else { + // This connection is a UDP server or dialer, need to close itself + // and close all children if this is a server. + _ = syscall.Close(u.parent.fd) + for _, c := range u.conns { + _ = c.Close() + } + u.conns = nil + } + return nil +} + +//go:norace +func (u *udpConn) getConn(p *poller, fd int, rsa syscall.Sockaddr) (*Conn, bool) { + rAddrKey := getUDPNetAddrKey(rsa) + u.mux.RLock() + c, ok := u.conns[rAddrKey] + u.mux.RUnlock() + + // new connection, create it. + if !ok { + c = &Conn{ + p: p, + fd: fd, + lAddr: u.parent.lAddr, + rAddr: getUDPNetAddr(rsa), + typ: ConnTypeUDPClientFromRead, + connUDP: &udpConn{ + rAddr: rsa, + rAddrKey: rAddrKey, + parent: u.parent, + }, + } + + // storage the consistent connection for the same remote addr. + u.mux.Lock() + u.conns[rAddrKey] = c + u.mux.Unlock() + } + + return c, ok +} + +type udpAddrKey [22]byte + +//go:norace +func getUDPNetAddrKey(sa syscall.Sockaddr) udpAddrKey { + var ret udpAddrKey + if sa == nil { + return ret + } + + switch vt := sa.(type) { + case *syscall.SockaddrInet4: + copy(ret[:], vt.Addr[:]) + binary.LittleEndian.PutUint16(ret[16:], uint16(vt.Port)) + case *syscall.SockaddrInet6: + copy(ret[:], vt.Addr[:]) + binary.LittleEndian.PutUint16(ret[16:], uint16(vt.Port)) + binary.LittleEndian.PutUint32(ret[18:], vt.ZoneId) + } + return ret +} + +//go:norace +func getUDPNetAddr(sa syscall.Sockaddr) *net.UDPAddr { + ret := &net.UDPAddr{} + switch vt := sa.(type) { + case *syscall.SockaddrInet4: + ret.IP = make([]byte, len(vt.Addr)) + copy(ret.IP[:], vt.Addr[:]) + ret.Port = vt.Port + case *syscall.SockaddrInet6: + ret.IP = make([]byte, len(vt.Addr)) + copy(ret.IP[:], vt.Addr[:]) + ret.Port = vt.Port + i, err := net.InterfaceByIndex(int(vt.ZoneId)) + if err == nil && i != nil { + ret.Zone = i.Name + } + } + return ret +} + +//go:norace +func (c *Conn) SyscallConn() (syscall.RawConn, error) { + return &rawConn{fd: c.fd}, nil +} + +type rawConn struct { + fd int +} + +//go:norace +func (c *rawConn) Control(f func(fd uintptr)) error { + f(uintptr(c.fd)) + return nil +} + +//go:norace +func (c *rawConn) Read(f func(fd uintptr) (done bool)) error { + return ErrUnsupported +} + +//go:norace +func (c *rawConn) Write(f func(fd uintptr) (done bool)) error { + return ErrUnsupported +} diff --git a/vendor/github.com/lesismal/nbio/engine.go b/vendor/github.com/lesismal/nbio/engine.go new file mode 100644 index 00000000..9514386f --- /dev/null +++ b/vendor/github.com/lesismal/nbio/engine.go @@ -0,0 +1,506 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package nbio + +import ( + "context" + "net" + "runtime" + "sync" + "time" + "unsafe" + + "github.com/lesismal/nbio/logging" + "github.com/lesismal/nbio/mempool" + "github.com/lesismal/nbio/taskpool" + "github.com/lesismal/nbio/timer" +) + +const ( + // DefaultReadBufferSize . + DefaultReadBufferSize = 1024 * 64 + + // DefaultMaxWriteBufferSize . + DefaultMaxWriteBufferSize = 0 + + // DefaultMaxConnReadTimesPerEventLoop . + DefaultMaxConnReadTimesPerEventLoop = 3 + + // DefaultUDPReadTimeout . + DefaultUDPReadTimeout = 120 * time.Second +) + +const ( + NETWORK_TCP = "tcp" + NETWORK_TCP4 = "tcp4" + NETWORK_TCP6 = "tcp6" + NETWORK_UDP = "udp" + NETWORK_UDP4 = "udp4" + NETWORK_UDP6 = "udp6" + NETWORK_UNIX = "unix" + NETWORK_UNIXGRAM = "unixgram" + NETWORK_UNIXPACKET = "unixpacket" +) + +var ( + // MaxOpenFiles . + MaxOpenFiles = 1024 * 1024 * 2 +) + +// Config Of Engine. +type Config struct { + // Name describes your gopher name for logging, it's set to "NB" by default. + Name string + + // Network is the listening protocol, used with Addrs together. + Network string + + // Addrs is the listening addr list for a nbio server. + // if it is empty, no listener created, then the Engine is used for client by default. + Addrs []string + + // NPoller represents poller goroutine num. + NPoller int + + // ReadBufferSize represents buffer size for reading, it's set to 64k by default. + ReadBufferSize int + + // MaxWriteBufferSize represents max write buffer size for Conn, 0 by default, represents no limit for writeBuffer + // if MaxWriteBufferSize is set greater than to 0, and the connection's Send-Q is full and the data cached by nbio is + // more than MaxWriteBufferSize, the connection would be closed by nbio. + MaxWriteBufferSize int + + // MaxConnReadTimesPerEventLoop represents max read times in one poller loop for one fd + MaxConnReadTimesPerEventLoop int + + // LockListener represents whether to lock thread for listener's goroutine, false by default. + LockListener bool + + // LockPoller represents whether to lock thread for poller's goroutine, false by default. + LockPoller bool + + // EpollMod sets the epoll mod, EPOLLLT by default. + EpollMod uint32 + + // EPOLLONESHOT sets EPOLLONESHOT, 0 by default. + EPOLLONESHOT uint32 + + // UDPReadTimeout sets the timeout for udp sessions. + UDPReadTimeout time.Duration + + // Listen is used to create listener for Engine. + // Users can set this func to customize listener, such as reuseport. + Listen func(network, addr string) (net.Listener, error) + + // ListenUDP is used to create udp listener for Engine. + ListenUDP func(network string, laddr *net.UDPAddr) (*net.UDPConn, error) + + // AsyncReadInPoller represents how the reading events and reading are handled + // by epoll goroutine: + // true : epoll goroutine handles the reading events only, another goroutine + // pool will handle the reading. + // false: epoll goroutine handles both the reading events and the reading. + AsyncReadInPoller bool + // IOExecute is used to handle the aysnc reading, users can customize it. + IOExecute func(f func(*[]byte)) + + // BodyAllocator sets the buffer allocator for write cache. + BodyAllocator mempool.Allocator +} + +// Gopher keeps old type to compatible with new name Engine. +type Gopher = Engine + +//go:norace +func NewGopher(conf Config) *Gopher { + return NewEngine(conf) +} + +// Engine is a manager of poller. +type Engine struct { + Config + *timer.Timer + sync.WaitGroup + + Execute func(f func()) + mux sync.Mutex + + isOneshot bool + + wgConn sync.WaitGroup + + // store std connections, for Windows only. + connsStd map[*Conn]struct{} + + // store *nix connections. + connsUnix []*Conn + + // listeners. + listeners []*poller + pollers []*poller + + // onAcceptError is called when accept error. + onAcceptError func(err error) + + // onUDPListen for udp listener created. + onUDPListen func(c *Conn) + // callback for new connection connected. + onOpen func(c *Conn) + // callback for connection closed. + onClose func(c *Conn, err error) + // callback for reading event. + onRead func(c *Conn) + // callback for coming data. + onDataPtr func(c *Conn, pdata *[]byte) + // callback for writing data size caculation. + onWrittenSize func(c *Conn, b []byte, n int) + // callback for allocationg the reading buffer. + onReadBufferAlloc func(c *Conn) *[]byte + // callback for freeing the reading buffer. + onReadBufferFree func(c *Conn, pbuf *[]byte) + + // depreacated. + // beforeRead func(c *Conn) + // afterRead func(c *Conn) + // beforeWrite func(c *Conn) + + // callback for Engine stop. + onStop func() + + ioTaskPool *taskpool.IOTaskPool +} + +// SetETAsyncRead . +// +//go:norace +func (e *Engine) SetETAsyncRead() { + if e.NPoller <= 0 { + e.NPoller = 1 + } + e.EpollMod = EPOLLET + e.AsyncReadInPoller = true +} + +// SetLTSyncRead . +// +//go:norace +func (e *Engine) SetLTSyncRead() { + if e.NPoller <= 0 { + e.NPoller = runtime.NumCPU() + } + e.EpollMod = EPOLLLT + e.AsyncReadInPoller = false +} + +// Stop closes listeners/pollers/conns/timer. +// +//go:norace +func (g *Engine) Stop() { + for _, l := range g.listeners { + l.stop() + } + + g.mux.Lock() + conns := g.connsStd + g.connsStd = map[*Conn]struct{}{} + connsUnix := g.connsUnix + g.mux.Unlock() + + g.wgConn.Done() + for c := range conns { + if c != nil { + cc := c + g.Async(func() { + _ = cc.Close() + }) + } + } + for _, c := range connsUnix { + if c != nil { + cc := c + g.Async(func() { + _ = cc.Close() + }) + } + } + + g.wgConn.Wait() + + g.onStop() + + g.Timer.Stop() + + if g.ioTaskPool != nil { + g.ioTaskPool.Stop() + } + + for i := 0; i < g.NPoller; i++ { + g.pollers[i].stop() + } + + g.Wait() + logging.Info("NBIO[%v] stop", g.Name) +} + +// Shutdown stops Engine gracefully with context. +// +//go:norace +func (g *Engine) Shutdown(ctx context.Context) error { + ch := make(chan struct{}) + go func() { + g.Stop() + close(ch) + }() + + select { + case <-ch: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} + +// AddConn adds conn to a poller. +// +//go:norace +func (g *Engine) AddConn(conn net.Conn) (*Conn, error) { + c, err := NBConn(conn) + if err != nil { + return nil, err + } + + p := g.pollers[c.Hash()%len(g.pollers)] + err = p.addConn(c) + if err != nil { + return nil, err + } + return c, nil +} + +//go:norace +func (g *Engine) addDialer(c *Conn) (*Conn, error) { + p := g.pollers[c.Hash()%len(g.pollers)] + err := p.addDialer(c) + if err != nil { + return nil, err + } + return c, nil +} + +// OnAcceptError is called when accept error. +// +//go:norace +func (g *Engine) OnAcceptError(h func(err error)) { + g.onAcceptError = h +} + +// OnOpen registers callback for new connection. +// +//go:norace +func (g *Engine) OnUDPListen(h func(c *Conn)) { + if h == nil { + panic("invalid handler: nil") + } + g.onUDPListen = h +} + +// OnOpen registers callback for new connection. +// +//go:norace +func (g *Engine) OnOpen(h func(c *Conn)) { + if h == nil { + panic("invalid handler: nil") + } + g.onOpen = func(c *Conn) { + g.wgConn.Add(1) + h(c) + } +} + +// OnClose registers callback for disconnected. +// +//go:norace +func (g *Engine) OnClose(h func(c *Conn, err error)) { + if h == nil { + panic("invalid handler: nil") + } + g.onClose = func(c *Conn, err error) { + g.Async(func() { + defer g.wgConn.Done() + h(c, err) + }) + } +} + +// OnRead registers callback for reading event. +// +//go:norace +func (g *Engine) OnRead(h func(c *Conn)) { + g.onRead = h +} + +// OnData registers callback for data. +// +//go:norace +func (g *Engine) OnData(h func(c *Conn, data []byte)) { + if h == nil { + panic("invalid handler: nil") + } + g.onDataPtr = func(c *Conn, pdata *[]byte) { + h(c, *pdata) + } +} + +// OnDataPtr registers callback for data ptr. +// +//go:norace +func (g *Engine) OnDataPtr(h func(c *Conn, pdata *[]byte)) { + if h == nil { + panic("invalid handler: nil") + } + g.onDataPtr = h +} + +// OnWrittenSize registers callback for written size. +// If len(b) is bigger than 0, it represents that it's writing a buffer, +// else it's operating by Sendfile. +// +//go:norace +func (g *Engine) OnWrittenSize(h func(c *Conn, b []byte, n int)) { + if h == nil { + panic("invalid handler: nil") + } + g.onWrittenSize = h +} + +// OnReadBufferAlloc registers callback for memory allocating. +// +//go:norace +func (g *Engine) OnReadBufferAlloc(h func(c *Conn) *[]byte) { + if h == nil { + panic("invalid handler: nil") + } + g.onReadBufferAlloc = h +} + +// OnReadBufferFree registers callback for memory release. +// +//go:norace +func (g *Engine) OnReadBufferFree(h func(c *Conn, pbuf *[]byte)) { + if h == nil { + panic("invalid handler: nil") + } + g.onReadBufferFree = h +} + +// Depracated . +// OnWriteBufferRelease registers callback for write buffer memory release. +// func (g *Engine) OnWriteBufferRelease(h func(c *Conn, b []byte)) { +// if h == nil { +// panic("invalid handler: nil") +// } +// g.onWriteBufferFree = h +// } + +// BeforeRead registers callback before syscall.Read +// the handler would be called on windows. +// func (g *Engine) BeforeRead(h func(c *Conn)) { +// if h == nil { +// panic("invalid handler: nil") +// } +// g.beforeRead = h +// } + +// Depracated . +// AfterRead registers callback after syscall.Read +// the handler would be called on *nix. +// func (g *Engine) AfterRead(h func(c *Conn)) { +// if h == nil { +// panic("invalid handler: nil") +// } +// g.afterRead = h +// } + +// Depracated . +// BeforeWrite registers callback befor syscall.Write and syscall.Writev +// the handler would be called on windows. +// func (g *Engine) BeforeWrite(h func(c *Conn)) { +// if h == nil { +// panic("invalid handler: nil") +// } +// g.beforeWrite = h +// } + +// OnStop registers callback before Engine is stopped. +// +//go:norace +func (g *Engine) OnStop(h func()) { + if h == nil { + panic("invalid handler: nil") + } + g.onStop = h +} + +// PollerBuffer returns Poller's buffer by Conn, can be used on linux/bsd. +// +//go:norace +func (g *Engine) PollerBuffer(c *Conn) []byte { + return c.p.ReadBuffer +} + +// PollerBufferPtr returns Poller's buffer by Conn, can be used on linux/bsd. +// +//go:norace +func (g *Engine) PollerBufferPtr(c *Conn) *[]byte { + return &c.p.ReadBuffer +} + +//go:norace +func (g *Engine) initHandlers() { + g.wgConn.Add(1) + g.OnOpen(func(c *Conn) {}) + g.OnClose(func(c *Conn, err error) {}) + // g.OnRead(func(c *Conn, b []byte) ([]byte, error) { + // n, err := c.Read(b) + // if n > 0 { + // return b[:n], err + // } + // return nil, err + // }) + g.OnData(func(c *Conn, data []byte) {}) + g.OnReadBufferAlloc(g.PollerBufferPtr) + g.OnReadBufferFree(func(c *Conn, pbuf *[]byte) {}) + // g.OnWriteBufferRelease(func(c *Conn, buffer []byte) {}) + // g.BeforeRead(func(c *Conn) {}) + // g.AfterRead(func(c *Conn) {}) + // g.BeforeWrite(func(c *Conn) {}) + g.OnUDPListen(func(*Conn) {}) + g.OnStop(func() {}) + + if g.Execute == nil { + g.Execute = func(f func()) { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("execute failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() + f() + } + } +} + +//go:norace +func (g *Engine) borrow(c *Conn) *[]byte { + return g.onReadBufferAlloc(c) +} + +//go:norace +func (g *Engine) payback(c *Conn, pbuf *[]byte) { + *pbuf = (*pbuf)[:cap(*pbuf)] + g.onReadBufferFree(c, pbuf) +} diff --git a/vendor/github.com/lesismal/nbio/engine_std.go b/vendor/github.com/lesismal/nbio/engine_std.go new file mode 100644 index 00000000..f0f4f4cc --- /dev/null +++ b/vendor/github.com/lesismal/nbio/engine_std.go @@ -0,0 +1,215 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build windows +// +build windows + +package nbio + +import ( + "net" + "runtime" + "strings" + "time" + + "github.com/lesismal/nbio/logging" + "github.com/lesismal/nbio/mempool" + "github.com/lesismal/nbio/timer" +) + +// Start inits and starts pollers. +// +//go:norace +func (g *Engine) Start() error { + // Create listener pollers. + udpListeners := make([]*net.UDPConn, len(g.Addrs))[0:0] + switch g.Network { + case NETWORK_TCP, NETWORK_TCP4, NETWORK_TCP6: + for i := range g.Addrs { + ln, err := newPoller(g, true, i) + if err != nil { + for j := 0; j < i; j++ { + g.listeners[j].stop() + } + return err + } + g.Addrs[i] = ln.listener.Addr().String() + g.listeners = append(g.listeners, ln) + } + case NETWORK_UDP, NETWORK_UDP4, NETWORK_UDP6: + for i, addrStr := range g.Addrs { + addr, err := net.ResolveUDPAddr(g.Network, addrStr) + if err != nil { + for j := 0; j < i; j++ { + udpListeners[j].Close() + } + return err + } + ln, err := g.ListenUDP("udp", addr) + if err != nil { + for j := 0; j < i; j++ { + udpListeners[j].Close() + } + return err + } + g.Addrs[i] = ln.LocalAddr().String() + udpListeners = append(udpListeners, ln) + } + } + + // Create IO pollers. + for i := 0; i < g.NPoller; i++ { + p, err := newPoller(g, false, i) + if err != nil { + for j := 0; j < len(g.listeners); j++ { + g.listeners[j].stop() + } + + for j := 0; j < i; j++ { + g.pollers[j].stop() + } + return err + } + g.pollers[i] = p + } + + // Start IO pollers. + for i := 0; i < g.NPoller; i++ { + g.Add(1) + go g.pollers[i].start() + } + + // Start TCP/Unix listener pollers. + for _, l := range g.listeners { + g.Add(1) + go l.start() + } + + // Start UDP listener pollers. + for _, ln := range udpListeners { + _, err := g.AddConn(ln) + if err != nil { + for j := 0; j < len(g.listeners); j++ { + g.listeners[j].stop() + } + + for j := 0; j < len(g.pollers); j++ { + g.pollers[j].stop() + } + + for j := 0; j < len(udpListeners); j++ { + udpListeners[j].Close() + } + + return err + } + } + + // g.Timer.Start() + + if len(g.Addrs) == 0 { + logging.Info("NBIO Engine[%v] start with [%v eventloop, MaxOpenFiles: %v]", + g.Name, + g.NPoller, + MaxOpenFiles, + ) + } else { + logging.Info("NBIO Engine[%v] start with [%v eventloop], listen on: [\"%v@%v\"], MaxOpenFiles: %v", + g.Name, + g.NPoller, + g.Network, + strings.Join(g.Addrs, `", "`), + MaxOpenFiles, + ) + } + + return nil +} + +// NewEngine creates an Engine and init default configurations. +// +//go:norace +func NewEngine(conf Config) *Engine { + cpuNum := runtime.NumCPU() + if conf.Name == "" { + conf.Name = "NB" + } + if conf.NPoller <= 0 { + conf.NPoller = cpuNum + } + if conf.ReadBufferSize <= 0 { + conf.ReadBufferSize = DefaultReadBufferSize + } + if conf.MaxWriteBufferSize <= 0 { + conf.MaxWriteBufferSize = DefaultMaxWriteBufferSize + } + if conf.Listen == nil { + conf.Listen = net.Listen + } + if conf.ListenUDP == nil { + conf.ListenUDP = net.ListenUDP + } + if conf.BodyAllocator == nil { + conf.BodyAllocator = mempool.DefaultMemPool + } + + g := &Engine{ + Config: conf, + Timer: timer.New(conf.Name), + listeners: make([]*poller, len(conf.Addrs))[0:0], + pollers: make([]*poller, conf.NPoller), + connsStd: map[*Conn]struct{}{}, + } + + g.initHandlers() + + g.OnReadBufferAlloc(func(c *Conn) *[]byte { + if c.ReadBuffer == nil { + c.ReadBuffer = make([]byte, g.ReadBufferSize) + } + return &c.ReadBuffer + }) + + return g +} + +// DialAsync connects asynchrony to the address on the named network. +// +//go:norace +func (engine *Engine) DialAsync(network, addr string, onConnected func(*Conn, error)) error { + return engine.DialAsyncTimeout(network, addr, 0, onConnected) +} + +// DialAsync connects asynchrony to the address on the named network with timeout. +// +//go:norace +func (engine *Engine) DialAsyncTimeout(network, addr string, timeout time.Duration, onConnected func(*Conn, error)) error { + go func() { + var err error + var conn net.Conn + if timeout > 0 { + conn, err = net.DialTimeout(network, addr, timeout) + } else { + conn, err = net.Dial(network, addr) + } + if err != nil { + onConnected(nil, err) + return + } + nbc, err := NBConn(conn) + if err != nil { + onConnected(nil, err) + return + } + engine.wgConn.Add(1) + nbc, err = engine.addDialer(nbc) + if err == nil { + nbc.SetWriteDeadline(time.Time{}) + } else { + engine.wgConn.Done() + } + onConnected(nbc, err) + }() + return nil +} diff --git a/vendor/github.com/lesismal/nbio/engine_unix.go b/vendor/github.com/lesismal/nbio/engine_unix.go new file mode 100644 index 00000000..7cf09155 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/engine_unix.go @@ -0,0 +1,305 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build linux || darwin || netbsd || freebsd || openbsd || dragonfly +// +build linux darwin netbsd freebsd openbsd dragonfly + +package nbio + +import ( + "errors" + "net" + "runtime" + "strings" + "syscall" + "time" + + "github.com/lesismal/nbio/logging" + "github.com/lesismal/nbio/mempool" + "github.com/lesismal/nbio/taskpool" + "github.com/lesismal/nbio/timer" +) + +// Start inits and starts pollers. +// +//go:norace +func (g *Engine) Start() error { + g.connsUnix = make([]*Conn, MaxOpenFiles) + + // Create pollers and listeners. + g.pollers = make([]*poller, g.NPoller) + g.listeners = make([]*poller, len(g.Addrs))[0:0] + udpListeners := make([]*net.UDPConn, len(g.Addrs))[0:0] + + switch g.Network { + case NETWORK_UNIX, NETWORK_TCP, NETWORK_TCP4, NETWORK_TCP6: + for i := range g.Addrs { + ln, err := newPoller(g, true, i) + if err != nil { + for j := 0; j < i; j++ { + g.listeners[j].stop() + } + return err + } + g.Addrs[i] = ln.listener.Addr().String() + g.listeners = append(g.listeners, ln) + } + case NETWORK_UDP, NETWORK_UDP4, NETWORK_UDP6: + for i, addrStr := range g.Addrs { + addr, err := net.ResolveUDPAddr(g.Network, addrStr) + if err != nil { + for j := 0; j < i; j++ { + _ = udpListeners[j].Close() + } + return err + } + ln, err := g.ListenUDP("udp", addr) + if err != nil { + for j := 0; j < i; j++ { + _ = udpListeners[j].Close() + } + return err + } + g.Addrs[i] = ln.LocalAddr().String() + udpListeners = append(udpListeners, ln) + } + } + + // Create IO pollers. + for i := 0; i < g.NPoller; i++ { + p, err := newPoller(g, false, i) + if err != nil { + for j := 0; j < len(g.listeners); j++ { + g.listeners[j].stop() + } + + for j := 0; j < i; j++ { + g.pollers[j].stop() + } + return err + } + g.pollers[i] = p + } + + // Start IO pollers. + for i := 0; i < g.NPoller; i++ { + g.pollers[i].ReadBuffer = make([]byte, g.ReadBufferSize) + g.Add(1) + go g.pollers[i].start() + } + + // Start TCP/Unix listener pollers. + for _, l := range g.listeners { + g.Add(1) + go l.start() + } + + // Start UDP listener pollers. + for _, ln := range udpListeners { + _, err := g.AddConn(ln) + if err != nil { + for j := 0; j < len(g.listeners); j++ { + g.listeners[j].stop() + } + + for j := 0; j < len(g.pollers); j++ { + g.pollers[j].stop() + } + + for j := 0; j < len(udpListeners); j++ { + _ = udpListeners[j].Close() + } + + return err + } + } + + g.Timer.Start() + g.isOneshot = (g.EpollMod == EPOLLET && g.EPOLLONESHOT == EPOLLONESHOT) + + if g.AsyncReadInPoller { + if g.IOExecute == nil { + g.ioTaskPool = taskpool.NewIO(0, 0, 0) + g.IOExecute = g.ioTaskPool.Go + } + } + + if len(g.Addrs) == 0 { + logging.Info("NBIO Engine[%v] start with [%v eventloop, MaxOpenFiles: %v]", + g.Name, + g.NPoller, + MaxOpenFiles, + ) + } else { + logging.Info("NBIO Engine[%v] start with [%v eventloop], listen on: [\"%v@%v\"], MaxOpenFiles: %v", + g.Name, + g.NPoller, + g.Network, + strings.Join(g.Addrs, `", "`), + MaxOpenFiles, + ) + } + + return nil +} + +// DialAsync connects asynchrony to the address on the named network. +// +//go:norace +func (engine *Engine) DialAsync(network, addr string, onConnected func(*Conn, error)) error { + return engine.DialAsyncTimeout(network, addr, 0, onConnected) +} + +// DialAsync connects asynchrony to the address on the named network with timeout. +// +//go:norace +func (engine *Engine) DialAsyncTimeout(network, addr string, timeout time.Duration, onConnected func(*Conn, error)) error { + h := func(c *Conn, err error) { + if err == nil { + _ = c.SetWriteDeadline(time.Time{}) + } + onConnected(c, err) + } + domain, typ, dialaddr, raddr, connType, err := parseDomainAndType(network, addr) + if err != nil { + return err + } + fd, err := syscall.Socket(domain, typ, 0) + if err != nil { + return err + } + err = syscall.SetNonblock(fd, true) + if err != nil { + _ = syscall.Close(fd) + return err + } + err = syscall.Connect(fd, dialaddr) + inprogress := false + if err != nil { + if errors.Is(err, syscall.EINPROGRESS) { + inprogress = true + } else { + _ = syscall.Close(fd) + return err + } + } + sa, _ := syscall.Getsockname(fd) + c := &Conn{ + fd: fd, + rAddr: raddr, + typ: connType, + } + if inprogress { + c.onConnected = h + } + switch vt := sa.(type) { + case *syscall.SockaddrInet4: + switch connType { + case ConnTypeTCP: + c.lAddr = &net.TCPAddr{ + IP: []byte{vt.Addr[0], vt.Addr[1], vt.Addr[2], vt.Addr[3]}, + Port: vt.Port, + } + case ConnTypeUDPClientFromDial: + c.lAddr = &net.TCPAddr{ + IP: []byte{vt.Addr[0], vt.Addr[1], vt.Addr[2], vt.Addr[3]}, + Port: vt.Port, + } + c.connUDP = &udpConn{ + parent: c, + } + } + case *syscall.SockaddrInet6: + var iface *net.Interface + iface, err = net.InterfaceByIndex(int(vt.ZoneId)) + if err != nil { + _ = syscall.Close(fd) + return err + } + switch connType { + case ConnTypeTCP: + c.lAddr = &net.TCPAddr{ + IP: make([]byte, len(vt.Addr)), + Port: vt.Port, + Zone: iface.Name, + } + case ConnTypeUDPClientFromDial: + c.lAddr = &net.UDPAddr{ + IP: make([]byte, len(vt.Addr)), + Port: vt.Port, + Zone: iface.Name, + } + c.connUDP = &udpConn{ + parent: c, + } + } + case *syscall.SockaddrUnix: + c.lAddr = &net.UnixAddr{ + Net: network, + Name: vt.Name, + } + } + + engine.wgConn.Add(1) + _, err = engine.addDialer(c) + if err != nil { + engine.wgConn.Done() + return err + } + + if !inprogress { + engine.Async(func() { + h(c, nil) + }) + } else if timeout > 0 { + _ = c.setDeadline(&c.wTimer, ErrDialTimeout, time.Now().Add(timeout)) + } + + return nil +} + +// NewEngine creates an Engine and init default configurations. +// +//go:norace +func NewEngine(conf Config) *Engine { + if conf.Name == "" { + conf.Name = "NB" + } + if conf.NPoller <= 0 { + conf.NPoller = runtime.NumCPU() / 4 + if conf.AsyncReadInPoller && conf.EpollMod == EPOLLET { + conf.NPoller = 1 + } + if conf.NPoller == 0 { + conf.NPoller = 1 + } + } + if conf.ReadBufferSize <= 0 { + conf.ReadBufferSize = DefaultReadBufferSize + } + if conf.MaxWriteBufferSize <= 0 { + conf.MaxWriteBufferSize = DefaultMaxWriteBufferSize + } + if conf.MaxConnReadTimesPerEventLoop <= 0 { + conf.MaxConnReadTimesPerEventLoop = DefaultMaxConnReadTimesPerEventLoop + } + if conf.Listen == nil { + conf.Listen = net.Listen + } + if conf.ListenUDP == nil { + conf.ListenUDP = net.ListenUDP + } + if conf.BodyAllocator == nil { + conf.BodyAllocator = mempool.DefaultMemPool + } + + g := &Engine{ + Config: conf, + Timer: timer.New(conf.Name), + } + + g.initHandlers() + + return g +} diff --git a/vendor/github.com/lesismal/nbio/error.go b/vendor/github.com/lesismal/nbio/error.go index 765ed9b7..cb20e8d1 100644 --- a/vendor/github.com/lesismal/nbio/error.go +++ b/vendor/github.com/lesismal/nbio/error.go @@ -9,7 +9,16 @@ import ( ) var ( - errClosed = errors.New("conn closed") - errReadTimeout = errors.New("read timeout") - errWriteTimeout = errors.New("write timeout") + ErrReadTimeout = errors.New("read timeout") + errReadTimeout = ErrReadTimeout + + ErrWriteTimeout = errors.New("write timeout") + errWriteTimeout = ErrWriteTimeout + + ErrOverflow = errors.New("write overflow") + errOverflow = ErrOverflow + + ErrDialTimeout = errors.New("dial timeout") + + ErrUnsupported = errors.New("unsupported operation") ) diff --git a/vendor/github.com/lesismal/nbio/extension/tls/tls.go b/vendor/github.com/lesismal/nbio/extension/tls/tls.go index ad3742a0..dd91d43e 100644 --- a/vendor/github.com/lesismal/nbio/extension/tls/tls.go +++ b/vendor/github.com/lesismal/nbio/extension/tls/tls.go @@ -1,5 +1,7 @@ package tls +// deprecated. + import ( "github.com/lesismal/llib/std/crypto/tls" "github.com/lesismal/nbio" @@ -12,7 +14,9 @@ type Conn = tls.Conn // Config . type Config = tls.Config -// Dial returns a net.Conn to be added to a Gopher. +// Dial returns a net.Conn to be added to a Engine. +// +//go:norace func Dial(network, addr string, config *Config) (*tls.Conn, error) { tlsConn, err := tls.Dial(network, addr, config, mempool.DefaultMemPool) if err != nil { @@ -22,7 +26,9 @@ func Dial(network, addr string, config *Config) (*tls.Conn, error) { return tlsConn, nil } -// WrapOpen returns an opening handler of nbio.Gopher. +// WrapOpen returns an opening handler of nbio.Engine. +// +//go:norace func WrapOpen(tlsConfig *Config, isClient bool, h func(c *nbio.Conn, tlsConn *Conn)) func(c *nbio.Conn) { return func(c *nbio.Conn) { var tlsConn *tls.Conn @@ -40,7 +46,9 @@ func WrapOpen(tlsConfig *Config, isClient bool, h func(c *nbio.Conn, tlsConn *Co } } -// WrapClose returns an closing handler of nbio.Gopher. +// WrapClose returns an closing handler of nbio.Engine. +// +//go:norace func WrapClose(h func(c *nbio.Conn, tlsConn *Conn, err error)) func(c *nbio.Conn, err error) { return func(c *nbio.Conn, err error) { if h != nil && c != nil { @@ -53,7 +61,9 @@ func WrapClose(h func(c *nbio.Conn, tlsConn *Conn, err error)) func(c *nbio.Conn } } -// WrapData returns a data handler of nbio.Gopher. +// WrapData returns a data handler of nbio.Engine. +// +//go:norace func WrapData(h func(c *nbio.Conn, tlsConn *Conn, data []byte), args ...interface{}) func(c *nbio.Conn, data []byte) { getBuffer := func() []byte { return make([]byte, 2048) @@ -66,12 +76,12 @@ func WrapData(h func(c *nbio.Conn, tlsConn *Conn, data []byte), args ...interfac return func(c *nbio.Conn, data []byte) { if session := c.Session(); session != nil { if tlsConn, ok := session.(*Conn); ok { - tlsConn.Append(data) + _, _ = tlsConn.Append(data) buffer := getBuffer() for { n, err := tlsConn.Read(buffer) if err != nil { - c.Close() + _ = c.Close() return } if h != nil && n > 0 { diff --git a/vendor/github.com/lesismal/nbio/go.mod b/vendor/github.com/lesismal/nbio/go.mod index 620f9a18..d53327c9 100644 --- a/vendor/github.com/lesismal/nbio/go.mod +++ b/vendor/github.com/lesismal/nbio/go.mod @@ -2,4 +2,4 @@ module github.com/lesismal/nbio go 1.16 -require github.com/lesismal/llib v1.1.4 +require github.com/lesismal/llib v1.2.3 diff --git a/vendor/github.com/lesismal/nbio/go.sum b/vendor/github.com/lesismal/nbio/go.sum index 9734170a..3840dc4c 100644 --- a/vendor/github.com/lesismal/nbio/go.sum +++ b/vendor/github.com/lesismal/nbio/go.sum @@ -1,8 +1,5 @@ -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/lesismal/llib v1.1.4 h1:eixNXLDaikVsrJHk4zMXRt+NvQFBg3b3/FjZb8cWmbI= -github.com/lesismal/llib v1.1.4/go.mod h1:3vmCrIMrpkaoA3bDu/sI+J7EyEUMPbOvmAxb7PlzilM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/lesismal/llib v1.2.3 h1:ImIIrrg+vPVy+jPam6ofRViH7RNDinGyPITv0vdyp3Y= +github.com/lesismal/llib v1.2.3/go.mod h1:70tFXXe7P1FZ02AU9l8LgSOK7d7sRrpnkUr3rd3gKSg= golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5 h1:N6Jp/LCiEoIBX56BZSR2bepK5GtbSC2DDOYT742mMfE= golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/vendor/github.com/lesismal/nbio/gopher.go b/vendor/github.com/lesismal/nbio/gopher.go deleted file mode 100644 index 5aa2596a..00000000 --- a/vendor/github.com/lesismal/nbio/gopher.go +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package nbio - -import ( - "container/heap" - "context" - "net" - "runtime" - "sync" - "time" - "unsafe" - - "github.com/lesismal/nbio/logging" -) - -const ( - // DefaultReadBufferSize . - DefaultReadBufferSize = 1024 * 32 - - // DefaultMaxWriteBufferSize . - DefaultMaxWriteBufferSize = 1024 * 1024 - - // DefaultMaxReadTimesPerEventLoop . - DefaultMaxReadTimesPerEventLoop = 3 - - // DefaultMinConnCacheSize . - DefaultMinConnCacheSize = 1024 * 2 -) - -var ( - // MaxOpenFiles . - MaxOpenFiles = 1024 * 1024 -) - -// Config Of Gopher. -type Config struct { - // Name describes your gopher name for logging, it's set to "NB" by default. - Name string - - // Network is the listening protocol, used with Addrs toghter. - // tcp* supported only by now, there's no plan for other protocol such as udp, - // because it's too easy to write udp server/client. - Network string - - // Addrs is the listening addr list for a nbio server. - // if it is empty, no listener created, then the Gopher is used for client by default. - Addrs []string - - // NPoller represents poller goroutine num, it's set to runtime.NumCPU() by default. - NPoller int - - // NListener represents poller goroutine num, it's set to runtime.NumCPU() by default. - NListener int - - // Backlog represents backlog arg for syscall.Listen - Backlog int - - // ReadBufferSize represents buffer size for reading, it's set to 16k by default. - ReadBufferSize int - - // MinConnCacheSize represents application layer's Conn write cache buffer size when the kernel sendQ is full - MinConnCacheSize int - - // MaxWriteBufferSize represents max write buffer size for Conn, it's set to 1m by default. - // if the connection's Send-Q is full and the data cached by nbio is - // more than MaxWriteBufferSize, the connection would be closed by nbio. - MaxWriteBufferSize int - - // MaxReadTimesPerEventLoop represents max read times in one poller loop for one fd - MaxReadTimesPerEventLoop int - - // LockListener represents listener's goroutine to lock thread or not, it's set to false by default. - LockListener bool - - // LockPoller represents poller's goroutine to lock thread or not, it's set to false by default. - LockPoller bool - - // EpollMod sets the epoll mod, EPOLLLT by default. - EpollMod uint32 -} - -// Gopher is a manager of poller. -type Gopher struct { - sync.WaitGroup - mux sync.Mutex - tmux sync.Mutex - - wgConn sync.WaitGroup - - Name string - - network string - addrs []string - pollerNum int - backlogSize int - readBufferSize int - maxWriteBufferSize int - maxReadTimesPerEventLoop int - minConnCacheSize int - epollMod uint32 - lockListener bool - lockPoller bool - - lfds []int - - connsStd map[*Conn]struct{} - connsUnix []*Conn - - listeners []*poller - pollers []*poller - - onOpen func(c *Conn) - onClose func(c *Conn, err error) - onRead func(c *Conn) - onData func(c *Conn, data []byte) - onReadBufferAlloc func(c *Conn) []byte - onReadBufferFree func(c *Conn, buffer []byte) - onWriteBufferFree func(c *Conn, buffer []byte) - beforeRead func(c *Conn) - afterRead func(c *Conn) - beforeWrite func(c *Conn) - onStop func() - - callings []func() - chCalling chan struct{} - timers timerHeap - trigger *time.Timer - chTimer chan struct{} - - Execute func(f func()) -} - -// Stop closes listeners/pollers/conns/timer. -func (g *Gopher) Stop() { - for _, l := range g.listeners { - l.stop() - } - - g.mux.Lock() - conns := g.connsStd - g.connsStd = map[*Conn]struct{}{} - connsUnix := g.connsUnix - g.mux.Unlock() - - g.wgConn.Done() - for c := range conns { - if c != nil { - cc := c - g.atOnce(func() { - cc.Close() - }) - } - } - for _, c := range connsUnix { - if c != nil { - cc := c - g.atOnce(func() { - cc.Close() - }) - } - } - - g.wgConn.Wait() - time.Sleep(time.Second / 5) - - g.onStop() - - g.trigger.Stop() - close(g.chTimer) - - for i := 0; i < g.pollerNum; i++ { - g.pollers[i].stop() - } - - g.Wait() - logging.Info("Gopher[%v] stop", g.Name) -} - -// Shutdown stops Gopher gracefully with context. -func (g *Gopher) Shutdown(ctx context.Context) error { - ch := make(chan struct{}) - go func() { - g.Stop() - close(ch) - }() - - select { - case <-ch: - case <-ctx.Done(): - return ctx.Err() - } - return nil -} - -// AddConn adds conn to a poller. -func (g *Gopher) AddConn(conn net.Conn) (*Conn, error) { - c, err := NBConn(conn) - if err != nil { - return nil, err - } - g.pollers[uint32(c.Hash())%uint32(g.pollerNum)].addConn(c) - return c, nil -} - -// OnOpen registers callback for new connection. -func (g *Gopher) OnOpen(h func(c *Conn)) { - if h == nil { - panic("invalid nil handler") - } - g.onOpen = func(c *Conn) { - g.wgConn.Add(1) - h(c) - } -} - -// OnClose registers callback for disconnected. -func (g *Gopher) OnClose(h func(c *Conn, err error)) { - if h == nil { - panic("invalid nil handler") - } - g.onClose = func(c *Conn, err error) { - // g.atOnce(func() { - defer g.wgConn.Done() - h(c, err) - // }) - } -} - -// OnRead registers callback for reading event. -func (g *Gopher) OnRead(h func(c *Conn)) { - g.onRead = h -} - -// OnData registers callback for data. -func (g *Gopher) OnData(h func(c *Conn, data []byte)) { - if h == nil { - panic("invalid nil handler") - } - g.onData = h -} - -// OnReadBufferAlloc registers callback for memory allocating. -func (g *Gopher) OnReadBufferAlloc(h func(c *Conn) []byte) { - if h == nil { - panic("invalid nil handler") - } - g.onReadBufferAlloc = h -} - -// OnReadBufferFree registers callback for memory release. -func (g *Gopher) OnReadBufferFree(h func(c *Conn, b []byte)) { - if h == nil { - panic("invalid nil handler") - } - g.onReadBufferFree = h -} - -// OnWriteBufferRelease registers callback for write buffer memory release. -func (g *Gopher) OnWriteBufferRelease(h func(c *Conn, b []byte)) { - if h == nil { - panic("invalid nil handler") - } - g.onWriteBufferFree = h -} - -// BeforeRead registers callback before syscall.Read -// the handler would be called on windows. -func (g *Gopher) BeforeRead(h func(c *Conn)) { - if h == nil { - panic("invalid nil handler") - } - g.beforeRead = h -} - -// AfterRead registers callback after syscall.Read -// the handler would be called on *nix. -func (g *Gopher) AfterRead(h func(c *Conn)) { - if h == nil { - panic("invalid nil handler") - } - g.afterRead = h -} - -// BeforeWrite registers callback befor syscall.Write and syscall.Writev -// the handler would be called on windows. -func (g *Gopher) BeforeWrite(h func(c *Conn)) { - if h == nil { - panic("invalid nil handler") - } - g.beforeWrite = h -} - -// OnStop registers callback before Gopher is stopped. -func (g *Gopher) OnStop(h func()) { - if h == nil { - panic("invalid nil handler") - } - g.onStop = h -} - -// After used as time.After. -func (g *Gopher) After(timeout time.Duration) <-chan time.Time { - c := make(chan time.Time, 1) - g.afterFunc(timeout, func() { - c <- time.Now() - }) - return c -} - -// AfterFunc used as time.AfterFunc. -func (g *Gopher) AfterFunc(timeout time.Duration, f func()) *Timer { - ht := g.afterFunc(timeout, f) - return &Timer{htimer: ht} -} - -func (g *Gopher) atOnce(f func()) { - if f != nil { - g.tmux.Lock() - g.callings = append(g.callings, f) - g.tmux.Unlock() - select { - case g.chCalling <- struct{}{}: - default: - } - } -} - -func (g *Gopher) afterFunc(timeout time.Duration, f func()) *htimer { - g.tmux.Lock() - defer g.tmux.Unlock() - - now := time.Now() - it := &htimer{ - index: len(g.timers), - expire: now.Add(timeout), - f: f, - parent: g, - } - heap.Push(&g.timers, it) - if g.timers[0] == it { - g.trigger.Reset(timeout) - } - - return it -} - -func (g *Gopher) removeTimer(it *htimer) { - g.tmux.Lock() - defer g.tmux.Unlock() - - index := it.index - if index < 0 || index >= len(g.timers) { - return - } - - if g.timers[index] == it { - heap.Remove(&g.timers, index) - if len(g.timers) > 0 { - if index == 0 { - g.trigger.Reset(time.Until(g.timers[0].expire)) - - } - } else { - g.trigger.Reset(timeForever) - } - } -} - -// ResetTimer removes a timer. -func (g *Gopher) resetTimer(it *htimer) { - g.tmux.Lock() - defer g.tmux.Unlock() - - index := it.index - if index < 0 || index >= len(g.timers) { - return - } - - if g.timers[index] == it { - heap.Fix(&g.timers, index) - if index == 0 || it.index == 0 { - g.trigger.Reset(time.Until(g.timers[0].expire)) - } - } -} - -func (g *Gopher) timerLoop() { - defer g.Done() - logging.Debug("Gopher[%v] timer start", g.Name) - defer logging.Debug("Gopher[%v] timer stopped", g.Name) - for { - select { - case <-g.chCalling: - for { - g.tmux.Lock() - if len(g.callings) == 0 { - g.callings = nil - g.tmux.Unlock() - break - } - f := g.callings[0] - g.callings = g.callings[1:] - g.tmux.Unlock() - func() { - defer func() { - err := recover() - if err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - logging.Error("Gopher[%v] exec call failed: %v\n%v\n", g.Name, err, *(*string)(unsafe.Pointer(&buf))) - } - }() - f() - }() - } - case <-g.trigger.C: - for { - g.tmux.Lock() - if g.timers.Len() == 0 { - g.trigger.Reset(timeForever) - g.tmux.Unlock() - break - } - now := time.Now() - it := g.timers[0] - if now.After(it.expire) { - heap.Remove(&g.timers, it.index) - g.tmux.Unlock() - func() { - defer func() { - err := recover() - if err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - logging.Error("Gopher[%v] exec timer failed: %v\n%v\n", g.Name, err, *(*string)(unsafe.Pointer(&buf))) - } - }() - it.f() - }() - } else { - g.trigger.Reset(it.expire.Sub(now)) - g.tmux.Unlock() - break - } - } - case <-g.chTimer: - return - } - } -} - -// PollerBuffer returns Poller's buffer by Conn, can be used on linux/bsd. -func (g *Gopher) PollerBuffer(c *Conn) []byte { - return g.pollers[uint32(c.Hash())%uint32(g.pollerNum)].ReadBuffer -} - -func (g *Gopher) initHandlers() { - g.wgConn.Add(1) - g.OnOpen(func(c *Conn) {}) - g.OnClose(func(c *Conn, err error) {}) - // g.OnRead(func(c *Conn, b []byte) ([]byte, error) { - // n, err := c.Read(b) - // if n > 0 { - // return b[:n], err - // } - // return nil, err - // }) - g.OnData(func(c *Conn, data []byte) {}) - g.OnReadBufferAlloc(g.PollerBuffer) - g.OnReadBufferFree(func(c *Conn, buffer []byte) {}) - g.OnWriteBufferRelease(func(c *Conn, buffer []byte) {}) - g.BeforeRead(func(c *Conn) {}) - g.AfterRead(func(c *Conn) {}) - g.BeforeWrite(func(c *Conn) {}) - g.OnStop(func() {}) - - if g.Execute == nil { - g.Execute = func(f func()) { - f() - } - } -} - -func (g *Gopher) borrow(c *Conn) []byte { - return g.onReadBufferAlloc(c) -} - -func (g *Gopher) payback(c *Conn, buffer []byte) { - g.onReadBufferFree(c, buffer) -} diff --git a/vendor/github.com/lesismal/nbio/gopher_std.go b/vendor/github.com/lesismal/nbio/gopher_std.go deleted file mode 100644 index 4dae5612..00000000 --- a/vendor/github.com/lesismal/nbio/gopher_std.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -//go:build windows -// +build windows - -package nbio - -import ( - "runtime" - "strings" - "time" - - "github.com/lesismal/nbio/logging" -) - -// Start init and start pollers -func (g *Gopher) Start() error { - var err error - - g.lfds = []int{} - - g.listeners = make([]*poller, len(g.addrs)) - for i := range g.addrs { - g.listeners[i], err = newPoller(g, true, int(i)) - if err != nil { - for j := 0; j < i; j++ { - g.listeners[j].stop() - } - return err - } - } - - for i := 0; i < g.pollerNum; i++ { - g.pollers[i], err = newPoller(g, false, int(i)) - if err != nil { - for j := 0; j < len(g.addrs); j++ { - g.listeners[j].stop() - } - - for j := 0; j < int(i); j++ { - g.pollers[j].stop() - } - return err - } - } - - for i := 0; i < g.pollerNum; i++ { - g.Add(1) - go g.pollers[i].start() - } - for _, l := range g.listeners { - g.Add(1) - go l.start() - } - - g.Add(1) - go g.timerLoop() - - if len(g.addrs) == 0 { - logging.Info("Gopher[%v] start", g.Name) - } else { - logging.Info("Gopher[%v] start listen on: [\"%v\"]", g.Name, strings.Join(g.addrs, `", "`)) - } - return nil -} - -// NewGopher is a factory impl -func NewGopher(conf Config) *Gopher { - cpuNum := runtime.NumCPU() - if conf.Name == "" { - conf.Name = "NB" - } - if conf.NPoller <= 0 { - conf.NPoller = cpuNum - } - if conf.ReadBufferSize <= 0 { - conf.ReadBufferSize = DefaultReadBufferSize - } - if conf.MinConnCacheSize == 0 { - conf.MinConnCacheSize = DefaultMinConnCacheSize - } - - g := &Gopher{ - Name: conf.Name, - network: conf.Network, - addrs: conf.Addrs, - pollerNum: conf.NPoller, - readBufferSize: conf.ReadBufferSize, - maxWriteBufferSize: conf.MaxWriteBufferSize, - minConnCacheSize: conf.MinConnCacheSize, - lockListener: conf.LockListener, - lockPoller: conf.LockPoller, - listeners: make([]*poller, len(conf.Addrs)), - pollers: make([]*poller, conf.NPoller), - connsStd: map[*Conn]struct{}{}, - callings: []func(){}, - chCalling: make(chan struct{}, 1), - trigger: time.NewTimer(timeForever), - chTimer: make(chan struct{}), - } - - g.initHandlers() - - g.OnReadBufferAlloc(func(c *Conn) []byte { - if c.ReadBuffer == nil { - c.ReadBuffer = make([]byte, int(g.readBufferSize)) - } - return c.ReadBuffer - }) - - return g -} diff --git a/vendor/github.com/lesismal/nbio/gopher_unix.go b/vendor/github.com/lesismal/nbio/gopher_unix.go deleted file mode 100644 index 1538dd89..00000000 --- a/vendor/github.com/lesismal/nbio/gopher_unix.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -//go:build linux || darwin || netbsd || freebsd || openbsd || dragonfly -// +build linux darwin netbsd freebsd openbsd dragonfly - -package nbio - -import ( - "runtime" - "strings" - "syscall" - "time" - - "github.com/lesismal/nbio/logging" -) - -// Start init and start pollers. -func (g *Gopher) Start() error { - var err error - - for i := 0; i < len(g.addrs); i++ { - g.listeners[i], err = newPoller(g, true, i) - if err != nil { - for j := 0; j < i; j++ { - g.listeners[j].stop() - } - return err - } - } - - for i := 0; i < g.pollerNum; i++ { - g.pollers[i], err = newPoller(g, false, i) - if err != nil { - for j := 0; j < len(g.lfds); j++ { - syscall.Close(g.lfds[j]) - } - - for j := 0; j < len(g.listeners); j++ { - g.listeners[j].stop() - } - - for j := 0; j < i; j++ { - g.pollers[j].stop() - } - return err - } - } - - for i := 0; i < g.pollerNum; i++ { - g.pollers[i].ReadBuffer = make([]byte, g.readBufferSize) - g.Add(1) - go g.pollers[i].start() - } - for _, l := range g.listeners { - g.Add(1) - go l.start() - } - - g.Add(1) - go g.timerLoop() - - if len(g.addrs) == 0 { - logging.Info("Gopher[%v] start", g.Name) - } else { - logging.Info("Gopher[%v] start listen on: [\"%v\"]", g.Name, strings.Join(g.addrs, `", "`)) - } - return nil -} - -// NewGopher is a factory impl. -func NewGopher(conf Config) *Gopher { - cpuNum := runtime.NumCPU() - if conf.Name == "" { - conf.Name = "NB" - } - if conf.NPoller <= 0 { - conf.NPoller = cpuNum - } - if len(conf.Addrs) > 0 && conf.NListener <= 0 { - conf.NListener = 1 - } - if conf.Backlog <= 0 { - conf.Backlog = 1024 * 64 - } - if conf.ReadBufferSize <= 0 { - conf.ReadBufferSize = DefaultReadBufferSize - } - if conf.MinConnCacheSize == 0 { - conf.MinConnCacheSize = DefaultMinConnCacheSize - } - if conf.MaxReadTimesPerEventLoop <= 0 { - conf.MaxReadTimesPerEventLoop = DefaultMaxReadTimesPerEventLoop - } - - g := &Gopher{ - Name: conf.Name, - network: conf.Network, - addrs: conf.Addrs, - pollerNum: conf.NPoller, - backlogSize: conf.Backlog, - readBufferSize: conf.ReadBufferSize, - maxWriteBufferSize: conf.MaxWriteBufferSize, - maxReadTimesPerEventLoop: conf.MaxReadTimesPerEventLoop, - minConnCacheSize: conf.MinConnCacheSize, - epollMod: conf.EpollMod, - lockListener: conf.LockListener, - lockPoller: conf.LockPoller, - listeners: make([]*poller, len(conf.Addrs)), - pollers: make([]*poller, conf.NPoller), - connsUnix: make([]*Conn, MaxOpenFiles), - callings: []func(){}, - chCalling: make(chan struct{}, 1), - trigger: time.NewTimer(timeForever), - chTimer: make(chan struct{}), - } - - g.initHandlers() - - return g -} diff --git a/vendor/github.com/lesismal/nbio/lmux/lmux.go b/vendor/github.com/lesismal/nbio/lmux/lmux.go new file mode 100644 index 00000000..ffd650a8 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/lmux/lmux.go @@ -0,0 +1,175 @@ +package lmux + +import ( + "errors" + "net" + "sync/atomic" + "time" + + "github.com/lesismal/nbio/logging" +) + +type event struct { + err error + conn net.Conn +} + +type listenerAB struct { + a, b *ChanListener +} + +// New returns a ListenerMux. +// +//go:norace +func New(maxOnlineA int) *ListenerMux { + return &ListenerMux{ + listeners: map[net.Listener]listenerAB{}, + chClose: make(chan struct{}), + maxOnlineA: int32(maxOnlineA), + } +} + +// ListenerMux manages listeners and handle the connection dispatching logic. +type ListenerMux struct { + shutdown bool + listeners map[net.Listener]listenerAB + chClose chan struct{} + onlineA int32 + maxOnlineA int32 +} + +// Mux creates and returns ChanListener A and B: +// If the online num of A is less than ListenerMux. maxOnlineA, the new connection will be dispatched to A; +// Else the new connection will be dispatched to B. +// +//go:norace +func (lm *ListenerMux) Mux(l net.Listener) (*ChanListener, *ChanListener) { + if l == nil || lm == nil { + return nil, nil + } + if lm.listeners == nil { + lm.listeners = map[net.Listener]listenerAB{} + } + ab := listenerAB{ + a: &ChanListener{ + addr: l.Addr(), + chClose: lm.chClose, + chEvent: make(chan event, 1024*64), + decrease: lm.DecreaseOnlineA, + }, + b: &ChanListener{ + addr: l.Addr(), + chClose: lm.chClose, + chEvent: make(chan event, 1024*64), + }, + } + lm.listeners[l] = ab + return ab.a, ab.b +} + +// Start starts to accept and dispatch the connections to ChanListener A or B. +// +//go:norace +func (lm *ListenerMux) Start() { + if lm == nil { + return + } + lm.shutdown = false + for k, v := range lm.listeners { + go func(l net.Listener, listenerA *ChanListener, listenerB *ChanListener) { + for !lm.shutdown { + c, err := l.Accept() + if err != nil { + var ne net.Error + if ok := errors.As(err, &ne); ok && ne.Timeout() { + logging.Error("Accept failed: timeout error, retrying...") + time.Sleep(time.Second / 20) + } else { + if !lm.shutdown { + logging.Error("Accept failed: %v, exit...", err) + } + listenerA.chEvent <- event{err: err, conn: c} + listenerB.chEvent <- event{err: err, conn: c} + + // Exit the loop after a non recoverable error + return + } + continue + } + if atomic.AddInt32(&lm.onlineA, 1) <= lm.maxOnlineA { + listenerA.chEvent <- event{err: nil, conn: c} + } else { + atomic.AddInt32(&lm.onlineA, -1) + listenerB.chEvent <- event{err: nil, conn: c} + } + } + }(k, v.a, v.b) + } +} + +// Stop stops all the listeners. +// +//go:norace +func (lm *ListenerMux) Stop() { + if lm == nil { + return + } + lm.shutdown = true + for l, ab := range lm.listeners { + _ = l.Close() + _ = ab.a.Close() + _ = ab.b.Close() + } + close(lm.chClose) +} + +// DecreaseOnlineA decreases the online num of ChanListener A. +// +//go:norace +func (lm *ListenerMux) DecreaseOnlineA() { + atomic.AddInt32(&lm.onlineA, -1) +} + +// ChanListener . +type ChanListener struct { + addr net.Addr + chEvent chan event + chClose chan struct{} + decrease func() +} + +// Accept accepts a connection. +// +//go:norace +func (l *ChanListener) Accept() (net.Conn, error) { + select { + case e := <-l.chEvent: + return e.conn, e.err + case <-l.chClose: + return nil, net.ErrClosed + } +} + +// Close does nothing but implementing net.Conn.Close. +// User should call ListenerMux.Close to close it automatically. +// +//go:norace +func (l *ChanListener) Close() error { + return nil +} + +// Addr returns the listener's network address. +// +//go:norace +func (l *ChanListener) Addr() net.Addr { + return l.addr +} + +// Decrease decreases the online num if it's A. +// +//go:norace +func (l *ChanListener) Decrease() { + if l.decrease != nil { + l.decrease() + } +} diff --git a/vendor/github.com/lesismal/nbio/lmux/lmux_test.go b/vendor/github.com/lesismal/nbio/lmux/lmux_test.go new file mode 100644 index 00000000..92d48bc4 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/lmux/lmux_test.go @@ -0,0 +1,123 @@ +package lmux + +import ( + "net" + "sync" + "testing" + "time" +) + +func TestListenerMux(t *testing.T) { + maxOnlineA := 3 + totalConn := 5 + network := "tcp" + addr1 := "localhost:8001" + addr2 := "localhost:8002" + lm := New(maxOnlineA) + + listen := func(addr string) net.Listener { + l, err := net.Listen(network, addr) + if err != nil { + t.Fatal(err) + } + return l + } + l1 := listen(addr1) + listenerA, listenerB := lm.Mux(l1) + l2 := listen(addr2) + listenerC, listenerD := lm.Mux(l2) + lm.Start() + + time.Sleep(time.Second / 5) + wg := sync.WaitGroup{} + chA := make(chan net.Conn, totalConn) + chB := make(chan net.Conn, totalConn) + chC := make(chan net.Conn, totalConn) + chD := make(chan net.Conn, totalConn) + chErr := make(chan error, totalConn) + conns := make([]net.Conn, totalConn)[:0] + + accept := func(ln net.Listener, chConn chan net.Conn) { + for { + conn, err := ln.Accept() + if err != nil { + chErr <- err + break + } + chConn <- conn + } + } + go accept(listenerA, chA) + go accept(listenerB, chB) + go accept(listenerC, chC) + go accept(listenerD, chD) + + dialN := func(n int, addr string) { + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < n; i++ { + conn, err := net.Dial(network, addr) + if err != nil { + chErr <- err + break + } + conns = append(conns, conn) + } + }() + } + closeConns := func() { + for _, v := range conns { + _ = v.Close() + } + } + dialN(totalConn, addr1) + dialN(totalConn, addr2) + + wg.Wait() + time.Sleep(time.Second / 5) + + if len(chErr) != 0 { + t.Fatalf("len(chA) != maxOnlineA, want %v, got %v", 0, len(chErr)) + } + + if len(chA)+len(chC) != maxOnlineA { + t.Fatalf("len(chA)+len(chC) != maxOnlineA, want %v, got %v[A=%v, C=%v]", maxOnlineA, len(chA)+len(chC), len(chA), len(chC)) + } + + if len(chB)+len(chD) != totalConn*2-maxOnlineA { + t.Fatalf("len(chB)+len(chD) != maxOnlineA, want %v, got %v[B=%v, D=%v]", totalConn*2-maxOnlineA, len(chB)+len(chD), len(chB), len(chD)) + } + + closeConns() + + clean := func(ln *ChanListener, chConn chan net.Conn) { + n := len(chConn) + for i := 0; i < n; i++ { + <-chConn + ln.Decrease() + } + } + clean(listenerA, chA) + clean(listenerB, chB) + clean(listenerC, chC) + clean(listenerD, chD) + + conns = conns[:0] + dialN(totalConn, addr1) + dialN(totalConn, addr2) + defer closeConns() + + wg.Wait() + time.Sleep(time.Second / 5) + + if len(chA)+len(chC) != maxOnlineA { + t.Fatalf("len(chA)+len(chC) != maxOnlineA, want %v, got %v[A=%v, C=%v]", maxOnlineA, len(chA)+len(chC), len(chA), len(chC)) + } + + if len(chB)+len(chD) != totalConn*2-maxOnlineA { + t.Fatalf("len(chB)+len(chD) != maxOnlineA, want %v, got %v[B=%v, D=%v]", totalConn*2-maxOnlineA, len(chB)+len(chD), len(chB), len(chD)) + } + + lm.Stop() +} diff --git a/vendor/github.com/lesismal/nbio/logging/log.go b/vendor/github.com/lesismal/nbio/logging/log.go index 0aab2184..22a702a7 100644 --- a/vendor/github.com/lesismal/nbio/logging/log.go +++ b/vendor/github.com/lesismal/nbio/logging/log.go @@ -6,6 +6,7 @@ package logging import ( "fmt" + "io" "os" "time" ) @@ -15,7 +16,7 @@ var ( TimeFormat = "2006/01/02 15:04:05.000" // Output is used to receive log output. - Output = os.Stdout + Output io.Writer = os.Stdout // DefaultLogger is the default logger and is used by arpc. DefaultLogger Logger = &logger{level: LevelInfo} @@ -38,7 +39,6 @@ const ( // Logger defines log interface. type Logger interface { - SetLevel(lvl int) Debug(format string, v ...interface{}) Info(format string, v ...interface{}) Warn(format string, v ...interface{}) @@ -46,17 +46,20 @@ type Logger interface { } // SetLogger sets default logger. +// +//go:norace func SetLogger(l Logger) { DefaultLogger = l } // SetLevel sets default logger's priority. +// +//go:norace func SetLevel(lvl int) { - switch lvl { - case LevelAll, LevelDebug, LevelInfo, LevelWarn, LevelError, LevelNone: - DefaultLogger.SetLevel(lvl) - default: - fmt.Fprintf(Output, "invalid log level: %v", lvl) + if l, ok := DefaultLogger.(interface { + SetLevel(lvl int) + }); ok { + l.SetLevel(lvl) } } @@ -66,44 +69,56 @@ type logger struct { } // SetLevel sets logs priority. +// +//go:norace func (l *logger) SetLevel(lvl int) { switch lvl { case LevelAll, LevelDebug, LevelInfo, LevelWarn, LevelError, LevelNone: l.level = lvl default: - fmt.Fprintf(Output, "invalid log level: %v", lvl) + _, _ = fmt.Fprintf(Output, "invalid log level: %v", lvl) } } // Debug uses fmt.Printf to log a message at LevelDebug. +// +//go:norace func (l *logger) Debug(format string, v ...interface{}) { if LevelDebug >= l.level { - fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [DBG] "+format+"\n", v...) + _, _ = fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [DBG] "+format+"\n", v...) } } // Info uses fmt.Printf to log a message at LevelInfo. +// +//go:norace func (l *logger) Info(format string, v ...interface{}) { if LevelInfo >= l.level { - fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [INF] "+format+"\n", v...) + _, _ = fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [INF] "+format+"\n", v...) } } // Warn uses fmt.Printf to log a message at LevelWarn. +// +//go:norace func (l *logger) Warn(format string, v ...interface{}) { if LevelWarn >= l.level { - fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [WRN] "+format+"\n", v...) + _, _ = fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [WRN] "+format+"\n", v...) } } // Error uses fmt.Printf to log a message at LevelError. +// +//go:norace func (l *logger) Error(format string, v ...interface{}) { if LevelError >= l.level { - fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [ERR] "+format+"\n", v...) + _, _ = fmt.Fprintf(Output, time.Now().Format(TimeFormat)+" [ERR] "+format+"\n", v...) } } // Debug uses DefaultLogger to log a message at LevelDebug. +// +//go:norace func Debug(format string, v ...interface{}) { if DefaultLogger != nil { DefaultLogger.Debug(format, v...) @@ -111,6 +126,8 @@ func Debug(format string, v ...interface{}) { } // Info uses DefaultLogger to log a message at LevelInfo. +// +//go:norace func Info(format string, v ...interface{}) { if DefaultLogger != nil { DefaultLogger.Info(format, v...) @@ -118,6 +135,8 @@ func Info(format string, v ...interface{}) { } // Warn uses DefaultLogger to log a message at LevelWarn. +// +//go:norace func Warn(format string, v ...interface{}) { if DefaultLogger != nil { DefaultLogger.Warn(format, v...) @@ -125,6 +144,8 @@ func Warn(format string, v ...interface{}) { } // Error uses DefaultLogger to log a message at LevelError. +// +//go:norace func Error(format string, v ...interface{}) { if DefaultLogger != nil { DefaultLogger.Error(format, v...) diff --git a/vendor/github.com/lesismal/nbio/mempool/aligned_allocator.go b/vendor/github.com/lesismal/nbio/mempool/aligned_allocator.go new file mode 100644 index 00000000..708a32c9 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/mempool/aligned_allocator.go @@ -0,0 +1,131 @@ +package mempool + +import ( + "sync" + "unsafe" +) + +var ( + alignedPools [alignedPoolBucketNum]sync.Pool + alignedIndexes [maxAlignedBufferSize + 1]byte +) + +const ( + minAlignedBufferSizeBits = 5 + maxAlignedBufferSizeBits = 15 + minAlignedBufferSize = 1 << minAlignedBufferSizeBits // 32 + minAlignedBufferSizeMask = minAlignedBufferSize - 1 // 31 + maxAlignedBufferSize = 1 << maxAlignedBufferSizeBits // 32k + alignedPoolBucketNum = maxAlignedBufferSizeBits - minAlignedBufferSizeBits + 1 // 12 +) + +//go:norace +func init() { + var poolSizes [alignedPoolBucketNum]int + for i := range alignedPools { + size := 1 << (i + minAlignedBufferSizeBits) + poolSizes[i] = size + alignedPools[i].New = func() interface{} { + b := make([]byte, size) + return &b + } + } + + getPoolBySize := func(size int) byte { + for i, n := range poolSizes { + if size <= n { + return byte(i) + } + } + return 0xFF + } + + for i := range alignedIndexes { + alignedIndexes[i] = getPoolBySize(i) + } +} + +// NewAligned . +// +//go:norace +func NewAligned() Allocator { + amp := &AlignedAllocator{ + debugger: &debugger{}, + } + return amp +} + +// AlignedAllocator . +type AlignedAllocator struct { + *debugger +} + +// Malloc . +// +//go:norace +func (amp *AlignedAllocator) Malloc(size int) *[]byte { + if size < 0 { + return nil + } + var ret []byte + if size <= maxAlignedBufferSize { + idx := alignedIndexes[size] + ret = (*(alignedPools[idx].Get().(*[]byte)))[:size] + } else { + ret = make([]byte, size) + } + amp.incrMalloc(&ret) + return &ret +} + +// Realloc . +// +//go:norace +func (amp *AlignedAllocator) Realloc(pbuf *[]byte, size int) *[]byte { + if size <= cap(*pbuf) { + *pbuf = (*pbuf)[:size] + return pbuf + } + newBufPtr := amp.Malloc(size) + copy(*newBufPtr, *pbuf) + amp.Free(pbuf) + return newBufPtr +} + +// Append . +// +//go:norace +func (amp *AlignedAllocator) Append(pbuf *[]byte, more ...byte) *[]byte { + if cap(*pbuf)-len(*pbuf) >= len(more) { + *pbuf = append(*pbuf, more...) + return pbuf + } + newBufPtr := amp.Malloc(len(*pbuf) + len(more)) + copy(*newBufPtr, *pbuf) + copy((*newBufPtr)[len(*pbuf):], more) + amp.Free(pbuf) + return newBufPtr +} + +// AppendString . +// +//go:norace +func (amp *AlignedAllocator) AppendString(pbuf *[]byte, s string) *[]byte { + x := (*[2]uintptr)(unsafe.Pointer(&s)) + h := [3]uintptr{x[0], x[1], x[1]} + more := *(*[]byte)(unsafe.Pointer(&h)) + return amp.Append(pbuf, more...) +} + +// Free . +// +//go:norace +func (amp *AlignedAllocator) Free(pbuf *[]byte) { + size := cap(*pbuf) + if (size&minAlignedBufferSizeMask) != 0 || size > maxAlignedBufferSize { + return + } + amp.incrFree(pbuf) + idx := alignedIndexes[size] + alignedPools[idx].Put(pbuf) +} diff --git a/vendor/github.com/lesismal/nbio/mempool/allocator.go b/vendor/github.com/lesismal/nbio/mempool/allocator.go new file mode 100644 index 00000000..4c4aee97 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/mempool/allocator.go @@ -0,0 +1,47 @@ +package mempool + +// DefaultMemPool . +var DefaultMemPool = New(1024, 1024*1024*1024) + +type Allocator interface { + Malloc(size int) *[]byte + Realloc(buf *[]byte, size int) *[]byte // deprecated. + Append(buf *[]byte, more ...byte) *[]byte + AppendString(buf *[]byte, more string) *[]byte + Free(buf *[]byte) +} + +type DebugAllocator interface { + Allocator + String() string + SetDebug(bool) +} + +//go:norace +func Malloc(size int) *[]byte { + return DefaultMemPool.Malloc(size) +} + +//go:norace +func Realloc(pbuf *[]byte, size int) *[]byte { + return DefaultMemPool.Realloc(pbuf, size) +} + +//go:norace +func Append(pbuf *[]byte, more ...byte) *[]byte { + return DefaultMemPool.Append(pbuf, more...) +} + +//go:norace +func AppendString(pbuf *[]byte, more string) *[]byte { + return DefaultMemPool.AppendString(pbuf, more) +} + +//go:norace +func Free(pbuf *[]byte) { + DefaultMemPool.Free(pbuf) +} + +// func Init(bufSize, freeSize int) { +// DefaultMemPool = New(bufSize, freeSize) +// } diff --git a/vendor/github.com/lesismal/nbio/mempool/debugger.go b/vendor/github.com/lesismal/nbio/mempool/debugger.go new file mode 100644 index 00000000..4ed3ecb5 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/mempool/debugger.go @@ -0,0 +1,91 @@ +package mempool + +import ( + "encoding/json" + "sync" + "sync/atomic" +) + +type sizeMap struct { + MallocCount int64 `json:"MallocCount"` + FreeCount int64 `json:"FreeCount"` + NeedFree int64 `json:"NeedFree"` +} + +type debugger struct { + mux sync.Mutex + on bool + MallocCount int64 `json:"MallocCount"` + FreeCount int64 `json:"FreeCount"` + NeedFree int64 `json:"NeedFree"` + SizeMap map[int]*sizeMap `json:"SizeMap"` +} + +//go:norace +func (d *debugger) SetDebug(dbg bool) { + d.on = dbg +} + +//go:norace +func (d *debugger) incrMalloc(pbuf *[]byte) { + if d.on { + d.incrMallocSlow(pbuf) + } +} + +//go:norace +func (d *debugger) incrMallocSlow(pbuf *[]byte) { + atomic.AddInt64(&d.MallocCount, 1) + atomic.AddInt64(&d.NeedFree, 1) + size := cap(*pbuf) + d.mux.Lock() + defer d.mux.Unlock() + if d.SizeMap == nil { + d.SizeMap = map[int]*sizeMap{} + } + if v, ok := d.SizeMap[size]; ok { + v.MallocCount++ + v.NeedFree++ + } else { + d.SizeMap[size] = &sizeMap{ + MallocCount: 1, + NeedFree: 1, + } + } +} + +//go:norace +func (d *debugger) incrFree(pbuf *[]byte) { + if d.on { + d.incrFreeSlow(pbuf) + } +} + +//go:norace +func (d *debugger) incrFreeSlow(pbuf *[]byte) { + atomic.AddInt64(&d.FreeCount, 1) + atomic.AddInt64(&d.NeedFree, -1) + size := cap(*pbuf) + d.mux.Lock() + defer d.mux.Unlock() + if v, ok := d.SizeMap[size]; ok { + v.FreeCount++ + v.NeedFree-- + } else { + d.SizeMap[size] = &sizeMap{ + MallocCount: 1, + NeedFree: -1, + } + } +} + +//go:norace +func (d *debugger) String() string { + if d.on { + b, err := json.Marshal(d) + if err == nil { + return string(b) + } + } + return "" +} diff --git a/vendor/github.com/lesismal/nbio/mempool/mempool.go b/vendor/github.com/lesismal/nbio/mempool/mempool.go index 5a8f3398..29cb216c 100644 --- a/vendor/github.com/lesismal/nbio/mempool/mempool.go +++ b/vendor/github.com/lesismal/nbio/mempool/mempool.go @@ -5,180 +5,102 @@ package mempool import ( - "fmt" - "runtime" "sync" ) -const maxAppendSize = 1024 * 1024 * 4 - -type Allocator interface { - Malloc(size int) []byte - Realloc(buf []byte, size int) []byte - Free(buf []byte) -} - -// DefaultMemPool . -var DefaultMemPool = New(64) - // MemPool . type MemPool struct { - minSize int - pool sync.Pool - - Debug bool - mux sync.Mutex - allocStacks map[*byte]string - freeStacks map[*byte]string + *debugger + bufSize int + freeSize int + pool *sync.Pool } // New . -func New(minSize int) Allocator { - if minSize <= 0 { - minSize = 64 +func New(bufSize, freeSize int) Allocator { + if bufSize <= 0 { + bufSize = 64 } + if freeSize <= 0 { + freeSize = 64 * 1024 + } + if freeSize < bufSize { + freeSize = bufSize + } + mp := &MemPool{ - minSize: minSize, - allocStacks: map[*byte]string{}, - freeStacks: map[*byte]string{}, + debugger: &debugger{}, + bufSize: bufSize, + freeSize: freeSize, + pool: &sync.Pool{}, // Debug: true, } mp.pool.New = func() interface{} { - buf := make([]byte, minSize) + buf := make([]byte, bufSize) return &buf } return mp } // Malloc . -func (mp *MemPool) Malloc(size int) []byte { - pbuf := mp.pool.Get().(*[]byte) - need := size - cap(*pbuf) - if need > 0 { - if need <= maxAppendSize { - *pbuf = (*pbuf)[:cap(*pbuf)] - *pbuf = append(*pbuf, make([]byte, need)...) - } else { - mp.pool.Put(pbuf) - newBuf := make([]byte, size) - pbuf = &newBuf - } +func (mp *MemPool) Malloc(size int) *[]byte { + var ret []byte + if size > mp.freeSize { + ret = make([]byte, size) + mp.incrMalloc(&ret) + return &ret } - - if mp.Debug { - mp.saveAllocStack(*pbuf) + pbuf := mp.pool.Get().(*[]byte) + n := cap(*pbuf) + if n < size { + *pbuf = append((*pbuf)[:n], make([]byte, size-n)...) } - - return (*pbuf)[:size] + (*pbuf) = (*pbuf)[:size] + mp.incrMalloc(pbuf) + return pbuf } // Realloc . -func (mp *MemPool) Realloc(buf []byte, size int) []byte { - if size <= cap(buf) { - return buf[:size] - } - if cap(buf) < mp.minSize { - newBuf := mp.Malloc(size) - copy(newBuf[:len(buf)], buf) - return newBuf - } - pbuf := &buf - need := size - cap(buf) - if need <= maxAppendSize { - *pbuf = (*pbuf)[:cap(*pbuf)] - *pbuf = append(*pbuf, make([]byte, need)...) - } else { - mp.pool.Put(pbuf) - newBuf := make([]byte, size) - pbuf = &newBuf - } - copy((*pbuf)[:len(buf)], buf) - - if mp.Debug { - mp.saveAllocStack(*pbuf) - } - return (*pbuf)[:size] -} - -// Free . -func (mp *MemPool) Free(buf []byte) { - if cap(buf) < mp.minSize { - return - } - if mp.Debug { - mp.saveFreeStack(buf) +func (mp *MemPool) Realloc(pbuf *[]byte, size int) *[]byte { + if size <= cap(*pbuf) { + *pbuf = (*pbuf)[:size] + return pbuf } - mp.pool.Put(&buf) -} -func (mp *MemPool) saveFreeStack(buf []byte) { - p := &(buf[:1][0]) - mp.mux.Lock() - defer mp.mux.Unlock() - s, ok := mp.freeStacks[p] - if ok { - allocStack := mp.allocStacks[p] - err := fmt.Errorf("\nbuffer exists: %p\nprevious allocation:\n%v\nprevious free:\n%v\ncurrent free:\n%v", p, allocStack, s, getStack()) - panic(err) + if cap(*pbuf) < mp.freeSize { + newBufPtr := mp.pool.Get().(*[]byte) + n := cap(*newBufPtr) + if n < size { + *newBufPtr = append((*newBufPtr)[:n], make([]byte, size-n)...) + } + *newBufPtr = (*newBufPtr)[:size] + copy(*newBufPtr, *pbuf) + mp.Free(pbuf) + return newBufPtr } - mp.freeStacks[p] = getStack() - delete(mp.allocStacks, p) + *pbuf = append((*pbuf)[:cap(*pbuf)], make([]byte, size-cap(*pbuf))...)[:size] + return pbuf } -func (mp *MemPool) saveAllocStack(buf []byte) { - p := &(buf[:1][0]) - mp.mux.Lock() - defer mp.mux.Unlock() - delete(mp.freeStacks, p) - mp.allocStacks[p] = getStack() +// Append . +func (mp *MemPool) Append(pbuf *[]byte, more ...byte) *[]byte { + *pbuf = append(*pbuf, more...) + return pbuf } -// NativeAllocator definition. -type NativeAllocator struct{} - -// Malloc . -func (a *NativeAllocator) Malloc(size int) []byte { - return make([]byte, size) -} - -// Realloc . -func (a *NativeAllocator) Realloc(buf []byte, size int) []byte { - if size <= cap(buf) { - return buf[:size] - } - newBuf := make([]byte, size) - copy(newBuf, buf) - return newBuf +// AppendString . +func (mp *MemPool) AppendString(pbuf *[]byte, more string) *[]byte { + *pbuf = append(*pbuf, more...) + return pbuf } // Free . -func (a *NativeAllocator) Free(buf []byte) { -} - -// Malloc exports default package method. -func Malloc(size int) []byte { - return DefaultMemPool.Malloc(size) -} - -// Realloc exports default package method. -func Realloc(buf []byte, size int) []byte { - return DefaultMemPool.Realloc(buf, size) -} - -// Free exports default package method. -func Free(buf []byte) { - DefaultMemPool.Free(buf) -} - -func getStack() string { - i := 2 - str := "" - for ; i < 10; i++ { - pc, file, line, ok := runtime.Caller(i) - if !ok { - break +func (mp *MemPool) Free(pbuf *[]byte) { + if pbuf != nil && cap(*pbuf) > 0 { + mp.incrFree(pbuf) + if cap(*pbuf) > mp.freeSize { + return } - str += fmt.Sprintf("\tstack: %d %v [file: %s] [func: %s] [line: %d]\n", i-1, ok, file, runtime.FuncForPC(pc).Name(), line) + mp.pool.Put(pbuf) } - return str } diff --git a/vendor/github.com/lesismal/nbio/mempool/mempool_test.go b/vendor/github.com/lesismal/nbio/mempool/mempool_test.go index 4af9d445..8564f35e 100644 --- a/vendor/github.com/lesismal/nbio/mempool/mempool_test.go +++ b/vendor/github.com/lesismal/nbio/mempool/mempool_test.go @@ -5,29 +5,119 @@ import ( ) func TestMemPool(t *testing.T) { - const minMemSize = 64 - pool := New(minMemSize) + pool := New(1024*1024*1024, 1024*1024*1024) for i := 0; i < 1024*1024; i++ { - buf := pool.Malloc(i) - if len(buf) != i { - t.Fatalf("invalid length: %v != %v", len(buf), i) + pbuf := pool.Malloc(i) + if len(*pbuf) != i { + t.Fatalf("invalid len: %v != %v", len(*pbuf), i) } - pool.Free(buf) + pool.Free(pbuf) } for i := 1024 * 1024; i < 1024*1024*1024; i += 1024 * 1024 { - buf := pool.Malloc(i) - if len(buf) != i { - t.Fatalf("invalid length: %v != %v", len(buf), i) + pbuf := pool.Malloc(i) + if len(*pbuf) != i { + t.Fatalf("invalid len: %v != %v", len(*pbuf), i) } - pool.Free(buf) + pool.Free(pbuf) } - buf := pool.Malloc(0) + pbuf := pool.Malloc(0) for i := 1; i < 1024*1024; i++ { - buf = pool.Realloc(buf, i) - if len(buf) != i { - t.Fatalf("invalid length: %v != %v", len(buf), i) + pbuf = pool.Realloc(pbuf, i) + if len(*pbuf) != i { + t.Fatalf("invalid len: %v != %v", len(*pbuf), i) } } - pool.Free(buf) + pool.Free(pbuf) +} + +func TestAlignedMemPool(t *testing.T) { + pool := NewAligned() + b := pool.Malloc(32769) + pool.Free(b) + tmpBuf := make([]byte, 60001) + pool.Free(&tmpBuf) + for i := 0; i < 1024*64+1024; i += 1 { + pbuf := pool.Malloc(i) + if len(*pbuf) != i { + t.Fatalf("invalid length: %v != %v", len(*pbuf), i) + } + pool.Free(pbuf) + } + for i := minAlignedBufferSizeBits; i < maxAlignedBufferSizeBits; i++ { + size := 1 << i + pbuf := pool.Malloc(size) + if len(*pbuf) != size || cap(*pbuf) > size*2 { + t.Fatalf("invalid len or cap: %v, %v %v, %v ", i, len(*pbuf), cap(*pbuf), size) + } + pbuf = pool.Malloc(size + 1) + if i != maxAlignedBufferSizeBits { + if len(*pbuf) != size+1 || cap(*pbuf) != size*2 || cap(*pbuf) > (size+1)*2 { + t.Fatalf("invalid len or cap: %v, %v %v, %v ", i, len(*pbuf), cap(*pbuf), size) + } + } else { + if len(*pbuf) != size+1 || cap(*pbuf) != size+1 { + t.Fatalf("invalid len or cap: %v, %v %v, %v ", i, len(*pbuf), cap(*pbuf), size) + } + } + pool.Free(pbuf) + } + for i := -10; i < 0; i++ { + pbuf := pool.Malloc(i) + if pbuf != nil { + t.Fatalf("invalid malloc, should be nil but got: %v, %v", len(*pbuf), cap(*pbuf)) + } + } + for i := 1 << maxAlignedBufferSizeBits; i < 1< 50 { + break + } + n, err := fmt.Fprintf(stackWriter, "\t%d [file: %s] [func: %s] [line: %d]\n", i-1, file, runtime.FuncForPC(pc).Name(), line) + if n > 0 { + nwrite += n + } + if err != nil { + break + } + } + + buf := stackBuf[:nwrite] + stack := *(*string)(unsafe.Pointer(&buf)) + if ptr, ok := stackMap[stack]; ok { + return ptr2StackString(ptr), ptr + } + stack = string(buf) + ptr := *(*[2]uintptr)(unsafe.Pointer(&stack)) + ptrCopy := [2]uintptr{ptr[0], ptr[1]} + stackMap[stack] = ptrCopy + return stack, ptrCopy +} + +//go:norace +func ptr2StackString(ptr [2]uintptr) string { + if ptr[0] == 0 && ptr[1] == 0 { + return "nil" + } + return *((*string)(unsafe.Pointer(&ptr))) +} + +// func bytesToStr(b []byte) string { +// return *(*string)(unsafe.Pointer(&b)) +// } + +// func strToBytes(s string) []byte { +// x := (*[2]uintptr)(unsafe.Pointer(&s)) +// h := [3]uintptr{x[0], x[1], x[1]} +// return *(*[]byte)(unsafe.Pointer(&h)) +// } + +//go:norace +func printStack(info string, preStackPtr [2]uintptr) { + var ( + currStack, _ = getStackAndPtr() + preStack = ptr2StackString(preStackPtr) + ) + fmt.Printf(` +------------------------------------------- +[mempool trace] %v -> + +previous stack: +%v + +------------------------------------------- + +current stack : +%v +------------------------------------------- + +`, info, preStack, currStack) + // os.Exit(-1) +} + +//go:norace +func bytesPointer(pbuf *[]byte) uintptr { + return (uintptr)(unsafe.Pointer(&((*pbuf)[:1][0]))) +} + +// func stringPointer(s *string) uintptr { +// ptr := (*uintptr)(unsafe.Pointer(s)) +// return (uintptr)(unsafe.Pointer(&ptr)) +// } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/body.go b/vendor/github.com/lesismal/nbio/nbhttp/body.go index b200824a..7fd08cc6 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/body.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/body.go @@ -7,93 +7,196 @@ package nbhttp import ( "io" "sync" - - "github.com/lesismal/nbio/mempool" ) var ( - bodyReaderPool = sync.Pool{ + emptyBodyReader = BodyReader{} + bodyReaderPool = sync.Pool{ New: func() interface{} { return &BodyReader{} }, } ) -// BodyReader . +// BodyReader implements io.ReadCloser and is to be used as HTTP body. type BodyReader struct { - index int - buffer []byte + index int // first buffer read index + left int // num of byte left + buffers []*[]byte // buffers that storage HTTP body + engine *Engine // allocator that manages buffers + closed bool } -// Read implements io.Reader. +// Read reads body bytes to p, returns the num of bytes read and error. +// +//go:norace func (br *BodyReader) Read(p []byte) (int, error) { + // Close() may run while a handler (e.g. json.Decoder) is still reading. + if br.closed { + return 0, io.EOF + } need := len(p) - available := len(br.buffer) - br.index - if available <= 0 { + if br.left <= 0 { return 0, io.EOF } - if available >= need { - copy(p, br.buffer[br.index:br.index+need]) - br.index += need - // if available == need { - // br.Close() - // } - return need, nil + ncopy := 0 + for ncopy < need && br.left > 0 { + if len(br.buffers) == 0 { + // left tracks bytes not yet copied out; buffers holds the backing + // slices. When they diverge (pool reuse, Close during Read, or + // zero-length head buffers), indexing buffers[0] panics. + br.left = 0 + if ncopy > 0 { + return ncopy, nil + } + return 0, io.EOF + } + pbuf := br.buffers[0] + if br.index >= len(*pbuf) { + // Drop exhausted head buffers without decrementing left; those + // bytes were already accounted for when the buffer was consumed. + br.engine.BodyAllocator.Free(pbuf) + br.buffers[0] = nil + br.buffers = br.buffers[1:] + br.index = 0 + continue + } + nc := copy(p[ncopy:], (*pbuf)[br.index:]) + if nc+br.index >= len(*pbuf) { + br.engine.BodyAllocator.Free(pbuf) + br.buffers[0] = nil + br.buffers = br.buffers[1:] + br.index = 0 + } else { + br.index += nc + } + ncopy += nc + br.left -= nc } - copy(p[:available], br.buffer[br.index:]) - br.index += available - return available, io.EOF + return ncopy, nil } -// Append . -func (br *BodyReader) Append(data []byte) { - if len(data) > 0 { - if br.buffer == nil { - br.buffer = mempool.Malloc(len(data)) - copy(br.buffer, data) - } else { - br.buffer = append(br.buffer, data...) +// Close frees buffers and resets itself to empty value. +// +//go:norace +func (br *BodyReader) Close() error { + if br.closed { + return nil + } + br.closed = true + if br.buffers != nil { + for _, b := range br.buffers { + br.engine.BodyAllocator.Free(b) } } + // Previously only freed backing memory; left/index/buffers were left + // stale, so a concurrent or subsequent Read could panic on buffers[0]. + br.buffers = nil + br.left = 0 + br.index = 0 + // *br = emptyBodyReader + // bodyReaderPool.Put(br) + return nil } -// RawBody returns BodyReader's buffer directly, -// the buffer returned would be released to the mempool after http handler func, -// the application layer should not hold it any longer after the http handler func. -func (br *BodyReader) RawBody() []byte { - return br.buffer +// Index returns current head buffer's reading index. +// +//go:norace +func (br *BodyReader) Index() int { + return br.index } -// TakeOver returns BodyReader's buffer, -// the buffer returned would not be released to the mempool after http handler func, -// the application layer could hold it longer and should manage when to release the buffer to the mempool. -func (br *BodyReader) TakeOver() []byte { - b := br.buffer - br.buffer = nil - br.index = 0 - return b +// Left returns how many bytes are left for reading. +// +//go:norace +func (br *BodyReader) Left() int { + return br.left } -// Close implements io. Closer. -func (br *BodyReader) Close() error { - return nil +// Buffers returns the underlayer buffers that store the HTTP Body. +// +//go:norace +func (br *BodyReader) Buffers() []*[]byte { + return br.buffers } -func (br *BodyReader) close() { - if br.buffer != nil { - mempool.Free(br.buffer) - br.buffer = nil - br.index = 0 +// RawBodyBuffers returns a reference of BodyReader's current buffers. +// The buffers returned will be closed(released automatically when closed) +// HTTP Handler is called, users should not free the buffers and should +// not hold it any longer after the HTTP Handler is called. +// +//go:norace +func (br *BodyReader) RawBodyBuffers() [][]byte { + buffers := make([][]byte, len(br.buffers)) + for i, pbuf := range br.buffers { + if i == 0 { + buffers[i] = (*pbuf)[br.index:] + } else { + buffers[i] = *pbuf + } } + return buffers +} + +// Engine returns Engine that creates this HTTP Body. +// +//go:norace +func (br *BodyReader) Engine() *Engine { + return br.engine +} + +// append appends data to buffers. +// +//go:norace +func (br *BodyReader) append(data []byte) error { + if len(data) == 0 { + return nil + } + + if br.engine.MaxHTTPBodySize > 0 && len(data)+br.left > br.engine.MaxHTTPBodySize { + return ErrTooLong + } + + br.left += (len(data)) + if len(br.buffers) == 0 { + pbuf := br.engine.BodyAllocator.Malloc(len(data)) + copy(*pbuf, data) + br.buffers = append(br.buffers, pbuf) + } else { + i := len(br.buffers) - 1 + pbuf := br.buffers[i] + l := len(*pbuf) + bLeft := cap(*pbuf) - len(*pbuf) + if bLeft > 0 { + if bLeft > len(data) { + *pbuf = (*pbuf)[:l+len(data)] + } else { + *pbuf = (*pbuf)[:cap(*pbuf)] + } + nc := copy((*pbuf)[l:], data) + data = data[nc:] + br.buffers[i] = pbuf + } + if len(data) > 0 { + pbuf = br.engine.BodyAllocator.Malloc(len(data)) + copy(*pbuf, data) + br.buffers = append(br.buffers, pbuf) + } + } + return nil } // NewBodyReader creates a BodyReader. -func NewBodyReader(data []byte) *BodyReader { +// +//go:norace +func NewBodyReader(engine *Engine) *BodyReader { br := bodyReaderPool.Get().(*BodyReader) - if len(data) > 0 { - br.buffer = mempool.Malloc(len(data)) - copy(br.buffer, data) - } + // Reset all fields on pool Get so stale left/buffers + // from a previous request don't leak through. br.index = 0 + br.left = 0 + br.buffers = nil + br.closed = false + br.engine = engine return br } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/body_test.go b/vendor/github.com/lesismal/nbio/nbhttp/body_test.go new file mode 100644 index 00000000..3744d714 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/nbhttp/body_test.go @@ -0,0 +1,136 @@ +package nbhttp + +import ( + "bytes" + "crypto/rand" + "io" + "testing" + + "github.com/lesismal/nbio/mempool" +) + +func TestBodyReaderPool(t *testing.T) { + br := bodyReaderPool.Get().(*BodyReader) + buf := make([]byte, 10) + pbuf := &buf + br.buffers = append(br.buffers, pbuf) + *br = emptyBodyReader + bodyReaderPool.Put(br) + + for i := 0; i < 1000; i++ { + br2 := bodyReaderPool.Get().(*BodyReader) + if br2.buffers != nil { + t.Fatal("len>0") + } + buf = make([]byte, 10) + pbuf = &buf + br2.buffers = append(br2.buffers, pbuf) + *br2 = emptyBodyReader + bodyReaderPool.Put(br2) + } +} + +func TestBodyReader(t *testing.T) { + engine := NewEngine(Config{ + BodyAllocator: mempool.NewAligned(), + }) + var ( + b0 []byte + b1 = make([]byte, 2049) + b2 = make([]byte, 1132) + b3 = make([]byte, 11111) + ) + _, _ = rand.Read(b1) + _, _ = rand.Read(b2) + _, _ = rand.Read(b3) + + allBytes := append(b0, b1...) + allBytes = append(allBytes, b2...) + allBytes = append(allBytes, b3...) + + newBR := func() *BodyReader { + br := NewBodyReader(engine) + _ = br.append(b1) + _ = br.append(b2) + _ = br.append(b3) + return br + } + + br1 := newBR() + body1, err := io.ReadAll(br1) + if err != nil { + t.Fatalf("io.ReadAll(br1) failed: %v", err) + } + if !bytes.Equal(allBytes, body1) { + t.Fatalf("!bytes.Equal(allBytes, body1)") + } + _ = br1.Close() + + br2 := newBR() + body2 := make([]byte, len(allBytes)) + for i := range body2 { + _, err := br2.Read(body2[i : i+1]) + if err != nil { + t.Fatalf("br2.Readbody2[%d:%d] failed: %v", i, i+1, err) + } + } + if !bytes.Equal(allBytes, body2) { + t.Fatalf("!bytes.Equal(allBytes, body2)") + } + _ = br2.Close() +} + +func TestBodyReaderReadAfterClose(t *testing.T) { + engine := NewEngine(Config{ + BodyAllocator: mempool.NewAligned(), + }) + br := NewBodyReader(engine) + data := []byte("hello") + if err := br.append(data); err != nil { + t.Fatalf("append failed: %v", err) + } + if err := br.Close(); err != nil { + t.Fatalf("close failed: %v", err) + } + buf := make([]byte, 8) + n, err := br.Read(buf) + if n != 0 || err != io.EOF { + t.Fatalf("Read after Close = (%d, %v), want (0, EOF)", n, err) + } +} + +func TestBodyReaderLeftWithoutBuffers(t *testing.T) { + engine := NewEngine(Config{ + BodyAllocator: mempool.NewAligned(), + }) + br := NewBodyReader(engine) + br.left = 10 + br.buffers = nil + buf := make([]byte, 8) + n, err := br.Read(buf) + if n != 0 || err != io.EOF { + t.Fatalf("Read with left>0 and no buffers = (%d, %v), want (0, EOF)", n, err) + } + if br.left != 0 { + t.Fatalf("left = %d, want 0", br.left) + } +} + +func TestBodyReaderPooledReuse(t *testing.T) { + engine := NewEngine(Config{ + BodyAllocator: mempool.NewAligned(), + }) + br := NewBodyReader(engine) + if err := br.append([]byte("x")); err != nil { + t.Fatalf("append failed: %v", err) + } + _ = br.Close() + *br = emptyBodyReader + bodyReaderPool.Put(br) + + br2 := NewBodyReader(engine) + if br2.left != 0 || br2.index != 0 || br2.buffers != nil || br2.closed { + t.Fatalf("pooled BodyReader not reset: left=%d index=%d buffers=%v closed=%v", + br2.left, br2.index, br2.buffers, br2.closed) + } +} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/client.go b/vendor/github.com/lesismal/nbio/nbhttp/client.go index 6398f5f8..1ff8b4b3 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/client.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/client.go @@ -17,6 +17,7 @@ import ( "github.com/lesismal/llib/std/crypto/tls" ) +//go:norace func newHostConns(cli *Client) *hostConns { hcs := &hostConns{ cli: cli, @@ -40,6 +41,7 @@ type hostConns struct { chConnss chan *ClientConn } +//go:norace func (hcs *hostConns) closeWithError(err error) { hcs.mux.Lock() for hc := range hcs.conns { @@ -48,13 +50,14 @@ func (hcs *hostConns) closeWithError(err error) { hcs.mux.Unlock() } +//go:norace func (hcs *hostConns) getConn() (*hostConns, *ClientConn, error) { c := hcs.cli if !c.closed { timer := time.NewTimer(c.Timeout) defer timer.Stop() - // 1. fast get an existed free connection + // 1. Get an existing free connection. select { case hc, ok := <-hcs.chConnss: if !ok { @@ -66,7 +69,8 @@ func (hcs *hostConns) getConn() (*hostConns, *ClientConn, error) { default: } - // 2. try to create a new connection if the num of existed connections is smaller than maxConnNum + // 2. Try to create a new connection if the num of existing + // connections is smaller than maxConnNum if atomic.AddInt32(&hcs.connNum, 1) <= hcs.maxConnNum { hc := &ClientConn{ Engine: c.Engine, @@ -74,6 +78,7 @@ func (hcs *hostConns) getConn() (*hostConns, *ClientConn, error) { Timeout: c.Timeout, IdleConnTimeout: c.IdleConnTimeout, TLSClientConfig: c.TLSClientConfig, + Dial: c.Dial, Proxy: c.Proxy, CheckRedirect: c.CheckRedirect, } @@ -84,7 +89,7 @@ func (hcs *hostConns) getConn() (*hostConns, *ClientConn, error) { } atomic.AddInt32(&hcs.connNum, -1) - // 3. wait for an old connection + // 3. Wait for an existed working connection to be free. select { case hc, ok := <-hcs.chConnss: if !ok { @@ -99,11 +104,12 @@ func (hcs *hostConns) getConn() (*hostConns, *ClientConn, error) { return nil, nil, ErrClientClosed } +//go:norace func (hcs *hostConns) releaseConn(hc *ClientConn) { hcs.chConnss <- hc } -// Client . +// Client implements the similar functions with std http.Client. type Client struct { mux sync.Mutex closed bool @@ -122,17 +128,23 @@ type Client struct { TLSClientConfig *tls.Config + Dial func(network, addr string) (net.Conn, error) + Proxy func(*http.Request) (*url.URL, error) CheckRedirect func(req *http.Request, via []*http.Request) error } -// Close . +// Close closes all underlayer connections with EOF. +// +//go:norace func (c *Client) Close() { c.CloseWithError(io.EOF) } -// CloseWithError . +// CloseWithError closes all underlayer connections with error. +// +//go:norace func (c *Client) CloseWithError(err error) { c.mux.Lock() if !c.closed { @@ -144,6 +156,7 @@ func (c *Client) CloseWithError(err error) { c.mux.Unlock() } +//go:norace func (c *Client) getConn(host string) (*hostConns, *ClientConn, error) { c.connsMux.Lock() if c.closed { @@ -164,7 +177,14 @@ func (c *Client) getConn(host string) (*hostConns, *ClientConn, error) { return hcs.getConn() } -// Do . +// Do sends an HTTP request and returns an HTTP response. +// Notice: +// 1. It's blocking when Dial to the server; +// 2. It's non-blocking for waiting for the response; +// 3. It calls the handler when the response is received +// or other errors occur, such as timeout. +// +//go:norace func (c *Client) Do(req *http.Request, handler func(res *http.Response, conn net.Conn, err error)) { c.Engine.ExecuteClient(func() { host := req.URL.Host @@ -183,12 +203,14 @@ func (c *Client) Do(req *http.Request, handler func(res *http.Response, conn net type netDialerFunc func(network, addr string) (net.Conn, error) +//go:norace func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { return fn(network, addr) } var proxySchemes map[string]func(*url.URL, proxyDialer) (proxyDialer, error) +//go:norace func proxyRegisterDialerType(scheme string, f func(*url.URL, proxyDialer) (proxyDialer, error)) { if proxySchemes == nil { proxySchemes = make(map[string]func(*url.URL, proxyDialer) (proxyDialer, error)) @@ -196,6 +218,7 @@ func proxyRegisterDialerType(scheme string, f func(*url.URL, proxyDialer) (proxy proxySchemes[scheme] = f } +//go:norace func proxyFromURL(u *url.URL, forward proxyDialer) (proxyDialer, error) { var auth *proxyAuth if u.User != nil { @@ -220,6 +243,7 @@ func proxyFromURL(u *url.URL, forward proxyDialer) (proxyDialer, error) { return nil, errors.New("proxy: unknown scheme: " + u.Scheme) } +//go:norace func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { hostPort = u.Host hostNoPort = u.Host @@ -247,6 +271,7 @@ type httpProxyDialer struct { forwardDial func(network, addr string) (net.Conn, error) } +//go:norace func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { hostPort, _ := hostPortNoPort(hpd.proxyURL) conn, err := hpd.forwardDial(network, hostPort) @@ -271,20 +296,20 @@ func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) } if errWrite := connectReq.Write(conn); errWrite != nil { - conn.Close() + _ = conn.Close() return nil, errWrite } br := bufio.NewReader(conn) resp, err := http.ReadResponse(br, connectReq) if err != nil { - conn.Close() + _ = conn.Close() return nil, err } - resp.Body.Close() + _ = resp.Body.Close() if resp.StatusCode != 200 { - conn.Close() + _ = conn.Close() f := strings.SplitN(resp.Status, " ", 2) return nil, errors.New(f[1]) } @@ -295,6 +320,7 @@ type proxyAuth struct { User, Password string } +//go:norace func proxySOCKS5(network, addr string, auth *proxyAuth, forward proxyDialer) (proxyDialer, error) { s := &proxySocks5{ network: network, @@ -342,6 +368,7 @@ var proxySocks5Errors = []string{ "address type not supported", } +//go:norace func (s *proxySocks5) Dial(network, addr string) (net.Conn, error) { switch network { case "tcp", "tcp6", "tcp4": @@ -354,12 +381,15 @@ func (s *proxySocks5) Dial(network, addr string) (net.Conn, error) { return nil, err } if err := s.connect(conn, addr); err != nil { - conn.Close() + _ = conn.Close() return nil, err } return conn, nil } +const errProxyAtSocks5Prefix = "proxy: SOCKS5 proxy at " + +//go:norace func (s *proxySocks5) connect(conn net.Conn, target string) error { host, portStr, err := net.SplitHostPort(target) if err != nil { @@ -392,10 +422,10 @@ func (s *proxySocks5) connect(conn net.Conn, target string) error { return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) } if buf[0] != 5 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + return errors.New(errProxyAtSocks5Prefix + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) } if buf[1] == 0xff { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + return errors.New(errProxyAtSocks5Prefix + s.addr + " requires authentication") } // See RFC 1929 @@ -416,7 +446,7 @@ func (s *proxySocks5) connect(conn net.Conn, target string) error { } if buf[1] != 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + return errors.New(errProxyAtSocks5Prefix + s.addr + " rejected username/password") } } @@ -455,7 +485,7 @@ func (s *proxySocks5) connect(conn net.Conn, target string) error { } if len(failure) > 0 { - return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + return errors.New(errProxyAtSocks5Prefix + s.addr + " failed to connect: " + failure) } var bytesToDiscard int @@ -490,6 +520,7 @@ func (s *proxySocks5) connect(conn net.Conn, target string) error { return nil } +//go:norace func init() { proxyRegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxyDialer) (proxyDialer, error) { return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil diff --git a/vendor/github.com/lesismal/nbio/nbhttp/client_conn.go b/vendor/github.com/lesismal/nbio/nbhttp/client_conn.go index d0766b15..98f316f8 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/client_conn.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/client_conn.go @@ -43,12 +43,16 @@ type ClientConn struct { TLSClientConfig *tls.Config + Dial func(network, addr string) (net.Conn, error) + Proxy func(*http.Request) (*url.URL, error) CheckRedirect func(req *http.Request, via []*http.Request) error } -// Reset . +// Reset resets itself as new created. +// +//go:norace func (c *ClientConn) Reset() { c.mux.Lock() if c.closed { @@ -59,27 +63,23 @@ func (c *ClientConn) Reset() { c.mux.Unlock() } -// OnClose . +// OnClose registers a callback for closing. +// +//go:norace func (c *ClientConn) OnClose(h func()) { - if h == nil { - return - } - - pre := c.onClose - c.onClose = func() { - if pre != nil { - pre() - } - h() - } + c.onClose = h } -// Close . +// Close closes underlayer connection with EOF. +// +//go:norace func (c *ClientConn) Close() { c.CloseWithError(io.EOF) } -// CloseWithError . +// CloseWithError closes underlayer connection with error. +// +//go:norace func (c *ClientConn) CloseWithError(err error) { c.mux.Lock() defer c.mux.Unlock() @@ -89,6 +89,7 @@ func (c *ClientConn) CloseWithError(err error) { } } +//go:norace func (c *ClientConn) closeWithErrorWithoutLock(err error) { if err == nil { err = io.EOF @@ -98,7 +99,19 @@ func (c *ClientConn) closeWithErrorWithoutLock(err error) { } c.handlers = nil if c.conn != nil { - c.conn.Close() + nbc, ok := c.conn.(*nbio.Conn) + if !ok { + if tlsConn, ok2 := c.conn.(*tls.Conn); ok2 { + nbc, ok = tlsConn.Conn().(*nbio.Conn) + } + } + if ok { + key, _ := conn2Array(nbc) + c.Engine.mux.Lock() + delete(c.Engine.dialerConns, key) + c.Engine.mux.Unlock() + } + _ = c.conn.Close() c.conn = nil } if c.onClose != nil { @@ -106,6 +119,7 @@ func (c *ClientConn) closeWithErrorWithoutLock(err error) { } } +//go:norace func (c *ClientConn) onResponse(res *http.Response, err error) { c.mux.Lock() defer c.mux.Unlock() @@ -124,13 +138,13 @@ func (c *ClientConn) onResponse(res *http.Response, err error) { c.closeWithErrorWithoutLock(ErrClientTimeout) } } else { - c.conn.SetReadDeadline(deadline) + _ = c.conn.SetReadDeadline(deadline) } } else { if c.IdleConnTimeout > 0 { - c.conn.SetReadDeadline(time.Now().Add(c.IdleConnTimeout)) + _ = c.conn.SetReadDeadline(time.Now().Add(c.IdleConnTimeout)) } else { - c.conn.SetReadDeadline(time.Time{}) + _ = c.conn.SetReadDeadline(time.Time{}) } } if len(c.handlers) == 0 { @@ -139,7 +153,14 @@ func (c *ClientConn) onResponse(res *http.Response, err error) { } } -// Do . +// Do sends an HTTP request and returns an HTTP response. +// Notice: +// 1. It's blocking when Dial to the server; +// 2. It's non-blocking for waiting for the response; +// 3. It calls the handler when the response is received +// or other errors occur, such as timeout. +// +//go:norace func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn net.Conn, err error)) { c.mux.Lock() defer func() { @@ -168,6 +189,9 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn } sendRequest := func() { + if c.Engine.WriteTimeout > 0 { + _ = c.conn.SetWriteDeadline(time.Now().Add(c.Engine.WriteTimeout)) + } err := req.Write(c.conn) if err != nil { c.closeWithErrorWithoutLock(err) @@ -177,7 +201,7 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn if c.conn != nil { if confTimeout > 0 && len(c.handlers) == 1 { - c.conn.SetReadDeadline(deadline) + _ = c.conn.SetReadDeadline(deadline) } sendRequest() } else { @@ -192,22 +216,38 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn strs := strings.Split(req.URL.Host, ":") host := strs[0] - port := req.URL.Scheme + port := "80" if len(strs) >= 2 { port = strs[1] + } else { + switch req.URL.Scheme { + case "http": + port = "80" + case "https": + port = "443" + } } addr := host + ":" + port + var dialer = c.Dial var netDial netDialerFunc if confTimeout <= 0 { + if dialer == nil { + dialer = net.Dial + } netDial = func(network, addr string) (net.Conn, error) { - return net.Dial(network, addr) + return dialer(network, addr) } } else { + if dialer == nil { + dialer = func(network, addr string) (net.Conn, error) { + return net.DialTimeout(network, addr, timeout) + } + } netDial = func(network, addr string) (net.Conn, error) { - conn, err := net.DialTimeout(network, addr, timeout) + conn, err := dialer(network, addr) if err == nil { - conn.SetReadDeadline(deadline) + _ = conn.SetReadDeadline(deadline) } return conn, err } @@ -244,18 +284,21 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn return } + key, _ := conn2Array(nbc) + engine.mux.Lock() + engine.dialerConns[key] = struct{}{} + engine.mux.Unlock() + c.conn = nbc processor := NewClientProcessor(c, c.onResponse) - parser := NewParser(processor, true, engine.ReadLimit, nbc.Execute) - parser.Conn = nbc - parser.Engine = engine + parser := NewParser(nbc, engine, processor, true, nbc.Execute) parser.OnClose(func(p *Parser, err error) { c.CloseWithError(err) }) nbc.SetSession(parser) nbc.OnData(engine.DataHandler) - engine.AddConn(nbc) + _, _ = engine.AddConn(nbc) case "https": tlsConfig := c.TLSClientConfig if tlsConfig == nil { @@ -263,7 +306,9 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn } else { tlsConfig = tlsConfig.Clone() } - tlsConfig.ServerName = req.URL.Host + if tlsConfig.ServerName == "" { + tlsConfig.ServerName = host + } tlsConn := tls.NewConn(netConn, tlsConfig, true, false, mempool.DefaultMemPool) err = tlsConn.Handshake() if err != nil { @@ -283,13 +328,24 @@ func (c *ClientConn) Do(req *http.Request, handler func(res *http.Response, conn return } + key, err := conn2Array(nbc) + if err != nil { + logging.Error("add dialer conn failed: %v", err) + c.closeWithErrorWithoutLock(err) + return + } + engine.mux.Lock() + engine.dialerConns[key] = struct{}{} + engine.mux.Unlock() + isNonblock := true tlsConn.ResetConn(nbc, isNonblock) - c.conn = tlsConn + nbhttpConn := &Conn{Conn: tlsConn} + c.conn = nbhttpConn processor := NewClientProcessor(c, c.onResponse) - parser := NewParser(processor, true, engine.ReadLimit, nbc.Execute) - parser.Conn = tlsConn + parser := NewParser(nbhttpConn, engine, processor, true, nbc.Execute) + parser.Conn = nbhttpConn parser.Engine = engine parser.OnClose(func(p *Parser, err error) { c.CloseWithError(err) diff --git a/vendor/github.com/lesismal/nbio/nbhttp/convert.go b/vendor/github.com/lesismal/nbio/nbhttp/convert.go new file mode 100644 index 00000000..1a89c7fe --- /dev/null +++ b/vendor/github.com/lesismal/nbio/nbhttp/convert.go @@ -0,0 +1,102 @@ +package nbhttp + +import ( + "crypto/tls" + "encoding/binary" + "fmt" + "net" + "unsafe" + + ltls "github.com/lesismal/llib/std/crypto/tls" + "github.com/lesismal/nbio" +) + +const ( + uintptrSize = int(unsafe.Sizeof(uintptr(0))) + connValueSize = uintptrSize + 1 +) + +const ( + connTypNONE byte = 0 + connTypNBIO byte = 1 + connTypTCP byte = 2 + connTypUNIX byte = 3 + connTypTLS byte = 4 + connTypLTLS byte = 5 +) + +// We can use this array-value as map key to reduce gc cost. +// Ref: https://github.com/lesismal/nbio/pull/304#issuecomment-1583880587 +type connValue [connValueSize]byte + +// Convert net.Conn to array value. +// +//go:norace +func conn2Array(conn net.Conn) (connValue, error) { + var p uintptr + var b connValue + switch vt := conn.(type) { + case *nbio.Conn: + p = uintptr(unsafe.Pointer(vt)) + b[uintptrSize] = connTypNBIO + case *net.TCPConn: + p = uintptr(unsafe.Pointer(vt)) + b[uintptrSize] = connTypTCP + case *net.UnixConn: + p = uintptr(unsafe.Pointer(vt)) + b[uintptrSize] = connTypUNIX + case *tls.Conn: + p = uintptr(unsafe.Pointer(vt)) + b[uintptrSize] = connTypTLS + case *ltls.Conn: + p = uintptr(unsafe.Pointer(vt)) + b[uintptrSize] = connTypLTLS + default: + return b, fmt.Errorf("invalid conn type: %v", vt) + } + switch uintptrSize { + case 4: + binary.LittleEndian.PutUint32(b[:uintptrSize], uint32(p)) + case 8: + binary.LittleEndian.PutUint64(b[:uintptrSize], uint64(p)) + default: + return b, fmt.Errorf("unsupported platform: invalid uintptr size %v", uintptrSize) + } + return b, nil +} + +// Convert array value to net.Conn. +// +//go:norace +func array2Conn(b connValue) (net.Conn, error) { + var p uintptr + switch uintptrSize { + case 4: + p = uintptr(binary.LittleEndian.Uint32(b[:uintptrSize])) + case 8: + p = uintptr(binary.LittleEndian.Uint64(b[:uintptrSize])) + default: + return nil, fmt.Errorf("unsupported platform: invalid uintptr size %v", uintptrSize) + } + + switch b[uintptrSize] { + case connTypNBIO: + conn := *((**nbio.Conn)(unsafe.Pointer(&p))) + return conn, nil + case connTypTCP: + conn := *((**net.TCPConn)(unsafe.Pointer(&p))) + return conn, nil + case connTypUNIX: + conn := *((**net.UnixConn)(unsafe.Pointer(&p))) + return conn, nil + case connTypTLS: + conn := *((**tls.Conn)(unsafe.Pointer(&p))) + return conn, nil + case connTypLTLS: + conn := *((**ltls.Conn)(unsafe.Pointer(&p))) + return conn, nil + default: + } + + return nil, fmt.Errorf("invalid conn type: %v", b[uintptrSize]) +} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/convert_test.go b/vendor/github.com/lesismal/nbio/nbhttp/convert_test.go new file mode 100644 index 00000000..3c903ec5 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/nbhttp/convert_test.go @@ -0,0 +1,91 @@ +package nbhttp + +import ( + "crypto/tls" + "net" + "testing" + + ltls "github.com/lesismal/llib/std/crypto/tls" + "github.com/lesismal/nbio" +) + +func TestConn2String(t *testing.T) { + var nbc = &nbio.Conn{} + snbc, err := conn2Array(nbc) + if err != nil { + t.Fatal(err) + } + nbc2, err := array2Conn(snbc) + if err != nil { + t.Fatal(err) + } + if nbc2 != nbc { + t.Fatalf("nbc2 != nbc") + } + + var tcp = &net.TCPConn{} + stcp, err := conn2Array(tcp) + if err != nil { + t.Fatal(err) + } + tcp2, err := array2Conn(stcp) + if err != nil { + t.Fatal(err) + } + if tcp2 != tcp { + t.Fatalf("tcp2 != tcp") + } + + var unix = &net.UnixConn{} + sunix, err := conn2Array(unix) + if err != nil { + t.Fatal(err) + } + unix2, err := array2Conn(sunix) + if err != nil { + t.Fatal(err) + } + if unix2 != unix { + t.Fatalf("unix2 != unix") + } + + var tls = &tls.Conn{} + stls, err := conn2Array(tls) + if err != nil { + t.Fatal(err) + } + tls2, err := array2Conn(stls) + if err != nil { + t.Fatal(err) + } + if tls2 != tls { + t.Fatalf("tls2 != tls") + } + + var ltls = <ls.Conn{} + sltls, err := conn2Array(ltls) + if err != nil { + t.Fatal(err) + } + ltls2, err := array2Conn(sltls) + if err != nil { + t.Fatal(err) + } + if ltls2 != ltls { + t.Fatalf("ltls2 != ltls") + } + + var udp = &net.UDPConn{} + _, err = conn2Array(udp) + if err == nil { + t.Fatal("err is nil") + } + _, err = array2Conn(connValue{'a', 'a', 'a'}) + if err == nil { + t.Fatal("err is nil") + } + _, err = array2Conn(connValue{}) + if err == nil { + t.Fatal("err is nil") + } +} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/engine.go b/vendor/github.com/lesismal/nbio/nbhttp/engine.go index 95a98bac..0478b280 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/engine.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/engine.go @@ -7,7 +7,7 @@ package nbhttp import ( "context" "errors" - "math/rand" + "io" "net" "net/http" "runtime" @@ -18,38 +18,43 @@ import ( "github.com/lesismal/llib/std/crypto/tls" "github.com/lesismal/nbio" + "github.com/lesismal/nbio/lmux" "github.com/lesismal/nbio/logging" "github.com/lesismal/nbio/mempool" "github.com/lesismal/nbio/taskpool" ) -var ( +const ( + // IOModNonBlocking represents that the server serve all the connections by nbio poller goroutines to handle io events. + IOModNonBlocking = 0 + // IOModBlocking represents that the server serve each connection with one goroutine at least to handle reading. + IOModBlocking = 1 + // IOModMixed represents that the server creates listener mux to handle different connections, 1 listener will be dispatch to two ChanListener: + // If ChanListener A's online is less than its max online num, the new connection will be dispatch to this listener A and served by single goroutine; + // Else the new connection will be dispatch to ChanListener B and served by nbio poller. + IOModMixed = 2 + + // DefaultIOMod represents the default IO Mod used by nbhttp.Engine. + DefaultIOMod = IOModNonBlocking + // DefaultMaxBlockingOnline represents the default num of connections that will be dispatched to ChanListner A. + DefaultMaxBlockingOnline = 10000 +) + +const ( // DefaultMaxLoad . DefaultMaxLoad = 1024 * 1024 // DefaultHTTPReadLimit . DefaultHTTPReadLimit = 1024 * 1024 * 64 - // DefaultMinBufferSize . - DefaultMinBufferSize = 1024 * 2 - - // DefaultHTTPWriteBufferSize . - DefaultHTTPWriteBufferSize = 1024 * 2 - // DefaultMaxWebsocketFramePayloadSize . DefaultMaxWebsocketFramePayloadSize = 1024 * 32 - // DefaultMessageHandlerPoolSize . - // DefaultMessageHandlerPoolSize = runtime.NumCPU() * 256. - - // DefaultMessageHandlerTaskIdleTime . - DefaultMessageHandlerTaskIdleTime = time.Second * 60 - // DefaultKeepaliveTime . DefaultKeepaliveTime = time.Second * 120 - // DefaultTLSHandshakeTimeout . - DefaultTLSHandshakeTimeout = time.Second * 10 + // DefaultBlockingReadBufferSize sets to 4k. + DefaultBlockingReadBufferSize = 1024 * 4 ) const defaultNetwork = "tcp" @@ -58,7 +63,9 @@ const defaultNetwork = "tcp" type ConfAddr struct { Network string Addr string + NListener int TLSConfig *tls.Config + pAddr *string } // Config . @@ -75,7 +82,7 @@ type Config struct { TLSConfig *tls.Config // Addrs is the non-tls listening addr list for an Engine. - // if it is empty, no listener created, then the Gopher is used for client by default. + // if it is empty, no listener created, then the Engine is used for client by default. Addrs []string // AddrsTLS is the tls listening addr list for an Engine. @@ -88,26 +95,32 @@ type Config struct { // AddrConfigsTLS is the tls listening addr details list for an Engine. AddrConfigsTLS []ConfAddr + // Listen is used to create listener for Engine. + Listen func(network, addr string) (net.Listener, error) + + // ListenUDP is used to create udp listener for Engine. + ListenUDP func(network string, laddr *net.UDPAddr) (*net.UDPConn, error) + // MaxLoad represents the max online num, it's set to 10k by default. MaxLoad int - // NPoller represents poller goroutine num, it's set to runtime.NumCPU() by default. - NPoller int - - // NListener represents poller goroutine num, it's set to runtime.NumCPU() by default. + // NListener represents listner goroutine num for each ConfAddr, it's set to 1 by default. NListener int - // NParser represents parser goroutine num, it's set to NPoller by default. - NParser int + // NPoller represents poller goroutine num. + NPoller int // ReadLimit represents the max size for parser reading, it's set to 64M by default. ReadLimit int - // ReadBufferSize represents buffer size for reading, it's set to 32k by default. + // MaxHTTPBodySize represents the max size of HTTP body for parser reading. + MaxHTTPBodySize int + + // ReadBufferSize represents buffer size for reading, it's set to 64k by default. ReadBufferSize int - // MaxWriteBufferSize represents max write buffer size for Conn, it's set to 1m by default. - // if the connection's Send-Q is full and the data cached by nbio is + // MaxWriteBufferSize represents max write buffer size for Conn, 0 by default, represents no limit for writeBuffer + // if MaxWriteBufferSize is set greater than to 0, and the connection's Send-Q is full and the data cached by nbio is // more than MaxWriteBufferSize, the connection would be closed by nbio. MaxWriteBufferSize int @@ -120,6 +133,9 @@ type Config struct { // MessageHandlerTaskIdleTime represents idle time for task pool's goroutine, it's set to 60s by default. // MessageHandlerTaskIdleTime time.Duration + // WriteTimeout represents Conn's write time out when response to a HTTP request. + WriteTimeout time.Duration + // KeepaliveTime represents Conn's ReadDeadline when waiting for a new request, it's set to 120s by default. KeepaliveTime time.Duration @@ -132,96 +148,222 @@ type Config struct { // DisableSendfile . DisableSendfile bool - // ReleaseWebsocketPayload automatically release data buffer after function each call to websocket OnMessage and OnDataFrame + // ReleaseWebsocketPayload automatically release data buffer after function each call to websocket OnMessage or OnDataFrame. ReleaseWebsocketPayload bool - // MaxReadTimesPerEventLoop represents max read times in one poller loop for one fd - MaxReadTimesPerEventLoop int + // RetainHTTPBody represents whether to automatically release HTTP body's buffer after calling HTTP handler. + RetainHTTPBody bool + + // MaxConnReadTimesPerEventLoop represents max read times in one poller loop for one fd. + MaxConnReadTimesPerEventLoop int + // OnAcceptError is called when accept error. + OnAcceptError func(err error) + + // Handler sets HTTP handler for Engine. Handler http.Handler + // `OnRequest` sets HTTP handler which will be called before `Handler.ServeHTTP`. + // + // A `Request` is pushed into a task queue and waits to be executed by the goroutine pool by default, which means the `Request` + // may not be executed at once and may wait for long to be executed: if the client-side supports `pipeline` and the previous + // `Requests` are handled for long. In some scenarios, we need to know when the `Request` is received, then we can control and + // customize whether we should drop the task or record the real processing time for the `Request`. That is what this func should + // be used for. + OnRequest http.HandlerFunc + + // ServerExecutor sets the executor for data reading callbacks. ServerExecutor func(f func()) + // ClientExecutor sets the executor for client callbacks. ClientExecutor func(f func()) + // TLSAllocator sets the buffer allocator for TLS. TLSAllocator tls.Allocator + // BodyAllocator sets the buffer allocator for HTTP. BodyAllocator mempool.Allocator + // Context sets common context for Engine. Context context.Context - Cancel func() + // Cancel sets the cancel func for common context. + Cancel func() + + // SupportServerOnly . SupportServerOnly bool + + // IOMod represents io mod, it is set to IOModNonBlocking by default. + IOMod int + // MaxBlockingOnline represents max blocking conn's online num. + MaxBlockingOnline int + // BlockingReadBufferSize represents read buffer size of blocking mod. + BlockingReadBufferSize int + + // EpollMod . + EpollMod uint32 + // EPOLLONESHOT . + EPOLLONESHOT uint32 + + // ReadBufferPool . + ReadBufferPool mempool.Allocator + + // Deprecated. + // WebsocketCompressor . + WebsocketCompressor func(w io.WriteCloser, level int) io.WriteCloser + + // Deprecated. + // WebsocketDecompressor . + WebsocketDecompressor func(r io.Reader) io.ReadCloser + + // AsyncReadInPoller represents how the reading events and reading are handled + // by epoll goroutine: + // true : epoll goroutine handles the reading events only, another goroutine + // pool will handles the reading. + // false: epoll goroutine handles both the reading events and the reading. + // false is by defalt. + AsyncReadInPoller bool + // IOExecute is used to handle the aysnc reading, users can customize it. + IOExecute func(f func(*[]byte)) } // Engine . type Engine struct { - *nbio.Gopher - *Config + *nbio.Engine + Config - MaxLoad int - MaxWebsocketFramePayloadSize int - ReleaseWebsocketPayload bool - BodyAllocator mempool.Allocator - CheckUtf8 func(data []byte) bool + CheckUtf8 func(data []byte) bool - listeners []net.Listener + shutdown bool - _onOpen func(c *nbio.Conn) - _onClose func(c *nbio.Conn, err error) - _onStop func() + listenerMux *lmux.ListenerMux + listeners []net.Listener - mux sync.Mutex - conns map[*nbio.Conn]struct{} + _onAcceptError func(err error) + _onOpen func(c net.Conn) + _onClose func(c net.Conn, err error) + _onStop func() - tlsBuffers [][]byte - getTLSBuffer func(c *nbio.Conn) []byte + mux sync.Mutex + conns map[connValue]struct{} + dialerConns map[connValue]struct{} + + // tlsBuffers [][]byte + // getTLSBuffer func(c *nbio.Conn) []byte emptyRequest *http.Request BaseCtx context.Context Cancel func() + SyncCall func(f func()) ExecuteClient func(f func()) + + // isOneshot bool +} + +// OnAcceptError is called when accept error. +// +//go:norace +func (e *Engine) OnAcceptError(h func(err error)) { + e._onAcceptError = h + e.Engine.OnAcceptError(h) } // OnOpen registers callback for new connection. -func (e *Engine) OnOpen(h func(c *nbio.Conn)) { +// +//go:norace +func (e *Engine) OnOpen(h func(c net.Conn)) { e._onOpen = h } // OnClose registers callback for disconnected. -func (e *Engine) OnClose(h func(c *nbio.Conn, err error)) { +// +//go:norace +func (e *Engine) OnClose(h func(c net.Conn, err error)) { e._onClose = h } -// OnStop registers callback before Gopher is stopped. +// OnStop registers callback before Engine is stopped. +// +//go:norace func (e *Engine) OnStop(h func()) { e._onStop = h } // Online . +// +//go:norace func (e *Engine) Online() int { return len(e.conns) } -func (e *Engine) closeIdleConns(chCloseQueue chan *nbio.Conn) { +// DialerOnline . +// +//go:norace +func (e *Engine) DialerOnline() int { + return len(e.dialerConns) +} + +//go:norace +func (e *Engine) closeAllConns() { e.mux.Lock() defer e.mux.Unlock() - for c := range e.conns { - sess := c.Session() - if sess != nil { - if c.ExecuteLen() == 0 { - select { - case chCloseQueue <- c: - default: + for key := range e.conns { + if c, err := array2Conn(key); err == nil { + _ = c.Close() + } + } + for key := range e.dialerConns { + if c, err := array2Conn(key); err == nil { + _ = c.Close() + } + } +} + +type Conn struct { + net.Conn + Parser *Parser + Trasfered bool +} + +//go:norace +func (e *Engine) listen(ln net.Listener, tlsConfig *tls.Config, addConn func(*Conn, *tls.Config, func()), decrease func()) { + e.Add(1) + go func() { + defer func() { + // ln.Close() + e.Done() + }() + for !e.shutdown { + conn, err := ln.Accept() + if err == nil && !e.shutdown { + addConn(&Conn{Conn: conn}, tlsConfig, decrease) + } else { + var ne net.Error + if ok := errors.As(err, &ne); ok && ne.Timeout() { + logging.Error("Accept failed: timeout error, retrying...") + time.Sleep(time.Second / 20) + } else { + if !e.shutdown { + logging.Error("Accept failed: %v, exit...", err) + } + if e._onAcceptError != nil { + e._onAcceptError(err) + } } } } - } + }() } +//go:norace func (e *Engine) startListeners() error { - for _, conf := range e.AddrConfigsTLS { + if e.IOMod == IOModMixed { + e.listenerMux = lmux.New(e.MaxBlockingOnline) + } + + for i := range e.AddrConfigsTLS { + conf := &e.AddrConfigsTLS[i] if conf.Addr != "" { network := conf.Network if network == "" { @@ -230,51 +372,46 @@ func (e *Engine) startListeners() error { if network == "" { network = defaultNetwork } - ln, err := net.Listen(network, conf.Addr) - if err != nil { - for _, l := range e.listeners { - l.Close() + for j := 0; j < conf.NListener; j++ { + ln, err := e.Listen(network, conf.Addr) + if err != nil { + for _, l := range e.listeners { + _ = l.Close() + } + return err } - return err - } - e.listeners = append(e.listeners, ln) - - logging.Info("Serve TLS On: [%v]", conf.Addr) - e.WaitGroup.Add(1) + conf.Addr = ln.Addr().String() + if conf.pAddr != nil { + *conf.pAddr = conf.Addr + } + logging.Info("NBHTTP Engine[%v] Serve HTTPS On: [%v@%v]", e.Engine.Name, conf.Network, conf.Addr) - tlsConfig := conf.TLSConfig - if tlsConfig == nil { - tlsConfig = e.TLSConfig + tlsConfig := conf.TLSConfig if tlsConfig == nil { - tlsConfig = &tls.Config{} + tlsConfig = e.TLSConfig + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } } - } - go func() { - defer func() { - ln.Close() - e.WaitGroup.Done() - }() - for { - conn, err := ln.Accept() - if err == nil { - e.AddConnTLS(conn, tlsConfig) - } else { - var ne net.Error - if ok := errors.As(err, &ne); ok && ne.Temporary() { - logging.Error("Accept failed: temporary error, retrying...") - time.Sleep(time.Second / 20) - } else { - logging.Error("Accept failed: %v, exit...", err) - break - } - } + switch e.IOMod { + case IOModMixed: + lnA, lnB := e.listenerMux.Mux(ln) + e.listen(lnA, tlsConfig, e.AddConnTLSBlocking, lnA.Decrease) + e.listen(lnB, tlsConfig, e.AddConnTLSNonBlocking, func() {}) + case IOModBlocking: + e.listeners = append(e.listeners, ln) + e.listen(ln, tlsConfig, e.AddConnTLSBlocking, func() {}) + case IOModNonBlocking: + e.listeners = append(e.listeners, ln) + e.listen(ln, tlsConfig, e.AddConnTLSNonBlocking, func() {}) } - }() + } } } - for _, conf := range e.AddrConfigs { + for i := range e.AddrConfigs { + conf := &e.AddrConfigs[i] if conf.Addr != "" { network := conf.Network if network == "" { @@ -283,294 +420,602 @@ func (e *Engine) startListeners() error { if network == "" { network = defaultNetwork } - ln, err := net.Listen(network, conf.Addr) - if err != nil { - for _, l := range e.listeners { - l.Close() - } - return err - } - e.listeners = append(e.listeners, ln) - - logging.Info("Serve NonTLS On: [%v]", conf.Addr) - e.WaitGroup.Add(1) - go func() { - defer func() { - ln.Close() - e.WaitGroup.Done() - }() - for { - conn, err := ln.Accept() - if err == nil { - e.AddConnNonTLS(conn) - } else { - var ne net.Error - if ok := errors.As(err, &ne); ok && ne.Temporary() { - logging.Error("Accept failed: temporary error, retrying...") - time.Sleep(time.Second / 20) - } else { - logging.Error("Accept failed: %v, exit...", err) - break - } + for j := 0; j < conf.NListener; j++ { + ln, err := e.Listen(network, conf.Addr) + if err != nil { + for _, l := range e.listeners { + _ = l.Close() } + return err } - }() + conf.Addr = ln.Addr().String() + if conf.pAddr != nil { + *conf.pAddr = conf.Addr + } + + logging.Info("NBHTTP Engine[%v] Serve HTTP On: [%v@%v]", e.Engine.Name, conf.Network, conf.Addr) + + switch e.IOMod { + case IOModMixed: + lnA, lnB := e.listenerMux.Mux(ln) + e.listen(lnA, nil, e.AddConnNonTLSBlocking, lnA.Decrease) + e.listen(lnB, nil, e.AddConnNonTLSNonBlocking, func() {}) + case IOModBlocking: + e.listeners = append(e.listeners, ln) + e.listen(ln, nil, e.AddConnNonTLSBlocking, func() {}) + case IOModNonBlocking: + e.listeners = append(e.listeners, ln) + e.listen(ln, nil, e.AddConnNonTLSNonBlocking, func() {}) + } + } } } + e.listenerMux.Start() + return nil } +//go:norace func (e *Engine) stopListeners() { + if e.IOMod == IOModMixed && e.listenerMux != nil { + e.listenerMux.Stop() + } for _, ln := range e.listeners { - ln.Close() + _ = ln.Close() + } +} + +// SetETAsyncRead . +// +//go:norace +func (e *Engine) SetETAsyncRead() { + if e.NPoller <= 0 { + e.NPoller = 1 } + e.EpollMod = nbio.EPOLLET + e.AsyncReadInPoller = true + e.Engine.SetETAsyncRead() +} + +// SetLTSyncRead . +// +//go:norace +func (e *Engine) SetLTSyncRead() { + if e.NPoller <= 0 { + e.NPoller = runtime.NumCPU() / 4 + if e.NPoller == 0 { + e.NPoller = 1 + } + } + e.EpollMod = nbio.EPOLLLT + e.AsyncReadInPoller = false + e.Engine.SetLTSyncRead() } // Start . +// +//go:norace func (e *Engine) Start() error { - err := e.Gopher.Start() + modNames := map[int]string{ + IOModMixed: "IOModMixed", + IOModBlocking: "IOModBlocking", + IOModNonBlocking: "IOModNonBlocking", + } + + err := e.Engine.Start() if err != nil { return err } + + if e.IOMod == IOModMixed { + logging.Info("NBHTTP Engine[%v] Start with %q, MaxBlockingOnline: %v", e.Engine.Name, modNames[e.IOMod], e.MaxBlockingOnline) + } else { + logging.Info("NBHTTP Engine[%v] Start with %q", e.Engine.Name, modNames[e.IOMod]) + } + err = e.startListeners() if err != nil { - e.Gopher.Stop() + e.Engine.Stop() return err } return err } // Stop . +// +//go:norace func (e *Engine) Stop() { + e.shutdown = true + + if e.Cancel != nil { + e.Cancel() + } + e.stopListeners() - e.Gopher.Stop() + e.Engine.Stop() } // Shutdown . +// +//go:norace func (e *Engine) Shutdown(ctx context.Context) error { + e.shutdown = true e.stopListeners() - pollIntervalBase := time.Millisecond - shutdownPollIntervalMax := time.Millisecond * 200 - nextPollInterval := func() time.Duration { - interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10))) - pollIntervalBase *= 2 - if pollIntervalBase > shutdownPollIntervalMax { - pollIntervalBase = shutdownPollIntervalMax - } - return interval - } - if e.Cancel != nil { e.Cancel() } - chCloseQueue := make(chan *nbio.Conn, 1024) - defer close(chCloseQueue) - - go func() { - for c := range chCloseQueue { - c.Close() - } - }() - - timer := time.NewTimer(nextPollInterval()) - defer timer.Stop() + defer e.closeAllConns() + ticker := time.NewTicker(time.Millisecond * 200) + defer ticker.Stop() for { - e.closeIdleConns(chCloseQueue) + e.closeAllConns() select { case <-ctx.Done(): + logging.Info("NBIO[%v] shutdown timeout", e.Engine.Name) return ctx.Err() - case <-timer.C: - if len(e.conns) == 0 { + case <-ticker.C: + if len(e.conns)+len(e.dialerConns) == 0 { goto Exit } - timer.Reset(nextPollInterval()) } } Exit: - err := e.Shutdown(ctx) - logging.Info("Gopher[%v] shutdown", e.Gopher.Name) + err := e.Engine.Shutdown(ctx) + logging.Info("NBIO[%v] shutdown", e.Engine.Name) return err } -// InitTLSBuffers . -func (e *Engine) InitTLSBuffers() { - if e.tlsBuffers != nil { - return - } - e.tlsBuffers = make([][]byte, e.NParser) - for i := 0; i < e.NParser; i++ { - e.tlsBuffers[i] = make([]byte, e.ReadBufferSize) - } - - e.getTLSBuffer = func(c *nbio.Conn) []byte { - return e.tlsBuffers[uint64(c.Hash())%uint64(e.NParser)] - } - - if runtime.GOOS == "windows" { - bufferMux := sync.Mutex{} - buffers := map[*nbio.Conn][]byte{} - e.getTLSBuffer = func(c *nbio.Conn) []byte { - bufferMux.Lock() - defer bufferMux.Unlock() - buf, ok := buffers[c] - if !ok { - buf = make([]byte, 4096) - buffers[c] = buf - } - return buf - } - } -} - -// TLSBuffer . -func (e *Engine) TLSBuffer(c *nbio.Conn) []byte { - return e.getTLSBuffer(c) -} - // DataHandler . +// +//go:norace func (e *Engine) DataHandler(c *nbio.Conn, data []byte) { defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - logging.Error("execute parser failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + logging.Error("execute ParserCloser failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) } }() - parser := c.Session().(*Parser) - if parser == nil { - logging.Error("nil parser") + readerCloser := c.Session().(ParserCloser) + if readerCloser == nil { + logging.Error("nil ParserCloser") return } - err := parser.Read(data) + err := readerCloser.Parse(data) if err != nil { - logging.Debug("parser.Read failed: %v", err) - c.CloseWithError(err) + logging.Debug("ParserCloser.Read failed: %v", err) + _ = c.CloseWithError(err) } } // TLSDataHandler . +// +//go:norace func (e *Engine) TLSDataHandler(c *nbio.Conn, data []byte) { defer func() { if err := recover(); err != nil { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - logging.Error("execute parser failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + logging.Error("execute ParserCloser failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) } }() - - parser := c.Session().(*Parser) - if parser == nil { - logging.Error("nil parser") - c.Close() + parserCloser := c.Session().(ParserCloser) + if parserCloser == nil { + logging.Error("nil ParserCloser") + _ = c.Close() return } - if tlsConn, ok := parser.Processor.Conn().(*tls.Conn); ok { - defer tlsConn.ResetOrFreeBuffer() - - buffer := e.getTLSBuffer(c) - for { - _, nread, err := tlsConn.AppendAndRead(data, buffer) - data = nil - if err != nil { - c.CloseWithError(err) - return - } - if nread > 0 { - err := parser.Read(buffer[:nread]) + nbhttpConn, ok := parserCloser.UnderlayerConn().(*Conn) + if ok { + if tlsConn, ok := nbhttpConn.Conn.(*tls.Conn); ok { + defer tlsConn.ResetOrFreeBuffer() + + readed := data + buffer := data + for { + _, nread, err := tlsConn.AppendAndRead(readed, buffer) + readed = nil if err != nil { - logging.Debug("parser.Read failed: %v", err) - c.CloseWithError(err) + _ = c.CloseWithError(err) + return + } + if nread > 0 { + parserCloser = c.Session().(ParserCloser) + err := parserCloser.Parse(buffer[:nread]) + if err != nil { + logging.Debug("ParserCloser.Read failed: %v", err) + _ = c.CloseWithError(err) + return + } + } + if nread == 0 { return } } - if nread == 0 { - return - } + // c.SetReadDeadline(time.Now().Add(conf.KeepaliveTime)) } - // c.SetReadDeadline(time.Now().Add(conf.KeepaliveTime)) } } -// AddConnNonTLS . -func (engine *Engine) AddConnNonTLS(c net.Conn) { - nbc, err := nbio.NBConn(c) +// AddTransferredConn . +// +//go:norace +func (engine *Engine) AddTransferredConn(nbc *nbio.Conn) error { + key, err := conn2Array(nbc) + if err != nil { + _ = nbc.Close() + logging.Error("AddTransferredConn failed: %v", err) + return err + } + + engine.mux.Lock() + if len(engine.conns) >= engine.MaxLoad { + engine.mux.Unlock() + _ = nbc.Close() + logging.Error("AddTransferredConn failed: overload, already has %v online", engine.MaxLoad) + return ErrServiceOverload + } + engine.conns[key] = struct{}{} + engine.mux.Unlock() + _, err = engine.AddConn(nbc) + if err != nil { + engine.mux.Lock() + delete(engine.conns, key) + engine.mux.Unlock() + return err + } + engine._onOpen(nbc) + return nil +} + +// AddConnNonTLSNonBlocking . +// +//go:norace +func (engine *Engine) AddConnNonTLSNonBlocking(conn *Conn, tlsConfig *tls.Config, decrease func()) { + nbc, err := nbio.NBConn(conn.Conn) if err != nil { - c.Close() + _ = conn.Close() + decrease() + logging.Error("AddConnNonTLSNonBlocking failed: %v", err) return } + conn.Conn = nbc if nbc.Session() != nil { + _ = nbc.Close() + decrease() + logging.Error("AddConnNonTLSNonBlocking failed, invalid session: %v", nbc.Session()) return } + key, err := conn2Array(nbc) + if err != nil { + _ = nbc.Close() + decrease() + logging.Error("AddConnNonTLSNonBlocking failed: %v", err) + return + } + engine.mux.Lock() if len(engine.conns) >= engine.MaxLoad { engine.mux.Unlock() - c.Close() + _ = nbc.Close() + decrease() + logging.Error("AddConnNonTLSNonBlocking failed: overload, already has %v online", engine.MaxLoad) return } - engine.conns[nbc] = struct{}{} + engine.conns[key] = struct{}{} engine.mux.Unlock() - engine._onOpen(nbc) - processor := NewServerProcessor(nbc, engine.Handler, engine.KeepaliveTime, !engine.DisableSendfile) - parser := NewParser(processor, false, engine.ReadLimit, nbc.Execute) - parser.Engine = engine - processor.(*ServerProcessor).parser = parser + engine._onOpen(conn.Conn) + processor := NewServerProcessor() + parser := NewParser(conn, engine, processor, false, nbc.Execute) + // if engine.isOneshot { + // parser.Execute = SyncExecutor + // } + conn.Parser = parser nbc.SetSession(parser) nbc.OnData(engine.DataHandler) - engine.AddConn(nbc) - nbc.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) + _, err = engine.AddConn(nbc) + if err != nil { + engine.mux.Lock() + delete(engine.conns, key) + engine.mux.Unlock() + return + } + _ = nbc.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) +} + +// AddConnNonTLSBlocking . +// +//go:norace +func (engine *Engine) AddConnNonTLSBlocking(conn *Conn, tlsConfig *tls.Config, decrease func()) { + engine.mux.Lock() + if len(engine.conns) >= engine.MaxLoad { + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnNonTLSBlocking failed: overload, already has %v online", engine.MaxLoad) + return + } + switch vt := conn.Conn.(type) { + case *net.TCPConn, *net.UnixConn: + key, err := conn2Array(vt) + if err != nil { + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnNonTLSBlocking failed: %v", err) + return + } + engine.conns[key] = struct{}{} + default: + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnNonTLSBlocking failed: unknown conn type: %v", vt) + return + } + engine.mux.Unlock() + engine._onOpen(conn) + processor := NewServerProcessor() + parser := NewParser(conn, engine, processor, false, SyncExecutor) + parser.Engine = engine + conn.Parser = parser + _ = conn.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) + go engine.readConnBlocking(conn, parser, decrease) } -// AddConnTLS . -func (engine *Engine) AddConnTLS(conn net.Conn, tlsConfig *tls.Config) { - nbc, err := nbio.NBConn(conn) +// AddConnTLSNonBlocking . +// +//go:norace +func (engine *Engine) AddConnTLSNonBlocking(conn *Conn, tlsConfig *tls.Config, decrease func()) { + nbc, err := nbio.NBConn(conn.Conn) if err != nil { - conn.Close() + _ = conn.Close() + decrease() + logging.Error("AddConnTLSNonBlocking failed: %v", err) return } + conn.Conn = nbc if nbc.Session() != nil { + _ = nbc.Close() + decrease() + logging.Error("AddConnTLSNonBlocking failed: session should not be nil") return } + key, err := conn2Array(nbc) + if err != nil { + _ = nbc.Close() + decrease() + logging.Error("AddConnTLSNonBlocking failed: %v", err) + return + } + engine.mux.Lock() if len(engine.conns) >= engine.MaxLoad { engine.mux.Unlock() - nbc.Close() + _ = nbc.Close() + decrease() + logging.Error("AddConnTLSNonBlocking failed: overload, already has %v online", engine.MaxLoad) return } - engine.conns[nbc] = struct{}{} + + engine.conns[key] = struct{}{} engine.mux.Unlock() - engine._onOpen(nbc) + engine._onOpen(conn.Conn) isClient := false isNonBlock := true tlsConn := tls.NewConn(nbc, tlsConfig, isClient, isNonBlock, engine.TLSAllocator) - processor := NewServerProcessor(tlsConn, engine.Handler, engine.KeepaliveTime, !engine.DisableSendfile) - parser := NewParser(processor, false, engine.ReadLimit, nbc.Execute) - parser.Conn = tlsConn + conn = &Conn{Conn: tlsConn} + processor := NewServerProcessor() + parser := NewParser(conn, engine, processor, false, nbc.Execute) + // if engine.isOneshot { + // parser.Execute = SyncExecutor + // } + parser.Conn = conn parser.Engine = engine - processor.(*ServerProcessor).parser = parser + conn.Parser = parser nbc.SetSession(parser) nbc.OnData(engine.TLSDataHandler) - engine.AddConn(nbc) - nbc.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) + _, err = engine.AddConn(nbc) + if err != nil { + engine.mux.Lock() + delete(engine.conns, key) + engine.mux.Unlock() + } + _ = nbc.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) +} + +// AddConnTLSBlocking . +// +//go:norace +func (engine *Engine) AddConnTLSBlocking(conn *Conn, tlsConfig *tls.Config, decrease func()) { + engine.mux.Lock() + if len(engine.conns) >= engine.MaxLoad { + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnTLSBlocking failed: overload, already has %v online", engine.MaxLoad) + return + } + + underLayerConn := conn.Conn + switch vt := underLayerConn.(type) { + case *net.TCPConn, *net.UnixConn: + key, err := conn2Array(vt) + if err != nil { + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnTLSBlocking failed: %v", err) + return + } + engine.conns[key] = struct{}{} + default: + engine.mux.Unlock() + _ = conn.Close() + decrease() + logging.Error("AddConnTLSBlocking unknown conn type: %v", vt) + return + } + engine.mux.Unlock() + engine._onOpen(conn) + + isClient := false + isNonBlock := true + tlsConn := tls.NewConn(underLayerConn, tlsConfig, isClient, isNonBlock, engine.TLSAllocator) + conn = &Conn{Conn: tlsConn} + processor := NewServerProcessor() + parser := NewParser(conn, engine, processor, false, SyncExecutor) + conn.Parser = parser + _ = conn.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) + tlsConn.SetSession(parser) + go engine.readTLSConnBlocking(conn, underLayerConn, tlsConn, parser, decrease) +} + +//go:norace +func (engine *Engine) readConnBlocking(conn *Conn, parser *Parser, decrease func()) { + var ( + n int + err error + ) + + readBufferPool := engine.ReadBufferPool + if readBufferPool == nil { + readBufferPool = getReadBufferPool(engine.BlockingReadBufferSize) + } + + pbuf := readBufferPool.Malloc(engine.BlockingReadBufferSize) + var parserCloser ParserCloser = parser + defer func() { + readBufferPool.Free(pbuf) + if !conn.Trasfered { + parserCloser.CloseAndClean(err) + } + engine.mux.Lock() + switch vt := conn.Conn.(type) { + case *net.TCPConn, *net.UnixConn: + key, _ := conn2Array(vt) + delete(engine.conns, key) + } + engine.mux.Unlock() + engine._onClose(conn, err) + decrease() + // }() + }() + + for { + n, err = conn.Read(*pbuf) + if err != nil { + return + } + _ = parserCloser.Parse((*pbuf)[:n]) + if conn.Trasfered { + parser.onClose = nil + parser.CloseAndClean(nil) + return + } + if parser != nil && parser.ParserCloser != nil { + parserCloser = parser.ParserCloser + parser.onClose = nil + parser.CloseAndClean(nil) + parser = nil + } + } +} + +//go:norace +func (engine *Engine) readTLSConnBlocking(conn *Conn, rconn net.Conn, tlsConn *tls.Conn, parser *Parser, decrease func()) { + var ( + err error + nread int + ) + + readBufferPool := engine.ReadBufferPool + if readBufferPool == nil { + readBufferPool = getReadBufferPool(engine.BlockingReadBufferSize) + } + pbuf := readBufferPool.Malloc(engine.BlockingReadBufferSize) + var parserCloser ParserCloser = parser + defer func() { + readBufferPool.Free(pbuf) + if !conn.Trasfered { + parserCloser.CloseAndClean(err) + _ = tlsConn.Close() + } + engine.mux.Lock() + switch vt := rconn.(type) { + case *net.TCPConn, *net.UnixConn: + key, _ := conn2Array(vt) + delete(engine.conns, key) + } + engine.mux.Unlock() + engine._onClose(conn, err) + decrease() + }() + + for { + nread, err = rconn.Read(*pbuf) + if err != nil { + return + } + + readed := (*pbuf)[:nread] + for { + _, nread, err = tlsConn.AppendAndRead(readed, *pbuf) + readed = nil + if err != nil { + return + } + if nread > 0 { + err = parserCloser.Parse((*pbuf)[:nread]) + if err != nil { + logging.Debug("parser.Read failed: %v", err) + return + } + if conn.Trasfered { + parser.onClose = nil + parser.CloseAndClean(nil) + return + } + if parser != nil && parser.ParserCloser != nil { + parserCloser = parser.ParserCloser + parser.onClose = nil + parser.CloseAndClean(nil) + parser = nil + } + } + if nread == 0 { + break + } + } + } } // NewEngine . +// +//go:norace func NewEngine(conf Config) *Engine { + if conf.Name == "" { + conf.Name = "NB" + } if conf.MaxLoad <= 0 { conf.MaxLoad = DefaultMaxLoad } if conf.NPoller <= 0 { - conf.NPoller = runtime.NumCPU() - } - if conf.NParser <= 0 { - conf.NParser = conf.NPoller + conf.NPoller = runtime.NumCPU() / 4 + if conf.AsyncReadInPoller && conf.EpollMod == nbio.EPOLLET { + conf.NPoller = 1 + } + if conf.NPoller == 0 { + conf.NPoller = 1 + } } if conf.ReadLimit <= 0 { conf.ReadLimit = DefaultHTTPReadLimit @@ -584,9 +1029,30 @@ func NewEngine(conf Config) *Engine { if conf.MaxWebsocketFramePayloadSize <= 0 { conf.MaxWebsocketFramePayloadSize = DefaultMaxWebsocketFramePayloadSize } + if conf.TLSAllocator == nil { + conf.TLSAllocator = mempool.DefaultMemPool + } if conf.BodyAllocator == nil { conf.BodyAllocator = mempool.DefaultMemPool } + if conf.BlockingReadBufferSize <= 0 { + conf.BlockingReadBufferSize = DefaultBlockingReadBufferSize + } + if conf.Listen == nil { + conf.Listen = net.Listen + } + if conf.ListenUDP == nil { + conf.ListenUDP = net.ListenUDP + } + switch conf.IOMod { + case IOModNonBlocking, IOModBlocking: + case IOModMixed: + if conf.MaxBlockingOnline <= 0 { + conf.MaxBlockingOnline = DefaultMaxBlockingOnline + } + default: + conf.IOMod = DefaultIOMod + } var handler = conf.Handler if handler == nil { @@ -594,19 +1060,21 @@ func NewEngine(conf Config) *Engine { } conf.Handler = handler + var serverCall = func(f func()) { f() } var serverExecutor = conf.ServerExecutor - var messageHandlerExecutePool *taskpool.MixedPool + var messageHandlerExecutePool *taskpool.TaskPool if serverExecutor == nil { if conf.MessageHandlerPoolSize <= 0 { conf.MessageHandlerPoolSize = runtime.NumCPU() * 1024 } nativeSize := conf.MessageHandlerPoolSize - 1 - messageHandlerExecutePool = taskpool.NewMixedPool(nativeSize, 1, 1024*1024, true) + messageHandlerExecutePool = taskpool.New(nativeSize, 1024*64) serverExecutor = messageHandlerExecutePool.Go + serverCall = messageHandlerExecutePool.Call } var clientExecutor = conf.ClientExecutor - var clientExecutePool *taskpool.MixedPool + var clientExecutePool *taskpool.TaskPool var goExecutor = func(f func()) { go func() { // avoid deadlock defer func() { @@ -614,7 +1082,7 @@ func NewEngine(conf Config) *Engine { const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - logging.Error("clientExecutor call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + logging.Error("ClientExecutor call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) } }() f() @@ -622,7 +1090,7 @@ func NewEngine(conf Config) *Engine { } if clientExecutor == nil { if !conf.SupportServerOnly { - clientExecutePool = taskpool.NewMixedPool(runtime.NumCPU()*1024-1, 1, 1024*1024) + clientExecutePool = taskpool.New(runtime.NumCPU()*1024-1, 1024*64) clientExecutor = clientExecutePool.Go } else { clientExecutor = goExecutor @@ -635,73 +1103,123 @@ func NewEngine(conf Config) *Engine { } gopherConf := nbio.Config{ - Name: conf.Name, - Network: conf.Network, - NPoller: conf.NPoller, - NListener: conf.NListener, - ReadBufferSize: conf.ReadBufferSize, - MaxWriteBufferSize: conf.MaxWriteBufferSize, - MaxReadTimesPerEventLoop: conf.MaxReadTimesPerEventLoop, - LockPoller: conf.LockPoller, - LockListener: conf.LockListener, - } - g := nbio.NewGopher(gopherConf) + Name: conf.Name, + Network: conf.Network, + NPoller: conf.NPoller, + ReadBufferSize: conf.ReadBufferSize, + MaxWriteBufferSize: conf.MaxWriteBufferSize, + MaxConnReadTimesPerEventLoop: conf.MaxConnReadTimesPerEventLoop, + LockPoller: conf.LockPoller, + LockListener: conf.LockListener, + EpollMod: conf.EpollMod, + EPOLLONESHOT: conf.EPOLLONESHOT, + AsyncReadInPoller: conf.AsyncReadInPoller, + IOExecute: conf.IOExecute, + } + g := nbio.NewEngine(gopherConf) g.Execute = serverExecutor - for _, addr := range conf.Addrs { - conf.AddrConfigs = append(conf.AddrConfigs, ConfAddr{Addr: addr}) + // init non-tls addr configs + for i, addr := range conf.Addrs { + conf.AddrConfigs = append(conf.AddrConfigs, ConfAddr{Network: conf.Network, Addr: addr, pAddr: &conf.Addrs[i], NListener: conf.NListener}) } - for _, addr := range conf.AddrsTLS { - conf.AddrConfigsTLS = append(conf.AddrConfigsTLS, ConfAddr{Addr: addr}) + for i := range conf.AddrConfigs { + if conf.AddrConfigs[i].NListener <= 0 { + conf.AddrConfigs[i].NListener = 1 + } + } + + // init tls addr configs + for i, addr := range conf.AddrsTLS { + conf.AddrConfigsTLS = append(conf.AddrConfigsTLS, ConfAddr{Network: conf.Network, Addr: addr, pAddr: &conf.AddrsTLS[i], NListener: conf.NListener}) + } + for i := range conf.AddrConfigsTLS { + if conf.AddrConfigsTLS[i].NListener <= 0 { + conf.AddrConfigsTLS[i].NListener = 1 + } } engine := &Engine{ - Gopher: g, - Config: &conf, - _onOpen: func(c *nbio.Conn) {}, - _onClose: func(c *nbio.Conn, err error) {}, - _onStop: func() {}, - MaxLoad: conf.MaxLoad, - MaxWebsocketFramePayloadSize: conf.MaxWebsocketFramePayloadSize, - ReleaseWebsocketPayload: conf.ReleaseWebsocketPayload, - CheckUtf8: utf8.Valid, - conns: map[*nbio.Conn]struct{}{}, - ExecuteClient: clientExecutor, + Engine: g, + Config: conf, + _onOpen: func(c net.Conn) {}, + _onClose: func(c net.Conn, err error) {}, + _onStop: func() {}, + CheckUtf8: utf8.Valid, + conns: map[connValue]struct{}{}, + dialerConns: map[connValue]struct{}{}, + ExecuteClient: clientExecutor, emptyRequest: (&http.Request{}).WithContext(baseCtx), BaseCtx: baseCtx, Cancel: cancel, - - BodyAllocator: conf.BodyAllocator, } - - shouldSupportTLS := !conf.SupportServerOnly || len(conf.AddrsTLS) > 0 - if shouldSupportTLS { - engine.InitTLSBuffers() + if engine.SyncCall == nil { + engine.SyncCall = serverCall } + // shouldSupportTLS := !conf.SupportServerOnly || len(conf.AddrsTLS) > 0 + // if shouldSupportTLS { + // engine.InitTLSBuffers() + // } + // g.OnOpen(engine.ServerOnOpen) g.OnClose(func(c *nbio.Conn, err error) { c.MustExecute(func() { - parser := c.Session().(*Parser) - if parser == nil { - logging.Error("nil parser") + switch vt := c.Session().(type) { + case ParserCloser: + vt.CloseAndClean(err) + default: } - parser.Close(err) engine._onClose(c, err) engine.mux.Lock() - delete(engine.conns, c) + key, _ := conn2Array(c) + delete(engine.conns, key) + delete(engine.dialerConns, key) engine.mux.Unlock() }) }) + g.OnData(func(c *nbio.Conn, data []byte) { - if c.DataHandler != nil { - c.DataHandler(c, data) - } - }) - g.OnWriteBufferRelease(func(c *nbio.Conn, buffer []byte) { - mempool.Free(buffer) + c.DataHandler()(c, data) }) + + // engine.isOneshot = (conf.EpollMod == nbio.EPOLLET && conf.EPOLLONESHOT == nbio.EPOLLONESHOT && runtime.GOOS == "linux") + // if engine.isOneshot { + // readBufferPool := conf.ReadBufferPool + // if readBufferPool == nil { + // readBufferPool = getReadBufferPool(conf.ReadBufferSize) + // } + + // g.OnRead(func(c *nbio.Conn) { + // serverExecutor(func() { + // buf := readBufferPool.Malloc(conf.ReadBufferSize) + // defer func() { + // readBufferPool.Free(buf) + // c.ResetPollerEvent() + // }() + // for { + // n, err := c.Read(buf) + // if n > 0 && c.DataHandler != nil { + // c.DataHandler(c, buf[:n]) + // } + // if errors.Is(err, syscall.EINTR) { + // continue + // } + // if errors.Is(err, syscall.EAGAIN) { + // break + // } + // if err != nil { + // c.CloseWithError(err) + // } + // if n < len(buf) { + // return + // } + // } + // }) + // }) + // } + g.OnStop(func() { engine._onStop() g.Execute = func(f func()) {} @@ -715,3 +1233,33 @@ func NewEngine(conf Config) *Engine { }) return engine } + +var ReadBufferPools = &sync.Map{} + +//go:norace +func getReadBufferPool(size int) mempool.Allocator { + pool, ok := ReadBufferPools.Load(size) + if ok { + readBufferPool, ok := pool.(mempool.Allocator) + if ok { + return readBufferPool + } + } + readBufferPool := mempool.New(size, size*2) + ReadBufferPools.Store(size, readBufferPool) + return readBufferPool +} + +//go:norace +func SyncExecutor(f func()) bool { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("ProtocolExecutor call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() + f() + return true +} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/error.go b/vendor/github.com/lesismal/nbio/nbhttp/error.go index 13c41cdd..862d7ce6 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/error.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/error.go @@ -9,9 +9,6 @@ import ( ) var ( - // ErrClosed . - ErrClosed = errors.New("closed") - // ErrInvalidCRLF . ErrInvalidCRLF = errors.New("invalid cr/lf at the end of line") @@ -92,3 +89,8 @@ var ( // ErrClientClosed . ErrClientClosed = errors.New("http client closed") ) + +var ( + // ErrServiceOverload . + ErrServiceOverload = errors.New("service overload") +) diff --git a/vendor/github.com/lesismal/nbio/nbhttp/parser.go b/vendor/github.com/lesismal/nbio/nbhttp/parser.go index 2a6985e5..46e5d32f 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/parser.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/parser.go @@ -9,10 +9,13 @@ import ( "net" "net/http" "net/textproto" + "runtime" "strconv" "strings" "sync" + "unsafe" + "github.com/lesismal/nbio/logging" "github.com/lesismal/nbio/mempool" ) @@ -27,48 +30,58 @@ const ( MaxInt = int64(int(MaxUint >> 1)) ) +type ParserCloser interface { + UnderlayerConn() net.Conn + Parse(data []byte) error + CloseAndClean(err error) +} + // Parser . type Parser struct { mux sync.Mutex - cache []byte - - proto string - - statusCode int - status string - - headerKey string - headerValue string + // bytesCached for half packet. + bytesCached *[]byte - header http.Header - trailer http.Header + // errClose error - contentLength int - chunkSize int - chunked bool - headerExists bool + onClose func(p *Parser, err error) - state int8 - isClient bool + ParserCloser ParserCloser - readLimit int + Engine *Engine - errClose error + // Underlayer Conn. + Conn net.Conn - onClose func(p *Parser, err error) + // used to call message handler when got a full Request/Response. + Execute func(f func()) bool Processor Processor - ConnState ReadCloser - - Engine *Engine + // http fields + proto string + statusCode int + status string + headerKey string + headerValue string + header http.Header + trailer http.Header + contentLength int + chunkSize int - Conn net.Conn + state int8 + chunked bool + isClient bool + headerExists bool +} - Execute func(f func()) +//go:norace +func (p *Parser) UnderlayerConn() net.Conn { + return p.Conn } +//go:norace func (p *Parser) nextState(state int8) { switch p.state { case stateClose: @@ -77,13 +90,17 @@ func (p *Parser) nextState(state int8) { } } -// OnClose . +// OnClose registers callback for closing. +// +//go:norace func (p *Parser) OnClose(h func(p *Parser, err error)) { p.onClose = h } -// Close . -func (p *Parser) Close(err error) { +// CloseAndClean closes the underlayer connection and cleans up related. +// +//go:norace +func (p *Parser) CloseAndClean(err error) { p.mux.Lock() defer p.mux.Unlock() @@ -93,22 +110,23 @@ func (p *Parser) Close(err error) { p.state = stateClose - p.errClose = err + // p.errClose = err - if p.ConnState != nil { - p.ConnState.Close(p, p.errClose) - } + // if p.ReadCloser != nil { + // p.ReadCloser.CloseWithError(p.errClose) + // } if p.Processor != nil { - p.Processor.Close(p, p.errClose) + p.Processor.Close(p, err) } - if len(p.cache) > 0 { - mempool.Free(p.cache) + if p.bytesCached != nil && len(*p.bytesCached) > 0 { + mempool.Free(p.bytesCached) } if p.onClose != nil { p.onClose(p, err) } } +//go:norace func parseAndValidateChunkSize(originalStr string) (int, error) { chunkSize, err := strconv.ParseInt(originalStr, 16, 63) if err != nil { @@ -123,52 +141,71 @@ func parseAndValidateChunkSize(originalStr string) (int, error) { return int(chunkSize), nil } -// Read . -func (p *Parser) Read(data []byte) error { +// Parse parses data bytes and calls HTTP handler when full request received. +// If the connection is upgraded, it passes the data bytes to the ParserCloser +// and doesn't parse them itself any more. +// +//go:norace +func (p *Parser) Parse(data []byte) error { p.mux.Lock() - defer p.mux.Unlock() + defer func() { + p.mux.Unlock() + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("HTTP Parse failed: %v\n%v\n", + err, + *(*string)(unsafe.Pointer(&buf)), + ) + } + }() if p.state == stateClose { - return ErrClosed + return net.ErrClosed } if len(data) == 0 { return nil } - var c byte var start = 0 - var offset = len(p.cache) + var offset = 0 + if p.bytesCached != nil { + offset = len(*p.bytesCached) + } if offset > 0 { - if offset+len(data) > p.readLimit { + if p.Engine.ReadLimit > 0 && offset+len(data) > p.Engine.ReadLimit { return ErrTooLong } - p.cache = append(p.cache, data...) - data = p.cache + p.bytesCached = mempool.Append(p.bytesCached, data...) + data = *p.bytesCached } UPGRADER: - if p.ConnState != nil { + if p.ParserCloser != nil { udata := data if start > 0 { udata = data[start:] } - err := p.ConnState.Read(p, udata) - if p.cache != nil { - mempool.Free(p.cache) - p.cache = nil + err := p.ParserCloser.Parse(udata) + if p.bytesCached != nil { + mempool.Free(p.bytesCached) + p.bytesCached = nil } return err } + var c byte for i := offset; i < len(data); i++ { - if p.ConnState != nil { + if p.ParserCloser != nil { + p.Processor.Clean(p) goto UPGRADER } c = data[i] switch p.state { case stateClose: - return ErrClosed + return net.ErrClosed case stateMethodBefore: if isValidMethodChar(c) { start = i @@ -182,7 +219,7 @@ UPGRADER: if !isValidMethod(method) { return ErrInvalidMethod } - p.Processor.OnMethod(method) + p.Processor.OnMethod(p, method) start = i + 1 p.nextState(statePathBefore) continue @@ -205,7 +242,7 @@ UPGRADER: case statePath: if c == ' ' { var uri = string(data[start:i]) - if err := p.Processor.OnURL(uri); err != nil { + if err := p.Processor.OnURL(p, uri); err != nil { return err } start = i + 1 @@ -226,7 +263,7 @@ UPGRADER: if p.proto == "" { p.proto = string(data[start:i]) } - if err := p.Processor.OnProto(p.proto); err != nil { + if err := p.Processor.OnProto(p, p.proto); err != nil { p.proto = "" return err } @@ -246,7 +283,7 @@ UPGRADER: if p.proto == "" { p.proto = string(data[start:i]) } - if err := p.Processor.OnProto(p.proto); err != nil { + if err := p.Processor.OnProto(p, p.proto); err != nil { p.proto = "" return err } @@ -299,7 +336,7 @@ UPGRADER: if p.status == "" { p.status = string(data[start:i]) } - p.Processor.OnStatus(p.statusCode, p.status) + p.Processor.OnStatus(p, p.statusCode, p.status) p.statusCode = 0 p.status = "" p.nextState(stateStatusLF) @@ -339,7 +376,8 @@ UPGRADER: if err != nil { return err } - p.Processor.OnContentLength(p.contentLength) + + p.Processor.OnContentLength(p, p.contentLength) err = p.parseTrailer() if err != nil { return err @@ -349,7 +387,7 @@ UPGRADER: case '\n': return ErrInvalidCharInHeader default: - if isAlpha(c) { + if isToken(c) { start = i p.nextState(stateHeaderKey) p.headerExists = true @@ -392,7 +430,7 @@ UPGRADER: default: } - p.Processor.OnHeader(p.headerKey, p.headerValue) + p.Processor.OnHeader(p, p.headerKey, p.headerValue) p.headerKey = "" p.headerValue = "" @@ -422,7 +460,7 @@ UPGRADER: default: } - p.Processor.OnHeader(p.headerKey, p.headerValue) + p.Processor.OnHeader(p, p.headerKey, p.headerValue) p.headerKey = "" p.headerValue = "" @@ -453,7 +491,10 @@ UPGRADER: cl := p.contentLength left := len(data) - start if left >= cl { - p.Processor.OnBody(data[start : start+cl]) + err := p.Processor.OnBody(p, data[start:start+cl]) + if err != nil { + return err + } p.handleMessage() start += cl i = start - 1 @@ -520,7 +561,10 @@ UPGRADER: cl := p.chunkSize left := len(data) - start if left >= cl { - p.Processor.OnBody(data[start : start+cl]) + err := p.Processor.OnBody(p, data[start:start+cl]) + if err != nil { + return err + } start += cl i = start - 1 p.nextState(stateBodyChunkDataCR) @@ -547,7 +591,7 @@ UPGRADER: } return ErrLFExpected case stateBodyTrailerHeaderKeyBefore: - if isAlpha(c) { + if isToken(c) { start = i p.nextState(stateBodyTrailerHeaderKey) continue @@ -587,7 +631,7 @@ UPGRADER: if p.headerValue == "" { p.headerValue = string(data[start:i]) } - p.Processor.OnTrailerHeader(p.headerKey, p.headerValue) + p.Processor.OnTrailerHeader(p, p.headerKey, p.headerValue) p.headerKey = "" p.headerValue = "" @@ -615,7 +659,7 @@ UPGRADER: } delete(p.trailer, p.headerKey) - p.Processor.OnTrailerHeader(p.headerKey, p.headerValue) + p.Processor.OnTrailerHeader(p, p.headerKey, p.headerValue) start = i + 1 p.headerKey = "" p.headerValue = "" @@ -645,23 +689,24 @@ UPGRADER: Exit: left := len(data) - start if left > 0 { - if p.cache == nil { - p.cache = mempool.Malloc(left) - copy(p.cache, data[start:]) + if p.bytesCached == nil { + p.bytesCached = mempool.Malloc(left) + copy(*p.bytesCached, data[start:]) } else if start > 0 { - oldCache := p.cache - p.cache = mempool.Malloc(left) - copy(p.cache, data[start:]) - mempool.Free(oldCache) + oldbytesCached := p.bytesCached + p.bytesCached = mempool.Malloc(left) + copy(*p.bytesCached, data[start:]) + mempool.Free(oldbytesCached) } - } else if len(p.cache) > 0 { - mempool.Free(p.cache) - p.cache = nil + } else if p.bytesCached != nil && len(*p.bytesCached) > 0 { + mempool.Free(p.bytesCached) + p.bytesCached = nil } return nil } +//go:norace func (p *Parser) parseTransferEncoding() error { raw, present := p.header[transferEncodingHeader] if !present { @@ -681,6 +726,7 @@ func (p *Parser) parseTransferEncoding() error { return nil } +//go:norace func (p *Parser) parseContentLength() (err error) { if cl := p.header.Get(contentLengthHeader); cl != "" { if p.chunked { @@ -712,6 +758,7 @@ func (p *Parser) parseContentLength() (err error) { return nil } +//go:norace func (p *Parser) parseTrailer() error { if !p.chunked { return nil @@ -759,9 +806,12 @@ func (p *Parser) parseTrailer() error { return nil } +//go:norace func (p *Parser) handleMessage() { p.Processor.OnComplete(p) + p.chunked = false p.header = nil + p.trailer = nil if !p.isClient { p.nextState(stateMethodBefore) @@ -770,8 +820,10 @@ func (p *Parser) handleMessage() { } } -// NewParser . -func NewParser(processor Processor, isClient bool, readLimit int, executor func(f func())) *Parser { +// NewParser creates an HTTP parser. +// +//go:norace +func NewParser(conn net.Conn, engine *Engine, processor Processor, isClient bool, executor func(f func()) bool) *Parser { if processor == nil { processor = NewEmptyProcessor() } @@ -779,20 +831,19 @@ func NewParser(processor Processor, isClient bool, readLimit int, executor func( if isClient { state = stateClientProtoBefore } - if readLimit <= 0 { - readLimit = DefaultHTTPReadLimit - } if executor == nil { - executor = func(f func()) { + executor = func(f func()) bool { f() + return true } } p := &Parser{ state: state, - readLimit: readLimit, isClient: isClient, - Execute: executor, Processor: processor, + Conn: conn, + Engine: engine, + Execute: executor, } return p } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/parser_test.go b/vendor/github.com/lesismal/nbio/nbhttp/parser_test.go index cd1dbbaa..6ea51d5e 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/parser_test.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/parser_test.go @@ -1,10 +1,9 @@ package nbhttp import ( - "errors" "fmt" - "io" "math/rand" + "net" "net/http" "testing" "time" @@ -44,6 +43,19 @@ func TestServerParserChunks(t *testing.T) { } } +func TestServerParserHeaderToken(t *testing.T) { + data := []byte("POST / HTTP/1.1\r\n123456789: value \r\n\r\n") + err := testParser(t, false, data) + if err != nil { + t.Fatalf("test failed: %v", err) + } + data = []byte("POST / HTTP/1.1\r\n!#$%&'*+-.^_`|~: value \r\n\r\n") + err = testParser(t, false, data) + if err != nil { + t.Fatalf("test failed: %v", err) + } +} + func TestServerParserTrailer(t *testing.T) { data := []byte("POST / HTTP/1.1\r\nHost : localhost:1235\r\n User-Agent : Go-http-client/1.1 \r\nTransfer-Encoding: chunked\r\nTrailer: Md5,Size\r\nAccept-Encoding: gzip \r\n\r\n4\r\nbody\r\n0\r\n Md5 : 841a2d689ad86bd1611447453c22c6fc \r\n Size : 4 \r\n\r\n") err := testParser(t, false, data) @@ -55,6 +67,11 @@ func TestServerParserTrailer(t *testing.T) { if err != nil { t.Fatalf("test failed: %v", err) } + data = []byte("POST / HTTP/1.1\r\nHost: localhost:1235\r\nUser-Agent: Go-http-client/1.1\r\nTransfer-Encoding: chunked\r\nTrailer: 123456789,!#$%&'*+-.^_`|~\r\nAccept-Encoding: gzip \r\n\r\n0\r\n123456789: value \r\n !#$%&'*+-.^_`|~: value \r\n\r\n") + err = testParser(t, false, data) + if err != nil { + t.Fatalf("test failed: %v", err) + } } func TestClientParserContentLength(t *testing.T) { @@ -105,18 +122,23 @@ func TestClientParserTrailer(t *testing.T) { func testParser(t *testing.T, isClient bool, data []byte) error { parser := newParser(isClient) - err := parser.Read(data) + defer func() { + if parser.Conn != nil { + _ = parser.Conn.Close() + } + }() + err := parser.Parse(data) if err != nil { t.Fatal(err) } for i := 0; i < len(data)-1; i++ { - err = parser.Read(append([]byte{}, data[i:i+1]...)) + err = parser.Parse(append([]byte{}, data[i:i+1]...)) if err != nil { t.Fatal(err) } } - err = parser.Read(append([]byte{}, data[len(data)-1:]...)) + err = parser.Parse(append([]byte{}, data[len(data)-1:]...)) if err != nil { t.Fatal(err) } @@ -124,24 +146,23 @@ func testParser(t *testing.T, isClient bool, data []byte) error { nRequest := 0 data = append(data, data...) - maxReadSize := 1024 * 1024 * 4 mux := &http.ServeMux{} mux.HandleFunc("/", func(w http.ResponseWriter, request *http.Request) { nRequest++ }) - processor := NewServerProcessor(nil, mux, DefaultKeepaliveTime, false) + conn := newConn() + defer func() { _ = conn.Close() }() + processor := NewServerProcessor() if isClient { processor = NewClientProcessor(nil, func(*http.Response, error) { nRequest++ }) } - svr := &Server{ - // Malloc: mempool.Malloc, - // Realloc: mempool.Realloc, - // Free: mempool.Free, - } - parser = NewParser(processor, isClient, maxReadSize, nil) - parser.Engine = svr.Engine + engine := NewEngine(Config{ + Handler: mux, + }) + parser = NewParser(conn, engine, processor, isClient, nil) + parser.Engine = engine tBegin := time.Now() loop := 10000 for i := 0; i < loop; i++ { @@ -152,7 +173,7 @@ func testParser(t *testing.T, isClient bool, data []byte) error { readBuf := append([]byte{}, tmp[:nRead]...) reads = append(reads, readBuf) tmp = tmp[nRead:] - err = parser.Read(readBuf) + err = parser.Parse(readBuf) if err != nil { t.Fatalf("nRead: %v, numOne: %v, reads: %v, error: %v", len(data)-len(tmp), len(data), reads, err) } @@ -169,61 +190,78 @@ func testParser(t *testing.T, isClient bool, data []byte) error { } func newParser(isClient bool) *Parser { - svr := &Server{ - // Malloc: mempool.Malloc, - // Realloc: mempool.Realloc, - // Free: mempool.Free, - } - maxReadSize := 1024 * 1024 * 4 + mux := &http.ServeMux{} + engine := NewEngine(Config{ + Handler: mux, + }) + conn := newConn() if isClient { processor := NewClientProcessor(nil, func(*http.Response, error) {}) - parser := NewParser(processor, isClient, maxReadSize, nil) - parser.Engine = svr.Engine + parser := NewParser(conn, engine, processor, isClient, nil) + parser.Engine = engine return parser } - mux := &http.ServeMux{} - mux.HandleFunc("/", pirntMessage) - processor := NewServerProcessor(nil, mux, DefaultKeepaliveTime, false) - - parser := NewParser(processor, isClient, maxReadSize, nil) - parser.Engine = svr.Engine + processor := NewServerProcessor() + parser := NewParser(conn, engine, processor, isClient, nil) + parser.Conn = conn return parser } -func pirntMessage(w http.ResponseWriter, request *http.Request) { - fmt.Printf("----------------------------------------------------------------\n") - fmt.Println("OnRequest") - fmt.Println("Method:", request.Method) - fmt.Println("Path:", request.URL.Path) - fmt.Println("Proto:", request.Proto) - fmt.Println("Host:", request.URL.Host) - fmt.Println("Rawpath:", request.URL.RawPath) - fmt.Println("Content-Length:", request.ContentLength) - for k, v := range request.Header { - fmt.Printf("Header: [\"%v\": \"%v\"]\n", k, v) - } - for k, v := range request.Trailer { - fmt.Printf("Trailer: [\"%v\": \"%v\"]\n", k, v) - } - body := request.Body - if body != nil { - nread := 0 - buffer := make([]byte, 1024) - for { - n, err := body.Read(buffer) - if n > 0 { - nread += n - } - if errors.Is(err, io.EOF) { - break - } +func newConn() net.Conn { + var conn net.Conn + for i := 0; i < 1000; i++ { + addr := fmt.Sprintf("127.0.0.1:%d", 8000+i) + ln, err := net.Listen("tcp", addr) + if err != nil { + continue } - fmt.Println("body:", string(buffer[:nread])) - } else { - fmt.Println("body: null") + go func() { + defer func() { _ = ln.Close() }() + _, _ = ln.Accept() + }() + conn, err = net.Dial("tcp", addr) + if err != nil { + panic(err) + } + break } + return conn } +// func printMessage(w http.ResponseWriter, request *http.Request) { +// fmt.Printf("----------------------------------------------------------------\n") +// fmt.Println("OnRequest") +// fmt.Println("Method:", request.Method) +// fmt.Println("Path:", request.URL.Path) +// fmt.Println("Proto:", request.Proto) +// fmt.Println("Host:", request.URL.Host) +// fmt.Println("Rawpath:", request.URL.RawPath) +// fmt.Println("Content-Length:", request.ContentLength) +// for k, v := range request.Header { +// fmt.Printf("Header: [\"%v\": \"%v\"]\n", k, v) +// } +// for k, v := range request.Trailer { +// fmt.Printf("Trailer: [\"%v\": \"%v\"]\n", k, v) +// } +// body := request.Body +// if body != nil { +// nread := 0 +// buffer := make([]byte, 1024) +// for { +// n, err := body.Read(buffer) +// if n > 0 { +// nread += n +// } +// if errors.Is(err, io.EOF) { +// break +// } +// } +// fmt.Println("body:", string(buffer[:nread])) +// } else { +// fmt.Println("body: null") +// } +// } + var benchData = []byte("POST /joyent/http-parser HTTP/1.1\r\n" + "Host: github.com\r\n" + "DNT: 1\r\n" + @@ -240,33 +278,23 @@ var benchData = []byte("POST /joyent/http-parser HTTP/1.1\r\n" + "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n") func BenchmarkServerProcessor(b *testing.B) { - maxReadSize := 1024 * 1024 * 4 isClient := false - mux := &http.ServeMux{} + processor := NewServerProcessor() + mux := http.NewServeMux() mux.HandleFunc("/", func(http.ResponseWriter, *http.Request) {}) - processor := NewServerProcessor(nil, mux, DefaultKeepaliveTime, false) - parser := NewParser(processor, isClient, maxReadSize, nil) - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := parser.Read(benchData); err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkEmpryProcessor(b *testing.B) { - maxReadSize := 1024 * 1024 * 4 - isClient := false - // processor := NewEmptyProcessor() - parser := NewParser(nil, isClient, maxReadSize, nil) - + engine := NewEngine(Config{ + Handler: mux, + }) + parser := NewParser(newConn(), engine, processor, isClient, nil) + defer func() { _ = parser.Conn.Close() }() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { - if err := parser.Read(benchData); err != nil { - b.Fatal(err) + for j := 0; j < 5; j++ { + err := parser.Parse(benchData) + if err != nil { + b.Fatal(err) + } } } } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/processor.go b/vendor/github.com/lesismal/nbio/nbhttp/processor.go index 925a0830..b3789718 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/processor.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/processor.go @@ -5,19 +5,23 @@ package nbhttp import ( - "errors" "fmt" - "net" "net/http" "net/url" "strings" "sync" "time" + + "github.com/lesismal/nbio/mempool" ) var ( - emptyRequest = http.Request{} + // used to reset a http.Request to empty value. + emptyRequest = http.Request{} + // used to reset a Response to empty value. emptyResponse = Response{} + // used to reset a http.Response to empty value. + emptyClientResponse = http.Response{} requestPool = sync.Pool{ New: func() interface{} { @@ -30,14 +34,29 @@ var ( return &Response{} }, } + + clientResponsePool = sync.Pool{ + New: func() interface{} { + return &http.Response{} + }, + } ) -func releaseRequest(req *http.Request) { +//go:norace +func releaseRequest(req *http.Request, retainHTTPBody bool) { if req != nil { if req.Body != nil { - br := req.Body.(*BodyReader) - br.close() - bodyReaderPool.Put(br) + if br, ok := req.Body.(*BodyReader); ok { + if retainHTTPBody { + // do not release the body + } else { + _ = br.Close() + *br = emptyBodyReader + bodyReaderPool.Put(br) + } + } else if !retainHTTPBody { + _ = req.Body.Close() + } } // fast gc for fields *req = emptyRequest @@ -45,64 +64,67 @@ func releaseRequest(req *http.Request) { } } +//go:norace func releaseResponse(res *Response) { if res != nil { + if res.buffer != nil { + mempool.Free(res.buffer) + } + if res.bodyBuffer != nil { + mempool.Free(res.bodyBuffer) + } *res = emptyResponse responsePool.Put(res) } } -// func releaseStdResponse(res *http.Response) { -// if res != nil { -// *res = emptyStdResponse -// stdResponsePool.Put(res) -// } -// } +//go:norace +func releaseClientResponse(res *http.Response) { + if res != nil { + if res.Body != nil { + br := res.Body.(*BodyReader) + _ = br.Close() + *br = emptyBodyReader + bodyReaderPool.Put(br) + } + *res = emptyClientResponse + clientResponsePool.Put(res) + } +} // Processor . type Processor interface { - Conn() net.Conn - OnMethod(method string) - OnURL(uri string) error - OnProto(proto string) error - OnStatus(code int, status string) - OnHeader(key, value string) - OnContentLength(contentLength int) - OnBody(data []byte) - OnTrailerHeader(key, value string) + OnMethod(parser *Parser, method string) + OnURL(parser *Parser, uri string) error + OnProto(parser *Parser, proto string) error + OnStatus(parser *Parser, code int, status string) + OnHeader(parser *Parser, key, value string) + OnContentLength(parser *Parser, contentLength int) + OnBody(parser *Parser, data []byte) error + OnTrailerHeader(parser *Parser, key, value string) OnComplete(parser *Parser) - Close(p *Parser, err error) + Close(parser *Parser, err error) + Clean(parser *Parser) } -// ServerProcessor . -type ServerProcessor struct { - // active int32 +var ( + emptyServerProcessor = ServerProcessor{} + emptyClientProcessor = ClientProcessor{} +) - // mux sync.Mutex - conn net.Conn - parser *Parser +// ServerProcessor is used for server side connection. +type ServerProcessor struct { request *http.Request - handler http.Handler - // executor func(index int, f func()) - - // resQueue []*Response - keepaliveTime time.Duration - enableSendfile bool - // isUpgrade bool - remoteAddr string -} - -// Conn . -func (p *ServerProcessor) Conn() net.Conn { - return p.conn } // OnMethod . -func (p *ServerProcessor) OnMethod(method string) { +// +//go:norace +func (p *ServerProcessor) OnMethod(parser *Parser, method string) { if p.request == nil { p.request = requestPool.Get().(*http.Request) - if p.parser != nil { - *p.request = *p.parser.Engine.emptyRequest + if parser != nil { + *p.request = *parser.Engine.emptyRequest } p.request.Method = method p.request.Header = http.Header{} @@ -112,18 +134,32 @@ func (p *ServerProcessor) OnMethod(method string) { } // OnURL . -func (p *ServerProcessor) OnURL(uri string) error { - u, err := url.ParseRequestURI(uri) +// +//go:norace +func (p *ServerProcessor) OnURL(parser *Parser, rawurl string) error { + p.request.RequestURI = rawurl + + justAuthority := p.request.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/") + if justAuthority { + rawurl = "http://" + rawurl + } + + u, err := url.ParseRequestURI(rawurl) if err != nil { return err } + if justAuthority { + u.Scheme = "" + } + p.request.URL = u - p.request.RequestURI = uri return nil } // OnProto . -func (p *ServerProcessor) OnProto(proto string) error { +// +//go:norace +func (p *ServerProcessor) OnProto(parser *Parser, proto string) error { protoMajor, protoMinor, ok := http.ParseHTTPVersion(proto) if !ok { return fmt.Errorf("%s %q", "malformed HTTP version", proto) @@ -135,12 +171,16 @@ func (p *ServerProcessor) OnProto(proto string) error { } // OnStatus . -func (p *ServerProcessor) OnStatus(code int, status string) { +// +//go:norace +func (p *ServerProcessor) OnStatus(parser *Parser, code int, status string) { } // OnHeader . -func (p *ServerProcessor) OnHeader(key, value string) { +// +//go:norace +func (p *ServerProcessor) OnHeader(parser *Parser, key, value string) { values := p.request.Header[key] values = append(values, value) p.request.Header[key] = values @@ -148,21 +188,26 @@ func (p *ServerProcessor) OnHeader(key, value string) { } // OnContentLength . -func (p *ServerProcessor) OnContentLength(contentLength int) { +// +//go:norace +func (p *ServerProcessor) OnContentLength(parser *Parser, contentLength int) { p.request.ContentLength = int64(contentLength) } // OnBody . -func (p *ServerProcessor) OnBody(data []byte) { +// +//go:norace +func (p *ServerProcessor) OnBody(parser *Parser, data []byte) error { if p.request.Body == nil { - p.request.Body = NewBodyReader(data) - } else { - p.request.Body.(*BodyReader).Append(data) + p.request.Body = NewBodyReader(parser.Engine) } + return p.request.Body.(*BodyReader).append(data) } // OnTrailerHeader . -func (p *ServerProcessor) OnTrailerHeader(key, value string) { +// +//go:norace +func (p *ServerProcessor) OnTrailerHeader(parser *Parser, key, value string) { if p.request.Trailer == nil { p.request.Trailer = http.Header{} } @@ -170,18 +215,21 @@ func (p *ServerProcessor) OnTrailerHeader(key, value string) { } // OnComplete . +// +//go:norace func (p *ServerProcessor) OnComplete(parser *Parser) { - // p.mux.Lock() request := p.request p.request = nil - // p.mux.Unlock() if request == nil { return } - if p.conn != nil { - request.RemoteAddr = p.remoteAddr + engine := parser.Engine + conn := parser.Conn + request.RemoteAddr = conn.RemoteAddr().String() + if parser.Engine.WriteTimeout > 0 { + _ = conn.SetWriteDeadline(time.Now().Add(engine.WriteTimeout)) } if request.URL.Host == "" { @@ -221,102 +269,105 @@ func (p *ServerProcessor) OnComplete(parser *Parser) { // } if request.Body == nil { - request.Body = NewBodyReader(nil) + request.Body = NewBodyReader(engine) } - response := NewResponse(p.parser, request, p.enableSendfile) - parser.Execute(func() { - p.handler.ServeHTTP(response, request) - p.flushResponse(response) - }) + response := NewResponse(parser, request) + + if engine.OnRequest != nil { + engine.OnRequest(response, request) + } + if !parser.Execute(func() { + engine.Handler.ServeHTTP(response, request) + p.flushResponse(parser, response) + }) { + releaseRequest(request, engine.RetainHTTPBody) + } } -func (p *ServerProcessor) flushResponse(res *Response) { - if p.conn != nil { +//go:norace +func (p *ServerProcessor) flushResponse(parser *Parser, res *Response) { + conn := parser.Conn + engine := parser.Engine + if conn != nil { req := res.request if !res.hijacked { + res.WriteHeader(http.StatusOK) + res.checkChunked() res.eoncodeHead() - if err := res.flushTrailer(p.conn); err != nil { - p.conn.Close() - releaseRequest(req) + if err := res.flush(conn); err != nil { + _ = conn.Close() + releaseRequest(req, engine.RetainHTTPBody) releaseResponse(res) return } + if req.Close { + // the data may still in the send queue + _ = conn.Close() + } else if parser.ParserCloser == nil { + _ = conn.SetReadDeadline(time.Now().Add(engine.KeepaliveTime)) + } } - if req.Close { - // the data may still in the send queue - p.conn.Close() - } else if p.parser == nil || p.parser.ConnState == nil { - p.conn.SetReadDeadline(time.Now().Add(p.keepaliveTime)) - } - releaseRequest(req) + releaseRequest(req, engine.RetainHTTPBody) releaseResponse(res) } } +// Clean . +// +//go:norace +func (p *ServerProcessor) Clean(parser *Parser) { + if p.request != nil { + releaseRequest(p.request, parser.Engine.RetainHTTPBody) + p.request = nil + } + *p = emptyServerProcessor +} + // Close . +// +//go:norace func (p *ServerProcessor) Close(parser *Parser, err error) { - + p.Clean(parser) } // NewServerProcessor . -func NewServerProcessor(conn net.Conn, handler http.Handler, keepaliveTime time.Duration, enableSendfile bool) Processor { - if handler == nil { - panic(errors.New("invalid handler for ServerProcessor: nil")) - } - // p := serverProcessorPool.Get().(*ServerProcessor) - // p.conn = conn - // p.handler = handler - // p.executor = executor - // p.keepaliveTime = keepaliveTime - // p.enableSendfile = enableSendfile - // p.remoteAddr = conn.RemoteAddr().String() - p := &ServerProcessor{ - conn: conn, - handler: handler, - keepaliveTime: keepaliveTime, - enableSendfile: enableSendfile, - } - if conn != nil { - p.remoteAddr = conn.RemoteAddr().String() - } - - return p +// +//go:norace +func NewServerProcessor() Processor { + return &ServerProcessor{} } -// ClientProcessor . +// ClientProcessor is used for client side connection. type ClientProcessor struct { conn *ClientConn response *http.Response handler func(res *http.Response, err error) } -// Conn . -func (p *ClientProcessor) Conn() net.Conn { - return p.conn.conn -} - // OnMethod . -func (p *ClientProcessor) OnMethod(method string) { +// +//go:norace +func (p *ClientProcessor) OnMethod(parser *Parser, method string) { } // OnURL . -func (p *ClientProcessor) OnURL(uri string) error { +// +//go:norace +func (p *ClientProcessor) OnURL(parser *Parser, uri string) error { return nil } // OnProto . -func (p *ClientProcessor) OnProto(proto string) error { +// +//go:norace +func (p *ClientProcessor) OnProto(parser *Parser, proto string) error { protoMajor, protoMinor, ok := http.ParseHTTPVersion(proto) if !ok { return fmt.Errorf("%s %q", "malformed HTTP version", proto) } if p.response == nil { - // p.response = &http.Response{ - // Proto: proto, - // Header: http.Header{}, - // } - p.response = &http.Response{} + p.response = clientResponsePool.Get().(*http.Response) p.response.Proto = proto p.response.Header = http.Header{} } else { @@ -328,32 +379,41 @@ func (p *ClientProcessor) OnProto(proto string) error { } // OnStatus . -func (p *ClientProcessor) OnStatus(code int, status string) { +// +//go:norace +func (p *ClientProcessor) OnStatus(parser *Parser, code int, status string) { p.response.StatusCode = code p.response.Status = status } // OnHeader . -func (p *ClientProcessor) OnHeader(key, value string) { +// +//go:norace +func (p *ClientProcessor) OnHeader(parser *Parser, key, value string) { p.response.Header.Add(key, value) } // OnContentLength . -func (p *ClientProcessor) OnContentLength(contentLength int) { +// +//go:norace +func (p *ClientProcessor) OnContentLength(parser *Parser, contentLength int) { p.response.ContentLength = int64(contentLength) } // OnBody . -func (p *ClientProcessor) OnBody(data []byte) { +// +//go:norace +func (p *ClientProcessor) OnBody(parser *Parser, data []byte) error { if p.response.Body == nil { - p.response.Body = NewBodyReader(data) - } else { - p.response.Body.(*BodyReader).Append(data) + p.response.Body = NewBodyReader(parser.Engine) } + return p.response.Body.(*BodyReader).append(data) } // OnTrailerHeader . -func (p *ClientProcessor) OnTrailerHeader(key, value string) { +// +//go:norace +func (p *ClientProcessor) OnTrailerHeader(parser *Parser, key, value string) { if p.response.Trailer == nil { p.response.Trailer = http.Header{} } @@ -361,20 +421,53 @@ func (p *ClientProcessor) OnTrailerHeader(key, value string) { } // OnComplete . +// +//go:norace func (p *ClientProcessor) OnComplete(parser *Parser) { res := p.response p.response = nil - parser.Execute(func() { + + // Fix #225 + // Handle upgrade handshake response in the io goroutine to avoid concurrent issue: + // 1. when the server may send a message together with handshake response + // 2. we handle the handshake response in another goroutine + // 3. poller continue reading data using http parser(the upgrader reader hasn't been set before 2) + // then we got parsing errors or panic. + if res.StatusCode == http.StatusSwitchingProtocols { + p.handler(res, nil) + releaseClientResponse(res) + return + } + + if !parser.Execute(func() { p.handler(res, nil) - }) + releaseClientResponse(res) + }) { + releaseClientResponse(res) + } +} + +// Clean . +// +//go:norace +func (p *ClientProcessor) Clean(parser *Parser) { + if p.response != nil { + releaseClientResponse(p.response) + } + *p = emptyClientProcessor } // Close . +// +//go:norace func (p *ClientProcessor) Close(parser *Parser, err error) { p.conn.CloseWithError(err) + p.Clean(parser) } // NewClientProcessor . +// +//go:norace func NewClientProcessor(conn *ClientConn, handler func(res *http.Response, err error)) Processor { return &ClientProcessor{ conn: conn, @@ -385,62 +478,86 @@ func NewClientProcessor(conn *ClientConn, handler func(res *http.Response, err e // EmptyProcessor . type EmptyProcessor struct{} -// Conn . -func (p *EmptyProcessor) Conn() net.Conn { - return nil -} - // OnMethod . -func (p *EmptyProcessor) OnMethod(method string) { +// +//go:norace +func (p *EmptyProcessor) OnMethod(parser *Parser, method string) { } // OnURL . -func (p *EmptyProcessor) OnURL(uri string) error { +// +//go:norace +func (p *EmptyProcessor) OnURL(parser *Parser, uri string) error { return nil } // OnProto . -func (p *EmptyProcessor) OnProto(proto string) error { +// +//go:norace +func (p *EmptyProcessor) OnProto(parser *Parser, proto string) error { return nil } // OnStatus . -func (p *EmptyProcessor) OnStatus(code int, status string) { +// +//go:norace +func (p *EmptyProcessor) OnStatus(parser *Parser, code int, status string) { } // OnHeader . -func (p *EmptyProcessor) OnHeader(key, value string) { +// +//go:norace +func (p *EmptyProcessor) OnHeader(parser *Parser, key, value string) { } // OnContentLength . -func (p *EmptyProcessor) OnContentLength(contentLength int) { +// +//go:norace +func (p *EmptyProcessor) OnContentLength(parser *Parser, contentLength int) { } // OnBody . -func (p *EmptyProcessor) OnBody(data []byte) { - +// +//go:norace +func (p *EmptyProcessor) OnBody(parser *Parser, data []byte) error { + return nil } // OnTrailerHeader . -func (p *EmptyProcessor) OnTrailerHeader(key, value string) { +// +//go:norace +func (p *EmptyProcessor) OnTrailerHeader(parser *Parser, key, value string) { } // OnComplete . +// +//go:norace func (p *EmptyProcessor) OnComplete(parser *Parser) { } +// Clean . +// +//go:norace +func (p *EmptyProcessor) Clean(parser *Parser) { + +} + // Close . +// +//go:norace func (p *EmptyProcessor) Close(parser *Parser, err error) { } // NewEmptyProcessor . +// +//go:norace func NewEmptyProcessor() Processor { return &EmptyProcessor{} } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/response.go b/vendor/github.com/lesismal/nbio/nbhttp/response.go index 27c8fa9b..75f087e3 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/response.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/response.go @@ -21,44 +21,51 @@ import ( // Response represents the server side of an HTTP response. type Response struct { - parser *Parser + Parser *Parser - request *http.Request // request for this response + request *http.Request // request for this response. status string - statusCode int // status code passed to WriteHeader + statusCode int // status code passed to WriteHeader. header http.Header trailer map[string]string trailerSize int - buffer []byte - bodyBuffer []byte + buffer *[]byte + bodyBuffer *[]byte + contentLen int + bodyWritten int intFormatBuf [10]byte - chunked bool - chunkChecked bool - headEncoded bool - hasBody bool - enableSendfile bool - hijacked bool + chunked bool + chunkChecked bool + headEncoded bool + hasBody bool + hijacked bool } // Hijack . +// +//go:norace func (res *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { - if res.parser.Processor == nil { + if res.Parser == nil { return nil, nil, errors.New("nil Proccessor") } res.hijacked = true - return res.parser.Processor.Conn(), nil, nil + return res.Parser.Conn, nil, nil } // Header . +// +//go:norace func (res *Response) Header() http.Header { return res.header } // WriteHeader . +// +//go:norace func (res *Response) WriteHeader(statusCode int) { if !res.hijacked && res.statusCode == 0 && res.statusCode != statusCode { status := http.StatusText(statusCode) @@ -75,14 +82,14 @@ func (res *Response) WriteHeader(statusCode int) { res.header.Del(contentLengthHeader) } } - - res.checkChunked() } } const maxPacketSize = 65536 // WriteString . +// +//go:norace func (res *Response) WriteString(s string) (int, error) { x := (*[2]uintptr)(unsafe.Pointer(&s)) h := [3]uintptr{x[0], x[1], x[1]} @@ -91,85 +98,217 @@ func (res *Response) WriteString(s string) (int, error) { } // Write . +// +//go:norace func (res *Response) Write(data []byte) (int, error) { l := len(data) - conn := res.parser.Processor.Conn() + conn := res.Parser.Conn if l == 0 || conn == nil { return 0, nil } res.WriteHeader(http.StatusOK) + res.checkChunked() res.hasBody = true if res.chunked { + return res.writeChunk(conn, data, l) + } + + cl, err := res.contentLength() + if err != nil { + return 0, err + } + if cl > 0 && res.bodyWritten+l > cl { + return 0, http.ErrContentLength + } + + if cl > 0 { res.eoncodeHead() - buf := res.buffer - hl := len(buf) + pbuf := res.buffer res.buffer = nil - lenStr := res.formatInt(l, 16) - size := hl + len(lenStr) + l + 4 - if size < maxPacketSize { - buf = append(buf, lenStr...) - buf = append(buf, "\r\n"...) - buf = append(buf, data...) - buf = append(buf, "\r\n"...) - res.buffer = buf - return l, nil + + // Header has been sent, no cached head buffer, + // append the data to body buffer. + if pbuf == nil { + goto APPEND_BODY } - _, err := conn.Write(buf) - if err != nil { - return 0, err + + // If has header buffer and total size < maxPacketSize, + // set the header buffer as the body buffer and process + // the new body data. + // Else, send header buffer first, then process the data. + if len(*pbuf)+len(data) < maxPacketSize { + res.bodyBuffer = pbuf + goto APPEND_BODY + } else { + _, err = conn.Write(*pbuf) + mempool.Free(pbuf) + if err != nil { + return 0, err + } + } + } + +APPEND_BODY: + if res.bodyBuffer == nil { + // If "Content-Length" has been set, + // and no cached buffer, + // and the data size >= maxPacketSize, + // send the data directly. + if cl > 0 && len(data) >= maxPacketSize { + res.bodyWritten += l + return conn.Write(data) + } + + // Prepare a new buffer for caching the data. + res.bodyBuffer = mempool.Malloc(l) + *res.bodyBuffer = (*res.bodyBuffer)[0:0] + } else if cl > 0 && len(*res.bodyBuffer)+len(data) > maxPacketSize { + // If "Content-Length" has been set, + // has cached buffer, and + // the data total size >= maxPacketSize, + // send the cached buffer first. + if len(*res.bodyBuffer) > 0 { + _, err = conn.Write(*res.bodyBuffer) + *res.bodyBuffer = (*res.bodyBuffer)[0:0] + if err != nil { + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + return 0, err + } } - buf = mempool.Malloc(0) - buf = append(buf, lenStr...) - buf = append(buf, "\r\n"...) - buf = append(buf, data...) - buf = append(buf, "\r\n"...) - if len(buf) < maxPacketSize { - res.buffer = buf - return l, nil + + // If the new data size >= maxPacketSize, + // send the new data directly. + if len(data) >= maxPacketSize { + res.bodyWritten += l + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + return conn.Write(data) } - return conn.Write(buf) } - if len(res.header[contentLengthHeader]) > 0 { - res.eoncodeHead() + // Append the data to the body buffer cache. + res.bodyWritten += l + res.bodyBuffer = mempool.Append(res.bodyBuffer, data...) + if cl > 0 && len(*res.bodyBuffer) >= maxPacketSize { + l, err = conn.Write(*res.bodyBuffer) + if err != nil { + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + } else { + *res.bodyBuffer = (*res.bodyBuffer)[0:0] + } + return l, err + } - buf := res.buffer - res.buffer = nil - if buf == nil { - buf = mempool.Malloc(l)[0:0] + return l, nil +} + +// writeChunk . +// +//go:norace +func (res *Response) writeChunk(conn net.Conn, data []byte, l int) (int, error) { + res.eoncodeHead() + + var pbuf = res.buffer + res.buffer = nil + var lenStr = res.formatInt(l, 16) + var totalSize = len(lenStr) + len(data) + 4 + + if pbuf != nil { + totalSize += len(*pbuf) + } + + // If total size < maxPacketSize, append the data to the cache buffer, + // then return and wait for new data. + if totalSize < maxPacketSize { + if pbuf == nil { + pbuf = mempool.Malloc(totalSize) } - buf = append(buf, data...) - return conn.Write(buf) + pbuf = mempool.AppendString(pbuf, lenStr) + pbuf = mempool.AppendString(pbuf, "\r\n") + pbuf = mempool.Append(pbuf, data...) + pbuf = mempool.AppendString(pbuf, "\r\n") + res.buffer = pbuf + return l, nil } - if res.bodyBuffer == nil { - res.bodyBuffer = mempool.Malloc(l)[0:0] + + var err error + + // When total size >= maxPacketSize: + // 1. If has cache buffer, send the cache buffer and length string first. + if pbuf != nil { + pbuf = mempool.AppendString(pbuf, lenStr) + pbuf = mempool.AppendString(pbuf, "\r\n") + _, err = conn.Write(*pbuf) + mempool.Free(pbuf) + if err != nil { + return 0, err + } + + // Reset the cache buffer. + *pbuf = (*pbuf)[0:0] + } else { + // 2. Append length string to the new buffer. + pbuf = mempool.Malloc(totalSize) + *pbuf = (*pbuf)[0:0] + pbuf = mempool.AppendString(pbuf, lenStr) + pbuf = mempool.AppendString(pbuf, "\r\n") } - res.bodyBuffer = append(res.bodyBuffer, data...) - // res.header[contentLengthHeader] = []string{res.formatInt(l, 10)} + // 3. Append data and tail to the buffer and send the buffer. + pbuf = mempool.Append(pbuf, data...) + pbuf = mempool.AppendString(pbuf, "\r\n") + if len(*pbuf) < maxPacketSize { + res.buffer = pbuf + return l, nil + } + _, err = conn.Write(*pbuf) + mempool.Free(pbuf) + if err != nil { + return 0, err + } return l, nil } +func (res *Response) contentLength() (int, error) { + if res.contentLen > 0 { + return res.contentLen, nil + } + cl := res.header.Get(contentLengthHeader) + if cl == "" { + return 0, nil + } + v, err := strconv.ParseInt(cl, 10, 64) + if err == nil { + res.contentLen = int(v) + } + return int(v), err +} + // ReadFrom . +// +//go:norace func (res *Response) ReadFrom(r io.Reader) (n int64, err error) { - c := res.parser.Processor.Conn() + c := res.Parser.Conn if c == nil { return 0, nil } res.hasBody = true res.eoncodeHead() - _, err = c.Write(res.buffer) + _, err = c.Write(*res.buffer) + mempool.Free(res.buffer) res.buffer = nil if err != nil { return 0, err } - if res.enableSendfile { + if !res.Parser.Engine.DisableSendfile { lr, ok := r.(*io.LimitedReader) if ok { n, r = lr.N, lr.R @@ -180,9 +319,22 @@ func (res *Response) ReadFrom(r io.Reader) (n int64, err error) { f, ok := r.(*os.File) if ok { - nc, ok := c.(interface { + rc := c + if hc, ok := c.(*Conn); ok { + rc = hc.Conn + } + nc, ok := rc.(interface { Sendfile(f *os.File, remain int64) (int64, error) }) + if !ok { + hc, ok2 := c.(*Conn) + if ok2 { + nc, ok = hc.Conn.(interface { + Sendfile(f *os.File, remain int64) (int64, error) + }) + } + + } if ok { ns, err := nc.Sendfile(f, lr.N) return ns, err @@ -193,79 +345,139 @@ func (res *Response) ReadFrom(r io.Reader) (n int64, err error) { return io.Copy(c, r) } +// Push implements the http.Pusher interface. +// It is not supported by nbhttp, so it always returns an error. +// +//go:norace +func (res *Response) Push(target string, opts *http.PushOptions) error { + return errors.New("http: server push not supported") +} + +// Flush implements http.Flusher. It sends any buffered data to the client. +// +//go:norace +func (res *Response) Flush() { + if res.hijacked { + return + } + if res.Parser == nil || res.Parser.Conn == nil { + return + } + + res.WriteHeader(http.StatusOK) + res.checkChunked() + res.eoncodeHead() + + conn := res.Parser.Conn + + if res.buffer != nil && len(*res.buffer) > 0 { + _, err := conn.Write(*res.buffer) + if err != nil { + logging.Error("Response.Flush: buffer write failed: %v", err) + mempool.Free(res.buffer) + res.buffer = nil + } else { + *res.buffer = (*res.buffer)[:0] + } + } + + if res.bodyBuffer != nil && len(*res.bodyBuffer) > 0 { + _, err := conn.Write(*res.bodyBuffer) + if err != nil { + logging.Error("Response.Flush: bodyBuffer write failed: %v", err) + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + } else { + *res.bodyBuffer = (*res.bodyBuffer)[:0] + } + } +} + // checkChunked . +// +//go:norace func (res *Response) checkChunked() { if res.chunkChecked { return } - res.chunkChecked = true - // res.WriteHeader(http.StatusOK) - - if res.request.ProtoAtLeast(1, 1) { - for _, v := range res.header[transferEncodingHeader] { - if v == "chunked" { - res.chunked = true - } + // 1. See if chunking is already set + for _, v := range res.header[transferEncodingHeader] { + if v == "chunked" { + res.chunked = true + delete(res.header, contentLengthHeader) + return } - if !res.chunked { - if len(res.header[trailerHeader]) > 0 { - res.chunked = true - hs := res.header[transferEncodingHeader] - res.header[transferEncodingHeader] = append(hs, "chunked") - } + } + + // 2. See if we should fall back to chunking + if res.request.ProtoAtLeast(1, 1) && res.header.Get(contentLengthHeader) == "" { + // Don't chunk for responses that are forbidden from having a body + if res.statusCode != http.StatusNoContent && res.statusCode != http.StatusNotModified { + res.chunked = true } } + + // 3. See if we need to chunk for trailers + if !res.chunked && len(res.header[trailerHeader]) > 0 { + res.chunked = true + } + if res.chunked { + res.header.Set(transferEncodingHeader, "chunked") delete(res.header, contentLengthHeader) } } // flush . +// +//go:norace func (res *Response) eoncodeHead() { if res.headEncoded { return } - res.WriteHeader(http.StatusOK) - res.headEncoded = true status := res.status statusCode := res.statusCode - data := mempool.Malloc(1024)[0:0] + pdata := mempool.Malloc(1024) + *pdata = (*pdata)[0:0] - data = append(data, res.request.Proto...) - data = append(data, ' ', '0'+byte(statusCode/100), '0'+byte(statusCode%100)/10, '0'+byte(statusCode%10), ' ') - data = append(data, status...) - data = append(data, '\r', '\n') + pdata = mempool.AppendString(pdata, res.request.Proto) + pdata = mempool.Append(pdata, ' ', '0'+byte(statusCode/100), '0'+byte(statusCode%100)/10, '0'+byte(statusCode%10), ' ') + pdata = mempool.AppendString(pdata, status) + pdata = mempool.Append(pdata, '\r', '\n') if res.hasBody && len(res.header["Content-Type"]) == 0 { const contentType = "Content-Type: text/plain; charset=utf-8\r\n" - data = append(data, contentType...) + pdata = mempool.AppendString(pdata, contentType) } - if !res.chunked { - const contentLenthKey = "Content-Length: " + if !res.chunked && len(res.header[contentLengthHeader]) == 0 { + const contentLenthPrefix = "Content-Length: " if !res.hasBody { - data = append(data, contentLenthKey...) - data = append(data, '0', '\r', '\n') + pdata = mempool.AppendString(pdata, contentLenthPrefix) + pdata = mempool.Append(pdata, '0', '\r', '\n') } else { - data = append(data, contentLenthKey...) - l := len(res.bodyBuffer) + pdata = mempool.AppendString(pdata, contentLenthPrefix) + l := 0 + if res.bodyBuffer != nil { + l = len(*res.bodyBuffer) + } if l > 0 { s := strconv.FormatInt(int64(l), 10) - data = append(data, s...) - data = append(data, '\r', '\n') + pdata = mempool.AppendString(pdata, s) + pdata = mempool.Append(pdata, '\r', '\n') } else { - data = append(data, '0', '\r', '\n') + pdata = mempool.Append(pdata, '0', '\r', '\n') } } } if res.request.Close && len(res.header["Connection"]) == 0 { const connection = "Connection: close\r\n" - data = append(data, connection...) + pdata = mempool.AppendString(pdata, connection) } if len(res.header["Date"]) == 0 { @@ -276,7 +488,7 @@ func (res *Response) eoncodeHead() { hh, mn, ss := t.Clock() day := days[3*t.Weekday():] mon := months[3*(mm-1):] - data = append(data, + pdata = mempool.Append(pdata, 'D', 'a', 't', 'e', ':', ' ', day[0], day[1], day[2], ',', ' ', byte('0'+dd/10), byte('0'+dd%10), ' ', @@ -297,10 +509,10 @@ func (res *Response) eoncodeHead() { for k, vv := range res.header { if _, ok := res.trailer[k]; !ok { for _, v := range vv { - data = append(data, k...) - data = append(data, ':', ' ') - data = append(data, v...) - data = append(data, '\r', '\n') + pdata = mempool.AppendString(pdata, k) + pdata = mempool.Append(pdata, ':', ' ') + pdata = mempool.AppendString(pdata, v) + pdata = mempool.Append(pdata, '\r', '\n') } } else if len(vv) > 0 { v := res.header.Get(k) @@ -309,57 +521,79 @@ func (res *Response) eoncodeHead() { } } - data = append(data, '\r', '\n') - res.buffer = data + pdata = mempool.Append(pdata, '\r', '\n') + res.buffer = pdata } -func (res *Response) flushTrailer(conn io.Writer) error { +//go:norace +func (res *Response) flush(conn io.Writer) error { var err error if !res.chunked { if res.buffer != nil { - if res.bodyBuffer != nil { - res.buffer = append(res.buffer, res.bodyBuffer...) - mempool.Free(res.bodyBuffer) - res.bodyBuffer = nil + if res.bodyBuffer != nil && len(*res.bodyBuffer) > 0 { + if len(*res.buffer)+len(*res.bodyBuffer) > maxPacketSize { + _, err = conn.Write(*res.buffer) + mempool.Free(res.buffer) + res.buffer = nil + if err != nil { + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + return err + } + res.buffer = res.bodyBuffer + res.bodyBuffer = nil + } else { + res.buffer = mempool.Append(res.buffer, (*res.bodyBuffer)...) + mempool.Free(res.bodyBuffer) + res.bodyBuffer = nil + } } - _, err = conn.Write(res.buffer) + _, err = conn.Write(*res.buffer) + mempool.Free(res.buffer) res.buffer = nil if err != nil { return err } } - if res.bodyBuffer != nil { - _, err = conn.Write(res.bodyBuffer) + if res.bodyBuffer != nil && len(*res.bodyBuffer) > 0 { + _, err = conn.Write(*res.bodyBuffer) + mempool.Free(res.bodyBuffer) res.bodyBuffer = nil } return err } - data := res.buffer + pdata := res.buffer res.buffer = nil - if data == nil { - data = mempool.Malloc(0) - } if len(res.trailer) == 0 { - data = append(data, "0\r\n\r\n"...) + if pdata == nil { + pdata = mempool.Malloc(0) + } + pdata = mempool.AppendString(pdata, "0\r\n\r\n") } else { - data = append(data, "0\r\n"...) + if pdata == nil { + pdata = mempool.Malloc(512) + *pdata = (*pdata)[0:0] + } + pdata = mempool.AppendString(pdata, "0\r\n") for k, v := range res.trailer { - data = append(data, k...) - data = append(data, ": "...) - data = append(data, v...) - data = append(data, "\r\n"...) + pdata = mempool.AppendString(pdata, k) + pdata = mempool.AppendString(pdata, ": ") + pdata = mempool.AppendString(pdata, v) + pdata = mempool.AppendString(pdata, "\r\n") } - data = append(data, "\r\n"...) + pdata = mempool.AppendString(pdata, "\r\n") } - _, err = conn.Write(data) + _, err = conn.Write(*pdata) + mempool.Free(pdata) return err } var numMap = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'} +//go:norace func (res *Response) formatInt(n int, base int) string { if n < 0 || n > 0x7FFFFFFF { return "" @@ -379,11 +613,12 @@ func (res *Response) formatInt(n int, base int) string { } // NewResponse . -func NewResponse(parser *Parser, request *http.Request, enableSendfile bool) *Response { +// +//go:norace +func NewResponse(parser *Parser, request *http.Request) *Response { res := responsePool.Get().(*Response) - res.parser = parser + res.Parser = parser res.request = request res.header = http.Header{ /*"Server": []string{"nbio"}*/ } - res.enableSendfile = enableSendfile return res } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/server.go b/vendor/github.com/lesismal/nbio/nbhttp/server.go index 25ed4334..955e6d7c 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/server.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/server.go @@ -16,6 +16,8 @@ type Server struct { } // NewServer . +// +//go:norace func NewServer(conf Config, v ...interface{}) *Server { if len(v) > 0 { if handler, ok := v[0].(http.Handler); ok { @@ -31,6 +33,8 @@ func NewServer(conf Config, v ...interface{}) *Server { } // NewServerTLS . +// +//go:norace func NewServerTLS(conf Config, v ...interface{}) *Server { if len(v) > 0 { if handler, ok := v[0].(http.Handler); ok { diff --git a/vendor/github.com/lesismal/nbio/nbhttp/table.go b/vendor/github.com/lesismal/nbio/nbhttp/table.go index 4421107a..0b4395df 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/table.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/table.go @@ -16,6 +16,7 @@ var ( "DELETE": true, "TRACE": true, "CONNECT": true, + "PATCH": true, // RFC 5789 // http 2.0 "PRI": true, @@ -111,6 +112,7 @@ var ( validMethodCharMap = [256]bool{} ) +//go:norace func init() { var dis byte = 'a' - 'A' @@ -145,14 +147,17 @@ func init() { // headerCharMap['?'] = true } +//go:norace func isAlpha(c byte) bool { return alphaCharMap[c] } +//go:norace func isNum(c byte) bool { return numCharMap[c] } +//go:norace func isHex(c byte) bool { return hexCharMap[c] } @@ -161,14 +166,17 @@ func isHex(c byte) bool { // return alphaNumCharMap[c] // } +//go:norace func isToken(c byte) bool { return tokenCharMap[c] } +//go:norace func isValidMethod(m string) bool { return validMethods[strings.ToUpper(m)] } +//go:norace func isValidMethodChar(c byte) bool { return validMethodCharMap[c] } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/tests/poller_test.go b/vendor/github.com/lesismal/nbio/nbhttp/tests/poller_test.go deleted file mode 100644 index 0a661159..00000000 --- a/vendor/github.com/lesismal/nbio/nbhttp/tests/poller_test.go +++ /dev/null @@ -1,206 +0,0 @@ -//go:build !unit -// +build !unit - -// run this test via :go test -tags=integration . - -package nbhttp - -import ( - "context" - "fmt" - "log" - "net/http" - "net/url" - "sync" - "testing" - "time" - - "github.com/lesismal/llib/std/crypto/tls" - "github.com/lesismal/nbio/nbhttp" - "github.com/lesismal/nbio/nbhttp/websocket" -) - -var ( - count = 3 - chWait = make(chan struct{}, count) - text = "hello world" - addr = "localhost:8889" - - svr *nbhttp.Server -) - -func onWebsocket(w http.ResponseWriter, r *http.Request) { - upgrader := websocket.NewUpgrader() - upgrader.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { - c.WriteMessage(messageType, data) - c.SetReadDeadline(time.Now().Add(nbhttp.DefaultKeepaliveTime)) - }) - upgrader.OnClose(func(c *websocket.Conn, err error) { - fmt.Println("OnClose:", c.RemoteAddr().String(), err) - }) - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - panic(err) - } - wsConn := conn.(*websocket.Conn) - fmt.Println("OnOpen:", wsConn.RemoteAddr().String()) -} - -func server(ctx context.Context, started *sync.WaitGroup, startupError *error) { - cert, err := tls.X509KeyPair(rsaCertPEM, rsaKeyPEM) - if err != nil { - log.Fatalf("tls.X509KeyPair failed: %v", err) - } - tlsConfig := &tls.Config{ - Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: true, - } - - mux := &http.ServeMux{} - mux.HandleFunc("/wss", onWebsocket) - - svr = nbhttp.NewServer(nbhttp.Config{ - Network: "tcp", - AddrsTLS: []string{addr}, - TLSConfig: tlsConfig, - Handler: mux, - }) - - err = svr.Start() - if err != nil { - fmt.Printf("nbio.Start failed: %v\n", err) - *startupError = err - started.Done() - return - } - started.Done() - defer svr.Stop() - - <-ctx.Done() - log.Println("exit") -} - -func client() error { - u := url.URL{Scheme: "wss", Host: addr, Path: "/wss"} - log.Printf("connecting to %s", u.String()) - - // engine := nbhttp.NewEngine(nbhttp.Config{ - // TLSConfig: , - // }) - // err := engine.Start() - // if err != nil { - // fmt.Printf("nbio.Start failed: %v\n", err) - // return err - // } - - dialer := &websocket.Dialer{ - Engine: svr.Engine, - Upgrader: func() *websocket.Upgrader { - u := websocket.NewUpgrader() - u.OnMessage(func(c *websocket.Conn, messageType websocket.MessageType, data []byte) { - c.WriteMessage(messageType, data) - chWait <- struct{}{} - }) - return u - }(), - DialTimeout: time.Second * 3, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - - c, res, err := dialer.Dial(u.String(), nil) - if err != nil { - log.Fatal("dial:", err) - } - if res.Body != nil { - res.Body.Close() - } - defer c.Close() - - err = c.WriteMessage(websocket.TextMessage, []byte(text)) - if err != nil { - log.Fatalf("write: %v", err) - return err - } - - for i := 0; i < count; i++ { - <-chWait - } - - return nil -} -func TestServerSimulation(t *testing.T) { - waitGrp := sync.WaitGroup{} - ctx, cancelFunc := context.WithCancel(context.Background()) - waitGrp.Add(1) - var err error - go server(ctx, &waitGrp, &err) - waitGrp.Wait() - if err != nil { - t.Errorf("client should not have errorred: %s", err) - cancelFunc() - return - } - err = client() - if err != nil { - t.Errorf("client should not have errorred: %s", err) - } - err = client() - if err != nil { - t.Errorf("client should not have errorred: %s", err) - } - cancelFunc() -} - -var rsaCertPEM = []byte(`-----BEGIN CERTIFICATE----- -MIIDazCCAlOgAwIBAgIUJeohtgk8nnt8ofratXJg7kUJsI4wDQYJKoZIhvcNAQEL -BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM -GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMDcwODIyNThaFw0zMDEy -MDUwODIyNThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw -HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQCy+ZrIvwwiZv4bPmvKx/637ltZLwfgh3ouiEaTchGu -IQltthkqINHxFBqqJg44TUGHWthlrq6moQuKnWNjIsEc6wSD1df43NWBLgdxbPP0 -x4tAH9pIJU7TQqbznjDBhzRbUjVXBIcn7bNknY2+5t784pPF9H1v7h8GqTWpNH9l -cz/v+snoqm9HC+qlsFLa4A3X9l5v05F1uoBfUALlP6bWyjHAfctpiJkoB9Yw1TJa -gpq7E50kfttwfKNkkAZIbib10HugkMoQJAs2EsGkje98druIl8IXmuvBIF6nZHuM -lt3UIZjS9RwPPLXhRHt1P0mR7BoBcOjiHgtSEs7Wk+j7AgMBAAGjUzBRMB0GA1Ud -DgQWBBQdheJv73XSOhgMQtkwdYPnfO02+TAfBgNVHSMEGDAWgBQdheJv73XSOhgM -QtkwdYPnfO02+TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBf -SKVNMdmBpD9m53kCrguo9iKQqmhnI0WLkpdWszc/vBgtpOE5ENOfHGAufHZve871 -2fzTXrgR0TF6UZWsQOqCm5Oh3URsCdXWewVMKgJ3DCii6QJ0MnhSFt6+xZE9C6Hi -WhcywgdR8t/JXKDam6miohW8Rum/IZo5HK9Jz/R9icKDGumcqoaPj/ONvY4EUwgB -irKKB7YgFogBmCtgi30beLVkXgk0GEcAf19lHHtX2Pv/lh3m34li1C9eBm1ca3kk -M2tcQtm1G89NROEjcG92cg+GX3GiWIjbI0jD1wnVy2LCOXMgOVbKfGfVKISFt0b1 -DNn00G8C6ttLoGU2snyk ------END CERTIFICATE----- -`) - -var rsaKeyPEM = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAsvmayL8MImb+Gz5rysf+t+5bWS8H4Id6LohGk3IRriEJbbYZ -KiDR8RQaqiYOOE1Bh1rYZa6upqELip1jYyLBHOsEg9XX+NzVgS4HcWzz9MeLQB/a -SCVO00Km854wwYc0W1I1VwSHJ+2zZJ2Nvube/OKTxfR9b+4fBqk1qTR/ZXM/7/rJ -6KpvRwvqpbBS2uAN1/Zeb9ORdbqAX1AC5T+m1soxwH3LaYiZKAfWMNUyWoKauxOd -JH7bcHyjZJAGSG4m9dB7oJDKECQLNhLBpI3vfHa7iJfCF5rrwSBep2R7jJbd1CGY -0vUcDzy14UR7dT9JkewaAXDo4h4LUhLO1pPo+wIDAQABAoIBAF6yWwekrlL1k7Xu -jTI6J7hCUesaS1yt0iQUzuLtFBXCPS7jjuUPgIXCUWl9wUBhAC8SDjWe+6IGzAiH -xjKKDQuz/iuTVjbDAeTb6exF7b6yZieDswdBVjfJqHR2Wu3LEBTRpo9oQesKhkTS -aFF97rZ3XCD9f/FdWOU5Wr8wm8edFK0zGsZ2N6r57yf1N6ocKlGBLBZ0v1Sc5ShV -1PVAxeephQvwL5DrOgkArnuAzwRXwJQG78L0aldWY2q6xABQZQb5+ml7H/kyytef -i+uGo3jHKepVALHmdpCGr9Yv+yCElup+ekv6cPy8qcmMBqGMISL1i1FEONxLcKWp -GEJi6QECgYEA3ZPGMdUm3f2spdHn3C+/+xskQpz6efiPYpnqFys2TZD7j5OOnpcP -ftNokA5oEgETg9ExJQ8aOCykseDc/abHerYyGw6SQxmDbyBLmkZmp9O3iMv2N8Pb -Nrn9kQKSr6LXZ3gXzlrDvvRoYUlfWuLSxF4b4PYifkA5AfsdiKkj+5sCgYEAzseF -XDTRKHHJnzxZDDdHQcwA0G9agsNj64BGUEjsAGmDiDyqOZnIjDLRt0O2X3oiIE5S -TXySSEiIkxjfErVJMumLaIwqVvlS4pYKdQo1dkM7Jbt8wKRQdleRXOPPN7msoEUk -Ta9ZsftHVUknPqblz9Uthb5h+sRaxIaE1llqDiECgYATS4oHzuL6k9uT+Qpyzymt -qThoIJljQ7TgxjxvVhD9gjGV2CikQM1Vov1JBigj4Toc0XuxGXaUC7cv0kAMSpi2 -Y+VLG+K6ux8J70sGHTlVRgeGfxRq2MBfLKUbGplBeDG/zeJs0tSW7VullSkblgL6 -nKNa3LQ2QEt2k7KHswryHwKBgENDxk8bY1q7wTHKiNEffk+aFD25q4DUHMH0JWti -fVsY98+upFU+gG2S7oOmREJE0aser0lDl7Zp2fu34IEOdfRY4p+s0O0gB+Vrl5VB -L+j7r9bzaX6lNQN6MvA7ryHahZxRQaD/xLbQHgFRXbHUyvdTyo4yQ1821qwNclLk -HUrhAoGAUtjR3nPFR4TEHlpTSQQovS8QtGTnOi7s7EzzdPWmjHPATrdLhMA0ezPj -Mr+u5TRncZBIzAZtButlh1AHnpN/qO3P0c0Rbdep3XBc/82JWO8qdb5QvAkxga3X -BpA7MNLxiqss+rCbwf3NbWxEMiDQ2zRwVoafVFys7tjmv6t2Xck= ------END RSA PRIVATE KEY----- -`) diff --git a/vendor/github.com/lesismal/nbio/nbhttp/upgrader.go b/vendor/github.com/lesismal/nbio/nbhttp/upgrader.go deleted file mode 100644 index 8cb5a212..00000000 --- a/vendor/github.com/lesismal/nbio/nbhttp/upgrader.go +++ /dev/null @@ -1,23 +0,0 @@ -package nbhttp - -import ( - "bufio" - "net" - "net/http" -) - -// Hijacker . -type Hijacker interface { - Hijack() (net.Conn, *bufio.ReadWriter, error) -} - -type ReadCloser interface { - Read(p *Parser, data []byte) error - Close(p *Parser, err error) -} - -// Upgrader . -type Upgrader interface { - ReadCloser - Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (net.Conn, error) -} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/compression.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/compression.go index a3ff9ac3..3cb05635 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/compression.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/compression.go @@ -22,13 +22,15 @@ var ( }} ) +//go:norace func isValidCompressionLevel(level int) bool { return minCompressionLevel <= level && level <= maxCompressionLevel } +//go:norace func decompressReader(r io.Reader) io.ReadCloser { fr, _ := flateReaderPool.Get().(io.ReadCloser) - fr.(flate.Resetter).Reset(r, nil) + _ = fr.(flate.Resetter).Reset(r, nil) return &flateReadWrapper{fr} } @@ -36,6 +38,7 @@ type flateReadWrapper struct { fr io.ReadCloser } +//go:norace func (r *flateReadWrapper) Read(p []byte) (int, error) { if r.fr == nil { return 0, io.ErrClosedPipe @@ -45,11 +48,12 @@ func (r *flateReadWrapper) Read(p []byte) (int, error) { // Preemptively place the reader back in the pool. This helps with // scenarios where the application does not call NextReader() soon after // this final read. - r.Close() + _ = r.Close() } return n, err } +//go:norace func (r *flateReadWrapper) Close() error { if r.fr == nil { return io.ErrClosedPipe @@ -60,6 +64,7 @@ func (r *flateReadWrapper) Close() error { return err } +//go:norace func compressWriter(w io.WriteCloser, level int) io.WriteCloser { p := &flateWriterPools[level-minCompressionLevel] fw, _ := p.Get().(*flate.Writer) @@ -78,6 +83,7 @@ type truncWriter struct { p [4]byte } +//go:norace func (w *truncWriter) Write(p []byte) (int, error) { n := 0 @@ -110,10 +116,12 @@ type flateWriteWrapper struct { p *sync.Pool } +//go:norace func (w *flateWriteWrapper) Write(p []byte) (int, error) { return w.fw.Write(p) } +//go:norace func (w *flateWriteWrapper) Close() error { err := w.fw.Flush() w.p.Put(w.fw) diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/conn.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/conn.go index 6fd41b45..99108539 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/conn.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/conn.go @@ -6,12 +6,20 @@ package websocket import ( "bytes" + "context" "encoding/binary" "errors" + "fmt" + "io" "math/rand" "net" + "runtime" + "strings" "sync" + "time" + "unsafe" + "github.com/lesismal/nbio/logging" "github.com/lesismal/nbio/mempool" "github.com/lesismal/nbio/nbhttp" ) @@ -45,101 +53,628 @@ const ( // Conn . type Conn struct { + *commonFields net.Conn mux sync.Mutex - isClient bool + closeErr error - onCloseCalled bool - remoteCompressionEnabled bool - enableWriteCompression bool - compressionLevel int + chSessionInited chan struct{} + session interface{} subprotocol string - session interface{} + compressionLevel int + onClose func(c *Conn, err error) + + sendQueue []*[]byte + sendQueueSize uint16 + closed bool + isClient bool + enableCompression bool + remoteCompressionEnabled bool + enableWriteCompression bool + isBlockingMod bool + isReadingByParser bool + isInReadingLoop bool + expectingFragments bool + compress bool + releasePayload bool + msgType MessageType + message *[]byte + bytesCached *[]byte - onClose func(c *Conn, err error) Engine *nbhttp.Engine + Execute func(f func()) bool } -func validCloseCode(code int) bool { - switch code { - case 1000: - return true //| Normal Closure | hybi@ietf.org | RFC 6455 | - case 1001: - return true // | Going Away | hybi@ietf.org | RFC 6455 | - case 1002: - return true // | Protocol error | hybi@ietf.org | RFC 6455 | - case 1003: - return true // | Unsupported Data| hybi@ietf.org | RFC 6455 | - case 1004: - return false // | ---Reserved---- | hybi@ietf.org | RFC 6455 | - case 1005: - return false // | No Status Rcvd | hybi@ietf.org | RFC 6455 | - case 1006: - return false // | Abnormal Closure| hybi@ietf.org | RFC 6455 | - case 1007: - return true // | Invalid frame | hybi@ietf.org | RFC 6455 | - // | | payload data | | | - case 1008: - return true // | Policy Violation| hybi@ietf.org | RFC 6455 | - case 1009: - return true // | Message Too Big | hybi@ietf.org | RFC 6455 | - case 1010: - return true // | Mandatory Ext. | hybi@ietf.org | RFC 6455 | - case 1011: - return true // | Internal Server | hybi@ietf.org | RFC 6455 | - // | | Error | | | - case 1015: - return true // | TLS handshake | hybi@ietf.org | RFC 6455 +//go:norace +func (c *Conn) UnderlayerConn() net.Conn { + return c.Conn +} + +// IsClient . +// +//go:norace +func (c *Conn) IsClient() bool { + return c.isClient +} + +// SetClient . +// +//go:norace +func (c *Conn) SetClient(isClient bool) { + c.isClient = isClient +} + +// IsBlockingMod . +// +//go:norace +func (c *Conn) IsBlockingMod() bool { + return c.isBlockingMod +} + +// IsAsyncWrite . +// +//go:norace +func (c *Conn) IsAsyncWrite() bool { + return c.sendQueue != nil +} + +// Close . +// +//go:norace +func (c *Conn) Close() error { + if c.Conn == nil { + return nil + } + if c.IsAsyncWrite() { + c.Engine.AfterFunc(c.BlockingModAsyncCloseDelay, func() { _ = c.Conn.Close() }) + return nil + } + return c.Conn.Close() +} + +// CloseWithError . +// +//go:norace +func (c *Conn) CloseWithError(err error) { + c.SetCloseError(err) + _ = c.Close() +} + +// SetCloseError . +// +//go:norace +func (c *Conn) SetCloseError(err error) { + c.mux.Lock() + if c.closeErr == nil { + c.closeErr = err + } + c.mux.Unlock() +} + +// CompressionEnabled . +// +//go:norace +func (c *Conn) CompressionEnabled() bool { + return c.compress +} + +//go:norace +func (c *Conn) safeBufferPointer(pbody *[]byte) *[]byte { + if pbody == nil { + var b []byte + pbody = &b + } + return pbody +} + +//go:norace +func (c *Conn) handleDataFrame(opcode MessageType, fin bool, pbody *[]byte) { + pbody = c.safeBufferPointer(pbody) + + h := c.dataFrameHandler + if c.isBlockingMod { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + c.Engine.SyncCall(func() { + h(c, opcode, fin, pbody) + }) + } else { + if !c.Execute(func() { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + h(c, opcode, fin, pbody) + }) { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + } + } +} + +//go:norace +func (c *Conn) handleMessage(opcode MessageType, pbody *[]byte) { + pbody = c.safeBufferPointer(pbody) + + if c.isBlockingMod { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + c.Engine.SyncCall(func() { + c.handleWsMessage(opcode, pbody) + }) + } else { + if !c.Execute(func() { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + c.handleWsMessage(opcode, pbody) + }) { + if c.releasePayload { + defer c.Engine.BodyAllocator.Free(pbody) + } + } + } +} + +//go:norace +func (c *Conn) handleProtocolMessage(opcode MessageType, pbody *[]byte) { + c.handleMessage(opcode, pbody) +} + +//go:norace +func (c *Conn) handleWsMessage(opcode MessageType, pData *[]byte) { + const errInvalidUtf8Text = "invalid UTF-8 bytes" + + if c.KeepaliveTime > 0 { + defer func() { _ = c.SetReadDeadline(time.Now().Add(c.KeepaliveTime)) }() + } + + dataToString := func() string { + s := "" + if pData != nil { + s = string(*pData) + } + return s + } + + switch opcode { + case BinaryMessage: + c.messageHandler(c, opcode, pData) + return + case TextMessage: + if pData != nil && !c.Engine.CheckUtf8(*pData) { + protoErrorData := make([]byte, 2+len(errInvalidUtf8Text)) + binary.BigEndian.PutUint16(protoErrorData, 1002) + copy(protoErrorData[2:], errInvalidUtf8Text) + c.SetCloseError(ErrInvalidUtf8) + _ = c.WriteMessage(CloseMessage, protoErrorData) + goto ErrExit + } + c.messageHandler(c, opcode, pData) + return + case PingMessage: + c.pingMessageHandler(c, dataToString()) + return + case PongMessage: + c.pongMessageHandler(c, dataToString()) + return + case CloseMessage: + var code int + var reason string + if pData == nil || len(*pData) == 0 { + code = 1005 // no status + } else if pData != nil && len(*pData) >= 2 { + code = int(binary.BigEndian.Uint16((*pData)[:2])) + if !validCloseCode(code) { + protoErrorCode := make([]byte, 2) + binary.BigEndian.PutUint16(protoErrorCode, 1002) + c.SetCloseError(ErrInvalidCloseCode) + _ = c.WriteMessage(CloseMessage, protoErrorCode) + goto ErrExit + } + if !c.Engine.CheckUtf8((*pData)[2:]) { + protoErrorData := make([]byte, 2+len(errInvalidUtf8Text)) + binary.BigEndian.PutUint16(protoErrorData, 1002) + copy(protoErrorData[2:], errInvalidUtf8Text) + c.SetCloseError(ErrInvalidUtf8) + _ = c.WriteMessage(CloseMessage, protoErrorData) + goto ErrExit + } + reason = string((*pData)[2:]) + } else { + code = 1002 // protocol_error + } + if code != 1000 { + c.SetCloseError(&CloseError{ + Code: code, + Reason: reason, + }) + } + c.closeMessageHandler(c, code, reason) + case FragmentMessage: + logging.Debug("invalid fragment message") + c.SetCloseError(ErrInvalidFragmentMessage) default: + logging.Debug("invalid message type: %v", opcode) + c.SetCloseError(fmt.Errorf("websocket: invalid message type: %v", opcode)) } - // IANA registration policy and should be granted in the range 3000-3999. - // The range of status codes from 4000-4999 is designated for Private - if code >= 3000 && code < 5000 { - return true + +ErrExit: + _ = c.Close() +} + +//go:norace +func (c *Conn) nextFrame() (int, MessageType, []byte, bool, bool, bool, error) { + var ( + opcode MessageType + body []byte + ok, fin, res1, res2, res3 bool + err error + pdata = c.bytesCached + l int64 + headLen = int64(2) + total int64 + ) + if pdata != nil { + l = int64(len(*pdata)) } - return false + if l >= 2 { + opcode = MessageType((*pdata)[0] & 0xF) + res1 = int8((*pdata)[0]&0x40) != 0 + res2 = int8((*pdata)[0]&0x20) != 0 + res3 = int8((*pdata)[0]&0x10) != 0 + fin = (((*pdata)[0] & 0x80) != 0) + payloadLen := (*pdata)[1] & 0x7F + bodyLen := int64(-1) + + switch payloadLen { + case 126: + if l >= 4 { + bodyLen = int64(binary.BigEndian.Uint16((*pdata)[2:4])) + headLen = 4 + } + case 127: + if len(*pdata) >= 10 { + bodyLen = int64(binary.BigEndian.Uint64((*pdata)[2:10])) + if bodyLen < 0 { + return 0, 0, nil, false, false, false, ErrInvalidFragmentMessage + } + headLen = 10 + } + default: + bodyLen = int64(payloadLen) + } + + ml := 0 + if c.message != nil { + ml = len(*c.message) + } + if c.isMessageTooLarge(ml + int(bodyLen)) { + return 0, 0, nil, false, false, false, ErrMessageTooLarge + } + + if (bodyLen > maxControlFramePayloadSize) && + ((opcode == PingMessage) || (opcode == PongMessage) || (opcode == CloseMessage)) { + return 0, 0, nil, false, false, false, ErrControlMessageTooBig + } + + if bodyLen >= 0 { + masked := ((*pdata)[1] & 0x80) != 0 + if masked { + headLen += 4 + } + total = headLen + bodyLen + if l >= total { + body = (*pdata)[headLen:total] + if masked { + maskXOR(body, (*pdata)[headLen-4:headLen]) + } + + ok = true + err = c.validFrame(opcode, fin, res1, res2, res3, c.expectingFragments) + } + } + } + + return int(total), opcode, body, ok, fin, res1, err } -// OnClose . -func (c *Conn) OnClose(h func(*Conn, error)) { - if h != nil { - c.onClose = func(c *Conn, err error) { +// Read . +// +//go:norace +func (c *Conn) Parse(data []byte) (retErr error) { + if len(data) == 0 { + return nil + } + + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return net.ErrClosed + } + + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("websocket.Conn.Parse failed: %v\n%v\n", + err, + *(*string)(unsafe.Pointer(&buf)), + ) + c.mux.Lock() + if c.bytesCached != nil { + c.Engine.BodyAllocator.Free(c.bytesCached) + c.bytesCached = nil + } + c.mux.Unlock() + retErr = fmt.Errorf("websocket: parse error: %v", err) + } + }() + + readLimit := c.Engine.ReadLimit + if readLimit > 0 && (c.bytesCached != nil && (len(*c.bytesCached)+len(data) > readLimit)) { + c.mux.Unlock() + return nbhttp.ErrTooLong + } + + var allocator = c.Engine.BodyAllocator + if c.bytesCached == nil { + c.bytesCached = allocator.Malloc(len(data)) + copy(*c.bytesCached, data) + } else { + c.bytesCached = allocator.Append(c.bytesCached, data...) + } + c.mux.Unlock() + + var err error + var body []byte + var frame *[]byte + var message *[]byte + var msgType MessageType + var protocolMessage *[]byte + var isProtocolMessage bool + var opcode MessageType + var ok, fin, compress bool + var totalFrameSize int + + releaseBuf := func() { + if frame != nil { + allocator.Free(frame) + } + if message != nil { + allocator.Free(message) + } + if protocolMessage != nil { + allocator.Free(protocolMessage) + } + } + + for !c.closed { + func() { c.mux.Lock() defer c.mux.Unlock() - if !c.onCloseCalled { - c.onCloseCalled = true - h(c, err) + if c.closed { + err = net.ErrClosed + return + } + totalFrameSize, opcode, body, ok, fin, compress, err = c.nextFrame() + if err != nil { + return + } + if !ok { + return + } + + bl := len(body) + switch opcode { + case FragmentMessage, TextMessage, BinaryMessage: + if c.msgType == 0 { + c.msgType = opcode + c.compress = compress + } + msgType = c.msgType + if bl > 0 && c.dataFrameHandler != nil { + frame = allocator.Malloc(bl) + copy(*frame, body) + // if compressed, should check utf8 after decompressed the whole message. + // if c.msgType == TextMessage && len(frame) > 0 && !c.Engine.CheckUtf8(frame) { + // c.Conn.Close() + // err = ErrInvalidUtf8 + // return + // } + } + if c.messageHandler != nil { + if bl > 0 { + if c.message == nil { + c.message = allocator.Malloc(len(body)) + copy(*c.message, body) + } else { + c.message = allocator.Append(c.message, body...) + } + } + if fin { + message = c.message + c.message = nil + if c.compress { + var pb *[]byte + var rc io.ReadCloser + if c.WebsocketDecompressor != nil { + rc = c.WebsocketDecompressor(c, io.MultiReader(bytes.NewBuffer(*message), strings.NewReader(flateReaderTail))) + } else { + rc = decompressReader(io.MultiReader(bytes.NewBuffer(*message), strings.NewReader(flateReaderTail))) + } + pb, err = c.readAll(rc, len(*message)*2) + allocator.Free(message) + message = pb + _ = rc.Close() + if err != nil { + releaseBuf() + return + } + } + c.msgType = 0 + c.compress = false + c.expectingFragments = false + } else { + c.expectingFragments = true + } + } + case PingMessage, PongMessage, CloseMessage: + isProtocolMessage = true + if bl > 0 { + protocolMessage = allocator.Malloc(len(body)) + copy(*protocolMessage, body) + } + default: + err = ErrInvalidFragmentMessage + return + } + + l := len(*c.bytesCached) + if totalFrameSize <= 0 || totalFrameSize > l { + releaseBuf() + c.Engine.BodyAllocator.Free(c.bytesCached) + c.bytesCached = nil + err = fmt.Errorf("websocket: invalid frame consumed size %d, cached %d", totalFrameSize, l) + return + } + if l == totalFrameSize { + c.Engine.BodyAllocator.Free(c.bytesCached) + c.bytesCached = nil + } else { + copy(*c.bytesCached, (*c.bytesCached)[totalFrameSize:l]) + *c.bytesCached = (*c.bytesCached)[:l-totalFrameSize] + } + }() + + if err != nil { + if errors.Is(err, ErrMessageTooLarge) || errors.Is(err, ErrControlMessageTooBig) { + _ = c.WriteClose(1009, err.Error()) + } + return err + } + + if message != nil { + c.handleMessage(msgType, message) + message = nil + } + if frame != nil { + c.handleDataFrame(msgType, fin, frame) + frame = nil + } + if isProtocolMessage { + c.handleProtocolMessage(opcode, protocolMessage) + protocolMessage = nil + isProtocolMessage = false + } + + // need more data + if !ok { + break + } + } + + return nil +} + +// OnMessage . +// +//go:norace +func (c *Conn) OnMessage(h func(*Conn, MessageType, []byte)) { + c.messageHandler = func(c *Conn, messageType MessageType, messagePtr *[]byte) { + if !c.closed && h != nil { + if messagePtr != nil { + h(c, messageType, *messagePtr) + } else { + h(c, messageType, nil) } } + } +} - // now all the upgrade, frames/messages and close are called in order - // nbc, ok := c.Conn.(*nbio.Conn) - // if ok { - // nbc.Lock() - // defer nbc.Unlock() - // closed, err := nbc.IsClosed() - // if closed { - // c.onClose(c, err) - // } - // } +// OnMessagePtr . +// +//go:norace +func (c *Conn) OnMessagePtr(h func(*Conn, MessageType, *[]byte)) { + c.messageHandler = func(c *Conn, messageType MessageType, messagePtr *[]byte) { + if !c.closed && h != nil { + h(c, messageType, messagePtr) + } } } +// OnDataFrame . +// +//go:norace +func (c *Conn) OnDataFrame(h func(*Conn, MessageType, bool, []byte)) { + c.dataFrameHandler = func(c *Conn, messageType MessageType, fin bool, framePtr *[]byte) { + if !c.closed && h != nil { + if framePtr != nil { + h(c, messageType, fin, *framePtr) + } else { + h(c, messageType, fin, nil) + } + } + } +} + +// OnDataFramePtr . +// +//go:norace +func (c *Conn) OnDataFramePtr(h func(*Conn, MessageType, bool, *[]byte)) { + c.dataFrameHandler = func(c *Conn, messageType MessageType, fin bool, framePtr *[]byte) { + if !c.closed && h != nil { + h(c, messageType, fin, framePtr) + } + } +} + +// EnableCompression . +// +//go:norace +func (c *Conn) EnableCompression(enable bool) { + c.enableCompression = enable +} + +//go:norace +func (c *Conn) OnClose(h func(*Conn, error)) { + c.onClose = h +} + +// WriteClose . +// +//go:norace +func (c *Conn) WriteClose(code int, reason string) error { + buf := make([]byte, 2+len(reason)) + binary.BigEndian.PutUint16(buf[:2], uint16(code)) + copy(buf[2:], reason) + return c.WriteMessage(CloseMessage, buf) +} + // WriteMessage . +// +//go:norace func (c *Conn) WriteMessage(messageType MessageType, data []byte) error { c.mux.Lock() defer c.mux.Unlock() + if c.closed { + return net.ErrClosed + } + switch messageType { case TextMessage: case BinaryMessage: case PingMessage, PongMessage, CloseMessage: if len(data) > maxControlFramePayloadSize { - return ErrInvalidControlFrame + return ErrControlMessageTooBig } case FragmentMessage: default: @@ -147,34 +682,42 @@ func (c *Conn) WriteMessage(messageType MessageType, data []byte) error { compress := c.enableWriteCompression && (messageType == TextMessage || messageType == BinaryMessage) if compress { - compress = true w := &writeBuffer{ - Buffer: bytes.NewBuffer(mempool.Malloc(len(data))), + allocator: c.Engine.BodyAllocator, + } + defer func() { _ = w.Close() }() + + var cw io.WriteCloser + if c.WebsocketCompressor != nil { + cw = c.WebsocketCompressor(c, w, c.compressionLevel) + } else { + cw = compressWriter(w, c.compressionLevel) } - defer w.Close() - w.Reset() - cw := compressWriter(w, c.compressionLevel) _, err := cw.Write(data) if err != nil { compress = false } else { - cw.Close() - data = w.Bytes() + _ = cw.Close() + if w.pbuf != nil { + data = *w.pbuf + } } } if len(data) > 0 { sendOpcode := true + sendCompress := compress for len(data) > 0 { n := len(data) if n > c.Engine.MaxWebsocketFramePayloadSize { n = c.Engine.MaxWebsocketFramePayloadSize } - err := c.writeFrame(messageType, sendOpcode, n == len(data), data[:n], compress) + err := c.writeFrame(messageType, sendOpcode, n == len(data), data[:n], sendCompress) if err != nil { return err } sendOpcode = false + sendCompress = false data = data[n:] } return nil @@ -183,34 +726,172 @@ func (c *Conn) WriteMessage(messageType MessageType, data []byte) error { return c.writeFrame(messageType, true, true, []byte{}, compress) } +// Keepalive . +// +//go:norace +func (c *Conn) Keepalive(d time.Duration) *time.Timer { + var fn func() + var timer *time.Timer + fn = func() { + err := c.WriteMessage(PingMessage, []byte{}) + if err != nil { + return + } + timer.Reset(d) + } + timer = time.AfterFunc(d, fn) + return timer +} + // Session returns user session. +// +//go:norace func (c *Conn) Session() interface{} { + if c.chSessionInited == nil { + return c.session + } + return c.SessionWithLock() +} + +// SessionWithLock returns user session with lock, returns as soon as the session has been seted. +// +//go:norace +func (c *Conn) SessionWithLock() interface{} { + c.mux.Lock() + ch := c.chSessionInited + c.mux.Unlock() + if ch != nil { + <-ch + } + return c.session +} + +// SessionWithContext returns user session, returns as soon as the session has been seted or +// waits until the context is done. +// +//go:norace +func (c *Conn) SessionWithContext(ctx context.Context) interface{} { + c.mux.Lock() + ch := c.chSessionInited + c.mux.Unlock() + if ch != nil { + select { + case <-ch: + case <-ctx.Done(): + } + + } return c.session } // SetSession sets user session. +// +//go:norace func (c *Conn) SetSession(session interface{}) { + c.mux.Lock() c.session = session + if c.chSessionInited != nil { + close(c.chSessionInited) + c.chSessionInited = nil + } + c.mux.Unlock() } type writeBuffer struct { - *bytes.Buffer + pbuf *[]byte + allocator mempool.Allocator +} + +// Write . +// +//go:norace +func (w *writeBuffer) Write(p []byte) (n int, err error) { + if w.pbuf == nil { + w.pbuf = w.allocator.Malloc(len(p)) + return copy(*w.pbuf, p), nil + } + w.pbuf = w.allocator.Append(w.pbuf, p...) + return len(p), nil } // Close . +// +//go:norace func (w *writeBuffer) Close() error { - mempool.Free(w.Bytes()) + if w.pbuf != nil { + w.allocator.Free(w.pbuf) + w.pbuf = nil + } return nil } +// CloseAndClean . +// +//go:norace +func (c *Conn) CloseAndClean(err error) { + // c.WriteClose(1000, "normal close") + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return + } + + c.closed = true + + if c.chSessionInited != nil { + close(c.chSessionInited) + c.chSessionInited = nil + } + + for i, b := range c.sendQueue { + if b != nil { + c.Engine.BodyAllocator.Free(b) + c.sendQueue[i] = nil + } + } + + if c.closeErr == nil { + c.closeErr = err + } + + if c.Conn != nil { + _ = c.Conn.Close() + } + + if c.bytesCached != nil { + c.Engine.BodyAllocator.Free(c.bytesCached) + c.bytesCached = nil + } + if c.message != nil { + c.Engine.BodyAllocator.Free(c.message) + c.message = nil + } + + c.mux.Unlock() + + if c.onClose != nil { + c.onClose(c, c.closeErr) + } +} + // WriteFrame . +// +//go:norace func (c *Conn) WriteFrame(messageType MessageType, sendOpcode, fin bool, data []byte) error { + c.mux.Lock() + defer c.mux.Unlock() + + if c.closed { + return net.ErrClosed + } + return c.writeFrame(messageType, sendOpcode, fin, data, false) } +//go:norace func (c *Conn) writeFrame(messageType MessageType, sendOpcode, fin bool, data []byte, compress bool) error { var ( - buf []byte + pbuf *[]byte byte1 byte maskLen int headLen int @@ -224,60 +905,111 @@ func (c *Conn) writeFrame(messageType MessageType, sendOpcode, fin bool, data [] if bodyLen < 126 { headLen = 2 + maskLen - buf = mempool.Malloc(len(data) + headLen) - buf[0] = 0 - buf[1] = (byte1 | byte(bodyLen)) + pbuf = c.Engine.BodyAllocator.Malloc(len(data) + headLen) + (*pbuf)[0] = 0 + (*pbuf)[1] = (byte1 | byte(bodyLen)) } else if bodyLen <= 65535 { headLen = 4 + maskLen - buf = mempool.Malloc(len(data) + headLen) - buf[0] = 0 - buf[1] = (byte1 | 126) - binary.BigEndian.PutUint16(buf[2:4], uint16(bodyLen)) + pbuf = c.Engine.BodyAllocator.Malloc(len(data) + headLen) + (*pbuf)[0] = 0 + (*pbuf)[1] = (byte1 | 126) + binary.BigEndian.PutUint16((*pbuf)[2:4], uint16(bodyLen)) } else { headLen = 10 + maskLen - buf = mempool.Malloc(len(data) + headLen) - buf[0] = 0 - buf[1] = (byte1 | 127) - binary.BigEndian.PutUint64(buf[2:10], uint64(bodyLen)) + pbuf = c.Engine.BodyAllocator.Malloc(len(data) + headLen) + (*pbuf)[0] = 0 + (*pbuf)[1] = (byte1 | 127) + binary.BigEndian.PutUint64((*pbuf)[2:10], uint64(bodyLen)) } if c.isClient { u32 := rand.Uint32() - maskKey := []byte{byte(u32), byte(u32 >> 8), byte(u32 >> 16), byte(u32 >> 24)} - copy(buf[headLen-4:headLen], maskKey) - for i := 0; i < len(data); i++ { - buf[headLen+i] = (data[i] ^ maskKey[i%4]) - } + binary.LittleEndian.PutUint32((*pbuf)[headLen-4:headLen], u32) + copy((*pbuf)[headLen:], data) + maskXOR((*pbuf)[headLen:], (*pbuf)[headLen-4:headLen]) } else { - copy(buf[headLen:], data) + copy((*pbuf)[headLen:], data) } // opcode if sendOpcode { - buf[0] = byte(messageType) + (*pbuf)[0] = byte(messageType) } else { - buf[0] = 0 + (*pbuf)[0] = 0 } if compress { - buf[0] |= 0x40 + (*pbuf)[0] |= 0x40 } // fin if fin { - buf[0] |= byte(0x80) + (*pbuf)[0] |= byte(0x80) + } + + if c.sendQueue != nil { + if c.sendQueueSize > 0 && len(c.sendQueue) >= int(c.sendQueueSize) { + c.Engine.BodyAllocator.Free(pbuf) + return ErrMessageSendQuqueIsFull + } + c.sendQueue = append(c.sendQueue, pbuf) + isHead := (len(c.sendQueue) == 1) + + if isHead { + c.sendQueue[0] = nil + go func() { + i := 0 + for { + _, err := c.Conn.Write(*pbuf) + c.Engine.BodyAllocator.Free(pbuf) + if err != nil { + c.CloseWithError(err) + return + } + + i++ + + c.mux.Lock() + if c.closed { + c.mux.Unlock() + return + } + if len(c.sendQueue) <= i { + c.sendQueue = c.sendQueue[:0] + c.mux.Unlock() + return + } + + pbuf = c.sendQueue[i] + c.sendQueue[i] = nil + + c.mux.Unlock() + + if pbuf == nil { + return + } + } + }() + } + return nil } - _, err := c.Conn.Write(buf) + _, err := c.Conn.Write(*pbuf) + c.Engine.BodyAllocator.Free(pbuf) + return err } // Write overwrites nbio.Conn.Write. +// +//go:norace func (c *Conn) Write(data []byte) (int, error) { return -1, ErrInvalidWriteCalling } // EnableWriteCompression . +// +//go:norace func (c *Conn) EnableWriteCompression(enable bool) { if enable { if c.remoteCompressionEnabled { @@ -288,25 +1020,233 @@ func (c *Conn) EnableWriteCompression(enable bool) { } } -// SetCompressionLevel . -func (c *Conn) SetCompressionLevel(level int) error { - if !isValidCompressionLevel(level) { - return errors.New("websocket: invalid compression level") - } - c.compressionLevel = level - return nil +// Subprotocol returns the negotiated websocket subprotocol. +// +//go:norace +func (c *Conn) Subprotocol() string { + return c.subprotocol } -func newConn(u *Upgrader, c net.Conn, subprotocol string, remoteCompressionEnabled bool) *Conn { - conn := &Conn{ +//go:norace +func NewClientConn(opt *Options, c net.Conn, subprotocol string, remoteCompressionEnabled bool, asyncWrite bool) *Conn { + return newConn(opt, c, subprotocol, remoteCompressionEnabled, asyncWrite, true) +} + +//go:norace +func NewServerConn(u *Upgrader, c net.Conn, subprotocol string, remoteCompressionEnabled bool, asyncWrite bool) *Conn { + return newConn(u, c, subprotocol, remoteCompressionEnabled, asyncWrite, false) +} + +//go:norace +func newConn(u *Upgrader, c net.Conn, subprotocol string, remoteCompressionEnabled bool, asyncWrite bool, isClient bool) *Conn { + wsc := &Conn{ + commonFields: &u.commonFields, + Engine: u.Engine, Conn: c, subprotocol: subprotocol, + enableCompression: u.enableCompression, remoteCompressionEnabled: remoteCompressionEnabled, - compressionLevel: defaultCompressionLevel, - onClose: func(*Conn, error) {}, + compressionLevel: u.compressionLevel, + onClose: u.onClose, + isClient: isClient, + } + wsc.EnableWriteCompression(remoteCompressionEnabled) + if asyncWrite { + wsc.sendQueue = make([]*[]byte, u.BlockingModSendQueueInitSize)[:0] + wsc.sendQueueSize = u.BlockingModSendQueueMaxSize + if wsc.BlockingModAsyncCloseDelay <= 0 { + wsc.BlockingModAsyncCloseDelay = DefaultBlockingModAsyncCloseDelay + } } - conn.EnableWriteCompression(u.enableWriteCompression) - conn.SetCompressionLevel(u.compressionLevel) + return wsc +} + +// HandleRead . +// +//go:norace +func (c *Conn) HandleRead(bufSize int) { + if c.isReadingByParser { + return + } + c.mux.Lock() + reading := c.isInReadingLoop + c.isInReadingLoop = true + c.mux.Unlock() + if reading { + return + } + + var ( + n int + err error + buf []byte + ) + + if bufSize <= 0 { + bufSize = DefaultBlockingReadBufferSize + } + buf = make([]byte, bufSize) + + defer func() { + c.CloseAndClean(err) + }() + + for { + n, err = c.Read(buf) + if err != nil { + break + } + err = c.Parse(buf[:n]) + if err != nil { + break + } + } +} + +// return false if length is ok. +// +//go:norace +func (c *Conn) isMessageTooLarge(len int) bool { + // <=0 means unlimitted size + if c.MessageLengthLimit <= 0 { + return false + } + return len > c.MessageLengthLimit +} + +//go:norace +func (c *Conn) validFrame(opcode MessageType, fin, res1, res2, res3, expectingFragments bool) error { + if res1 && !c.enableCompression { + return ErrReserveBitSet + } + if res2 || res3 { + return ErrReserveBitSet + } + if opcode > BinaryMessage && opcode < CloseMessage { + return fmt.Errorf("%w: opcode=%d", ErrReservedMessageType, opcode) + } + if !fin && (opcode != FragmentMessage && opcode != TextMessage && opcode != BinaryMessage) { + return fmt.Errorf("%w: opcode=%d", ErrControlMessageFragmented, opcode) + } + if expectingFragments && (opcode == TextMessage || opcode == BinaryMessage) { + return ErrFragmentsShouldNotHaveBinaryOrTextMessage + } + return nil +} + +//go:norace +func (c *Conn) readAll(r io.Reader, size int) (*[]byte, error) { + const maxAppendSize = 1024 * 1024 * 4 + if c.MessageLengthLimit > 0 && size > c.MessageLengthLimit { + size = c.MessageLengthLimit + } + pbuf := c.Engine.BodyAllocator.Malloc(size) + *pbuf = (*pbuf)[0:0] + for { + n, err := r.Read((*pbuf)[len(*pbuf):cap(*pbuf)]) + if n > 0 { + *pbuf = (*pbuf)[:len(*pbuf)+n] + } + if err != nil { + if err == io.EOF { + err = nil + } + return pbuf, err + } + if len(*pbuf) == cap(*pbuf) { + l := len(*pbuf) + // can not extend more bytes. + if c.isMessageTooLarge(l + 1) { + return nil, ErrMessageTooLarge + } + al := l + if al > maxAppendSize { + al = maxAppendSize + } + // extend to the limit size at most. + if (c.MessageLengthLimit > 0) && (l+al > c.MessageLengthLimit) { + al = c.MessageLengthLimit - l + } + pbuf = c.Engine.BodyAllocator.Append(pbuf, make([]byte, al)...) + *pbuf = (*pbuf)[:l] + } + } +} - return conn +//go:norace +func validCloseCode(code int) bool { + switch code { + case 1000: + return true //| Normal Closure | hybi@ietf.org | RFC 6455 | + case 1001: + return true // | Going Away | hybi@ietf.org | RFC 6455 | + case 1002: + return true // | Protocol error | hybi@ietf.org | RFC 6455 | + case 1003: + return true // | Unsupported Data| hybi@ietf.org | RFC 6455 | + case 1004: + return false // | ---Reserved---- | hybi@ietf.org | RFC 6455 | + case 1005: + return false // | No Status Rcvd | hybi@ietf.org | RFC 6455 | + case 1006: + return false // | Abnormal Closure| hybi@ietf.org | RFC 6455 | + case 1007: + return true // | Invalid frame | hybi@ietf.org | RFC 6455 | + // | | payload data | | | + case 1008: + return true // | Policy Violation| hybi@ietf.org | RFC 6455 | + case 1009: + return true // | Message Too Big | hybi@ietf.org | RFC 6455 | + case 1010: + return true // | Mandatory Ext. | hybi@ietf.org | RFC 6455 | + case 1011: + return true // | Internal Server | hybi@ietf.org | RFC 6455 | + // | | Error | | | + case 1015: + return true // | TLS handshake | hybi@ietf.org | RFC 6455 + default: + } + // IANA registration policy and should be granted in the range 3000-3999. + // The range of status codes from 4000-4999 is designated for Private + if code >= 3000 && code < 5000 { + return true + } + return false +} + +//go:norace +func maskXOR(b, key []byte) { + key64 := uint64(binary.LittleEndian.Uint32(key)) + key64 |= (key64 << 32) + + for len(b) >= 64 { + v := binary.LittleEndian.Uint64(b) + binary.LittleEndian.PutUint64(b, v^key64) + v = binary.LittleEndian.Uint64(b[8:16]) + binary.LittleEndian.PutUint64(b[8:16], v^key64) + v = binary.LittleEndian.Uint64(b[16:24]) + binary.LittleEndian.PutUint64(b[16:24], v^key64) + v = binary.LittleEndian.Uint64(b[24:32]) + binary.LittleEndian.PutUint64(b[24:32], v^key64) + v = binary.LittleEndian.Uint64(b[32:40]) + binary.LittleEndian.PutUint64(b[32:40], v^key64) + v = binary.LittleEndian.Uint64(b[40:48]) + binary.LittleEndian.PutUint64(b[40:48], v^key64) + v = binary.LittleEndian.Uint64(b[48:56]) + binary.LittleEndian.PutUint64(b[48:56], v^key64) + v = binary.LittleEndian.Uint64(b[56:64]) + binary.LittleEndian.PutUint64(b[56:64], v^key64) + b = b[64:] + } + + for len(b) >= 8 { + v := binary.LittleEndian.Uint64(b[:8]) + binary.LittleEndian.PutUint64(b[:8], v^key64) + b = b[8:] + } + + for i := 0; i < len(b); i++ { + idx := i & 3 + b[i] ^= key[idx] + } } diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/dialer.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/dialer.go index abcf5b2f..41f5364d 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/dialer.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/dialer.go @@ -28,6 +28,7 @@ const ( type Dialer struct { Engine *nbhttp.Engine + Options *Options Upgrader *Upgrader Jar http.CookieJar @@ -48,6 +49,8 @@ type Dialer struct { } // Dial . +// +//go:norace func (d *Dialer) Dial(urlStr string, requestHeader http.Header, v ...interface{}) (*Conn, *http.Response, error) { ctx := context.Background() if d.DialTimeout > 0 { @@ -57,14 +60,19 @@ func (d *Dialer) Dial(urlStr string, requestHeader http.Header, v ...interface{} } // DialContext . +// +//go:norace func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header, v ...interface{}) (*Conn, *http.Response, error) { if d.Cancel != nil { defer d.Cancel() } - upgrader := d.Upgrader - if upgrader == nil { - return nil, nil, errors.New("invalid Upgrader: nil") + options := d.Options + if options == nil { + options = d.Upgrader + } + if options == nil { + return nil, nil, errors.New("invalid Options: nil") } challengeKey, err := challengeKey() @@ -133,7 +141,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h } } - if d.EnableCompression { + if options.enableCompression { req.Header[secWebsocketExtHeaderField] = []string{"permessage-deflate; server_no_context_takeover; client_no_context_takeover"} } @@ -148,7 +156,7 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h var res *http.Response var errCh chan error if asyncHandler == nil { - errCh = make(chan error) + errCh = make(chan error, 1) } cliConn := &nbhttp.ClientConn{ @@ -168,11 +176,13 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h case errCh <- e: case <-ctx.Done(): if conn != nil { - conn.Close() + _ = conn.Close() } } } else { - asyncHandler(wsConn, res, e) + d.Engine.Execute(func() { + asyncHandler(wsConn, res, e) + }) } } @@ -183,7 +193,13 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h nbc, ok := conn.(*nbio.Conn) if !ok { - tlsConn, tlsOk := conn.(*tls.Conn) + nbhttpConn, ok2 := conn.(*nbhttp.Conn) + if !ok2 { + err = ErrBadHandshake + notifyResult(err) + return + } + tlsConn, tlsOk := nbhttpConn.Conn.(*tls.Conn) if !tlsOk { err = ErrBadHandshake notifyResult(err) @@ -203,9 +219,6 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h notifyResult(err) return } - state := &connState{common: upgrader} - - parser.ConnState = state if d.Jar != nil { if rc := resp.Cookies(); len(rc) > 0 { @@ -239,16 +252,14 @@ func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader h break } - wsConn = newConn(upgrader, conn, resp.Header.Get(secWebsocketProtoHeaderField), remoteCompressionEnabled) - wsConn.isClient = true - wsConn.Engine = d.Engine - wsConn.OnClose(upgrader.onClose) - - state.conn = wsConn - state.Engine = parser.Engine + wsConn = NewClientConn(options, conn, resp.Header.Get(secWebsocketProtoHeaderField), remoteCompressionEnabled, false) + parser.ParserCloser = wsConn + wsConn.Engine = parser.Engine + wsConn.Execute = parser.Execute + nbc.SetSession(wsConn) - if upgrader.openHandler != nil { - upgrader.openHandler(wsConn) + if wsConn.openHandler != nil { + wsConn.openHandler(wsConn) } notifyResult(err) diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/error.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/error.go index bd775d06..63a73f16 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/error.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/error.go @@ -6,6 +6,7 @@ package websocket import ( "errors" + "fmt" ) var ( @@ -39,14 +40,17 @@ var ( // ErrReserveBitSet . ErrReserveBitSet = errors.New("websocket: reserved bit set it frame") - // ErrReservedOpcodeSet . - ErrReservedOpcodeSet = errors.New("websocket: reserved opcode received") + // ErrReservedMessageType . + ErrReservedMessageType = errors.New("websocket: reserved message type received") // ErrControlMessageFragmented . ErrControlMessageFragmented = errors.New("websocket: control messages must not be fragmented") - // ErrFragmentsShouldNotHaveBinaryOrTextOpcode . - ErrFragmentsShouldNotHaveBinaryOrTextOpcode = errors.New("websocket: fragments should not have opcode of text or binary") + // ErrControlMessageTooBig . + ErrControlMessageTooBig = errors.New("websocket: control frame length > 125") + + // ErrFragmentsShouldNotHaveBinaryOrTextMessage . + ErrFragmentsShouldNotHaveBinaryOrTextMessage = errors.New("websocket: fragments should not have message type of text or binary") // ErrInvalidCloseCode . ErrInvalidCloseCode = errors.New("websocket: invalid close code") @@ -57,9 +61,53 @@ var ( // ErrInvalidCompression . ErrInvalidCompression = errors.New("websocket: invalid compression negotiation") + // ErrInvalidUtf8 . + ErrInvalidUtf8 = errors.New("websocket: invalid UTF-8 bytes") + + // ErrInvalidFragmentMessage . + ErrInvalidFragmentMessage = errors.New("invalid fragment message") + // ErrMalformedURL . - ErrMalformedURL = errors.New("malformed ws or wss URL") + ErrMalformedURL = errors.New("websocket: malformed ws or wss URL") // ErrMessageTooLarge. ErrMessageTooLarge = errors.New("message exceeds the configured limit") + + // ErrMessageSendQuqueIsFull . + ErrMessageSendQuqueIsFull = errors.New("message send queue is full") ) + +// CloseError . +type CloseError struct { + Code int + Reason string +} + +// Error . +// +//go:norace +func (ce CloseError) Error() string { + return fmt.Sprintf("websocket: close code=%d and reason=%q", ce.Code, ce.Reason) +} + +// CloseCode . +// +//go:norace +func CloseCode(err error) int { + var ce CloseError + if errors.As(err, &ce) { + return ce.Code + } + return -1 +} + +// CloseReason . +// +//go:norace +func CloseReason(err error) string { + var ce CloseError + if errors.As(err, &ce) { + return ce.Reason + } + return "" +} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader.go index 666eb46d..fb4e9832 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader.go @@ -1,101 +1,210 @@ package websocket import ( - "bytes" "crypto/rand" "crypto/sha1" "encoding/base64" "encoding/binary" - "fmt" + "errors" "io" "net" "net/http" "net/url" + "runtime" "strings" "time" "unicode/utf8" + "unsafe" "github.com/lesismal/llib/std/crypto/tls" "github.com/lesismal/nbio" "github.com/lesismal/nbio/logging" - "github.com/lesismal/nbio/mempool" "github.com/lesismal/nbio/nbhttp" ) -// Hijacker . -type Hijacker interface { - Hijack() (net.Conn, error) -} +var ( + // DefaultBlockingReadBufferSize . + DefaultBlockingReadBufferSize = 1024 * 4 -// Upgrader . -type Upgrader struct { - ReadLimit int64 - // MessageLengthLimit is the maximum length of websocket message. 0 for unlimited. - MessageLengthLimit int64 - HandshakeTimeout time.Duration + // DefaultBlockingModAsyncWrite . + DefaultBlockingModAsyncWrite = true - enableCompression bool - enableWriteCompression bool - compressionLevel int - Subprotocols []string + // DefaultBlockingModHandleRead . + DefaultBlockingModHandleRead = true - CheckOrigin func(r *http.Request) bool + // DefaultBlockingModTransferConnToPoller . + DefaultBlockingModTransferConnToPoller = false + + // DefaultBlockingModSendQueueInitSize . + DefaultBlockingModSendQueueInitSize = 4 + + // DefaultBlockingModSendQueueMaxSize . + DefaultBlockingModSendQueueMaxSize uint16 = 0 + + // DefaultMessageLengthLimit . + DefaultMessageLengthLimit = 1024 * 1024 * 4 + + // DefaultBlockingModAsyncCloseDelay . + DefaultBlockingModAsyncCloseDelay = time.Second / 10 + + // DefaultEngine will be set to a Upgrader.Engine to handle details such as buffers. + DefaultEngine = nbhttp.NewEngine(nbhttp.Config{ + ReleaseWebsocketPayload: true, + }) +) + +type commonFields struct { + KeepaliveTime time.Duration + MessageLengthLimit int + BlockingModAsyncCloseDelay time.Duration + + ReleasePayload bool + WebsocketCompressor func(c *Conn, w io.WriteCloser, level int) io.WriteCloser + WebsocketDecompressor func(c *Conn, r io.Reader) io.ReadCloser pingMessageHandler func(c *Conn, appData string) pongMessageHandler func(c *Conn, appData string) closeMessageHandler func(c *Conn, code int, text string) openHandler func(*Conn) - messageHandler func(c *Conn, messageType MessageType, data []byte) - dataFrameHandler func(c *Conn, messageType MessageType, fin bool, data []byte) - onClose func(c *Conn, err error) + messageHandler func(c *Conn, messageType MessageType, messagePtr *[]byte) + dataFrameHandler func(c *Conn, messageType MessageType, fin bool, framePtr *[]byte) } -type connState struct { - common *Upgrader - conn *Conn - expectingFragments bool - compress bool - opcode MessageType - buffer []byte - message []byte - Engine *nbhttp.Engine + +type Options = Upgrader + +//go:norace +func NewOptions() *Options { + return NewUpgrader() } -// CompressionEnabled . -func (u *connState) CompressionEnabled() bool { - return u.compress +// Upgrader . +type Upgrader struct { + commonFields + + // Engine . + Engine *nbhttp.Engine + + // Subprotocols . + Subprotocols []string + + // CheckOrigin . + CheckOrigin func(r *http.Request) bool + + // HandshakeTimeout represents the timeout duration during websocket handshake. + HandshakeTimeout time.Duration + + // BlockingModReadBufferSize represents the read buffer size of a Conn if it's in blocking mod. + BlockingModReadBufferSize int + + // BlockingModAsyncWrite represents whether use a goroutine to handle writing: + // true: use dynamic goroutine to handle writing. + // false: write buffer to the conn directely. + BlockingModAsyncWrite bool + + // BlockingModHandleRead represents whether start a goroutine to handle reading automatically during `Upgrade``: + // true: start a new goroutine to handle reading. + // false: use the current goroutine to handle reading. + // + // Notice: + // If we start a goroutine to handle read during `Upgrade`, we may receive a new websocket message. + // before we have left the http.Handler for the `Websocket Handshake`. + // Then if we have the logic of `websocket.Conn.SetSession` in the http.Handler, it's possible that when we receive + // and are handling a websocket message and call `websocket.Conn.Session()`, we get nil. + // + // To fix this nil session problem, can use `websocket.Conn.SessionWithLock()`. + // + // For other concurrent problems(including the nil session problem), we can: + // 1st: set this `BlockingModHandleRead = false` + // 2nd: `go wsConn.HandleRead(YourBufSize)` after `Upgrade` and finished initialization. + // Then the websocket message wouldn't come before the http.Handler for `Websocket Handshake` has done. + BlockingModHandleRead bool + + // BlockingModTrasferConnToPoller represents whether try to transfer a blocking connection to nonblocking and add to `Engine``. + // true: try to transfer. + // false: don't try to transfer. + // + // Notice: + // Only `net.TCPConn` and `llib's blocking tls.Conn` can be transferred to nonblocking. + BlockingModTrasferConnToPoller bool + + // BlockingModSendQueueInitSize represents the init size of a Conn's send queue, + // only takes effect when `BlockingModAsyncWrite` is true. + BlockingModSendQueueInitSize int + + // BlockingModSendQueueInitSize represents the max size of a Conn's send queue, + // only takes effect when `BlockingModAsyncWrite` is true. + BlockingModSendQueueMaxSize uint16 + + enableCompression bool + compressionLevel int + onClose func(c *Conn, err error) } // NewUpgrader . +// +//go:norace func NewUpgrader() *Upgrader { - u := &Upgrader{} + u := &Upgrader{ + commonFields: commonFields{ + KeepaliveTime: nbhttp.DefaultKeepaliveTime, + MessageLengthLimit: DefaultMessageLengthLimit, + BlockingModAsyncCloseDelay: DefaultBlockingModAsyncCloseDelay, + }, + compressionLevel: defaultCompressionLevel, + Engine: DefaultEngine, + BlockingModReadBufferSize: DefaultBlockingReadBufferSize, + BlockingModAsyncWrite: DefaultBlockingModAsyncWrite, + BlockingModHandleRead: DefaultBlockingModHandleRead, + BlockingModTrasferConnToPoller: DefaultBlockingModTransferConnToPoller, + BlockingModSendQueueInitSize: DefaultBlockingModSendQueueInitSize, + BlockingModSendQueueMaxSize: DefaultBlockingModSendQueueMaxSize, + } u.pingMessageHandler = func(c *Conn, data string) { - if len(data) > 125 { - c.Close() - return - } err := c.WriteMessage(PongMessage, []byte(data)) if err != nil { logging.Debug("failed to send pong %v", err) - c.Close() + _ = c.Close() return } } u.pongMessageHandler = func(*Conn, string) {} u.closeMessageHandler = func(c *Conn, code int, text string) { - if len(text)+2 > maxControlFramePayloadSize { - return //ErrInvalidControlFrame + if code == 1005 { + _ = c.WriteMessage(CloseMessage, nil) + return } - buf := mempool.Malloc(len(text) + 2) - binary.BigEndian.PutUint16(buf[:2], uint16(code)) - copy(buf[2:], text) - c.WriteMessage(CloseMessage, buf) - mempool.Free(buf) + pbuf := u.Engine.BodyAllocator.Malloc(len(text) + 2) + binary.BigEndian.PutUint16((*pbuf)[:2], uint16(code)) + copy((*pbuf)[2:], text) + _ = c.WriteMessage(CloseMessage, (*pbuf)) + u.Engine.BodyAllocator.Free(pbuf) } + return u } +// EnableCompression . +// +//go:norace +func (u *Upgrader) EnableCompression(enable bool) { + u.enableCompression = enable +} + +// SetCompressionLevel . +// +//go:norace +func (u *Upgrader) SetCompressionLevel(level int) error { + if !isValidCompressionLevel(level) { + return errors.New("websocket: invalid compression level") + } + u.compressionLevel = level + return nil +} + // SetCloseHandler . +// +//go:norace func (u *Upgrader) SetCloseHandler(h func(*Conn, int, string)) { if h != nil { u.closeMessageHandler = h @@ -103,6 +212,8 @@ func (u *Upgrader) SetCloseHandler(h func(*Conn, int, string)) { } // SetPingHandler . +// +//go:norace func (u *Upgrader) SetPingHandler(h func(*Conn, string)) { if h != nil { u.pingMessageHandler = h @@ -110,6 +221,8 @@ func (u *Upgrader) SetPingHandler(h func(*Conn, string)) { } // SetPongHandler . +// +//go:norace func (u *Upgrader) SetPongHandler(h func(*Conn, string)) { if h != nil { u.pongMessageHandler = h @@ -117,75 +230,387 @@ func (u *Upgrader) SetPongHandler(h func(*Conn, string)) { } // OnOpen . +// +//go:norace func (u *Upgrader) OnOpen(h func(*Conn)) { u.openHandler = h } // OnMessage . +// +//go:norace func (u *Upgrader) OnMessage(h func(*Conn, MessageType, []byte)) { - if h != nil { - u.messageHandler = func(c *Conn, messageType MessageType, data []byte) { - if c.Engine.ReleaseWebsocketPayload { - defer c.Engine.BodyAllocator.Free(data) + u.messageHandler = func(c *Conn, messageType MessageType, messagePtr *[]byte) { + if !c.closed && h != nil { + if messagePtr != nil { + h(c, messageType, *messagePtr) + } else { + h(c, messageType, nil) } - h(c, messageType, data) + } + } +} + +// OnMessage . +// +//go:norace +func (u *Upgrader) OnMessagePtr(h func(*Conn, MessageType, *[]byte)) { + u.messageHandler = func(c *Conn, messageType MessageType, messagePtr *[]byte) { + if !c.closed && h != nil { + h(c, messageType, messagePtr) } } } // OnDataFrame . +// +//go:norace func (u *Upgrader) OnDataFrame(h func(*Conn, MessageType, bool, []byte)) { - if h != nil { - u.dataFrameHandler = func(c *Conn, messageType MessageType, fin bool, data []byte) { - if c.Engine.ReleaseWebsocketPayload { - defer c.Engine.BodyAllocator.Free(data) + u.dataFrameHandler = func(c *Conn, messageType MessageType, fin bool, framePtr *[]byte) { + if !c.closed && h != nil { + if framePtr != nil { + h(c, messageType, fin, *framePtr) + } else { + h(c, messageType, fin, nil) } - h(c, messageType, fin, data) + } + } +} + +// OnDataFramePtr . +// +//go:norace +func (u *Upgrader) OnDataFramePtr(h func(*Conn, MessageType, bool, *[]byte)) { + u.dataFrameHandler = func(c *Conn, messageType MessageType, fin bool, framePtr *[]byte) { + if !c.closed && h != nil { + h(c, messageType, fin, framePtr) } } } // OnClose . +// +//go:norace func (u *Upgrader) OnClose(h func(*Conn, error)) { u.onClose = h } -// EnableCompression . -func (u *Upgrader) EnableCompression(enable bool) { - u.enableCompression = enable +// Upgrade . +// +//go:norace +func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, args ...interface{}) (*Conn, error) { + challengeKey, subprotocol, compress, err := u.commCheck(w, r, responseHeader) + if err != nil { + return nil, err + } + + h, ok := w.(http.Hijacker) + if !ok { + return nil, u.returnError(w, r, http.StatusInternalServerError, ErrUpgradeNotHijacker) + } + conn, _, err := h.Hijack() + if err != nil { + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + + var wsc *Conn + var nbc *nbio.Conn + var engine = u.Engine + var parser *nbhttp.Parser + var transferConn = u.BlockingModTrasferConnToPoller + + if len(args) > 0 { + var b bool + b, ok = args[0].(bool) + transferConn = ok && b + } + + getParser := func() { + var nbResonse *nbhttp.Response + nbResonse, ok = w.(*nbhttp.Response) + if ok { + parser = nbResonse.Parser + } + } + + clearNBCWSSession := func() { + if nbc != nil { + if _, ok = nbc.Session().(*Conn); ok { + nbc.SetSession(nil) + } + } + } + + var underLayerConn net.Conn + nbhttpConn, isReadingByParser := conn.(*nbhttp.Conn) + if isReadingByParser { + underLayerConn = nbhttpConn.Conn + parser = nbhttpConn.Parser + } else { + underLayerConn = conn + } + + switch vt := underLayerConn.(type) { + case *nbio.Conn: + // Scenario 1: *nbio.Conn, handled by nbhttp.Engine. + nbc = vt + parser, ok = nbc.Session().(*nbhttp.Parser) + if !ok { + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + wsc = NewServerConn(u, conn, subprotocol, compress, false) + wsc.Engine = parser.Engine + wsc.Execute = parser.Execute + nbc.SetSession(wsc) + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + case *tls.Conn: + // Scenario 2: llib's *tls.Conn. + nbc, ok = vt.Conn().(*nbio.Conn) + if !ok { + // 2.1 The conn may be from std's http.Server.Serve(llib's tls.Listener), + // or from nbhttp.Engine's IOModBlocking/Mixed(blocking part). + if transferConn { + // 2.1.1 Transfer the conn to poller. + nbc, err = nbio.NBConn(vt.Conn()) + if err != nil { + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + if nbhttpConn != nil { + nbhttpConn.Trasfered = true + } + vt.ResetRawInput() + wsc = NewServerConn(u, vt, subprotocol, compress, false) + wsc.Engine = engine + wsc.Execute = nbc.Execute + if engine.EpollMod == nbio.EPOLLET && engine.EPOLLONESHOT == nbio.EPOLLONESHOT { + wsc.Execute = nbhttp.SyncExecutor + } + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + nbc.SetSession(wsc) + nbc.OnData(func(c *nbio.Conn, data []byte) { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("execute failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() + defer vt.ResetOrFreeBuffer() + + var nread int + var readed = data + var buffer = data + var errRead error + for { + _, nread, errRead = vt.AppendAndRead(readed, buffer) + readed = nil + if errRead != nil { + _ = c.CloseWithError(err) + return + } + if nread > 0 { + errRead = wsc.Parse(buffer[:nread]) + if err != nil { + logging.Debug("websocket Conn Parse failed: %v", errRead) + _ = c.CloseWithError(errRead) + return + } + } + if nread == 0 { + return + } + } + }) + nonblock := true + vt.ResetConn(nbc, nonblock) + err = engine.AddTransferredConn(nbc) + if err != nil { + clearNBCWSSession() + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + } else { + // 2.1.2 Don't transfer the conn to poller. + wsc = NewServerConn(u, conn, subprotocol, compress, u.BlockingModAsyncWrite) + wsc.isBlockingMod = true + getParser() + if parser != nil { + wsc.Execute = parser.Execute + parser.ParserCloser = wsc + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + } + } + } else { + // 2.2 The conn is from nbio poller. + parser, ok = nbc.Session().(*nbhttp.Parser) + if !ok { + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + wsc = NewServerConn(u, conn, subprotocol, compress, false) + wsc.Engine = parser.Engine + wsc.Execute = parser.Execute + nbc.SetSession(wsc) + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + } + case *net.TCPConn: + // Scenario 3: std's *net.TCPConn. + if transferConn { + // 3.1 Transfer the conn to poller. + nbc, err = nbio.NBConn(vt) + if err != nil { + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + if nbhttpConn != nil { + nbhttpConn.Trasfered = true + } + + wsc = NewServerConn(u, nbc, subprotocol, compress, false) + wsc.Engine = engine + wsc.Execute = nbc.Execute + if engine.EpollMod == nbio.EPOLLET && engine.EPOLLONESHOT == nbio.EPOLLONESHOT { + wsc.Execute = nbhttp.SyncExecutor + } + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + nbc.SetSession(wsc) + nbc.OnData(func(c *nbio.Conn, data []byte) { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("execute failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() + + errRead := wsc.Parse(data) + if errRead != nil { + logging.Debug("websocket Conn Parse failed: %v", errRead) + _ = c.CloseWithError(errRead) + return + } + }) + err = engine.AddTransferredConn(nbc) + if err != nil { + clearNBCWSSession() + return nil, u.returnError(w, r, http.StatusInternalServerError, err) + } + } else { + // 3.2 Don't transfer the conn to poller. + wsc = NewServerConn(u, conn, subprotocol, compress, u.BlockingModAsyncWrite) + wsc.isBlockingMod = true + getParser() + if parser != nil { + wsc.Execute = parser.Execute + parser.ParserCloser = wsc + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + } + } + default: + // Scenario 4: Unknown conn type, mostly is std *tls.Conn, from std http.Server. + wsc = NewServerConn(u, conn, subprotocol, compress, u.BlockingModAsyncWrite) + wsc.isBlockingMod = true + getParser() + if parser != nil { + wsc.Execute = parser.Execute + parser.ParserCloser = wsc + if nbhttpConn != nil { + nbhttpConn.Parser = nil + } + } + } + + err = u.commResponse(wsc.Conn, responseHeader, challengeKey, subprotocol, compress) + if err != nil { + clearNBCWSSession() + return nil, err + } + + if u.KeepaliveTime > 0 { + _ = conn.SetReadDeadline(time.Now().Add(u.KeepaliveTime)) + } else { + _ = conn.SetReadDeadline(time.Time{}) + } + + if wsc.openHandler != nil { + wsc.openHandler(wsc) + } + + // if parser != nil { + // parser.ReadCloser = wsc + // wsc.Execute = parser.Execute + // } + wsc.isReadingByParser = isReadingByParser + if wsc.isBlockingMod && (!wsc.isReadingByParser) { + var handleRead = u.BlockingModHandleRead + if len(args) > 1 { + var b bool + b, ok = args[1].(bool) + handleRead = ok && b + } + if handleRead { + wsc.chSessionInited = make(chan struct{}) + go wsc.HandleRead(u.BlockingModReadBufferSize) + } + } + + // If upgrader.ReleasePayload is false, which maybe by default, + // then set it to engine.ReleaseWebsocketPayload. + // Considering compatibility with old versions, should set it to + // upgrader.ReleasePayload only when upgrader.ReleasePayload is true. + wsc.releasePayload = u.ReleasePayload + if !wsc.releasePayload { + wsc.releasePayload = wsc.Engine.ReleaseWebsocketPayload + } + + return wsc, nil } -// EnableWriteCompression . -func (u *Upgrader) EnableWriteCompression(enable bool) { - u.enableWriteCompression = enable +//go:norace +func (u *Upgrader) UpgradeAndTransferConnToPoller(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { + const trasferConn = true + return u.Upgrade(w, r, responseHeader, trasferConn) } -// SetCompressionLevel . -func (u *Upgrader) SetCompressionLevel(level int) error { - u.compressionLevel = level - return nil +//go:norace +func (u *Upgrader) UpgradeWithoutHandlingReadForConnFromSTDServer(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { + // handle std server's conn, no need transfer conn to nbio Engine + const trasferConn = false + const handleRead = false + return u.Upgrade(w, r, responseHeader, trasferConn, handleRead) } -// Upgrade . -func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (net.Conn, error) { +//go:norace +func (u *Upgrader) commCheck(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (string, string, bool, error) { if !headerContains(r.Header, "Connection", "upgrade") { - return nil, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeTokenNotFound) + return "", "", false, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeTokenNotFound) } if !headerContains(r.Header, "Upgrade", "websocket") { - return nil, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeTokenNotFound) + return "", "", false, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeTokenNotFound) } if r.Method != "GET" { - return nil, u.returnError(w, r, http.StatusMethodNotAllowed, ErrUpgradeMethodIsGet) + return "", "", false, u.returnError(w, r, http.StatusMethodNotAllowed, ErrUpgradeMethodIsGet) } if !headerContains(r.Header, "Sec-Websocket-Version", "13") { - return nil, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeInvalidWebsocketVersion) + return "", "", false, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeInvalidWebsocketVersion) } if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { - return nil, u.returnError(w, r, http.StatusInternalServerError, ErrUpgradeUnsupportedExtensions) + return "", "", false, u.returnError(w, r, http.StatusInternalServerError, ErrUpgradeUnsupportedExtensions) } checkOrigin := u.CheckOrigin @@ -193,12 +618,12 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade checkOrigin = checkSameOrigin } if !checkOrigin(r) { - return nil, u.returnError(w, r, http.StatusForbidden, ErrUpgradeOriginNotAllowed) + return "", "", false, u.returnError(w, r, http.StatusForbidden, ErrUpgradeOriginNotAllowed) } challengeKey := r.Header.Get("Sec-Websocket-Key") if challengeKey == "" { - return nil, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeMissingWebsocketKey) + return "", "", false, u.returnError(w, r, http.StatusBadRequest, ErrUpgradeMissingWebsocketKey) } subprotocol := u.selectSubprotocol(r, responseHeader) @@ -214,366 +639,67 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade break } } + return challengeKey, subprotocol, compress, nil +} - h, ok := w.(http.Hijacker) - if !ok { - return nil, u.returnError(w, r, http.StatusInternalServerError, ErrUpgradeNotHijacker) - } - conn, _, err := h.Hijack() - if err != nil { - return nil, u.returnError(w, r, http.StatusInternalServerError, err) - } - - nbc, ok := conn.(*nbio.Conn) - if !ok { - tlsConn, tlsOk := conn.(*tls.Conn) - if !tlsOk { - return nil, u.returnError(w, r, http.StatusInternalServerError, err) - } - nbc, tlsOk = tlsConn.Conn().(*nbio.Conn) - if !tlsOk { - return nil, u.returnError(w, r, http.StatusInternalServerError, err) - } - } - - parser, ok := nbc.Session().(*nbhttp.Parser) - if !ok { - return nil, u.returnError(w, r, http.StatusInternalServerError, err) - } - - state := &connState{common: u} - parser.ConnState = state - - buf := mempool.Malloc(1024)[0:0] - buf = append(buf, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) - buf = append(buf, acceptKeyBytes(challengeKey)...) - buf = append(buf, "\r\n"...) +//go:norace +func (u *Upgrader) commResponse(conn net.Conn, responseHeader http.Header, challengeKey, subprotocol string, compress bool) error { + allocator := u.Engine.BodyAllocator + pbuf := allocator.Malloc(1024) + *pbuf = (*pbuf)[0:0] + pbuf = allocator.AppendString(pbuf, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ") + pbuf = allocator.Append(pbuf, acceptKeyBytes(challengeKey)...) + pbuf = allocator.AppendString(pbuf, "\r\n") if subprotocol != "" { - buf = append(buf, "Sec-WebSocket-Protocol: "...) - buf = append(buf, subprotocol...) - buf = append(buf, "\r\n"...) + pbuf = allocator.AppendString(pbuf, "Sec-WebSocket-Protocol: ") + pbuf = allocator.AppendString(pbuf, subprotocol) + pbuf = allocator.AppendString(pbuf, "\r\n") } if compress { - buf = append(buf, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) + pbuf = allocator.AppendString(pbuf, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n") } for k, vs := range responseHeader { if k == "Sec-Websocket-Protocol" { continue } for _, v := range vs { - buf = append(buf, k...) - buf = append(buf, ": "...) + pbuf = allocator.AppendString(pbuf, k) + pbuf = allocator.AppendString(pbuf, ": ") for i := 0; i < len(v); i++ { b := v[i] if b <= 31 { // prevent response splitting. b = ' ' } - buf = append(buf, b) + pbuf = allocator.Append(pbuf, b) } - buf = append(buf, "\r\n"...) + pbuf = allocator.AppendString(pbuf, "\r\n") } } - buf = append(buf, "\r\n"...) + pbuf = allocator.AppendString(pbuf, "\r\n") if u.HandshakeTimeout > 0 { - conn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) + _ = conn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) } - state.conn = newConn(u, conn, subprotocol, compress) - state.Engine = parser.Engine - state.conn.Engine = parser.Engine - - if u.openHandler != nil { - u.openHandler(state.conn) - } - - if _, err = conn.Write(buf); err != nil { - conn.Close() - return nil, err + _, err := conn.Write(*pbuf) + allocator.Free(pbuf) + if err != nil { + _ = conn.Close() + return err } - state.conn.OnClose(u.onClose) - - return state.conn, nil -} - -func (u *connState) validFrame(opcode MessageType, fin, res1, res2, res3, expectingFragments bool) error { - if res1 && !u.common.enableCompression { - return ErrReserveBitSet - } - if res2 || res3 { - return ErrReserveBitSet - } - if opcode > BinaryMessage && opcode < CloseMessage { - return fmt.Errorf("%w: opcode=%d", ErrReservedOpcodeSet, opcode) - } - if !fin && (opcode != FragmentMessage && opcode != TextMessage && opcode != BinaryMessage) { - return fmt.Errorf("%w: opcode=%d", ErrControlMessageFragmented, opcode) - } - if expectingFragments && (opcode == TextMessage || opcode == BinaryMessage) { - return ErrFragmentsShouldNotHaveBinaryOrTextOpcode - } return nil } -// return false if length is ok. -func (u *connState) isMessageTooLarge(len int) bool { - if u.common.MessageLengthLimit == 0 { - // 0 means unlimitted size - return false - } - return len > int(u.common.MessageLengthLimit) -} - -// Read . -func (u *connState) Read(p *nbhttp.Parser, data []byte) error { - bufLen := len(u.buffer) - if u.common.ReadLimit > 0 && (int64(bufLen+len(data)) > u.common.ReadLimit || int64(bufLen+len(u.message)) > u.common.ReadLimit) { - return nbhttp.ErrTooLong - } - - var oldBuffer []byte - if bufLen == 0 { - u.buffer = data - } else { - u.buffer = append(u.buffer, data...) - oldBuffer = u.buffer - } - - var err error - for i := 0; true; i++ { - opcode, body, ok, fin, res1, res2, res3 := u.nextFrame() - if !ok { - break - } - if err = u.validFrame(opcode, fin, res1, res2, res3, u.expectingFragments); err != nil { - break - } - if opcode == FragmentMessage || opcode == TextMessage || opcode == BinaryMessage { - if u.opcode == 0 { - u.opcode = opcode - u.compress = res1 - } - bl := len(body) - if u.common.dataFrameHandler != nil { - var frame []byte - if bl > 0 { - if u.isMessageTooLarge(bl) { - err = ErrMessageTooLarge - break - } - frame = u.Engine.BodyAllocator.Malloc(bl) - copy(frame, body) - } - if u.opcode == TextMessage && len(frame) > 0 && !u.Engine.CheckUtf8(frame) { - u.conn.Close() - } else { - u.common.handleDataFrame(p, u.conn, u.opcode, fin, frame) - } - } - if bl > 0 && u.common.messageHandler != nil { - if u.message == nil { - u.message = u.Engine.BodyAllocator.Malloc(len(body)) - if u.isMessageTooLarge(len(body)) { - err = ErrMessageTooLarge - break - } - copy(u.message, body) - } else { - if u.isMessageTooLarge(len(u.message) + len(body)) { - err = ErrMessageTooLarge - break - } - u.message = append(u.message, body...) - } - } - if fin { - if u.common.messageHandler != nil { - if u.compress { - var b []byte - rc := decompressReader(io.MultiReader(bytes.NewBuffer(u.message), strings.NewReader(flateReaderTail))) - b, err = u.readAll(rc, len(u.message)*2) - u.Engine.BodyAllocator.Free(u.message) - u.message = b - rc.Close() - if err != nil { - break - } - } - u.handleMessage(p, u.opcode, u.message) - } - u.compress = false - u.expectingFragments = false - u.message = nil - u.opcode = 0 - } else { - u.expectingFragments = true - } - } else { - var frame []byte - if len(body) > 0 { - if u.isMessageTooLarge(len(body)) { - err = ErrMessageTooLarge - break - } - frame = u.Engine.BodyAllocator.Malloc(len(body)) - copy(frame, body) - } - u.handleProtocolMessage(p, opcode, frame) - } - - if len(u.buffer) == 0 { - break - } - } - - if bufLen == 0 { - if len(u.buffer) > 0 { - tmp := u.buffer - u.buffer = mempool.Malloc(len(tmp)) - copy(u.buffer, tmp) - } - } else { - if len(u.buffer) < len(oldBuffer) { - tmp := u.buffer - u.buffer = mempool.Malloc(len(tmp)) - copy(u.buffer, tmp) - mempool.Free(oldBuffer) - } - } - - return err -} - -// Close . -func (u *connState) Close(p *nbhttp.Parser, err error) { - if u.conn != nil { - u.conn.onClose(u.conn, err) - } - if len(u.buffer) > 0 { - mempool.Free(u.buffer) - } - if len(u.message) > 0 { - mempool.Free(u.message) - } -} - -func (u *Upgrader) handleDataFrame(p *nbhttp.Parser, c *Conn, opcode MessageType, fin bool, data []byte) { - h := u.dataFrameHandler - p.Execute(func() { - h(c, opcode, fin, data) - }) -} - -func (u *connState) handleMessage(p *nbhttp.Parser, opcode MessageType, body []byte) { - if u.opcode == TextMessage && !u.Engine.CheckUtf8(u.message) { - u.conn.Close() - return - } - - p.Execute(func() { - u.common.handleWsMessage(u.conn, opcode, body) - }) - -} - -func (u *connState) handleProtocolMessage(p *nbhttp.Parser, opcode MessageType, body []byte) { - p.Execute(func() { - u.common.handleWsMessage(u.conn, opcode, body) - if len(body) > 0 && u.Engine.ReleaseWebsocketPayload { - u.Engine.BodyAllocator.Free(body) - } - }) -} - -func (u *Upgrader) handleWsMessage(c *Conn, opcode MessageType, data []byte) { - switch opcode { - case TextMessage, BinaryMessage: - u.messageHandler(c, opcode, data) - case CloseMessage: - if len(data) >= 2 { - code := int(binary.BigEndian.Uint16(data[:2])) - if !validCloseCode(code) || !c.Engine.CheckUtf8(data[2:]) { - protoErrorCode := make([]byte, 2) - binary.BigEndian.PutUint16(protoErrorCode, 1002) - c.WriteMessage(CloseMessage, protoErrorCode) - } else { - u.closeMessageHandler(c, code, string(data[2:])) - } - } else { - c.WriteMessage(CloseMessage, nil) - } - // close immediately, no need to wait for data flushed on a blocked conn - c.Close() - case PingMessage: - u.pingMessageHandler(c, string(data)) - case PongMessage: - u.pongMessageHandler(c, string(data)) - case FragmentMessage: - logging.Debug("invalid fragment message") - c.Close() - default: - c.Close() - } -} - -func (u *connState) nextFrame() (opcode MessageType, body []byte, ok, fin, res1, res2, res3 bool) { - l := int64(len(u.buffer)) - headLen := int64(2) - if l >= 2 { - opcode = MessageType(u.buffer[0] & 0xF) - res1 = int8(u.buffer[0]&0x40) != 0 - res2 = int8(u.buffer[0]&0x20) != 0 - res3 = int8(u.buffer[0]&0x10) != 0 - fin = ((u.buffer[0] & 0x80) != 0) - payloadLen := u.buffer[1] & 0x7F - bodyLen := int64(-1) - - switch payloadLen { - case 126: - if l >= 4 { - bodyLen = int64(binary.BigEndian.Uint16(u.buffer[2:4])) - headLen = 4 - } - case 127: - if len(u.buffer) >= 10 { - bodyLen = int64(binary.BigEndian.Uint64(u.buffer[2:10])) - headLen = 10 - } - default: - bodyLen = int64(payloadLen) - } - if bodyLen >= 0 { - masked := (u.buffer[1] & 0x80) != 0 - if masked { - headLen += 4 - } - total := headLen + bodyLen - if l >= total { - body = u.buffer[headLen:total] - if masked { - maskKey := u.buffer[headLen-4 : headLen] - for i := 0; i < len(body); i++ { - body[i] ^= maskKey[i%4] - } - } - - ok = true - u.buffer = u.buffer[total:l] - } - } - } - - return opcode, body, ok, fin, res1, res2, res3 -} - +//go:norace func (u *Upgrader) returnError(w http.ResponseWriter, _ *http.Request, status int, err error) error { w.Header().Set("Sec-Websocket-Version", "13") http.Error(w, http.StatusText(status), status) return err } +//go:norace func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { if u.Subprotocols != nil { clientProtocols := subprotocols(r) @@ -590,6 +716,7 @@ func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header return "" } +//go:norace func subprotocols(r *http.Request) []string { h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) if h == "" { @@ -604,6 +731,7 @@ func subprotocols(r *http.Request) []string { var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") +//go:norace func acceptKeyString(challengeKey string) string { h := sha1.New() //nolint:gosec // per websocket protocol spec h.Write([]byte(challengeKey)) @@ -611,6 +739,7 @@ func acceptKeyString(challengeKey string) string { return base64.StdEncoding.EncodeToString(h.Sum(nil)) } +//go:norace func acceptKeyBytes(challengeKey string) []byte { h := sha1.New() //nolint:gosec // per websocket protocol spec h.Write([]byte(challengeKey)) @@ -621,6 +750,7 @@ func acceptKeyBytes(challengeKey string) []byte { return buf } +//go:norace func challengeKey() (string, error) { p := make([]byte, 16) if _, err := io.ReadFull(rand.Reader, p); err != nil { @@ -629,6 +759,7 @@ func challengeKey() (string, error) { return base64.StdEncoding.EncodeToString(p), nil } +//go:norace func checkSameOrigin(r *http.Request) bool { origin := r.Header["Origin"] if len(origin) == 0 { @@ -641,24 +772,26 @@ func checkSameOrigin(r *http.Request) bool { return equalASCIIFold(u.Host, r.Host) } +//go:norace func headerContains(header http.Header, name string, value string) bool { var t string values := header[name] +headers: for _, s := range values { for { t, s = nextToken(skipSpace(s)) if t == "" { - continue + continue headers } s = skipSpace(s) if s != "" && s[0] != ',' { - continue + continue headers } if equalASCIIFold(t, value) { return true } if s == "" { - continue + continue headers } s = s[1:] } @@ -666,6 +799,7 @@ func headerContains(header http.Header, name string, value string) bool { return false } +//go:norace func equalASCIIFold(s, t string) bool { for s != "" && t != "" { sr, size := utf8.DecodeRuneInString(s) @@ -688,6 +822,7 @@ func equalASCIIFold(s, t string) bool { return s == t } +//go:norace func parseExtensions(header http.Header) []map[string]string { var result []map[string]string headers: @@ -813,6 +948,7 @@ var isTokenOctet = [256]bool{ '~': true, } +//go:norace func skipSpace(s string) (rest string) { i := 0 for ; i < len(s); i++ { @@ -823,6 +959,7 @@ func skipSpace(s string) (rest string) { return s[i:] } +//go:norace func nextToken(s string) (token, rest string) { i := 0 for ; i < len(s); i++ { @@ -833,6 +970,7 @@ func nextToken(s string) (token, rest string) { return s[:i], s[i:] } +//go:norace func nextTokenOrQuoted(s string) (value string, rest string) { if !strings.HasPrefix(s, "\"") { return nextToken(s) @@ -867,28 +1005,3 @@ func nextTokenOrQuoted(s string) (value string, rest string) { } return "", "" } - -func (u *connState) readAll(r io.Reader, size int) ([]byte, error) { - const maxAppendSize = 1024 * 1024 * 4 - buf := u.Engine.BodyAllocator.Malloc(size)[0:0] - for { - n, err := r.Read(buf[len(buf):cap(buf)]) - if n > 0 { - buf = buf[:len(buf)+n] - } - if err != nil { - if err == io.EOF { - err = nil - } - return buf, err - } - if len(buf) == cap(buf) { - l := len(buf) - al := l - if al > maxAppendSize { - al = maxAppendSize - } - buf = append(buf, make([]byte, al)...)[:l] - } - } -} diff --git a/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader_test.go b/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader_test.go index f613c62b..bc9d3533 100644 --- a/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader_test.go +++ b/vendor/github.com/lesismal/nbio/nbhttp/websocket/upgrader_test.go @@ -28,8 +28,9 @@ func Test_validFrame(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - upgrader := connState{common: NewUpgrader()} - if err := upgrader.validFrame(tt.args.opcode, tt.args.fin, tt.args.res1, tt.args.res2, tt.args.res3, tt.args.expectingFragments); (err != nil) != tt.wantErr { + u := NewUpgrader() + wsc := NewServerConn(u, nil, "", true, true) + if err := wsc.validFrame(tt.args.opcode, tt.args.fin, tt.args.res1, tt.args.res2, tt.args.res3, tt.args.expectingFragments); (err != nil) != tt.wantErr { t.Errorf("validFrame() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/vendor/github.com/lesismal/nbio/nbio_test.go b/vendor/github.com/lesismal/nbio/nbio_test.go index d168ea94..d316b943 100644 --- a/vendor/github.com/lesismal/nbio/nbio_test.go +++ b/vendor/github.com/lesismal/nbio/nbio_test.go @@ -1,10 +1,9 @@ package nbio import ( + "fmt" "io" - "io/ioutil" "log" - "math/rand" "net" "os" "runtime" @@ -14,50 +13,93 @@ import ( "time" ) -var addr = "127.0.0.1:8888" +var addr = "127.0.0.1:9999" var testfile = "test_tmp.file" -var gopher *Gopher +var engine *Engine +var testFileSize = 1024 * 1024 * 32 + +const osWindows = "windows" func init() { - if err := ioutil.WriteFile(testfile, make([]byte, 1024*100), 0600); err != nil { + if err := os.WriteFile(testfile, make([]byte, testFileSize), 0600); err != nil { log.Panicf("write file failed: %v", err) } addrs := []string{addr} - g := NewGopher(Config{ + g := NewEngine(Config{ Network: "tcp", Addrs: addrs, }) + type writtenSizeSession struct { + sumRecv int + sumSend int + isFile bool + } g.OnOpen(func(c *Conn) { - c.SetReadDeadline(time.Now().Add(time.Second * 10)) + wsess := &writtenSizeSession{} + c.SetSession(wsess) + _ = c.SetReadDeadline(time.Now().Add(time.Second * 10)) }) g.OnData(func(c *Conn, data []byte) { + var wsess *writtenSizeSession + if session := c.Session(); session == nil { + panic("invalid session nil") + } else { + wsess = session.(*writtenSizeSession) + wsess.sumRecv += len(data) + } + if len(data) == 8 && string(data) == "sendfile" { - fd, err := os.Open(testfile) + wsess.isFile = true + file, err := os.Open(testfile) if err != nil { log.Panicf("open file failed: %v", err) } - if _, err = c.Sendfile(fd, 0); err != nil { + if _, err = c.Sendfile(file, 0); err != nil { panic(err) } - if err := fd.Close(); err != nil { + if err := file.Close(); err != nil { log.Panicf("close file failed: %v", err) } } else { - c.Write(append([]byte{}, data...)) + _, _ = c.Write(append([]byte{}, data...)) + } + }) + + g.OnWrittenSize(func(c *Conn, b []byte, n int) { + if session := c.Session(); session == nil { + panic("invalid session nil") + } else { + wsess := session.(*writtenSizeSession) + wsess.sumSend += n + } + }) + g.OnClose(func(c *Conn, err error) { + session := c.Session() + if session == nil { + panic("invalid session nil") + } + wsess := session.(*writtenSizeSession) + if wsess.isFile { + if wsess.sumSend != testFileSize { + panic(fmt.Errorf("invalid send size for sendfile: %v, %v", wsess.sumSend, testFileSize)) + } + } else { + if wsess.sumSend != wsess.sumRecv { + panic("invalid send size: not equal to recv size") + } } }) - g.OnClose(func(c *Conn, err error) {}) err := g.Start() if err != nil { log.Panicf("Start failed: %v\n", err) } - gopher = g + engine = g } func TestEcho(t *testing.T) { @@ -66,7 +108,7 @@ func TestEcho(t *testing.T) { var msgSize = 1024 var total int64 = 0 - g := NewGopher(Config{}) + g := NewEngine(Config{}) err := g.Start() if err != nil { log.Panicf("Start failed: %v\n", err) @@ -78,15 +120,18 @@ func TestEcho(t *testing.T) { if c.Session() != 1 { log.Panicf("invalid session: %v", c.Session()) } - c.SetLinger(1, 0) - c.SetNoDelay(true) - c.SetKeepAlive(true) - c.SetKeepAlivePeriod(time.Second * 60) - c.SetDeadline(time.Now().Add(time.Second)) - c.SetReadBuffer(1024 * 4) - c.SetWriteBuffer(1024 * 4) + _ = c.SetLinger(1, 0) + _ = c.SetNoDelay(true) + _ = c.SetKeepAlive(true) + _ = c.SetKeepAlivePeriod(time.Second * 60) + _ = c.SetDeadline(time.Now().Add(time.Second * 10)) + _ = c.SetReadBuffer(1024 * 4) + _ = c.SetWriteBuffer(1024 * 4) log.Printf("connected, local addr: %v, remote addr: %v", c.LocalAddr(), c.RemoteAddr()) }) + // g.BeforeWrite(func(c *Conn) { + // c.SetWriteDeadline(time.Now().Add(time.Second * 5)) + // }) g.OnData(func(c *Conn, data []byte) { recved := atomic.AddInt64(&total, int64(len(data))) if len(data) > 0 && recved >= int64(clientNum*msgSize) { @@ -94,28 +139,27 @@ func TestEcho(t *testing.T) { } }) - g.OnReadBufferAlloc(func(c *Conn) []byte { - return make([]byte, 1024) - }) - g.OnReadBufferFree(func(c *Conn, b []byte) { - + g.OnReadBufferAlloc(func(c *Conn) *[]byte { + b := make([]byte, 1024) + return &b }) + g.OnReadBufferFree(func(c *Conn, pbuf *[]byte) {}) one := func(n int) { c, err := Dial("tcp", addr) if err != nil { log.Panicf("Dial failed: %v", err) } - g.AddConn(c) + _, _ = g.AddConn(c) if n%2 == 0 { - c.Writev([][]byte{make([]byte, msgSize)}) + _, _ = c.Writev([][]byte{make([]byte, msgSize)}) } else { - c.Write(make([]byte, msgSize)) + _, _ = c.Write(make([]byte, msgSize)) } } for i := 0; i < clientNum; i++ { - if runtime.GOOS != "windows" { + if runtime.GOOS != osWindows { one(i) } else { go one(i) @@ -131,21 +175,25 @@ func TestSendfile(t *testing.T) { panic(err) } - buf := make([]byte, 1024*100) + buf := make([]byte, testFileSize) for i := 0; i < 3; i++ { if _, err := conn.Write([]byte("sendfile")); err != nil { log.Panicf("write 'sendfile' failed: %v", err) } - if _, err := io.ReadFull(conn, buf); err != nil { + n, err := io.ReadFull(conn, buf) + if err != nil { log.Panicf("read file failed: %v", err) } + if n != testFileSize { + log.Panicf("read wrong file size: %v != %v", n, testFileSize) + } } } func TestTimeout(t *testing.T) { - g := NewGopher(Config{}) + g := NewEngine(Config{}) err := g.Start() if err != nil { log.Panicf("Start failed: %v\n", err) @@ -156,8 +204,11 @@ func TestTimeout(t *testing.T) { var begin time.Time var timeout = time.Second g.OnOpen(func(c *Conn) { + c.IsTCP() + c.IsUDP() + c.IsUnix() begin = time.Now() - c.SetReadDeadline(begin.Add(timeout)) + _ = c.SetReadDeadline(begin.Add(timeout)) }) g.OnClose(func(c *Conn, err error) { to := time.Since(begin) @@ -172,7 +223,7 @@ func TestTimeout(t *testing.T) { if err != nil { log.Panicf("Dial failed: %v", err) } - g.AddConn(c) + _, _ = g.AddConn(c) } one() @@ -186,9 +237,9 @@ func TestFuzz(t *testing.T) { go func(idx int) { defer wg.Done() if idx%2 == 0 { - Dial("tcp4", addr) + _, _ = Dial("tcp4", addr) } else { - Dial("tcp4", addr) + _, _ = Dial("tcp4", addr) } }(i) } @@ -198,7 +249,7 @@ func TestFuzz(t *testing.T) { readed := 0 wg2 := sync.WaitGroup{} wg2.Add(1) - g := NewGopher(Config{NPoller: 1}) + g := NewEngine(Config{NPoller: 1}) g.OnData(func(c *Conn, data []byte) { readed += len(data) if readed == 4 { @@ -213,9 +264,9 @@ func TestFuzz(t *testing.T) { c, err := Dial("tcp", addr) if err == nil { log.Printf("Dial tcp4: %v, %v, %v", c.LocalAddr(), c.RemoteAddr(), err) - g.AddConn(c) - c.SetWriteDeadline(time.Now().Add(time.Second)) - c.Write([]byte{1}) + _, _ = g.AddConn(c) + _ = c.SetWriteDeadline(time.Now().Add(time.Second)) + _, _ = c.Write([]byte{1}) time.Sleep(time.Second / 10) @@ -223,144 +274,311 @@ func TestFuzz(t *testing.T) { bs = append(bs, []byte{2}) bs = append(bs, []byte{3}) bs = append(bs, []byte{4}) - c.Writev(bs) + _, _ = c.Writev(bs) time.Sleep(time.Second / 10) - c.Close() - c.Write([]byte{1}) + _ = c.Close() + _, _ = c.Write([]byte{1}) } else { log.Panicf("Dial tcp4: %v", err) } - gErr := NewGopher(Config{ + gErr := NewEngine(Config{ Network: "tcp4", Addrs: []string{"127.0.0.1:8889", "127.0.0.1:8889"}, }) - gErr.Start() + _ = gErr.Start() } -func TestHeapTimer(t *testing.T) { - g := NewGopher(Config{}) - g.Start() +func TestUDP(t *testing.T) { + g := NewEngine(Config{}) + timeout := time.Second / 5 + chTimeout := make(chan *Conn, 1) + g.OnOpen(func(c *Conn) { + log.Printf("onOpen: %v, %v", c.LocalAddr().String(), c.RemoteAddr().String()) + _ = c.SetReadDeadline(time.Now().Add(timeout)) + }) + g.OnData(func(c *Conn, data []byte) { + log.Println("onData:", c.LocalAddr().String(), c.RemoteAddr().String(), string(data)) + _, err := c.Write(data) + if err != nil { + t.Fatal(err) + } + }) + g.OnClose(func(c *Conn, err error) { + log.Println("onClose:", c.RemoteAddr().String(), err) + select { + case chTimeout <- c: + default: + } + }) + + err := g.Start() + if err != nil { + t.Fatalf("Start failed: %v", err) + } defer g.Stop() - timeout := time.Second / 10 + addrstr := fmt.Sprintf("127.0.0.1:%d", 9999) + addr, err := net.ResolveUDPAddr("udp", addrstr) + if err != nil { + t.Fatalf("ResolveUDPAddr error: %v", err) + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + t.Fatalf("listen error: %v", err) + } - testHeapTimerNormal(g, timeout) - testHeapTimerExecPanic(g, timeout) - testHeapTimerNormalExecMany(g, timeout) - testHeapTimerExecManyRandtime(g) -} + lisConn, _ := g.AddConn(conn) -func testHeapTimerNormal(g *Gopher, timeout time.Duration) { - t1 := time.Now() - ch1 := make(chan int) - g.AfterFunc(timeout*5, func() { - close(ch1) - }) - <-ch1 - to1 := time.Since(t1) - if to1 < timeout*4 || to1 > timeout*10 { - log.Panicf("invalid to1: %v", to1) + newClientConn := func() *net.UDPConn { + connUDP, errDial := net.DialUDP("udp4", nil, &net.UDPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: 9999, + }) + if errDial != nil { + t.Fatalf("net.DialUDP failed: %v", err) + } + return connUDP } - t2 := time.Now() - ch2 := make(chan int) - it2 := g.afterFunc(timeout, func() { - close(ch2) - }) - it2.Reset(timeout * 5) - <-ch2 - to2 := time.Since(t2) - if to2 < timeout*4 || to2 > timeout*10 { - log.Panicf("invalid to2: %v", to2) + connTimeout := newClientConn() + n, err := connTimeout.Write([]byte("test timeout")) + if err != nil { + log.Fatalf("write udp failed: %v, %v", n, err) + } + defer func() { _ = connTimeout.Close() }() + begin := time.Now() + select { + case c := <-chTimeout: + if c.RemoteAddr().String() != connTimeout.LocalAddr().String() { + log.Fatalf("invalid udp conn") + } + used := time.Since(begin) + if used < timeout { + log.Fatalf("test timeout failed: %v < %v", used.Seconds(), timeout.Seconds()) + } + log.Printf("test udp conn timeout success") + case <-time.After(timeout + time.Second): + log.Fatalf("timeout") } - ch3 := make(chan int) - it3 := g.afterFunc(timeout, func() { - close(ch3) + clientNum := 2 + msgPerClient := 2 + wg := sync.WaitGroup{} + for i := 0; i < clientNum; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + conn := newClientConn() + defer func() { _ = conn.Close() }() + for j := 0; j < msgPerClient; j++ { + str := fmt.Sprintf("message-%d", clientNum*idx+j) + wbuf := []byte(str) + rbuf := make([]byte, 1024) + if _, werr := conn.Write(wbuf); werr == nil { + log.Printf("send msg success: %v, %s", conn.LocalAddr().String(), str) + if packLen, _, rerr := conn.ReadFromUDP(rbuf); rerr == nil { + if str != string(wbuf[:packLen]) { + log.Fatalf("recv msg not equal: %v, [%v != %v]", conn.LocalAddr().String(), str, string(rbuf[:packLen])) + } + log.Printf("recv msg success: %v, %s", conn.LocalAddr().String(), str) + } else { + log.Printf("recv msg failed: %v, %v", conn.LocalAddr().String(), rerr) + } + } else { + log.Println("send msg failed:", werr) + } + } + }(i) + } + wg.Wait() + + var done = make(chan int) + var cntFromServer int32 + var fromClientStr = "from client" + var fromServerStr = "from server" + g.OnOpen(func(c *Conn) { + c.IsTCP() + c.IsUDP() + c.IsUnix() + log.Println("onOpen:", c.LocalAddr().String(), c.RemoteAddr().String()) + _ = c.SetReadDeadline(time.Now().Add(timeout)) }) - it3.Stop() - <-g.After(timeout * 2) - select { - case <-ch3: - log.Panicf("stop failed") - default: + g.OnData(func(c *Conn, data []byte) { + log.Println("OnData:", c.LocalAddr().String(), c.RemoteAddr().String(), string(data)) + if string(data) == fromClientStr { + _, _ = c.Write([]byte(fromServerStr)) + } else { + if atomic.AddInt32(&cntFromServer, 1) == 3 { + _ = c.Close() + } else { + _, _ = c.Write([]byte(fromClientStr)) + } + } + }) + clientConn := newClientConn() + nbc, err := g.AddConn(clientConn) + if err != nil { + t.Fatal(err) } + g.OnClose(func(c *Conn, err error) { + log.Println("onClose:", c.LocalAddr().String(), c.RemoteAddr().String(), err) + if nbc == c { + close(done) + } + }) + _, _ = nbc.Write([]byte(fromClientStr)) + <-done + _ = lisConn.Close() + time.Sleep(timeout * 2) } -func testHeapTimerExecPanic(g *Gopher, timeout time.Duration) { - g.afterFunc(timeout, func() { - panic("test") - }) +func TestDialAsyncTCP(t *testing.T) { + network := "tcp" + addr := "127.0.0.1:10001" + testDialAsync(t, network, addr) } -func testHeapTimerNormalExecMany(g *Gopher, timeout time.Duration) { - ch4 := make(chan int, 5) - for i := 0; i < 5; i++ { - n := i + 1 - if n == 3 { - n = 5 - } else if n == 5 { - n = 3 - } +func TestDialAsyncUDP(t *testing.T) { + network := "udp" + addr := "127.0.0.1:10001" + testDialAsync(t, network, addr) +} - g.afterFunc(timeout*time.Duration(n), func() { - ch4 <- n - }) +func TestDialAsyncUnix(t *testing.T) { + if runtime.GOOS == osWindows { + return } + network := "unix" + addr := "unix.server" + testDialAsync(t, network, addr) +} - for i := 0; i < 5; i++ { - n := <-ch4 - if n != i+1 { - log.Panicf("invalid n: %v, %v", i, n) +func testDialAsync(t *testing.T, network, addr string) { + done := make(chan error, 1) + engineAsync := NewEngine(Config{ + Name: "udp-testing", + Network: network, + Addrs: []string{addr}, + NPoller: 1, + }) + engineAsync.OnOpen(func(c *Conn) { + log.Printf("TestDialAsync[%v, %v] OnOpen: %v, %v", network, addr, c.LocalAddr().String(), c.RemoteAddr().String()) + }) + cnt := 0 + engineAsync.OnData(func(c *Conn, data []byte) { + cnt++ + if cnt == 1 { + _, _ = c.Write(data) + log.Printf("TestDialAsync[%v, %v] Server OnData: %v, %v, %v", network, addr, c.LocalAddr().String(), c.RemoteAddr().String(), string(data)) + } else { + log.Printf("TestDialAsync[%v, %v] Client OnData: %v, %v, %v", network, addr, c.LocalAddr().String(), c.RemoteAddr().String(), string(data)) + close(done) } + }) + engineAsync.OnClose(func(c *Conn, err error) { + log.Printf("TestDialAsync[%v, %v] OnClose: %v, %v", network, addr, c.LocalAddr().String(), c.RemoteAddr().String()) + }) + err := engineAsync.Start() + if err != nil { + t.Fatalf("engineAsync start failed: %v", err) } -} + defer engineAsync.Stop() -func testHeapTimerExecManyRandtime(g *Gopher) { - its := make([]*htimer, 100)[0:0] - ch5 := make(chan int, 100) - for i := 0; i < 100; i++ { - n := 500 + rand.Int()%200 - to := time.Duration(n) * time.Second / 1000 - its = append(its, g.afterFunc(to, func() { - ch5 <- n - })) + onConnected := func(c *Conn, err error) { + log.Printf("TestTestDialAsync[%v, %v] OnConnected: %v, %v, %v", network, addr, c.LocalAddr().String(), c.RemoteAddr().String(), err) + if err == nil { + var n int + n, err = c.Write([]byte("hello")) + if err != nil { + done <- err + } + log.Printf("TestTestDialAsync[%v, %v] OnConnected Write n: %v", network, addr, n) + } else { + done <- err + } } - if len(its) != 100 || g.timers.Len() != 100 { - log.Panicf("invalid timers length: %v, %v", len(its), g.timers.Len()) + + time.Sleep(time.Second / 10) + err = engineAsync.DialAsyncTimeout(network, addr, time.Second*10, onConnected) + if err != nil { + t.Fatalf("TestTestDialAsync[%v, %v] DialAsyncTimeout failed: %v", network, addr, err) } - for i := 0; i < 50; i++ { - if its[0] == nil { - log.Panicf("invalid its[0]") - } - its[0].Stop() - its = its[1:] + err = <-done + if err != nil { + t.Fatalf("TestTestDialAsync[%v, %v] DialAsyncTimeout failed: %v", network, addr, err) } - if len(its) != 50 || g.timers.Len() != 50 { - log.Panicf("invalid timers length: %v, %v", len(its), g.timers.Len()) +} + +func TestUnix(t *testing.T) { + if runtime.GOOS == osWindows { + return } - recved := 0 -LOOP_RECV: - for { - select { - case <-ch5: - recved++ - case <-time.After(time.Second): - break LOOP_RECV + + unixAddr := "./test.unix" + defer func() { _ = os.Remove(unixAddr) }() + g := NewEngine(Config{ + Network: "unix", + Addrs: []string{unixAddr}, + }) + var connSvr *Conn + var connCli *Conn + g.OnOpen(func(c *Conn) { + if connSvr == nil { + connSvr = c } + c.Type() + c.IsTCP() + c.IsUDP() + c.IsUnix() + log.Printf("unix onOpen: %v, %v", c.LocalAddr().String(), c.RemoteAddr().String()) + }) + g.OnData(func(c *Conn, data []byte) { + log.Println("unix onData:", c.LocalAddr().String(), c.RemoteAddr().String(), string(data)) + if c == connSvr { + _, err := c.Write([]byte("world")) + if err != nil { + t.Fatal(err) + } + } + if c == connCli && string(data) == "world" { + _ = c.Close() + } + }) + chClose := make(chan *Conn, 2) + g.OnClose(func(c *Conn, err error) { + log.Println("unix onClose:", c.LocalAddr().String(), c.RemoteAddr().String(), err) + chClose <- c + }) + + err := g.Start() + if err != nil { + t.Fatalf("Start failed: %v", err) } - if recved != 50 { - log.Panicf("invalid recved num: %v", recved) - } + defer g.Stop() - it := &htimer{parent: g, index: -1} - it.Stop() + c, err := net.Dial("unix", unixAddr) + if err != nil { + t.Fatalf("unix Dial: %v, %v, %v", c.LocalAddr(), c.RemoteAddr(), err) + } + defer func() { _ = c.Close() }() + time.Sleep(time.Second / 10) + buf := []byte("hello") + connCli, err = g.AddConn(c) + if err != nil { + t.Fatalf("unix AddConn: %v, %v, %v", c.LocalAddr(), c.RemoteAddr(), err) + } + _, err = connCli.Write(buf) + if err != nil { + t.Fatalf("unix Write: %v, %v, %v", c.LocalAddr(), c.RemoteAddr(), err) + } + <-chClose + <-chClose } func TestStop(t *testing.T) { - gopher.Stop() - os.Remove(testfile) + engine.Stop() + _ = os.Remove(testfile) } diff --git a/vendor/github.com/lesismal/nbio/net_unix.go b/vendor/github.com/lesismal/nbio/net_unix.go index 93d43850..b7af8f71 100644 --- a/vendor/github.com/lesismal/nbio/net_unix.go +++ b/vendor/github.com/lesismal/nbio/net_unix.go @@ -10,9 +10,21 @@ package nbio import ( "errors" "net" + "strings" "syscall" ) +//go:norace +func init() { + var limit syscall.Rlimit + if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err == nil { + if n := int(limit.Max); n > 0 && n < MaxOpenFiles { + MaxOpenFiles = n + } + } +} + +//go:norace func dupStdConn(conn net.Conn) (*Conn, error) { sc, ok := conn.(interface { SyscallConn() (syscall.RawConn, error) @@ -38,7 +50,10 @@ func dupStdConn(conn net.Conn) (*Conn, error) { return nil, err } - conn.Close() + lAddr := conn.LocalAddr() + rAddr := conn.RemoteAddr() + + _ = conn.Close() // err = syscall.SetNonblock(newFd, true) // if err != nil { @@ -46,9 +61,117 @@ func dupStdConn(conn net.Conn) (*Conn, error) { // return nil, err // } - return &Conn{ + c := &Conn{ fd: newFd, - lAddr: conn.LocalAddr(), - rAddr: conn.RemoteAddr(), - }, nil + lAddr: lAddr, + rAddr: rAddr, + } + + switch conn.(type) { + case *net.TCPConn: + c.typ = ConnTypeTCP + case *net.UnixConn: + c.typ = ConnTypeUnix + case *net.UDPConn: + lAddrUDP := lAddr.(*net.UDPAddr) + newLAddr := net.UDPAddr{ + IP: make([]byte, len(lAddrUDP.IP)), + Port: lAddrUDP.Port, + Zone: lAddrUDP.Zone, + } + + copy(newLAddr.IP, lAddrUDP.IP) + + c.lAddr = &newLAddr + + // no remote addr, this is a listener + if rAddr == nil { + c.typ = ConnTypeUDPServer + c.connUDP = &udpConn{ + parent: c, + conns: map[udpAddrKey]*Conn{}, + } + } else { + // has remote addr, this is a dialer + c.typ = ConnTypeUDPClientFromDial + c.connUDP = &udpConn{ + parent: c, + } + } + default: + } + + return c, nil +} + +//go:norace +func parseDomainAndType(network, addr string) (int, int, syscall.Sockaddr, net.Addr, ConnType, error) { + var ( + isIPv4 = len(strings.Split(addr, ":")) == 2 + ) + + socketResult := func(sockType int, connType ConnType) (int, int, syscall.Sockaddr, net.Addr, ConnType, error) { + var ( + ip net.IP + port int + zone string + retAddr net.Addr + ) + if connType == ConnTypeTCP { + dstAddr, err := net.ResolveTCPAddr(network, addr) + if err != nil { + return 0, 0, nil, nil, 0, err + } + ip, port, zone, retAddr = dstAddr.IP, dstAddr.Port, dstAddr.Zone, dstAddr + } else { + dstAddr, err := net.ResolveUDPAddr(network, addr) + if err != nil { + return 0, 0, nil, nil, 0, err + } + ip, port, zone, retAddr = dstAddr.IP, dstAddr.Port, dstAddr.Zone, dstAddr + } + + if isIPv4 { + return syscall.AF_INET, sockType, &syscall.SockaddrInet4{ + Addr: [4]byte{ip[0], ip[1], ip[2], ip[3]}, + Port: port, + }, retAddr, connType, nil + } + + iface, err := net.InterfaceByName(zone) + if err != nil { + return 0, 0, nil, nil, 0, err + } + addr6 := &syscall.SockaddrInet6{ + Port: port, + ZoneId: uint32(iface.Index), + } + copy(addr6.Addr[:], ip) + return syscall.AF_INET6, sockType, addr6, retAddr, connType, nil + } + + switch network { + case NETWORK_TCP, NETWORK_TCP4, NETWORK_TCP6: + return socketResult(syscall.SOCK_STREAM, ConnTypeTCP) + case NETWORK_UDP, NETWORK_UDP4, NETWORK_UDP6: + return socketResult(syscall.SOCK_DGRAM, ConnTypeUDPClientFromDial) + case NETWORK_UNIX, NETWORK_UNIXGRAM, NETWORK_UNIXPACKET: + sotype := syscall.SOCK_STREAM + switch network { + case NETWORK_UNIX: + sotype = syscall.SOCK_STREAM + case NETWORK_UNIXGRAM: + sotype = syscall.SOCK_DGRAM + case NETWORK_UNIXPACKET: + sotype = syscall.SOCK_SEQPACKET + default: + } + dstAddr := &net.UnixAddr{ + Net: network, + Name: addr, + } + return syscall.AF_UNIX, sotype, &syscall.SockaddrUnix{Name: addr}, dstAddr, ConnTypeUnix, nil + default: + } + return 0, 0, nil, nil, 0, net.UnknownNetworkError(network) } diff --git a/vendor/github.com/lesismal/nbio/poller_epoll.go b/vendor/github.com/lesismal/nbio/poller_epoll.go index 7c00da2a..faafc14f 100644 --- a/vendor/github.com/lesismal/nbio/poller_epoll.go +++ b/vendor/github.com/lesismal/nbio/poller_epoll.go @@ -9,8 +9,10 @@ package nbio import ( "errors" + "fmt" "io" "net" + "os" "runtime" "syscall" "time" @@ -25,6 +27,9 @@ const ( // EPOLLET . EPOLLET = 0x80000000 + + // EPOLLONESHOT . + EPOLLONESHOT = syscall.EPOLLONESHOT ) const ( @@ -33,73 +38,132 @@ const ( epollEventsError = syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLRDHUP ) +const ( + IPPROTO_TCP = syscall.IPPROTO_TCP + TCP_KEEPINTVL = syscall.TCP_KEEPINTVL + TCP_KEEPIDLE = syscall.TCP_KEEPIDLE +) + type poller struct { - g *Gopher + g *Engine // parent engine - epfd int - evtfd int + epfd int // epoll fd + evtfd int // event fd for trigger - index int + index int // poller index in engine - shutdown bool + pollType string // listener or io poller - listener net.Listener - isListener bool + shutdown bool // state - ReadBuffer []byte + // whether poller is used for listener. + isListener bool + // listener. + listener net.Listener + // if poller is used as UnixConn listener, + // store the addr and remove it when exit. + unixSockAddr string - pollType string + ReadBuffer []byte // default reading buffer } -func (p *poller) addConn(c *Conn) { - c.g = p.g - p.g.onOpen(c) +// add the connection to poller and handle its io events. +// +//go:norace +func (p *poller) addConn(c *Conn) error { fd := c.fd + if fd >= len(p.g.connsUnix) { + err := fmt.Errorf("too many open files, fd[%d] >= MaxOpenFiles[%d]", + fd, + len(p.g.connsUnix), + ) + _ = c.closeWithError(err) + return err + } + c.p = p + if c.typ != ConnTypeUDPServer { + p.g.onOpen(c) + } else { + p.g.onUDPListen(c) + } p.g.connsUnix[fd] = c err := p.addRead(fd) if err != nil { p.g.connsUnix[fd] = nil - c.closeWithError(err) - logging.Error("[%v] add read event failed: %v", c.fd, err) - return + _ = c.closeWithError(err) + } + return err +} + +// add the connection to poller and handle its io events. +// +//go:norace +func (p *poller) addDialer(c *Conn) error { + fd := c.fd + if fd >= len(p.g.connsUnix) { + err := fmt.Errorf("too many open files, fd[%d] >= MaxOpenFiles[%d]", + fd, + len(p.g.connsUnix), + ) + _ = c.closeWithError(err) + return err + } + c.p = p + p.g.connsUnix[fd] = c + c.isWAdded = true + err := p.addReadWrite(fd) + if err != nil { + p.g.connsUnix[fd] = nil + _ = c.closeWithError(err) } + return err } +//go:norace func (p *poller) getConn(fd int) *Conn { return p.g.connsUnix[fd] } +//go:norace func (p *poller) deleteConn(c *Conn) { if c == nil { return } fd := c.fd - if c == p.g.connsUnix[fd] { - p.g.connsUnix[fd] = nil - p.deleteEvent(fd) + + if c.typ != ConnTypeUDPClientFromRead { + if c == p.g.connsUnix[fd] { + p.g.connsUnix[fd] = nil + } + // p.deleteEvent(fd) + } + + if c.typ != ConnTypeUDPServer { + p.g.onClose(c, c.closeErr) } - p.g.onClose(c, c.closeErr) } +//go:norace func (p *poller) start() { defer p.g.Done() - logging.Debug("Poller[%v_%v_%v] start", p.g.Name, p.pollType, p.index) - defer logging.Debug("Poller[%v_%v_%v] stopped", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] start", p.g.Name, p.pollType, p.index) + defer logging.Debug("NBIO[%v][%v_%v] stopped", p.g.Name, p.pollType, p.index) if p.isListener { p.acceptorLoop() } else { defer func() { - syscall.Close(p.epfd) - syscall.Close(p.evtfd) + _ = syscall.Close(p.epfd) + _ = syscall.Close(p.evtfd) }() p.readWriteLoop() } } +//go:norace func (p *poller) acceptorLoop() { - if p.g.lockListener { + if p.g.LockListener { runtime.LockOSThread() defer runtime.UnlockOSThread() } @@ -111,26 +175,48 @@ func (p *poller) acceptorLoop() { var c *Conn c, err = NBConn(conn) if err != nil { - conn.Close() + _ = conn.Close() continue } - o := p.g.pollers[c.fd%len(p.g.pollers)] - o.addConn(c) + err = p.g.pollers[c.Hash()%len(p.g.pollers)].addConn(c) + if err != nil { + logging.Error("NBIO[%v][%v_%v] addConn [fd: %v] failed: %v", + p.g.Name, + p.pollType, + p.index, + c.fd, + err, + ) + } } else { var ne net.Error - if ok := errors.As(err, &ne); ok && ne.Temporary() { - logging.Error("Poller[%v_%v_%v] Accept failed: temporary error, retrying...", p.g.Name, p.pollType, p.index) + if ok := errors.As(err, &ne); ok && ne.Timeout() { + logging.Error("NBIO[%v][%v_%v] Accept failed: timeout error, retrying...", + p.g.Name, + p.pollType, + p.index, + ) time.Sleep(time.Second / 20) } else { - logging.Error("Poller[%v_%v_%v] Accept failed: %v, exit...", p.g.Name, p.pollType, p.index, err) - break + if !p.shutdown { + logging.Error("NBIO[%v][%v_%v] Accept failed: %v, exit...", + p.g.Name, + p.pollType, + p.index, + err, + ) + } + if p.g.onAcceptError != nil { + p.g.onAcceptError(err) + } } } } } +//go:norace func (p *poller) readWriteLoop() { - if p.g.lockPoller { + if p.g.LockPoller { runtime.LockOSThread() defer runtime.UnlockOSThread() } @@ -138,121 +224,230 @@ func (p *poller) readWriteLoop() { msec := -1 events := make([]syscall.EpollEvent, 1024) - if p.g.onRead == nil && p.g.epollMod == EPOLLET { - p.g.maxReadTimesPerEventLoop = 1<<31 - 1 + if p.g.onRead == nil && p.g.EpollMod == EPOLLET { + p.g.MaxConnReadTimesPerEventLoop = 1<<31 - 1 } + g := p.g p.shutdown = false - + isOneshot := g.isOneshot + asyncReadEnabled := g.AsyncReadInPoller && (g.EpollMod == EPOLLET) for !p.shutdown { n, err := syscall.EpollWait(p.epfd, events, msec) if err != nil && !errors.Is(err, syscall.EINTR) { + logging.Error("NBIO[%v][%v_%v] EpollWait failed: %v, exit...", + p.g.Name, + p.pollType, + p.index, + err, + ) return } if n <= 0 { - msec = -1 - // runtime.Gosched() continue } - msec = 20 for _, ev := range events[:n] { fd := int(ev.Fd) switch fd { - case p.evtfd: - default: + case p.evtfd: // triggered by stop, exit event loop + + default: // for socket connections c := p.getConn(fd) if c != nil { - if ev.Events&epollEventsError != 0 { - c.closeWithError(io.EOF) - continue - } - if ev.Events&epollEventsWrite != 0 { - c.flush() + if c.onConnected == nil { + _ = c.flush() + } else { + c.onConnected(c, nil) + c.onConnected = nil + c.resetRead() + } } if ev.Events&epollEventsRead != 0 { - if p.g.onRead == nil { - for i := 0; i < p.g.maxReadTimesPerEventLoop; i++ { - buffer := p.g.borrow(c) - n, err := c.Read(buffer) - if n > 0 { - p.g.onData(c, buffer[:n]) - } - p.g.payback(c, buffer) - if errors.Is(err, syscall.EINTR) { - continue - } - if errors.Is(err, syscall.EAGAIN) { - break + if g.onRead == nil { + if asyncReadEnabled { + c.AsyncRead() + } else { + for i := 0; i < g.MaxConnReadTimesPerEventLoop; i++ { + pbuf := g.borrow(c) + bufLen := len(*pbuf) + rc, n, err := c.ReadAndGetConn(pbuf) + if n > 0 { + *pbuf = (*pbuf)[:n] + g.onDataPtr(rc, pbuf) + } + g.payback(c, pbuf) + if errors.Is(err, syscall.EINTR) { + continue + } + if errors.Is(err, syscall.EAGAIN) { + break + } + if err != nil { + _ = c.closeWithError(err) + break + } + if n < bufLen { + break + } } - if err != nil { - c.closeWithError(err) - } - if n < len(buffer) { - break + if isOneshot { + c.ResetPollerEvent() } } } else { - p.g.onRead(c) + g.onRead(c) } } - } else { - syscall.Close(fd) - p.deleteEvent(fd) + + if ev.Events&epollEventsError != 0 { + _ = c.closeWithError(io.EOF) + continue + } } } } } } +//go:norace func (p *poller) stop() { - logging.Debug("Poller[%v_%v_%v] stop...", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] stop...", p.g.Name, p.pollType, p.index) p.shutdown = true if p.listener != nil { - p.listener.Close() + _ = p.listener.Close() + if p.unixSockAddr != "" { + _ = os.Remove(p.unixSockAddr) + } } else { n := uint64(1) - syscall.Write(p.evtfd, (*(*[8]byte)(unsafe.Pointer(&n)))[:]) + _, _ = syscall.Write(p.evtfd, (*(*[8]byte)(unsafe.Pointer(&n)))[:]) } } +//go:norace func (p *poller) addRead(fd int) error { - switch p.g.epollMod { + return p.setRead(syscall.EPOLL_CTL_ADD, fd) +} + +//go:norace +func (p *poller) resetRead(fd int) error { + return p.setRead(syscall.EPOLL_CTL_MOD, fd) +} + +//go:norace +func (p *poller) setRead(op int, fd int) error { + switch p.g.EpollMod { case EPOLLET: - return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLRDHUP | syscall.EPOLLPRI | syscall.EPOLLIN | EPOLLET}) + events := syscall.EPOLLERR | + syscall.EPOLLHUP | + syscall.EPOLLRDHUP | + syscall.EPOLLPRI | + syscall.EPOLLIN | + EPOLLET | + p.g.EPOLLONESHOT + if p.g.EPOLLONESHOT != EPOLLONESHOT { + if op == syscall.EPOLL_CTL_ADD { + return syscall.EpollCtl(p.epfd, op, fd, &syscall.EpollEvent{ + Fd: int32(fd), + Events: events | syscall.EPOLLOUT, + }) + } + return nil + } + return syscall.EpollCtl(p.epfd, op, fd, &syscall.EpollEvent{ + Fd: int32(fd), + Events: events, + }) default: - return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLRDHUP | syscall.EPOLLPRI | syscall.EPOLLIN}) + return syscall.EpollCtl( + p.epfd, + op, + fd, + &syscall.EpollEvent{ + Fd: int32(fd), + Events: syscall.EPOLLERR | + syscall.EPOLLHUP | + syscall.EPOLLRDHUP | + syscall.EPOLLPRI | + syscall.EPOLLIN, + }, + ) } } -// func (p *poller) addWrite(fd int) error { -// return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: syscall.EPOLLOUT}) -// } - +//go:norace func (p *poller) modWrite(fd int) error { - switch p.g.epollMod { + return p.setReadWrite(syscall.EPOLL_CTL_MOD, fd) +} + +//go:norace +func (p *poller) addReadWrite(fd int) error { + return p.setReadWrite(syscall.EPOLL_CTL_ADD, fd) +} + +//go:norace +func (p *poller) setReadWrite(op int, fd int) error { + switch p.g.EpollMod { case EPOLLET: - return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLRDHUP | syscall.EPOLLPRI | syscall.EPOLLIN | syscall.EPOLLOUT | EPOLLET}) + events := syscall.EPOLLERR | + syscall.EPOLLHUP | + syscall.EPOLLRDHUP | + syscall.EPOLLPRI | + syscall.EPOLLIN | + syscall.EPOLLOUT | + EPOLLET | + p.g.EPOLLONESHOT + if p.g.EPOLLONESHOT != EPOLLONESHOT { + if op == syscall.EPOLL_CTL_ADD { + return syscall.EpollCtl(p.epfd, op, fd, &syscall.EpollEvent{ + Fd: int32(fd), + Events: events, + }) + } + return nil + } + return syscall.EpollCtl(p.epfd, op, fd, &syscall.EpollEvent{ + Fd: int32(fd), + Events: events, + }) default: - return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_MOD, fd, &syscall.EpollEvent{Fd: int32(fd), Events: syscall.EPOLLERR | syscall.EPOLLHUP | syscall.EPOLLRDHUP | syscall.EPOLLPRI | syscall.EPOLLIN | syscall.EPOLLOUT}) + return syscall.EpollCtl( + p.epfd, op, fd, + &syscall.EpollEvent{ + Fd: int32(fd), + Events: syscall.EPOLLERR | + syscall.EPOLLHUP | + syscall.EPOLLRDHUP | + syscall.EPOLLPRI | + syscall.EPOLLIN | + syscall.EPOLLOUT, + }, + ) } } -func (p *poller) deleteEvent(fd int) error { - return syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_DEL, fd, &syscall.EpollEvent{Fd: int32(fd)}) -} +// func (p *poller) deleteEvent(fd int) error { +// return syscall.EpollCtl( +// p.epfd, +// syscall.EPOLL_CTL_DEL, +// fd, +// &syscall.EpollEvent{Fd: int32(fd)}, +// ) +// } -func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { +//go:norace +func newPoller(g *Engine, isListener bool, index int) (*poller, error) { if isListener { - if len(g.addrs) == 0 { + if len(g.Addrs) == 0 { panic("invalid listener num") } - addr := g.addrs[index%len(g.listeners)] - ln, err := net.Listen(g.network, addr) + addr := g.Addrs[index%len(g.Addrs)] + ln, err := g.Listen(g.Network, addr) if err != nil { return nil, err } @@ -264,6 +459,9 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { isListener: isListener, pollType: "LISTENER", } + if g.Network == "unix" { + p.unixSockAddr = addr + } return p, nil } @@ -275,8 +473,8 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { r0, _, e0 := syscall.Syscall(syscall.SYS_EVENTFD2, 0, syscall.O_NONBLOCK, 0) if e0 != 0 { - syscall.Close(fd) - return nil, err + _ = syscall.Close(fd) + return nil, e0 } err = syscall.EpollCtl(fd, syscall.EPOLL_CTL_ADD, int(r0), @@ -285,8 +483,8 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { }, ) if err != nil { - syscall.Close(fd) - syscall.Close(int(r0)) + _ = syscall.Close(fd) + _ = syscall.Close(int(r0)) return nil, err } @@ -301,3 +499,17 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { return p, nil } + +//go:norace +func (c *Conn) ResetPollerEvent() { + p := c.p + g := p.g + fd := c.fd + if g.isOneshot && !c.closed { + if len(c.writeList) == 0 { + _ = p.resetRead(fd) + } else { + _ = p.modWrite(fd) + } + } +} diff --git a/vendor/github.com/lesismal/nbio/poller_kqueue.go b/vendor/github.com/lesismal/nbio/poller_kqueue.go index e55f5021..12f91532 100644 --- a/vendor/github.com/lesismal/nbio/poller_kqueue.go +++ b/vendor/github.com/lesismal/nbio/poller_kqueue.go @@ -8,7 +8,11 @@ package nbio import ( + "errors" + "fmt" + "io" "net" + "os" "runtime" "sync" "syscall" @@ -23,23 +27,32 @@ const ( // EPOLLET . EPOLLET = 1 + + // EPOLLONESHOT . + EPOLLONESHOT = 0 +) + +const ( + IPPROTO_TCP = 0 + TCP_KEEPINTVL = 0 + TCP_KEEPIDLE = 0 ) type poller struct { mux sync.Mutex - g *Gopher + g *Engine kfd int evtfd int - listener net.Listener - index int shutdown bool - isListener bool + listener net.Listener + isListener bool + unixSockAddr string ReadBuffer []byte @@ -48,55 +61,119 @@ type poller struct { eventList []syscall.Kevent_t } -func (p *poller) addConn(c *Conn) { - c.g = p.g - p.g.onOpen(c) +//go:norace +func (p *poller) addConn(c *Conn) error { fd := c.fd + if fd >= len(p.g.connsUnix) { + err := fmt.Errorf("too many open files, fd[%d] >= MaxOpenFiles[%d]", + fd, + len(p.g.connsUnix)) + _ = c.closeWithError(err) + return err + } + c.p = p + if c.typ != ConnTypeUDPServer { + p.g.onOpen(c) + } else { + p.g.onUDPListen(c) + } p.g.connsUnix[fd] = c - p.addRead(c.fd) + p.addRead(fd) + return nil } +//go:norace +func (p *poller) addDialer(c *Conn) error { + fd := c.fd + if fd >= len(p.g.connsUnix) { + err := fmt.Errorf("too many open files, fd[%d] >= MaxOpenFiles[%d]", + fd, + len(p.g.connsUnix), + ) + _ = c.closeWithError(err) + return err + } + c.p = p + p.g.connsUnix[fd] = c + c.isWAdded = true + p.addReadWrite(fd) + return nil +} + +//go:norace func (p *poller) getConn(fd int) *Conn { return p.g.connsUnix[fd] } +//go:norace func (p *poller) deleteConn(c *Conn) { if c == nil { return } fd := c.fd - if c == p.g.connsUnix[fd] { - p.g.connsUnix[fd] = nil - p.deleteEvent(fd) + + if c.typ != ConnTypeUDPClientFromRead { + if c == p.g.connsUnix[fd] { + p.g.connsUnix[fd] = nil + } + // p.deleteEvent(fd) + } + + if c.typ != ConnTypeUDPServer { + p.g.onClose(c, c.closeErr) } - p.g.onClose(c, c.closeErr) } -func (p *poller) trigger() { - syscall.Kevent(p.kfd, []syscall.Kevent_t{{Ident: 0, Filter: syscall.EVFILT_USER, Fflags: syscall.NOTE_TRIGGER}}, nil, nil) +//go:norace +func (p *poller) trigger() error { + _, err := syscall.Kevent(p.kfd, []syscall.Kevent_t{{Ident: 0, Filter: syscall.EVFILT_USER, Fflags: syscall.NOTE_TRIGGER}}, nil, nil) + return err } +//go:norace func (p *poller) addRead(fd int) { p.mux.Lock() p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ}) + // p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE}) p.mux.Unlock() p.trigger() } -func (p *poller) modWrite(fd int) { +//go:norace +func (p *poller) resetRead(fd int) error { + p.mux.Lock() + p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_WRITE}) + p.mux.Unlock() + return p.trigger() +} + +//go:norace +func (p *poller) modWrite(fd int) error { p.mux.Lock() p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE}) p.mux.Unlock() - p.trigger() + return p.trigger() } -func (p *poller) deleteEvent(fd int) { +//go:norace +func (p *poller) addReadWrite(fd int) { p.mux.Lock() - p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_DELETE, Filter: syscall.EVFILT_READ}) + p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_READ}) + p.eventList = append(p.eventList, syscall.Kevent_t{Ident: uint64(fd), Flags: syscall.EV_ADD, Filter: syscall.EVFILT_WRITE}) p.mux.Unlock() p.trigger() } +// func (p *poller) deleteEvent(fd int) { +// p.mux.Lock() +// p.eventList = append(p.eventList, +// 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}) +// p.mux.Unlock() +// p.trigger() +// } + +//go:norace func (p *poller) readWrite(ev *syscall.Kevent_t) { if ev.Flags&syscall.EV_DELETE > 0 { return @@ -104,62 +181,82 @@ func (p *poller) readWrite(ev *syscall.Kevent_t) { fd := int(ev.Ident) c := p.getConn(fd) if c != nil { - if ev.Filter&syscall.EVFILT_READ == syscall.EVFILT_READ { + if ev.Filter == syscall.EVFILT_READ { if p.g.onRead == nil { for { - buffer := p.g.borrow(c) - n, err := c.Read(buffer) + pbuf := p.g.borrow(c) + bufLen := len(*pbuf) + rc, n, err := c.ReadAndGetConn(pbuf) if n > 0 { - p.g.onData(c, buffer[:n]) + *pbuf = (*pbuf)[:n] + p.g.onDataPtr(rc, pbuf) } - p.g.payback(c, buffer) - if err == syscall.EINTR { + p.g.payback(c, pbuf) + if errors.Is(err, syscall.EINTR) { continue } - if err == syscall.EAGAIN { + if errors.Is(err, syscall.EAGAIN) { return } if (err != nil || n == 0) && ev.Flags&syscall.EV_DELETE == 0 { - c.closeWithError(err) + if err == nil { + err = io.EOF + } + _ = c.closeWithError(err) } - if n < len(buffer) { + if n < bufLen { break } } } else { p.g.onRead(c) } + + if ev.Flags&syscall.EV_EOF != 0 { + if c.onConnected == nil { + _ = c.flush() + } else { + c.onConnected(c, nil) + c.onConnected = nil + c.resetRead() + } + } } - if ev.Filter&syscall.EVFILT_WRITE == syscall.EVFILT_WRITE { - c.flush() + if ev.Filter == syscall.EVFILT_WRITE { + if c.onConnected == nil { + _ = c.flush() + } else { + c.resetRead() + c.onConnected(c, nil) + c.onConnected = nil + } } - } else { - syscall.Close(fd) - p.deleteEvent(fd) } } +//go:norace func (p *poller) start() { - if p.g.lockPoller { + if p.g.LockPoller { runtime.LockOSThread() defer runtime.UnlockOSThread() } defer p.g.Done() - logging.Debug("Poller[%v_%v_%v] start", p.g.Name, p.pollType, p.index) - defer logging.Debug("Poller[%v_%v_%v] stopped", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] start", p.g.Name, p.pollType, p.index) + defer logging.Debug("NBIO[%v][%v_%v] stopped", p.g.Name, p.pollType, p.index) if p.isListener { p.acceptorLoop() } else { - defer syscall.Close(p.kfd) + defer func() { _ = syscall.Close(p.kfd) }() p.readWriteLoop() } } +//go:norace func (p *poller) acceptorLoop() { - if p.g.lockListener { + if p.g.LockListener { runtime.LockOSThread() defer runtime.UnlockOSThread() } @@ -168,32 +265,38 @@ func (p *poller) acceptorLoop() { for !p.shutdown { conn, err := p.listener.Accept() if err == nil { - c, err := NBConn(conn) + var c *Conn + c, err = NBConn(conn) if err != nil { - conn.Close() + _ = conn.Close() continue } - o := p.g.pollers[int(c.fd)%len(p.g.pollers)] - o.addConn(c) + _ = p.g.pollers[c.Hash()%len(p.g.pollers)].addConn(c) } else { - if ne, ok := err.(net.Error); ok && ne.Temporary() { - logging.Error("Poller[%v_%v_%v] Accept failed: temporary error, retrying...", p.g.Name, p.pollType, p.index) + var ne net.Error + if ok := errors.As(err, &ne); ok && ne.Timeout() { + logging.Error("NBIO[%v][%v_%v] Accept failed: timeout error, retrying...", p.g.Name, p.pollType, p.index) time.Sleep(time.Second / 20) } else { - logging.Error("Poller[%v_%v_%v] Accept failed: %v, exit...", p.g.Name, p.pollType, p.index, err) - break + if !p.shutdown { + logging.Error("NBIO[%v][%v_%v] Accept failed: %v, exit...", p.g.Name, p.pollType, p.index, err) + } + if p.g.onAcceptError != nil { + p.g.onAcceptError(err) + } } } } } +//go:norace func (p *poller) readWriteLoop() { - if p.g.lockPoller { + if p.g.LockPoller { runtime.LockOSThread() defer runtime.UnlockOSThread() } - var events = make([]syscall.Kevent_t, 1024) + events := make([]syscall.Kevent_t, 1024) var changes []syscall.Kevent_t p.shutdown = false @@ -203,7 +306,8 @@ func (p *poller) readWriteLoop() { p.eventList = nil p.mux.Unlock() n, err := syscall.Kevent(p.kfd, changes, events, nil) - if err != nil && err != syscall.EINTR { + if err != nil && !errors.Is(err, syscall.EINTR) && !errors.Is(err, syscall.EBADF) && !errors.Is(err, syscall.ENOENT) && !errors.Is(err, syscall.EINVAL) { + logging.Error("NBIO[%v][%v_%v] Kevent failed: %v, exit...", p.g.Name, p.pollType, p.index, err) return } @@ -217,23 +321,28 @@ func (p *poller) readWriteLoop() { } } +//go:norace func (p *poller) stop() { - logging.Debug("Poller[%v_%v_%v] stop...", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] stop...", p.g.Name, p.pollType, p.index) p.shutdown = true if p.listener != nil { - p.listener.Close() + _ = p.listener.Close() + if p.unixSockAddr != "" { + _ = os.Remove(p.unixSockAddr) + } } p.trigger() } -func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { +//go:norace +func newPoller(g *Engine, isListener bool, index int) (*poller, error) { if isListener { - if len(g.addrs) == 0 { + if len(g.Addrs) == 0 { panic("invalid listener num") } - addr := g.addrs[index%len(g.listeners)] - ln, err := net.Listen(g.network, addr) + addr := g.Addrs[index%len(g.Addrs)] + ln, err := g.Listen(g.Network, addr) if err != nil { return nil, err } @@ -245,6 +354,10 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { isListener: isListener, pollType: "LISTENER", } + if g.Network == "unix" { + p.unixSockAddr = addr + } + return p, nil } @@ -260,7 +373,7 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { }}, nil, nil) if err != nil { - syscall.Close(fd) + _ = syscall.Close(fd) return nil, err } @@ -274,3 +387,7 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { return p, nil } + +//go:norace +func (c *Conn) ResetPollerEvent() { +} diff --git a/vendor/github.com/lesismal/nbio/poller_std.go b/vendor/github.com/lesismal/nbio/poller_std.go index 6bc8d28b..033e4985 100644 --- a/vendor/github.com/lesismal/nbio/poller_std.go +++ b/vendor/github.com/lesismal/nbio/poller_std.go @@ -8,6 +8,7 @@ package nbio import ( + "errors" "net" "runtime" "time" @@ -21,10 +22,13 @@ const ( // EPOLLET . EPOLLET = 1 + + // EPOLLONESHOT . + EPOLLONESHOT = 0 ) type poller struct { - g *Gopher + g *Engine index int @@ -38,6 +42,7 @@ type poller struct { chStop chan struct{} } +//go:norace func (p *poller) accept() error { conn, err := p.listener.Accept() if err != nil { @@ -51,11 +56,12 @@ func (p *poller) accept() error { return nil } +//go:norace func (p *poller) readConn(c *Conn) { for { - buffer := p.g.borrow(c) - _, err := c.read(buffer) - p.g.payback(c, buffer) + pbuf := p.g.borrow(c) + _, err := c.read(*pbuf) + p.g.payback(c, pbuf) if err != nil { c.Close() return @@ -63,33 +69,57 @@ func (p *poller) readConn(c *Conn) { } } +//go:norace func (p *poller) addConn(c *Conn) error { - c.g = p.g + c.p = p p.g.mux.Lock() p.g.connsStd[c] = struct{}{} p.g.mux.Unlock() - p.g.onOpen(c) - go p.readConn(c) + // should not call onOpen for udp server conn + if c.typ != ConnTypeUDPServer { + p.g.onOpen(c) + } else { + p.g.onUDPListen(c) + } + // should not read udp client from reading udp server conn + if c.typ != ConnTypeUDPClientFromRead { + go p.readConn(c) + } + + return nil +} +//go:norace +func (p *poller) addDialer(c *Conn) error { + c.p = p + p.g.mux.Lock() + p.g.connsStd[c] = struct{}{} + p.g.mux.Unlock() + go p.readConn(c) return nil } +//go:norace func (p *poller) deleteConn(c *Conn) { p.g.mux.Lock() delete(p.g.connsStd, c) p.g.mux.Unlock() - p.g.onClose(c, c.closeErr) + // should not call onClose for udp server conn + if c.typ != ConnTypeUDPServer { + p.g.onClose(c, c.closeErr) + } } +//go:norace func (p *poller) start() { - if p.g.lockListener { + if p.g.LockListener { runtime.LockOSThread() defer runtime.UnlockOSThread() } defer p.g.Done() - logging.Debug("Poller[%v_%v_%v] start", p.g.Name, p.pollType, p.index) - defer logging.Debug("Poller[%v_%v_%v] stopped", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] start", p.g.Name, p.pollType, p.index) + defer logging.Debug("NBIO[%v][%v_%v] stopped", p.g.Name, p.pollType, p.index) if p.isListener { var err error @@ -97,12 +127,17 @@ func (p *poller) start() { for !p.shutdown { err = p.accept() if err != nil { - if ne, ok := err.(net.Error); ok && ne.Temporary() { - logging.Error("Poller[%v_%v_%v] Accept failed: temporary error, retrying...", p.g.Name, p.pollType, p.index) + var ne net.Error + if ok := errors.As(err, &ne); ok && ne.Timeout() { + logging.Error("NBIO[%v][%v_%v] Accept failed: timeout error, retrying...", p.g.Name, p.pollType, p.index) time.Sleep(time.Second / 20) } else { - logging.Error("Poller[%v_%v_%v] Accept failed: %v, exit...", p.g.Name, p.pollType, p.index, err) - break + if !p.shutdown { + logging.Error("NBIO[%v][%v_%v] Accept failed: %v, exit...", p.g.Name, p.pollType, p.index, err) + } + if p.g.onAcceptError != nil { + p.g.onAcceptError(err) + } } } @@ -111,8 +146,9 @@ func (p *poller) start() { <-p.chStop } +//go:norace func (p *poller) stop() { - logging.Debug("Poller[%v_%v_%v] stop...", p.g.Name, p.pollType, p.index) + logging.Debug("NBIO[%v][%v_%v] stop...", p.g.Name, p.pollType, p.index) p.shutdown = true if p.isListener { p.listener.Close() @@ -120,7 +156,8 @@ func (p *poller) stop() { close(p.chStop) } -func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { +//go:norace +func newPoller(g *Engine, isListener bool, index int) (*poller, error) { p := &poller{ g: g, index: index, @@ -130,8 +167,8 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { if isListener { var err error - var addr = g.addrs[index%len(g.addrs)] - p.listener, err = net.Listen(g.network, addr) + var addr = g.Addrs[index%len(g.Addrs)] + p.listener, err = g.Listen(g.Network, addr) if err != nil { return nil, err } @@ -142,3 +179,8 @@ func newPoller(g *Gopher, isListener bool, index int) (*poller, error) { return p, nil } + +//go:norace +func (c *Conn) ResetPollerEvent() { + +} diff --git a/vendor/github.com/lesismal/nbio/protocol_stack.go b/vendor/github.com/lesismal/nbio/protocol_stack.go new file mode 100644 index 00000000..71ab1ac8 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/protocol_stack.go @@ -0,0 +1,56 @@ +package nbio + +import ( + "net" +) + +type Protocol interface { + Parse(c net.Conn, b []byte, ps *ProtocolStack) (net.Conn, []byte, error) + Write(b []byte) (int, error) +} + +type ProtocolStack struct { + stack []Protocol +} + +//go:norace +func (ps *ProtocolStack) Add(p Protocol) { + ps.stack = append(ps.stack, p) +} + +//go:norace +func (ps *ProtocolStack) Delete(p Protocol) { + i := len(ps.stack) - 1 + for i >= 0 { + if ps.stack[i] == p { + ps.stack[i] = nil + if i+1 > len(ps.stack)-1 { + ps.stack = ps.stack[:i] + } else { + ps.stack = append(ps.stack[:i], ps.stack[i+1:]...) + } + return + } + i-- + } +} + +//go:norace +func (ps *ProtocolStack) Parse(c net.Conn, b []byte, ps_ ProtocolStack) (net.Conn, []byte, error) { + var err error + for _, p := range ps.stack { + if p == nil { + continue + } + c, b, err = p.Parse(c, b, ps) + if err != nil { + break + } + } + return c, b, err +} + +//go:norace +func (ps *ProtocolStack) Write(b []byte) (int, error) { + return -1, ErrUnsupported +} diff --git a/vendor/github.com/lesismal/nbio/sendfile_bsd.go b/vendor/github.com/lesismal/nbio/sendfile_bsd.go deleted file mode 100644 index 07f03aae..00000000 --- a/vendor/github.com/lesismal/nbio/sendfile_bsd.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -//go:build darwin || netbsd || freebsd || openbsd || dragonfly -// +build darwin netbsd freebsd openbsd dragonfly - -package nbio - -import ( - "io" - "os" - - "github.com/lesismal/nbio/mempool" -) - -// Sendfile . -func (c *Conn) Sendfile(f *os.File, remain int64) (written int64, err error) { - if f == nil { - return 0, nil - } - - if remain <= 0 { - stat, err := f.Stat() - if err != nil { - return 0, err - } - remain = stat.Size() - } - - for remain > 0 { - bufLen := 1024 * 32 - if bufLen > int(remain) { - bufLen = int(remain) - } - buf := mempool.Malloc(bufLen) - nr, er := f.Read(buf) - if nr > 0 { - nw, ew := c.Write(buf[0:nr]) - if nw < 0 { - nw = 0 - } - remain -= int64(nw) - written += int64(nw) - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er != nil { - if er != io.EOF { - err = er - } - break - } - } - return written, err -} diff --git a/vendor/github.com/lesismal/nbio/sendfile_linux.go b/vendor/github.com/lesismal/nbio/sendfile_linux.go deleted file mode 100644 index 06ebc11f..00000000 --- a/vendor/github.com/lesismal/nbio/sendfile_linux.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -//go:build linux -// +build linux - -package nbio - -import ( - "errors" - "os" - "syscall" -) - -const maxSendfileSize = 4 << 20 - -// Sendfile . -func (c *Conn) Sendfile(f *os.File, remain int64) (int64, error) { - if f == nil { - return 0, nil - } - c.mux.Lock() - if c.closed { - c.mux.Unlock() - return -1, errClosed - } - - if remain <= 0 { - stat, err := f.Stat() - if err != nil { - return 0, err - } - remain = stat.Size() - } - - if len(c.writeBuffer) > 0 { - if c.chWaitWrite == nil { - c.chWaitWrite = make(chan struct{}, 1) - } - c.mux.Unlock() - <-c.chWaitWrite - if c.closed { - c.chWaitWrite = nil - return -1, errClosed - } - c.mux.Lock() - } - - c.g.beforeWrite(c) - - var ( - err error - n int - src = int(f.Fd()) - dst = c.fd - total = remain - ) - - for remain > 0 { - n = maxSendfileSize - if int64(n) > remain { - n = int(remain) - } - n, err = syscall.Sendfile(dst, src, nil, n) - if n > 0 { - remain -= int64(n) - } else if n == 0 && err == nil { - break - } - if errors.Is(err, syscall.EINTR) { - continue - } - if errors.Is(err, syscall.EAGAIN) { - c.modWrite() - if c.chWaitWrite == nil { - c.chWaitWrite = make(chan struct{}, 1) - } - c.mux.Unlock() - <-c.chWaitWrite - c.chWaitWrite = nil - if c.closed { - return total - remain, err - } - c.mux.Lock() - continue - } - if err != nil { - c.closeWithErrorWithoutLock(err) - c.mux.Unlock() - return total - remain, err - } - } - - c.chWaitWrite = nil - c.mux.Unlock() - return total - remain, err -} diff --git a/vendor/github.com/lesismal/nbio/sendfile_std.go b/vendor/github.com/lesismal/nbio/sendfile_std.go index 51d0cd75..3c15e38f 100644 --- a/vendor/github.com/lesismal/nbio/sendfile_std.go +++ b/vendor/github.com/lesismal/nbio/sendfile_std.go @@ -10,20 +10,20 @@ package nbio import ( "io" "os" - - "github.com/lesismal/nbio/mempool" ) // Sendfile . +// +//go:norace func (c *Conn) Sendfile(f *os.File, remain int64) (written int64, err error) { if f == nil { return 0, nil } if remain <= 0 { - stat, err := f.Stat() - if err != nil { - return 0, err + stat, e := f.Stat() + if e != nil { + return 0, e } remain = stat.Size() } @@ -33,10 +33,11 @@ func (c *Conn) Sendfile(f *os.File, remain int64) (written int64, err error) { if bufLen > int(remain) { bufLen = int(remain) } - buf := mempool.Malloc(bufLen) - nr, er := f.Read(buf) + pbuf := c.p.g.BodyAllocator.Malloc(bufLen) + nr, er := f.Read(*pbuf) if nr > 0 { - nw, ew := c.Write(buf[0:nr]) + nw, ew := c.Write((*pbuf)[0:nr]) + c.p.g.BodyAllocator.Free(pbuf) if nw < 0 { nw = 0 } @@ -58,5 +59,10 @@ func (c *Conn) Sendfile(f *os.File, remain int64) (written int64, err error) { break } } + + if c.p.g.onWrittenSize != nil && written > 0 { + c.p.g.onWrittenSize(c, nil, int(written)) + } + return written, err } diff --git a/vendor/github.com/lesismal/nbio/sendfile_unix.go b/vendor/github.com/lesismal/nbio/sendfile_unix.go new file mode 100644 index 00000000..2f5ee92f --- /dev/null +++ b/vendor/github.com/lesismal/nbio/sendfile_unix.go @@ -0,0 +1,112 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build linux || darwin || netbsd || freebsd || openbsd || dragonfly +// +build linux darwin netbsd freebsd openbsd dragonfly + +package nbio + +import ( + "errors" + "io" + "net" + "os" + "syscall" +) + +const maxSendfileSize = 4 << 20 + +// Sendfile . +// +//go:norace +func (c *Conn) Sendfile(f *os.File, remain int64) (int64, error) { + if f == nil { + return 0, nil + } + + c.mux.Lock() + defer c.mux.Unlock() + if c.closed { + return 0, net.ErrClosed + } + + offset, err := f.Seek(0, io.SeekCurrent) + if err != nil { + return 0, err + } + stat, err := f.Stat() + if err != nil { + return 0, err + } + size := stat.Size() + if (remain <= 0) || (remain > size-offset) { + remain = size - offset + } + + // f.Fd() will set the fd to blocking mod. + // We need to set the fd to non-blocking mod again. + src := int(f.Fd()) + err = syscall.SetNonblock(src, true) + if err != nil { + return 0, err + } + + // If c.writeList is not empty, the socket is not writable now. + // We push this File to writeList and wait to send it when writable. + if len(c.writeList) > 0 { + // After this Sendfile func returns, fs will be closed by the caller. + // So we need to dup the fd and close it when we don't need it any more. + src, err = syscall.Dup(src) + if err != nil { + return 0, err + } + c.newToWriteFile(src, offset, remain) + // c.appendWrite(t) + return remain, nil + } + + // c.p.g.beforeWrite(c) + + var ( + n int + dst = c.fd + total = remain + ) + + for remain > 0 { + n = maxSendfileSize + if int64(n) > remain { + n = int(remain) + } + var tmpOffset = offset + n, err = syscall.Sendfile(dst, src, &tmpOffset, n) + if n > 0 { + remain -= int64(n) + offset += int64(n) + } else if n == 0 && err == nil { + break + } + if errors.Is(err, syscall.EINTR) { + continue + } + if errors.Is(err, syscall.EAGAIN) { + // After this Sendfile func returns, fs will be closed by the caller. + // So we need to dup the fd and close it when we don't need it any more. + src, err = syscall.Dup(src) + if err == nil { + c.newToWriteFile(src, offset, remain) + // c.appendWrite(t) + c.modWrite() + } + break + } + if err != nil { + c.closed = true + _ = c.closeWithErrorWithoutLock(err) + return 0, err + } + } + + return total, nil +} diff --git a/vendor/github.com/lesismal/nbio/taskpool/caller.go b/vendor/github.com/lesismal/nbio/taskpool/caller.go deleted file mode 100644 index d5392bea..00000000 --- a/vendor/github.com/lesismal/nbio/taskpool/caller.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package taskpool - -import ( - "runtime" - "unsafe" - - "github.com/lesismal/nbio/logging" -) - -func call(f func()) { - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - logging.Error("taskpool call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) - } - }() - f() -} diff --git a/vendor/github.com/lesismal/nbio/taskpool/fixednoorderpool.go b/vendor/github.com/lesismal/nbio/taskpool/fixednoorderpool.go deleted file mode 100644 index 3a2ff7ed..00000000 --- a/vendor/github.com/lesismal/nbio/taskpool/fixednoorderpool.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package taskpool - -// FixedNoOrderPool . -type FixedNoOrderPool struct { - chTask chan func() -} - -func (np *FixedNoOrderPool) taskLoop() { - for f := range np.chTask { - call(f) - } -} - -// Go . -func (np *FixedNoOrderPool) Go(f func()) { - np.chTask <- f -} - -// GoByIndex . -func (np *FixedNoOrderPool) GoByIndex(index int, f func()) { - np.Go(f) -} - -// Stop . -func (np *FixedNoOrderPool) Stop() { - close(np.chTask) -} - -// NewFixedNoOrderPool . -func NewFixedNoOrderPool(size int, bufferSize int) *FixedNoOrderPool { - np := &FixedNoOrderPool{ - chTask: make(chan func(), bufferSize), - } - - for i := 0; i < size; i++ { - go np.taskLoop() - } - - return np -} diff --git a/vendor/github.com/lesismal/nbio/taskpool/fixedpool.go b/vendor/github.com/lesismal/nbio/taskpool/fixedpool.go deleted file mode 100644 index 1ee1803c..00000000 --- a/vendor/github.com/lesismal/nbio/taskpool/fixedpool.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package taskpool - -import ( - "sync" - "sync/atomic" -) - -// fixedRunner . -type fixedRunner struct { - wg *sync.WaitGroup - - chTask chan func() - chTaskBy chan func() - chClose chan struct{} -} - -func (r *fixedRunner) taskLoop() { - defer r.wg.Done() - - // run all tasks - defer func() { - for { - select { - case f := <-r.chTaskBy: - call(f) - case f := <-r.chTask: - call(f) - default: - return - } - } - }() - - for { - select { - case f := <-r.chTaskBy: - call(f) - case f := <-r.chTask: - call(f) - case <-r.chClose: - return - } - } -} - -// FixedPool . -type FixedPool struct { - wg *sync.WaitGroup - stopped int32 - - chTask chan func() - chClose chan struct{} - - runners []*fixedRunner -} - -func (tp *FixedPool) push(f func()) { - select { - case tp.chTask <- f: - case <-tp.chClose: - } -} - -func (tp *FixedPool) pushByIndex(index int, f func()) { - r := tp.runners[uint32(index)%uint32(len(tp.runners))] - select { - case r.chTaskBy <- f: - case <-tp.chClose: - } -} - -// Go . -func (tp *FixedPool) Go(f func()) { - if atomic.LoadInt32(&tp.stopped) == 1 { - return - } - tp.push(f) -} - -// GoByIndex . -func (tp *FixedPool) GoByIndex(index int, f func()) { - if atomic.LoadInt32(&tp.stopped) == 1 { - return - } - tp.pushByIndex(index, f) -} - -// Stop . -func (tp *FixedPool) Stop() { - if atomic.CompareAndSwapInt32(&tp.stopped, 0, 1) { - close(tp.chClose) - tp.wg.Done() - tp.wg.Wait() - } -} - -// NewFixedPool . -func NewFixedPool(size int, bufferSize int) *FixedPool { - tp := &FixedPool{ - wg: &sync.WaitGroup{}, - chTask: make(chan func(), bufferSize), - chClose: make(chan struct{}), - runners: make([]*fixedRunner, size), - } - tp.wg.Add(1) - - for i := 0; i < size; i++ { - r := &fixedRunner{ - wg: tp.wg, - chTask: tp.chTask, - chTaskBy: make(chan func(), bufferSize), - chClose: tp.chClose, - } - tp.runners[i] = r - tp.wg.Add(1) - go r.taskLoop() - } - - return tp -} diff --git a/vendor/github.com/lesismal/nbio/taskpool/iotaskpool.go b/vendor/github.com/lesismal/nbio/taskpool/iotaskpool.go new file mode 100644 index 00000000..fd6a54bf --- /dev/null +++ b/vendor/github.com/lesismal/nbio/taskpool/iotaskpool.go @@ -0,0 +1,59 @@ +package taskpool + +import ( + "sync" +) + +// IOTaskPool . +type IOTaskPool struct { + task *TaskPool + pool sync.Pool +} + +// Call . +// +//go:norace +func (tp *IOTaskPool) Call(f func(*[]byte)) { + tp.task.Call(func() { + pbuf := tp.pool.Get().(*[]byte) + f(pbuf) + tp.pool.Put(pbuf) + }) +} + +// Go . +// +//go:norace +func (tp *IOTaskPool) Go(f func(*[]byte)) { + tp.task.Go(func() { + pbuf := tp.pool.Get().(*[]byte) + f(pbuf) + tp.pool.Put(pbuf) + }) +} + +// Stop . +// +//go:norace +func (tp *IOTaskPool) Stop() { + tp.task.Stop() +} + +// NewIO creates and returns a IOTaskPool. +// +//go:norace +func NewIO(concurrent, queueSize, bufSize int, v ...interface{}) *IOTaskPool { + task := New(concurrent, queueSize, v...) + + tp := &IOTaskPool{ + task: task, + pool: sync.Pool{ + New: func() interface{} { + buf := make([]byte, bufSize) + return &buf + }, + }, + } + + return tp +} diff --git a/vendor/github.com/lesismal/nbio/taskpool/mixedpool.go b/vendor/github.com/lesismal/nbio/taskpool/mixedpool.go deleted file mode 100644 index 0e8c37f3..00000000 --- a/vendor/github.com/lesismal/nbio/taskpool/mixedpool.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package taskpool - -import ( - "runtime" - "sync/atomic" - "unsafe" - - "github.com/lesismal/nbio/logging" -) - -// MixedPool . -type MixedPool struct { - *FixedNoOrderPool - cuncurrent int32 - nativeSize int32 - call func(f func()) -} - -func (mp *MixedPool) callWithRecover(f func()) { - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - logging.Error("taskpool call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) - } - atomic.AddInt32(&mp.cuncurrent, -1) - }() - f() -} - -func (mp *MixedPool) callWitoutRecover(f func()) { - defer atomic.AddInt32(&mp.cuncurrent, -1) - f() -} - -// Go . -func (mp *MixedPool) Go(f func()) { - if atomic.AddInt32(&mp.cuncurrent, 1) <= mp.nativeSize { - go func() { - mp.call(f) - for len(mp.chTask) > 0 { - select { - case f = <-mp.chTask: - mp.call(f) - default: - return - } - } - }() - } else { - atomic.AddInt32(&mp.cuncurrent, -1) - mp.FixedNoOrderPool.Go(f) - } -} - -// GoByIndex . -func (mp *MixedPool) GoByIndex(index int, f func()) { - mp.Go(f) -} - -// Stop . -func (mp *MixedPool) Stop() { - close(mp.chTask) -} - -// NewMixedPool . -func NewMixedPool(nativeSize int, fixedSize int, bufferSize int, v ...interface{}) *MixedPool { - mp := &MixedPool{ - FixedNoOrderPool: NewFixedNoOrderPool(fixedSize, bufferSize), - nativeSize: int32(nativeSize), - } - mp.call = mp.callWithRecover - if len(v) > 0 { - if withoutRecover, ok := v[0].(bool); ok && withoutRecover { - mp.call = mp.callWitoutRecover - } - } - return mp -} diff --git a/vendor/github.com/lesismal/nbio/taskpool/taskpool.go b/vendor/github.com/lesismal/nbio/taskpool/taskpool.go index f6ecaf1c..b368f8b3 100644 --- a/vendor/github.com/lesismal/nbio/taskpool/taskpool.go +++ b/vendor/github.com/lesismal/nbio/taskpool/taskpool.go @@ -5,106 +5,122 @@ package taskpool import ( - "errors" - "sync" + "runtime" "sync/atomic" - "time" -) + "unsafe" -var ( - // ErrStopped . - ErrStopped = errors.New("stopped") + "github.com/lesismal/nbio/logging" ) -// runner . -type runner struct { - parent *TaskPool -} - -func (r *runner) taskLoop(maxIdleTime time.Duration, chTask <-chan func(), chClose <-chan struct{}, f func()) { - defer func() { - r.parent.wg.Done() - <-r.parent.chRunner - }() - - call(f) - - timer := time.NewTimer(maxIdleTime) - defer timer.Stop() - for r.parent.running { - select { - case f := <-chTask: - call(f) - timer.Reset(maxIdleTime) - case <-timer.C: - return - case <-chClose: - return - } - } -} - // TaskPool . type TaskPool struct { - wg *sync.WaitGroup - running bool - stopped int32 - - chTask chan func() - chRunner chan struct{} - chClose chan struct{} - - maxIdleTime time.Duration + concurrent int64 + maxConcurrent int64 + chQqueue chan func() + chClose chan struct{} + caller func(f func()) } -func (tp *TaskPool) push(f func()) { - select { - case tp.chTask <- f: - case tp.chRunner <- struct{}{}: - r := &runner{parent: tp} - tp.wg.Add(1) - go r.taskLoop(tp.maxIdleTime, tp.chTask, tp.chClose, f) - case <-tp.chClose: +// fork . +// +//go:norace +func (tp *TaskPool) fork(f func()) bool { + if atomic.AddInt64(&tp.concurrent, 1) < tp.maxConcurrent { + go func() { + defer atomic.AddInt64(&tp.concurrent, -1) + tp.caller(f) + for { + select { + case f = <-tp.chQqueue: + if f != nil { + tp.caller(f) + } + default: + return + } + } + }() + return true } + return false +} + +// Call . +// +//go:norace +func (tp *TaskPool) Call(f func()) { + tp.caller(f) } // Go . +// +//go:norace func (tp *TaskPool) Go(f func()) { - // if atomic.LoadInt32(&tp.stopped) == 1 { - // return - // } - tp.push(f) -} + // If current goroutine num is less than maxConcurrent, + // creat a new goroutine to exec new task. + if tp.fork(f) { + return + } -// GoByIndex . -func (tp *TaskPool) GoByIndex(index int, f func()) { - tp.Go(f) + // Else push the new task into chan/queue. + atomic.AddInt64(&tp.concurrent, -1) + select { + case tp.chQqueue <- f: + case <-tp.chClose: + } } // Stop . +// +//go:norace func (tp *TaskPool) Stop() { - if atomic.CompareAndSwapInt32(&tp.stopped, 0, 1) { - tp.running = false - close(tp.chClose) - tp.wg.Done() - tp.wg.Wait() - } + atomic.AddInt64(&tp.concurrent, tp.maxConcurrent) + close(tp.chClose) } -// New . -func New(size int, maxIdleTime time.Duration) *TaskPool { - if maxIdleTime <= time.Second { - maxIdleTime = time.Second * 60 - } +// New creates and returns a TaskPool. +// +//go:norace +func New(maxConcurrent int, chQqueueSize int, v ...interface{}) *TaskPool { tp := &TaskPool{ - wg: &sync.WaitGroup{}, - running: true, - chTask: make(chan func(), 1024), - chRunner: make(chan struct{}, size), - chClose: make(chan struct{}), - maxIdleTime: maxIdleTime, + maxConcurrent: int64(maxConcurrent - 1), + chQqueue: make(chan func(), chQqueueSize), + chClose: make(chan struct{}), } - tp.wg.Add(1) - + tp.caller = func(f func()) { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("taskpool call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() + f() + } + if len(v) > 0 { + if caller, ok := v[0].(func(f func())); ok { + tp.caller = func(f func()) { + defer atomic.AddInt64(&tp.concurrent, -1) + caller(f) + } + } + } + go func() { + for { + select { + case f := <-tp.chQqueue: + if tp.fork(f) { + continue + } + + if f != nil { + tp.caller(f) + } + case <-tp.chClose: + return + } + } + }() return tp } diff --git a/vendor/github.com/lesismal/nbio/taskpool/taskpool_test.go b/vendor/github.com/lesismal/nbio/taskpool/taskpool_test.go index 81eb7757..50cf3f27 100644 --- a/vendor/github.com/lesismal/nbio/taskpool/taskpool_test.go +++ b/vendor/github.com/lesismal/nbio/taskpool/taskpool_test.go @@ -1,13 +1,17 @@ package taskpool import ( + "runtime" "sync" "testing" "time" + "unsafe" + + "github.com/lesismal/nbio/logging" ) -const testLoopNum = 1024 -const sleepTime = time.Nanosecond * 10 +const testLoopNum = 1024 * 8 +const sleepTime = time.Nanosecond * 0 func BenchmarkGo(b *testing.B) { b.ReportAllocs() @@ -17,85 +21,27 @@ func BenchmarkGo(b *testing.B) { wg := sync.WaitGroup{} wg.Add(testLoopNum) for j := 0; j < testLoopNum; j++ { - go call(func() { + go func() { + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("taskpool call failed: %v\n%v\n", err, *(*string)(unsafe.Pointer(&buf))) + } + }() if sleepTime > 0 { time.Sleep(sleepTime) } wg.Done() - }) + }() } wg.Wait() } } -func BenchmarkFixedPoolGo(b *testing.B) { - p := NewFixedPool(32, 512) - defer p.Stop() - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - wg := sync.WaitGroup{} - wg.Add(testLoopNum) - for j := 0; j < testLoopNum; j++ { - p.Go(func() { - if sleepTime > 0 { - time.Sleep(sleepTime) - } - wg.Done() - }) - } - wg.Wait() - } -} - -func BenchmarkFixedPoolGoByIndex(b *testing.B) { - p := NewFixedPool(32, 512) - defer p.Stop() - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - wg := sync.WaitGroup{} - wg.Add(testLoopNum) - for j := 0; j < testLoopNum; j++ { - p.GoByIndex(j, func() { - if sleepTime > 0 { - time.Sleep(sleepTime) - } - wg.Done() - }) - } - wg.Wait() - } -} - -func BenchmarkFixedNoOrderPool(b *testing.B) { - p := NewFixedNoOrderPool(32, 1024) - defer p.Stop() - - b.ReportAllocs() - b.ResetTimer() - - for i := 0; i < b.N; i++ { - wg := sync.WaitGroup{} - wg.Add(testLoopNum) - for j := 0; j < testLoopNum; j++ { - p.Go(func() { - if sleepTime > 0 { - time.Sleep(sleepTime) - } - wg.Done() - }) - } - wg.Wait() - } -} - -func BenchmarkMixedPool(b *testing.B) { - p := NewMixedPool(32, 4, 1024) +func BenchmarkTaskPool(b *testing.B) { + p := New(32, 1024) defer p.Stop() b.ReportAllocs() @@ -116,18 +62,17 @@ func BenchmarkMixedPool(b *testing.B) { } } -func BenchmarkTaskPool(b *testing.B) { - p := New(32, time.Second*10) +func BenchmarkIOTaskPool(b *testing.B) { + p := NewIO(32, 1024, 1024) defer p.Stop() b.ReportAllocs() b.ResetTimer() - for i := 0; i < b.N; i++ { wg := sync.WaitGroup{} wg.Add(testLoopNum) for j := 0; j < testLoopNum; j++ { - p.Go(func() { + p.Go(func(pbuf *[]byte) { if sleepTime > 0 { time.Sleep(sleepTime) } diff --git a/vendor/github.com/lesismal/nbio/timer/timer.go b/vendor/github.com/lesismal/nbio/timer/timer.go new file mode 100644 index 00000000..9c332b56 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/timer/timer.go @@ -0,0 +1,130 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package timer + +import ( + "math" + "runtime" + "sync" + "time" + "unsafe" + + "github.com/lesismal/nbio/logging" +) + +const ( + TimeForever = time.Duration(math.MaxInt64) +) + +type Timer struct { + name string + asyncMux sync.Mutex + asyncList []func() +} + +//go:norace +func New(name string) *Timer { + return &Timer{name: name, asyncList: make([]func(), 8)[0:0]} +} + +// IsTimerRunning . +// +//go:norace +func (t *Timer) IsTimerRunning() bool { + return true +} + +// Start . +// +//go:norace +func (t *Timer) Start() {} + +// Stop . +// +//go:norace +func (t *Timer) Stop() {} + +// After used as time.After. +// +//go:norace +func (t *Timer) After(d time.Duration) <-chan time.Time { + return time.After(d) +} + +// AfterFunc used as time.AfterFunc. +// +//go:norace +func (t *Timer) AfterFunc(timeout time.Duration, f func()) *time.Timer { + return time.AfterFunc(timeout, func() { + defer func() { + err := recover() + if err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("Timer[%v] exec call failed: %v\n%v\n", t.name, err, *(*string)(unsafe.Pointer(&buf))) + } + }() + f() + }) +} + +// Async executes f in another goroutine. +// +//go:norace +func (t *Timer) Async(f func()) { + t.asyncMux.Lock() + isHead := (len(t.asyncList) == 0) + t.asyncList = append(t.asyncList, f) + t.asyncMux.Unlock() + if isHead { + go func() { + i := 0 + for { + t.asyncMux.Lock() + if i == len(t.asyncList) { + if cap(t.asyncList) > 1024 { + t.asyncList = make([]func(), 0, 8) + } else { + t.asyncList = t.asyncList[0:0] + } + t.asyncMux.Unlock() + return + } + f := t.asyncList[i] + i++ + t.asyncMux.Unlock() + func() { + defer func() { + err := recover() + if err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + logging.Error("Timer[%v] async call failed: %v\n%v\n", t.name, err, *(*string)(unsafe.Pointer(&buf))) + } + }() + f() + }() + } + }() + } +} + +// func (t *Timer) Async(f func()) { + +// go func() { +// defer func() { +// err := recover() +// if err != nil { +// const size = 64 << 10 +// buf := make([]byte, size) +// buf = buf[:runtime.Stack(buf, false)] +// logging.Error("Timer[%v] exec call failed: %v\n%v\n", t.name, err, *(*string)(unsafe.Pointer(&buf))) +// } +// }() +// f() +// }() +// } diff --git a/vendor/github.com/lesismal/nbio/timer/timer_test.go b/vendor/github.com/lesismal/nbio/timer/timer_test.go new file mode 100644 index 00000000..6d29436c --- /dev/null +++ b/vendor/github.com/lesismal/nbio/timer/timer_test.go @@ -0,0 +1,134 @@ +package timer + +import ( + "log" + "math/rand" + "sync" + "testing" + "time" +) + +func TestTimer(t *testing.T) { + tg := New("nbio") + tg.Start() + defer tg.Stop() + + timeout := time.Second / 50 + + testAsync(tg) + testTimerNormal(tg, timeout) + testTimerExecPanic(tg, timeout) + testTimerNormalExecMany(tg, timeout) + testTimerExecManyRandtime(tg) +} + +func testAsync(tg *Timer) { + loops := 3 + wg := sync.WaitGroup{} + for i := 0; i < loops; i++ { + wg.Add(1) + tg.Async(func() { + defer wg.Done() + }) + } + wg.Wait() +} + +func testTimerNormal(tg *Timer, timeout time.Duration) { + t1 := time.Now() + ch1 := make(chan int) + tg.AfterFunc(timeout*5, func() { + close(ch1) + }) + <-ch1 + to1 := time.Since(t1) + if to1 < timeout*4 || to1 > timeout*10 { + log.Panicf("invalid to1: %v", to1) + } + + t2 := time.Now() + ch2 := make(chan int) + it2 := tg.AfterFunc(timeout, func() { + close(ch2) + }) + it2.Reset(timeout * 5) + <-ch2 + to2 := time.Since(t2) + if to2 < timeout*4 || to2 > timeout*10 { + log.Panicf("invalid to2: %v", to2) + } + + ch3 := make(chan int) + it3 := tg.AfterFunc(timeout, func() { + close(ch3) + }) + it3.Stop() + <-tg.After(timeout * 2) + select { + case <-ch3: + log.Panicf("stop failed") + default: + } +} + +func testTimerExecPanic(tg *Timer, timeout time.Duration) { + tg.AfterFunc(timeout, func() { + panic("test") + }) +} + +func testTimerNormalExecMany(tg *Timer, timeout time.Duration) { + ch4 := make(chan int, 5) + for i := 0; i < 5; i++ { + n := i + 1 + switch n { + case 3: + n = 5 + case 5: + n = 3 + } + + tg.AfterFunc(timeout*time.Duration(n), func() { + ch4 <- n + }) + } + + for i := 0; i < 5; i++ { + n := <-ch4 + if n != i+1 { + log.Panicf("invalid n: %v, %v", i, n) + } + } +} + +func testTimerExecManyRandtime(tg *Timer) { + its := make([]*time.Timer, 100)[0:0] + ch5 := make(chan int, 100) + for i := 0; i < 100; i++ { + n := 500 + rand.Int()%200 + to := time.Duration(n) * time.Second / 1000 + its = append(its, tg.AfterFunc(to, func() { + ch5 <- n + })) + } + for i := 0; i < 50; i++ { + if its[0] == nil { + log.Panicf("invalid its[0]") + } + its[0].Stop() + its = its[1:] + } + recved := 0 +LOOP_RECV: + for { + select { + case <-ch5: + recved++ + case <-time.After(time.Second): + break LOOP_RECV + } + } + if recved != 50 { + log.Panicf("invalid recved num: %v", recved) + } +} diff --git a/vendor/github.com/lesismal/nbio/timer_heap.go b/vendor/github.com/lesismal/nbio/timer_heap.go deleted file mode 100644 index 561b970c..00000000 --- a/vendor/github.com/lesismal/nbio/timer_heap.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2020 lesismal. All rights reserved. -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file. - -package nbio - -import ( - "math" - "time" -) - -const ( - timeForever = time.Duration(math.MaxInt64) -) - -// Timer type for export. -type Timer struct { - *htimer -} - -// heap timer item. -type htimer struct { - index int - expire time.Time - f func() - parent *Gopher -} - -// cancel timer. -func (it *htimer) Stop() { - it.parent.removeTimer(it) -} - -// reset timer. -func (it *htimer) Reset(timeout time.Duration) { - it.expire = time.Now().Add(timeout) - it.parent.resetTimer(it) -} - -type timerHeap []*htimer - -func (h timerHeap) Len() int { return len(h) } -func (h timerHeap) Less(i, j int) bool { return h[i].expire.Before(h[j].expire) } -func (h timerHeap) Swap(i, j int) { - h[i], h[j] = h[j], h[i] - h[i].index = i - h[j].index = j -} - -func (h *timerHeap) Push(x interface{}) { - *h = append(*h, x.(*htimer)) - n := len(*h) - (*h)[n-1].index = n - 1 -} -func (h *timerHeap) Pop() interface{} { - old := *h - n := len(old) - x := old[n-1] - old[n-1] = nil // avoid memory leak - *h = old[0 : n-1] - return x -} diff --git a/vendor/github.com/lesismal/nbio/tools/norace/norace.go b/vendor/github.com/lesismal/nbio/tools/norace/norace.go new file mode 100644 index 00000000..3c361824 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/tools/norace/norace.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +var ( + root, _ = filepath.Abs("./") + + skipPaths = []string{ + "_test.go", + "norace.go", + } + + chTask = make(chan func(), 32) +) + +func main() { + defer close(chTask) + + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for f := range chTask { + f() + } + }() + } + + wg := &sync.WaitGroup{} + + walk(wg, root) + + // wg.Done() + fmt.Println("wait") + wg.Wait() + + fmt.Println("exit") +} + +func run(f func()) { + chTask <- f +} + +func walk(wg *sync.WaitGroup, currRoot string) { + err := filepath.Walk(currRoot, func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + + } else { + if shouldSkip(path) { + return nil + } + wg.Add(1) + run(func() { + defer wg.Done() + addNorace(path, info) + }) + } + return nil + }) + if err != nil { + panic(err) + } +} + +func shouldSkip(path string) bool { + path, _ = filepath.Abs(path) + if !strings.HasSuffix(path, ".go") { + return true + } + for _, v := range skipPaths { + if strings.Contains(path, v) { + return true + } + } + return path == root || path == "." || path == "./" || path == "\\." +} + +func addNorace(path string, info os.FileInfo) { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + s := string(data) + s = strings.ReplaceAll(s, "\nfunc", "\n//go:norace\nfunc") + tag := "//go:norace\n" + tag2 := tag + tag + for strings.Contains(s, tag2) { + s = strings.ReplaceAll(s, tag2, tag) + } + data = []byte(s) + + tmpFile := path + time.Now().Format(".20060102150405.dec") + err = os.WriteFile(tmpFile, data, info.Mode().Perm()) + if err != nil { + log.Printf("xxx WriteFile origin [%v] failed: %v", path, err) + panic(err) + } + + err = os.Remove(path) + if err != nil { + log.Printf("xxx Remove origin [%v] failed: %v", path, err) + panic(err) + } + + err = os.Rename(tmpFile, path) + if err != nil { + log.Printf("xxx Rename tmp file[%v] -> origin file[%v] failed: %v", tmpFile, path, err) + panic(err) + } + log.Printf("+++ add norace for file[%v]", path) +} diff --git a/vendor/github.com/lesismal/nbio/writev_bsd.go b/vendor/github.com/lesismal/nbio/writev_bsd.go new file mode 100644 index 00000000..1bc74336 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/writev_bsd.go @@ -0,0 +1,28 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build darwin || netbsd || freebsd || openbsd || dragonfly +// +build darwin netbsd freebsd openbsd dragonfly + +package nbio + +import ( + "syscall" +) + +//go:norace +func writev(c *Conn, iovs [][]byte) (int, error) { + size := 0 + for _, v := range iovs { + size += len(v) + } + pbuf := c.p.g.BodyAllocator.Malloc(size) + *pbuf = (*pbuf)[0:0] + for _, v := range iovs { + pbuf = c.p.g.BodyAllocator.Append(pbuf, v...) + } + n, err := syscall.Write(c.fd, *pbuf) + c.p.g.BodyAllocator.Free(pbuf) + return n, err +} diff --git a/vendor/github.com/lesismal/nbio/writev_linux.go b/vendor/github.com/lesismal/nbio/writev_linux.go new file mode 100644 index 00000000..1e33d138 --- /dev/null +++ b/vendor/github.com/lesismal/nbio/writev_linux.go @@ -0,0 +1,36 @@ +// Copyright 2020 lesismal. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +//go:build linux +// +build linux + +package nbio + +import ( + "syscall" + "unsafe" +) + +//go:norace +func writev(c *Conn, bs [][]byte) (int, error) { + iovs := make([]syscall.Iovec, len(bs))[0:0] + for _, b := range bs { + if len(b) > 0 { + v := syscall.Iovec{} + v.SetLen(len(b)) + v.Base = &b[0] + iovs = append(iovs, v) + } + } + + if len(iovs) > 0 { + var _p0 = unsafe.Pointer(&iovs[0]) + var n, _, err = syscall.Syscall(syscall.SYS_WRITEV, uintptr(c.fd), uintptr(_p0), uintptr(len(iovs))) + if err == 0 { + return int(n), nil + } + return int(n), err + } + return 0, nil +}