perf test

pull/265/head
ffdfgdfg 2019-11-02 22:59:52 +08:00
parent 5f35415849
commit 5f58c34c8b
7 changed files with 207 additions and 158 deletions

1
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/klauspost/cpuid v1.2.1 // indirect github.com/klauspost/cpuid v1.2.1 // indirect
github.com/klauspost/reedsolomon v1.9.2 // indirect github.com/klauspost/reedsolomon v1.9.2 // indirect
github.com/onsi/gomega v1.5.0 // indirect github.com/onsi/gomega v1.5.0 // indirect
github.com/panjf2000/ants/v2 v2.2.2
github.com/pkg/errors v0.8.0 github.com/pkg/errors v0.8.0
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/shirou/gopsutil v2.18.12+incompatible github.com/shirou/gopsutil v2.18.12+incompatible

2
go.sum
View File

@ -44,6 +44,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok=
github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View File

@ -2,6 +2,8 @@ package common
import ( import (
"bytes" "bytes"
"github.com/panjf2000/ants/v2"
"net"
"sync" "sync"
) )
@ -149,11 +151,62 @@ func (Self *muxPackagerPool) Put(pack *MuxPackager) {
Self.pool.Put(pack) Self.pool.Put(pack)
} }
type connGroup struct {
src net.Conn
dst net.Conn
wg *sync.WaitGroup
}
func newConnGroup(src net.Conn, dst net.Conn, wg *sync.WaitGroup) connGroup {
return connGroup{
src: src,
dst: dst,
wg: wg,
}
}
func copyConnGroup(group interface{}) {
cg, ok := group.(connGroup)
if !ok {
return
}
_, err := CopyBuffer(cg.src, cg.dst)
if err != nil {
cg.src.Close()
cg.dst.Close()
//logs.Warn("close npc by copy from nps", err, c.connId)
}
cg.wg.Done()
}
type Conns struct {
conn1 net.Conn
conn2 net.Conn
}
func NewConns(c1 net.Conn, c2 net.Conn) Conns {
return Conns{
conn1: c1,
conn2: c2,
}
}
func copyConns(group interface{}) {
conns := group.(Conns)
wg := new(sync.WaitGroup)
wg.Add(2)
_ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg))
_ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg))
wg.Wait()
}
var once = sync.Once{} var once = sync.Once{}
var BuffPool = bufferPool{} var BuffPool = bufferPool{}
var CopyBuff = copyBufferPool{} var CopyBuff = copyBufferPool{}
var MuxPack = muxPackagerPool{} var MuxPack = muxPackagerPool{}
var WindowBuff = windowBufferPool{} var WindowBuff = windowBufferPool{}
var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false))
var CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false))
func newPool() { func newPool() {
BuffPool.New() BuffPool.New()

View File

@ -156,7 +156,7 @@ type ReceiveWindow struct {
bufQueue ReceiveWindowQueue bufQueue ReceiveWindowQueue
element *ListElement element *ListElement
readLength uint32 readLength uint32
readOp chan struct{} //readOp chan struct{}
readWait bool readWait bool
windowFull uint32 windowFull uint32
count int8 count int8
@ -167,7 +167,7 @@ type ReceiveWindow struct {
func (Self *ReceiveWindow) New(mux *Mux) { func (Self *ReceiveWindow) New(mux *Mux) {
// initial a window for receive // initial a window for receive
Self.readOp = make(chan struct{}) //Self.readOp = make(chan struct{})
Self.bufQueue.New() Self.bufQueue.New()
//Self.bw = new(bandwidth) //Self.bw = new(bandwidth)
Self.element = new(ListElement) Self.element = new(ListElement)

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"math" "math"
"net" "net"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -29,7 +28,7 @@ type Mux struct {
connType string connType string
writeQueue PriorityQueue writeQueue PriorityQueue
//bufQueue BytesQueue //bufQueue BytesQueue
sync.Mutex //sync.Mutex
} }
func NewMux(c net.Conn, connType string) *Mux { func NewMux(c net.Conn, connType string) *Mux {
@ -216,7 +215,7 @@ func (s *Mux) pingReturn() {
if latency < 0.5 && latency > 0 { if latency < 0.5 && latency > 0 {
s.latency = latency s.latency = latency
} }
//logs.Warn("latency", s.latency) logs.Warn("latency", s.latency)
common.WindowBuff.Put(data) common.WindowBuff.Put(data)
} }
}() }()
@ -242,15 +241,19 @@ func (s *Mux) readSession() {
case common.MUX_NEW_CONN: //new connection case common.MUX_NEW_CONN: //new connection
connection := NewConn(pack.Id, s, "npc ") connection := NewConn(pack.Id, s, "npc ")
s.connMap.Set(pack.Id, connection) //it has been set before send ok s.connMap.Set(pack.Id, connection) //it has been set before send ok
//go func(connection *conn) {
s.newConnCh <- connection s.newConnCh <- connection
s.sendInfo(common.MUX_NEW_CONN_OK, connection.connId, nil) s.sendInfo(common.MUX_NEW_CONN_OK, connection.connId, nil)
//}(connection)
continue continue
case common.MUX_PING_FLAG: //ping case common.MUX_PING_FLAG: //ping
s.sendInfo(common.MUX_PING_RETURN, common.MUX_PING, pack.Content) s.sendInfo(common.MUX_PING_RETURN, common.MUX_PING, pack.Content)
common.WindowBuff.Put(pack.Content) common.WindowBuff.Put(pack.Content)
continue continue
case common.MUX_PING_RETURN: case common.MUX_PING_RETURN:
//go func(content []byte) {
s.pingCh <- pack.Content s.pingCh <- pack.Content
//}(pack.Content)
continue continue
} }
if connection, ok := s.connMap.Get(pack.Id); ok && !connection.isClose { if connection, ok := s.connMap.Get(pack.Id); ok && !connection.isClose {
@ -275,8 +278,10 @@ func (s *Mux) readSession() {
continue continue
case common.MUX_CONN_CLOSE: //close the connection case common.MUX_CONN_CLOSE: //close the connection
s.connMap.Delete(pack.Id) s.connMap.Delete(pack.Id)
//go func(connection *conn) {
connection.closeFlag = true connection.closeFlag = true
connection.receiveWindow.Stop() // close signal to receive window connection.receiveWindow.Stop() // close signal to receive window
//}(connection)
continue continue
} }
} else if pack.Flag == common.MUX_CONN_CLOSE { } else if pack.Flag == common.MUX_CONN_CLOSE {

View File

@ -11,7 +11,6 @@ import (
"net/http/httputil" "net/http/httputil"
_ "net/http/pprof" _ "net/http/pprof"
"strconv" "strconv"
"sync"
"testing" "testing"
"time" "time"
"unsafe" "unsafe"
@ -30,6 +29,7 @@ func TestNewMux(t *testing.T) {
logs.SetLogFuncCallDepth(3) logs.SetLogFuncCallDepth(3)
server() server()
client() client()
//poolConnCopy, _ := ants.NewPoolWithFunc(200000, common.copyConn, ants.WithNonblocking(false))
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
go func() { go func() {
m2 := NewMux(conn2, "tcp") m2 := NewMux(conn2, "tcp")
@ -49,31 +49,34 @@ func TestNewMux(t *testing.T) {
} }
//c2.(*net.TCPConn).SetReadBuffer(0) //c2.(*net.TCPConn).SetReadBuffer(0)
//c2.(*net.TCPConn).SetReadBuffer(0) //c2.(*net.TCPConn).SetReadBuffer(0)
go func(c2 net.Conn, c *conn) { _ = common.CopyConnsPool.Invoke(common.NewConns(c2, c))
wg := sync.WaitGroup{} //go func(c2 net.Conn, c *conn) {
wg.Add(1) // wg := new(sync.WaitGroup)
go func() { // wg.Add(2)
_, err = common.CopyBuffer(c2, c) // _ = poolConnCopy.Invoke(common.newConnGroup(c2, c, wg))
if err != nil { // //go func() {
c2.Close() // // _, err = common.CopyBuffer(c2, c)
c.Close() // // if err != nil {
//logs.Warn("close npc by copy from nps", err, c.connId) // // c2.Close()
} // // c.Close()
wg.Done() // // //logs.Warn("close npc by copy from nps", err, c.connId)
}() // // }
wg.Add(1) // // wg.Done()
go func() { // //}()
_, err = common.CopyBuffer(c, c2) // //wg.Add(1)
if err != nil { // _ = poolConnCopy.Invoke(common.newConnGroup(c, c2, wg))
c2.Close() // //go func() {
c.Close() // // _, err = common.CopyBuffer(c, c2)
//logs.Warn("close npc by copy from server", err, c.connId) // // if err != nil {
} // // c2.Close()
wg.Done() // // c.Close()
}() // // //logs.Warn("close npc by copy from server", err, c.connId)
//logs.Warn("npc wait") // // }
wg.Wait() // // wg.Done()
}(c2, c.(*conn)) // //}()
// //logs.Warn("npc wait")
// wg.Wait()
//}(c2, c.(*conn))
} }
}() }()
@ -99,23 +102,30 @@ func TestNewMux(t *testing.T) {
continue continue
} }
//logs.Warn("nps new conn success ", tmpCpnn.connId) //logs.Warn("nps new conn success ", tmpCpnn.connId)
go func(tmpCpnn *conn, conns net.Conn) { _ = common.CopyConnsPool.Invoke(common.NewConns(tmpCpnn, conns))
go func() { //go func(tmpCpnn *conn, conns net.Conn) {
_, err := common.CopyBuffer(tmpCpnn, conns) // wg := new(sync.WaitGroup)
if err != nil { // wg.Add(2)
conns.Close() // _ = poolConnCopy.Invoke(common.newConnGroup(tmpCpnn, conns, wg))
tmpCpnn.Close() // //go func() {
//logs.Warn("close nps by copy from user", tmpCpnn.connId, err) // // _, err := common.CopyBuffer(tmpCpnn, conns)
} // // if err != nil {
}() // // conns.Close()
//time.Sleep(time.Second) // // tmpCpnn.Close()
_, err = common.CopyBuffer(conns, tmpCpnn) // // //logs.Warn("close nps by copy from user", tmpCpnn.connId, err)
if err != nil { // // }
conns.Close() // //}()
tmpCpnn.Close() // //wg.Add(1)
//logs.Warn("close nps by copy from npc ", tmpCpnn.connId, err) // _ = poolConnCopy.Invoke(common.newConnGroup(conns, tmpCpnn, wg))
} // //time.Sleep(time.Second)
}(tmpCpnn, conns) // //_, err = common.CopyBuffer(conns, tmpCpnn)
// //if err != nil {
// // conns.Close()
// // tmpCpnn.Close()
// // //logs.Warn("close nps by copy from npc ", tmpCpnn.connId, err)
// //}
// wg.Wait()
//}(tmpCpnn, conns)
} }
}() }()
@ -180,7 +190,7 @@ Connection: keep-alive
} }
func test_raw(k int) { func test_raw(k int) {
for i := 0; i < 10; i++ { for i := 0; i < 1; i++ {
ti := time.Now() ti := time.Now()
conn, err := net.Dial("tcp", "127.0.0.1:7777") conn, err := net.Dial("tcp", "127.0.0.1:7777")
if err != nil { if err != nil {
@ -303,7 +313,7 @@ func TestFIFO(t *testing.T) {
d.New() d.New()
go func() { go func() {
time.Sleep(time.Second) time.Sleep(time.Second)
for i := 0; i < 30010; i++ { for i := 0; i < 300100; i++ {
data, err := d.Pop() data, err := d.Pop()
if err == nil { if err == nil {
//fmt.Println(i, string(data.buf), err) //fmt.Println(i, string(data.buf), err)
@ -318,17 +328,13 @@ func TestFIFO(t *testing.T) {
}() }()
go func() { go func() {
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
for i := 0; i < 3000; i++ { for i := 0; i < 300000; i++ {
go func(i int) { data := new(ListElement)
for n := 0; n < 10; n++ { by := []byte("test " + strconv.Itoa(i) + " ") //
data := new(ListElement) _ = data.New(by, uint16(len(by)), true)
by := []byte("test " + strconv.Itoa(i) + " " + strconv.Itoa(n)) // //fmt.Println(string((*data).buf), data)
_ = data.New(by, uint16(len(by)), true) //logs.Warn(string((*data).buf), data)
//fmt.Println(string((*data).buf), data) d.Push(data)
//logs.Warn(string((*data).buf), data)
d.Push(data)
}
}(i)
} }
}() }()
time.Sleep(time.Second * 100000) time.Sleep(time.Second * 100000)
@ -345,7 +351,7 @@ func TestPriority(t *testing.T) {
d.New() d.New()
go func() { go func() {
time.Sleep(time.Second) time.Sleep(time.Second)
for i := 0; i < 36005; i++ { for i := 0; i < 360050; i++ {
data := d.Pop() data := d.Pop()
//fmt.Println(i, string(data.buf), err) //fmt.Println(i, string(data.buf), err)
logs.Warn(i, string(data.Content), data) logs.Warn(i, string(data.Content), data)
@ -354,7 +360,7 @@ func TestPriority(t *testing.T) {
}() }()
go func() { go func() {
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
for i := 0; i < 3000; i++ { for i := 0; i < 30000; i++ {
go func(i int) { go func(i int) {
for n := 0; n < 10; n++ { for n := 0; n < 10; n++ {
data := new(common.MuxPackager) data := new(common.MuxPackager)

View File

@ -1,52 +1,24 @@
package mux package mux
import ( import (
"bytes"
"errors" "errors"
"github.com/cnlh/nps/lib/common" "github.com/cnlh/nps/lib/common"
"io" "io"
"math" "math"
"runtime" "runtime"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"unsafe" "unsafe"
) )
type QueueOp struct {
readOp chan struct{}
cleanOp chan struct{}
popWait uint32
}
func (Self *QueueOp) New() {
Self.readOp = make(chan struct{})
Self.cleanOp = make(chan struct{}, 2)
}
func (Self *QueueOp) allowPop() (closed bool) {
if atomic.CompareAndSwapUint32(&Self.popWait, 1, 0) {
select {
case Self.readOp <- struct{}{}:
return false
case <-Self.cleanOp:
return true
}
}
return
}
func (Self *QueueOp) Clean() {
Self.cleanOp <- struct{}{}
Self.cleanOp <- struct{}{}
close(Self.cleanOp)
}
type PriorityQueue struct { type PriorityQueue struct {
QueueOp
highestChain *bufChain highestChain *bufChain
middleChain *bufChain middleChain *bufChain
lowestChain *bufChain lowestChain *bufChain
starving uint8 starving uint8
stop bool
cond *sync.Cond
} }
func (Self *PriorityQueue) New() { func (Self *PriorityQueue) New() {
@ -56,7 +28,8 @@ func (Self *PriorityQueue) New() {
Self.middleChain.new(32) Self.middleChain.new(32)
Self.lowestChain = new(bufChain) Self.lowestChain = new(bufChain)
Self.lowestChain.new(256) Self.lowestChain.new(256)
Self.QueueOp.New() locker := new(sync.Mutex)
Self.cond = sync.NewCond(locker)
} }
func (Self *PriorityQueue) Push(packager *common.MuxPackager) { func (Self *PriorityQueue) Push(packager *common.MuxPackager) {
@ -71,14 +44,44 @@ func (Self *PriorityQueue) Push(packager *common.MuxPackager) {
default: default:
Self.lowestChain.pushHead(unsafe.Pointer(packager)) Self.lowestChain.pushHead(unsafe.Pointer(packager))
} }
Self.allowPop() //atomic.AddUint32(&Self.count, 1)
Self.cond.Signal()
return return
} }
const maxStarving uint8 = 8 const maxStarving uint8 = 8
func (Self *PriorityQueue) Pop() (packager *common.MuxPackager) { func (Self *PriorityQueue) Pop() (packager *common.MuxPackager) {
startPop: // PriorityQueue is empty, notice Push method
var iter bool
for {
packager = Self.pop()
if packager != nil {
return
}
if Self.stop {
return
}
if iter {
break
}
iter = true
runtime.Gosched()
}
Self.cond.L.Lock()
defer Self.cond.L.Unlock()
for packager = Self.pop(); packager == nil; {
if Self.stop {
return
}
Self.cond.Wait()
packager = Self.pop()
}
//atomic.AddUint32(&Self.count, ^uint32(0))
return
}
func (Self *PriorityQueue) pop() (packager *common.MuxPackager) {
ptr, ok := Self.highestChain.popTail() ptr, ok := Self.highestChain.popTail()
if ok { if ok {
packager = (*common.MuxPackager)(ptr) packager = (*common.MuxPackager)(ptr)
@ -108,16 +111,12 @@ startPop:
return return
} }
} }
// PriorityQueue is empty, notice Push method return
if atomic.CompareAndSwapUint32(&Self.popWait, 0, 1) { }
select {
case <-Self.readOp: func (Self *PriorityQueue) Stop() {
goto startPop Self.stop = true
case <-Self.cleanOp: Self.cond.Broadcast()
return nil
}
}
goto startPop
} }
type ListElement struct { type ListElement struct {
@ -137,18 +136,19 @@ func (Self *ListElement) New(buf []byte, l uint16, part bool) (err error) {
} }
type ReceiveWindowQueue struct { type ReceiveWindowQueue struct {
QueueOp
chain *bufChain chain *bufChain
length uint32 length uint32
stopOp chan struct{} stopOp chan struct{}
readOp chan struct{}
popWait uint32
timeout time.Time timeout time.Time
} }
func (Self *ReceiveWindowQueue) New() { func (Self *ReceiveWindowQueue) New() {
Self.QueueOp.New() Self.readOp = make(chan struct{})
Self.chain = new(bufChain) Self.chain = new(bufChain)
Self.chain.new(64) Self.chain.new(64)
Self.stopOp = make(chan struct{}, 1) Self.stopOp = make(chan struct{}, 2)
} }
func (Self *ReceiveWindowQueue) Push(element *ListElement) { func (Self *ReceiveWindowQueue) Push(element *ListElement) {
@ -158,15 +158,30 @@ func (Self *ReceiveWindowQueue) Push(element *ListElement) {
return return
} }
func (Self *ReceiveWindowQueue) Pop() (element *ListElement, err error) { func (Self *ReceiveWindowQueue) pop() (element *ListElement) {
startPop:
ptr, ok := Self.chain.popTail() ptr, ok := Self.chain.popTail()
if ok { if ok {
element = (*ListElement)(ptr) element = (*ListElement)(ptr)
atomic.AddUint32(&Self.length, ^uint32(element.l-1)) atomic.AddUint32(&Self.length, ^uint32(element.l-1))
return return
} }
return
}
func (Self *ReceiveWindowQueue) Pop() (element *ListElement, err error) {
var iter bool
startPop:
element = Self.pop()
if element != nil {
return
}
if !iter {
iter = true
runtime.Gosched()
goto startPop
}
if atomic.CompareAndSwapUint32(&Self.popWait, 0, 1) { if atomic.CompareAndSwapUint32(&Self.popWait, 0, 1) {
iter = false
t := Self.timeout.Sub(time.Now()) t := Self.timeout.Sub(time.Now())
if t <= 0 { if t <= 0 {
t = time.Minute t = time.Minute
@ -176,8 +191,6 @@ startPop:
select { select {
case <-Self.readOp: case <-Self.readOp:
goto startPop goto startPop
case <-Self.cleanOp:
return
case <-Self.stopOp: case <-Self.stopOp:
err = io.EOF err = io.EOF
return return
@ -189,62 +202,31 @@ startPop:
goto startPop goto startPop
} }
func (Self *ReceiveWindowQueue) allowPop() (closed bool) {
if atomic.CompareAndSwapUint32(&Self.popWait, 1, 0) {
select {
case Self.readOp <- struct{}{}:
return false
case <-Self.stopOp:
return true
}
}
return
}
func (Self *ReceiveWindowQueue) Len() (n uint32) { func (Self *ReceiveWindowQueue) Len() (n uint32) {
return atomic.LoadUint32(&Self.length) return atomic.LoadUint32(&Self.length)
} }
func (Self *ReceiveWindowQueue) Stop() { func (Self *ReceiveWindowQueue) Stop() {
Self.stopOp <- struct{}{} Self.stopOp <- struct{}{}
Self.stopOp <- struct{}{}
} }
func (Self *ReceiveWindowQueue) SetTimeOut(t time.Time) { func (Self *ReceiveWindowQueue) SetTimeOut(t time.Time) {
Self.timeout = t Self.timeout = t
} }
type BytesQueue struct {
QueueOp
chain *bufChain
stopOp chan struct{}
}
func (Self *BytesQueue) New() {
Self.QueueOp.New()
Self.chain = new(bufChain)
Self.chain.new(8)
Self.stopOp = make(chan struct{}, 1)
}
func (Self *BytesQueue) Push(buf *bytes.Buffer) {
Self.chain.pushHead(unsafe.Pointer(buf))
Self.allowPop()
return
}
func (Self *BytesQueue) Pop() (buf *bytes.Buffer, err error) {
startPop:
ptr, ok := Self.chain.popTail()
if ok {
buf = (*bytes.Buffer)(ptr)
return
}
if atomic.CompareAndSwapUint32(&Self.popWait, 0, 1) {
select {
case <-Self.readOp:
goto startPop
case <-Self.cleanOp:
return
case <-Self.stopOp:
err = io.EOF
return
}
}
goto startPop
}
func (Self *BytesQueue) Stop() {
Self.stopOp <- struct{}{}
}
// https://golang.org/src/sync/poolqueue.go // https://golang.org/src/sync/poolqueue.go
type bufDequeue struct { type bufDequeue struct {