From 280922b49b76fdf343f52674f1b5ab9ccaf89299 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sun, 16 Jun 2024 15:17:29 +0800 Subject: [PATCH 01/29] chore: Update READMEs --- README.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++---- README_ZH.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 71469bbc9..e7ebf4f95 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ English | [中文](README_ZH.md) `gnet` is available as a Go module and we highly recommend that you use `gnet` via [Go Modules](https://go.dev/blog/using-go-modules), with Go 1.11 Modules enabled (Go 1.11+), you can just simply add `import "github.com/panjf2000/gnet/v2"` to the codebase and run `go mod download/go mod tidy` or `go [build|run|test]` to download the necessary dependencies automatically. -## With v2 +## With v2 ```bash go get -u github.com/panjf2000/gnet/v2 @@ -68,7 +68,44 @@ go get -u github.com/panjf2000/gnet The following companies/organizations use `gnet` as the underlying network service in production. -           + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
If you have `gnet` integrated into projects, feel free to open a pull request refreshing this list. @@ -160,7 +197,7 @@ The source code of `gnet` should be distributed under the Apache-2.0 license. Please read the [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `gnet`! - + # ⚓ Relevant Articles @@ -193,7 +230,47 @@ Become a bronze sponsor with a monthly donation of $10 and get your logo on our # 💴 Patrons -Patrick Othmer Jimmy ChenZhen Mai Yang 王开帅 Unger Alejandro Swaggadan Weng Wei + + + + + + + + + + + + +
+ + Patrick Othmer + + + + Jimmy + + + + ChenZhen + + + + Mai Yang + + + + 王开帅 + + + + Unger Alejandro + + + + Weng Wei + +
# 🔑 JetBrains OS licenses @@ -204,7 +281,7 @@ Become a bronze sponsor with a monthly donation of $10 and get your logo on our # 🔋 Sponsorship

-

This project is supported by:

- - +

This project is supported by:

+ +

\ No newline at end of file diff --git a/README_ZH.md b/README_ZH.md index 70f1c2c4d..a6a2b2e7d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -52,7 +52,7 @@ `gnet` 是一个 Go module,而且我们也强烈推荐通过 [Go Modules](https://go.dev/blog/using-go-modules) 来使用 `gnet`,在开启 Go Modules 支持(Go 1.11+)之后可以通过简单地在代码中写 `import "github.com/panjf2000/gnet/v2"` 来引入 `gnet`,然后执行 `go mod download/go mod tidy` 或者 `go [build|run|test]` 这些命令来自动下载所依赖的包。 -## 使用 v2 +## 使用 v2 ```bash go get -u github.com/panjf2000/gnet/v2 @@ -68,7 +68,44 @@ go get -u github.com/panjf2000/gnet 以下公司/组织在生产环境上使用了 `gnet` 作为底层网络服务。 -           + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
如果你的项目也在使用 `gnet`,欢迎给我提 Pull Request 来更新这份列表。 @@ -160,7 +197,7 @@ Test duration : 15s 请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `gnet` 贡献过代码的开发者! - + # ⚓ 相关文章 @@ -193,7 +230,47 @@ Test duration : 15s # 💴 资助者 -Patrick Othmer Jimmy ChenZhen Mai Yang 王开帅 Unger Alejandro Swaggadan Weng Wei + + + + + + + + + + + + +
+ + Patrick Othmer + + + + Jimmy + + + + ChenZhen + + + + Mai Yang + + + + 王开帅 + + + + Unger Alejandro + + + + Weng Wei + +
# 🔑 JetBrains 开源证书支持 @@ -204,7 +281,7 @@ Test duration : 15s # 🔋 赞助商

-

本项目由以下机构赞助:

- - +

本项目由以下机构赞助:

+ +

\ No newline at end of file From 5346527602153e314b558e308d484f9d78c27bf9 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 18 Jun 2024 18:22:48 +0800 Subject: [PATCH 02/29] chore: upgrade modules and update .gitignore (#613) --- .gitignore | 3 ++- go.mod | 6 +++--- go.sum | 12 +++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index acd5ee6c4..8e37a6b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# env +# IDEs .idea/ +.vscode/ # dependencies /node_modules diff --git a/go.mod b/go.mod index 02ad938c6..178d229c9 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/panjf2000/gnet/v2 require ( - github.com/panjf2000/ants/v2 v2.9.0 - github.com/stretchr/testify v1.8.4 + github.com/panjf2000/ants/v2 v2.10.0 + github.com/stretchr/testify v1.9.0 github.com/valyala/bytebufferpool v1.0.0 go.uber.org/zap v1.21.0 // don't upgrade this one golang.org/x/sync v0.7.0 - golang.org/x/sys v0.19.0 + golang.org/x/sys v0.21.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 0823447e5..7ba46041c 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/panjf2000/ants/v2 v2.9.0 h1:SztCLkVxBRigbg+vt0S5QvF5vxAbxbKt09/YfAJ0tEo= -github.com/panjf2000/ants/v2 v2.9.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= +github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8= +github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -17,13 +17,15 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -53,8 +55,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 36df9ef65a4c9e856d00814888f028dc1a05b309 Mon Sep 17 00:00:00 2001 From: serious-snow <30310631+serious-snow@users.noreply.github.com> Date: Sat, 22 Jun 2024 18:27:14 +0800 Subject: [PATCH 03/29] bug: fix Conn.Next and Conn.Peek panic on Unix (#616) Co-authored-by: wangjian --- connection_unix.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connection_unix.go b/connection_unix.go index 68699f16b..cfce3a928 100644 --- a/connection_unix.go +++ b/connection_unix.go @@ -325,13 +325,13 @@ func (c *conn) Next(n int) (buf []byte, err error) { } head, tail := c.inboundBuffer.Peek(n) defer c.inboundBuffer.Discard(n) //nolint:errcheck - if len(head) == n { + if len(head) >= n { return head[:n], err } c.loop.cache.Reset() c.loop.cache.Write(head) c.loop.cache.Write(tail) - if inBufferLen == n { + if inBufferLen >= n { return c.loop.cache.Bytes(), err } @@ -352,13 +352,13 @@ func (c *conn) Peek(n int) (buf []byte, err error) { return c.buffer[:n], err } head, tail := c.inboundBuffer.Peek(n) - if len(head) == n { + if len(head) >= n { return head[:n], err } c.loop.cache.Reset() c.loop.cache.Write(head) c.loop.cache.Write(tail) - if inBufferLen == n { + if inBufferLen >= n { return c.loop.cache.Bytes(), err } From 5205c5f8c18b0897a5d3d8e76491785b3fcd1ab2 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 25 Jun 2024 18:50:41 +0800 Subject: [PATCH 04/29] test: increase the test code coverage a little bit (#618) --- acceptor_unix.go | 46 ++++++++++++++++++++++------------------------ client_test.go | 32 +++++++++++++++++++++++++++++--- client_unix.go | 9 ++++++--- gnet_test.go | 14 +++++++++++++- 4 files changed, 70 insertions(+), 31 deletions(-) diff --git a/acceptor_unix.go b/acceptor_unix.go index ae6e9a35e..7622f1a70 100644 --- a/acceptor_unix.go +++ b/acceptor_unix.go @@ -31,19 +31,18 @@ import ( func (el *eventloop) accept0(fd int, _ netpoll.IOEvent, _ netpoll.IOFlags) error { for { nfd, sa, err := socket.Accept(fd) - if err != nil { - switch err { - case unix.EAGAIN: // the Accept queue has been drained out, we can return now - return nil - case unix.EINTR, unix.ECONNRESET, unix.ECONNABORTED: - // ECONNRESET or ECONNABORTED could indicate that a socket - // in the Accept queue was closed before we Accept()ed it. - // It's a silly error, let's retry it. - continue - default: - el.getLogger().Errorf("Accept() failed due to error: %v", err) - return errors.ErrAcceptSocket - } + switch err { + case nil: + case unix.EAGAIN: // the Accept queue has been drained out, we can return now + return nil + case unix.EINTR, unix.ECONNRESET, unix.ECONNABORTED: + // ECONNRESET or ECONNABORTED could indicate that a socket + // in the Accept queue was closed before we Accept()ed it. + // It's a silly error, let's retry it. + continue + default: + el.getLogger().Errorf("Accept() failed due to error: %v", err) + return errors.ErrAcceptSocket } remoteAddr := socket.SockaddrToTCPOrUnixAddr(sa) @@ -71,17 +70,16 @@ func (el *eventloop) accept(fd int, ev netpoll.IOEvent, flags netpoll.IOFlags) e } nfd, sa, err := socket.Accept(fd) - if err != nil { - switch err { - case unix.EINTR, unix.EAGAIN, unix.ECONNRESET, unix.ECONNABORTED: - // ECONNRESET or ECONNABORTED could indicate that a socket - // in the Accept queue was closed before we Accept()ed it. - // It's a silly error, let's retry it. - return nil - default: - el.getLogger().Errorf("Accept() failed due to error: %v", err) - return errors.ErrAcceptSocket - } + switch err { + case nil: + case unix.EINTR, unix.EAGAIN, unix.ECONNRESET, unix.ECONNABORTED: + // ECONNRESET or ECONNABORTED could indicate that a socket + // in the Accept queue was closed before we Accept()ed it. + // It's a silly error, let's retry it. + return nil + default: + el.getLogger().Errorf("Accept() failed due to error: %v", err) + return errors.ErrAcceptSocket } remoteAddr := socket.SockaddrToTCPOrUnixAddr(sa) diff --git a/client_test.go b/client_test.go index 8b56f5494..d1c89f458 100644 --- a/client_test.go +++ b/client_test.go @@ -8,6 +8,7 @@ import ( "io" "math/rand" "net" + "path/filepath" "sync" "sync/atomic" "testing" @@ -15,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" @@ -437,7 +439,6 @@ func runClient(t *testing.T, network, addr string, et, reuseport, multicore, asy clientEV := &clientEvents{tester: t, packetLen: streamLen, svr: ts} ts.client, err = NewClient( clientEV, - WithLogLevel(logging.DebugLevel), WithLockOSThread(true), WithTicker(true), ) @@ -599,9 +600,22 @@ func testConnWakeImmediately(t *testing.T, client *Client, clientEV *clientEvent } func TestWakeConnImmediately(t *testing.T) { + currentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher() + t.Cleanup(func() { + logging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore + }) + clientEV := &clientEventsForWake{tester: t} - client, err := NewClient(clientEV, WithLogLevel(logging.DebugLevel)) + logPath := filepath.Join(t.TempDir(), "gnet-test-wake-conn-immediately.log") + client, err := NewClient(clientEV, + WithSocketRecvBuffer(4*1024), + WithSocketSendBuffer(4*1024), + WithLogPath(logPath), + WithLogLevel(logging.WarnLevel), + WithReadBufferCap(512), + WithWriteBufferCap(512)) assert.NoError(t, err) + logging.Cleanup() err = client.Start() assert.NoError(t, err) @@ -614,6 +628,11 @@ func TestWakeConnImmediately(t *testing.T) { } func TestClientReadOnEOF(t *testing.T) { + currentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher() + t.Cleanup(func() { + logging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore + }) + ln, err := net.Listen("tcp", "127.0.0.1:9999") assert.NoError(t, err) defer ln.Close() @@ -635,7 +654,14 @@ func TestClientReadOnEOF(t *testing.T) { }, 1), data: []byte("test"), } - cli, err := NewClient(ev) + cli, err := NewClient(ev, + WithSocketRecvBuffer(4*1024), + WithSocketSendBuffer(4*1024), + WithTCPNoDelay(TCPDelay), + WithTCPKeepAlive(time.Minute), + WithLogger(zap.NewExample().Sugar()), + WithReadBufferCap(32*1024), + WithWriteBufferCap(32*1024)) assert.NoError(t, err) defer cli.Stop() //nolint:errcheck diff --git a/client_unix.go b/client_unix.go index 597d73592..0ce24f463 100644 --- a/client_unix.go +++ b/client_unix.go @@ -200,7 +200,8 @@ func (cli *Client) EnrollContext(c net.Conn, ctx interface{}) (Conn, error) { ) switch c.(type) { case *net.UnixConn: - if sockAddr, _, _, err = socket.GetUnixSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()); err != nil { + sockAddr, _, _, err = socket.GetUnixSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()) + if err != nil { return nil, err } ua := c.LocalAddr().(*net.UnixAddr) @@ -217,12 +218,14 @@ func (cli *Client) EnrollContext(c net.Conn, ctx interface{}) (Conn, error) { return nil, err } } - if sockAddr, _, _, _, err = socket.GetTCPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()); err != nil { + sockAddr, _, _, _, err = socket.GetTCPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()) + if err != nil { return nil, err } gc = newTCPConn(dupFD, cli.el, sockAddr, c.LocalAddr(), c.RemoteAddr()) case *net.UDPConn: - if sockAddr, _, _, _, err = socket.GetUDPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()); err != nil { + sockAddr, _, _, _, err = socket.GetUDPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String()) + if err != nil { return nil, err } gc = newUDPConn(dupFD, cli.el, c.LocalAddr(), sockAddr, true) diff --git a/gnet_test.go b/gnet_test.go index 11c7df997..7c341d45b 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -9,6 +9,7 @@ import ( "io" "math/rand" "net" + "path/filepath" "runtime" "strings" "sync/atomic" @@ -883,8 +884,19 @@ func (t *testShutdownServer) OnTick() (delay time.Duration, action Action) { } func testShutdown(t *testing.T, network, addr string) { + currentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher() + t.Cleanup(func() { + logging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore + }) + events := &testShutdownServer{tester: t, network: network, addr: addr, N: 100} - err := Run(events, network+"://"+addr, WithTicker(true), WithReadBufferCap(512), WithWriteBufferCap(512)) + logPath := filepath.Join(t.TempDir(), "gnet-test-shutdown.log") + err := Run(events, network+"://"+addr, + WithLogPath(logPath), + WithLogLevel(logging.WarnLevel), + WithTicker(true), + WithReadBufferCap(512), + WithWriteBufferCap(512)) assert.NoError(t, err) require.Equal(t, 0, int(events.clients), "did not close all clients") } From 66c2259fee470e3cbe2279c2ad7e9d62f372c743 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 25 Jun 2024 19:38:02 +0800 Subject: [PATCH 05/29] bug: fix the wrong default behavior of TCPNoDelay on client side (#619) --- client_test.go | 3 +-- client_unix.go | 4 ++-- gnet_test.go | 2 +- options.go | 7 +++++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client_test.go b/client_test.go index d1c89f458..1f446501c 100644 --- a/client_test.go +++ b/client_test.go @@ -439,6 +439,7 @@ func runClient(t *testing.T, network, addr string, et, reuseport, multicore, asy clientEV := &clientEvents{tester: t, packetLen: streamLen, svr: ts} ts.client, err = NewClient( clientEV, + WithTCPNoDelay(TCPNoDelay), WithLockOSThread(true), WithTicker(true), ) @@ -456,7 +457,6 @@ func runClient(t *testing.T, network, addr string, et, reuseport, multicore, asy WithReusePort(reuseport), WithTicker(true), WithTCPKeepAlive(time.Minute*1), - WithTCPNoDelay(TCPDelay), WithLoadBalancing(lb)) assert.NoError(t, err) } @@ -657,7 +657,6 @@ func TestClientReadOnEOF(t *testing.T) { cli, err := NewClient(ev, WithSocketRecvBuffer(4*1024), WithSocketSendBuffer(4*1024), - WithTCPNoDelay(TCPDelay), WithTCPKeepAlive(time.Minute), WithLogger(zap.NewExample().Sugar()), WithReadBufferCap(32*1024), diff --git a/client_unix.go b/client_unix.go index 0ce24f463..2d82f8e98 100644 --- a/client_unix.go +++ b/client_unix.go @@ -208,8 +208,8 @@ func (cli *Client) EnrollContext(c net.Conn, ctx interface{}) (Conn, error) { ua.Name = c.RemoteAddr().String() + "." + strconv.Itoa(dupFD) gc = newTCPConn(dupFD, cli.el, sockAddr, c.LocalAddr(), c.RemoteAddr()) case *net.TCPConn: - if cli.opts.TCPNoDelay == TCPDelay { - if err = socket.SetNoDelay(dupFD, 0); err != nil { + if cli.opts.TCPNoDelay == TCPNoDelay { + if err = socket.SetNoDelay(dupFD, 1); err != nil { return nil, err } } diff --git a/gnet_test.go b/gnet_test.go index 7c341d45b..3147bb70f 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -643,7 +643,7 @@ func runServer(t *testing.T, addrs []string, et, reuseport, multicore, async, wr WithReusePort(reuseport), WithTicker(true), WithTCPKeepAlive(time.Minute), - WithTCPNoDelay(TCPDelay), + WithTCPNoDelay(TCPNoDelay), WithLoadBalancing(lb)) } else { err = Run(ts, diff --git a/options.go b/options.go index 86caef7fb..ef50639d1 100644 --- a/options.go +++ b/options.go @@ -98,9 +98,12 @@ type Options struct { // TCPNoDelay controls whether the operating system should delay // packet transmission in hopes of sending fewer packets (Nagle's algorithm). + // When this option is assign to TCPNoDelay, TCP_NODELAY socket option will + // be turned on, on the contrary, if it is assigned to TCPDelay, the socket + // option will be turned off. // - // The default is true (no delay), meaning that data is sent - // as soon as possible after a write operation. + // The default is TCPNoDelay, meaning that TCP_NODELAY is turned on and data + // will not be buffered but sent as soon as possible after a write operation. TCPNoDelay TCPSocketOpt // SocketRecvBuffer sets the maximum socket receive buffer in bytes. From 8a80aaf09d71364a09c4c51dee62628d6f1d6b66 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 26 Jun 2024 11:24:43 +0800 Subject: [PATCH 06/29] doc: fix a few typos and update the comments for Options --- options.go | 76 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/options.go b/options.go index ef50639d1..ed1f8e2ec 100644 --- a/options.go +++ b/options.go @@ -46,21 +46,23 @@ type Options struct { // Multicore indicates whether the engine will be effectively created with multi-cores, if so, // then you must take care with synchronizing memory between all event callbacks, otherwise, - // it will run the engine with single thread. The number of threads in the engine will be automatically - // assigned to the value of logical CPUs usable by the current process. + // it will run the engine with single thread. The number of threads in the engine will be + // automatically assigned to the number of usable logical CPUs that can be leveraged by the + // current process. Multicore bool - // NumEventLoop is set up to start the given number of event-loop goroutine. - // Note: Setting up NumEventLoop will override Multicore. + // NumEventLoop is set up to start the given number of event-loop goroutines. + // Note that a non-negative NumEventLoop will override Multicore. NumEventLoop int - // LB represents the load-balancing algorithm used when assigning new connections. + // LB represents the load-balancing algorithm used when assigning new connections + // to event loops. LB LoadBalancing - // ReuseAddr indicates whether to set up the SO_REUSEADDR socket option. + // ReuseAddr indicates whether to set the SO_REUSEADDR socket option. ReuseAddr bool - // ReusePort indicates whether to set up the SO_REUSEPORT socket option. + // ReusePort indicates whether to set the SO_REUSEPORT socket option. ReusePort bool // MulticastInterfaceIndex is the index of the interface name where the multicast UDP addresses will be bound to. @@ -77,28 +79,29 @@ type Options struct { ReadBufferCap int // WriteBufferCap is the maximum number of bytes that a static outbound buffer can hold, - // if the data exceeds this value, the overflow will be stored in the elastic linked list buffer. + // if the data exceeds this value, the overflow bytes will be stored in the elastic linked list buffer. // The default value is 64KB. // // Note that WriteBufferCap will always be converted to the least power of two integer value greater than // or equal to its real amount. WriteBufferCap int - // LockOSThread is used to determine whether each I/O event-loop is associated to an OS thread, it is useful when you - // need some kind of mechanisms like thread local storage, or invoke certain C libraries (such as graphics lib: GLib) - // that require thread-level manipulation via cgo, or want all I/O event-loops to actually run in parallel for a - // potential higher performance. + // LockOSThread is used to determine whether each I/O event-loop should be associated to an OS thread, + // it is useful when you need some kind of mechanisms like thread local storage, or invoke certain C + // libraries (such as graphics lib: GLib) that require thread-level manipulation via cgo, or want all I/O + // event-loops to actually run in parallel for a potential higher performance. LockOSThread bool // Ticker indicates whether the ticker has been set up. Ticker bool - // TCPKeepAlive sets up a duration for (SO_KEEPALIVE) socket option. + // TCPKeepAlive enable the TCP keep-alive mechanism (SO_KEEPALIVE) and set its value + // on TCP_KEEPIDLE, 1/5 of its value on TCP_KEEPINTVL, and 5 on TCP_KEEPCNT. TCPKeepAlive time.Duration // TCPNoDelay controls whether the operating system should delay // packet transmission in hopes of sending fewer packets (Nagle's algorithm). - // When this option is assign to TCPNoDelay, TCP_NODELAY socket option will + // When this option is assigned to TCPNoDelay, TCP_NODELAY socket option will // be turned on, on the contrary, if it is assigned to TCPDelay, the socket // option will be turned off. // @@ -106,20 +109,21 @@ type Options struct { // will not be buffered but sent as soon as possible after a write operation. TCPNoDelay TCPSocketOpt - // SocketRecvBuffer sets the maximum socket receive buffer in bytes. + // SocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes. SocketRecvBuffer int - // SocketSendBuffer sets the maximum socket send buffer in bytes. + // SocketSendBuffer sets the maximum socket send buffer of kernel in bytes. SocketSendBuffer int - // LogPath the local path where logs will be written, this is the easiest way to set up logging, - // gnet instantiates a default uber-go/zap logger with this given log path, you are also allowed to employ - // you own logger during the lifetime by implementing the following log.Logger interface. + // LogPath specifies a local path where logs will be written, this is the easiest + // way to set up logging, gnet instantiates a default uber-go/zap logger with this + // given log path, you are also allowed to employ your own logger during the lifetime + // by implementing the following logging.Logger interface. // - // Note that this option can be overridden by the option Logger. + // Note that this option can be overridden by a non-nil option Logger. LogPath string - // LogLevel indicates the logging level, it should be used along with LogPath. + // LogLevel specifies the logging level, it should be used along with LogPath. LogLevel logging.Level // Logger is the customized logger for logging info, if it is not set, @@ -139,63 +143,63 @@ func WithOptions(options Options) Option { } } -// WithMulticore sets up multi-cores in gnet engine. +// WithMulticore enables multi-cores mode for gnet engine. func WithMulticore(multicore bool) Option { return func(opts *Options) { opts.Multicore = multicore } } -// WithLockOSThread sets up LockOSThread mode for I/O event-loops. +// WithLockOSThread enables LockOSThread mode for I/O event-loops. func WithLockOSThread(lockOSThread bool) Option { return func(opts *Options) { opts.LockOSThread = lockOSThread } } -// WithReadBufferCap sets up ReadBufferCap for reading bytes. +// WithReadBufferCap sets ReadBufferCap for reading bytes. func WithReadBufferCap(readBufferCap int) Option { return func(opts *Options) { opts.ReadBufferCap = readBufferCap } } -// WithWriteBufferCap sets up WriteBufferCap for pending bytes. +// WithWriteBufferCap sets WriteBufferCap for pending bytes. func WithWriteBufferCap(writeBufferCap int) Option { return func(opts *Options) { opts.WriteBufferCap = writeBufferCap } } -// WithLoadBalancing sets up the load-balancing algorithm in gnet engine. +// WithLoadBalancing picks the load-balancing algorithm for gnet engine. func WithLoadBalancing(lb LoadBalancing) Option { return func(opts *Options) { opts.LB = lb } } -// WithNumEventLoop sets up NumEventLoop in gnet engine. +// WithNumEventLoop sets the number of event loops for gnet engine. func WithNumEventLoop(numEventLoop int) Option { return func(opts *Options) { opts.NumEventLoop = numEventLoop } } -// WithReusePort sets up SO_REUSEPORT socket option. +// WithReusePort sets SO_REUSEPORT socket option. func WithReusePort(reusePort bool) Option { return func(opts *Options) { opts.ReusePort = reusePort } } -// WithReuseAddr sets up SO_REUSEADDR socket option. +// WithReuseAddr sets SO_REUSEADDR socket option. func WithReuseAddr(reuseAddr bool) Option { return func(opts *Options) { opts.ReuseAddr = reuseAddr } } -// WithTCPKeepAlive sets up the SO_KEEPALIVE socket option with duration. +// WithTCPKeepAlive enables the TCP keep-alive mechanism and sets its values. func WithTCPKeepAlive(tcpKeepAlive time.Duration) Option { return func(opts *Options) { opts.TCPKeepAlive = tcpKeepAlive @@ -209,42 +213,42 @@ func WithTCPNoDelay(tcpNoDelay TCPSocketOpt) Option { } } -// WithSocketRecvBuffer sets the maximum socket receive buffer in bytes. +// WithSocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes. func WithSocketRecvBuffer(recvBuf int) Option { return func(opts *Options) { opts.SocketRecvBuffer = recvBuf } } -// WithSocketSendBuffer sets the maximum socket send buffer in bytes. +// WithSocketSendBuffer sets the maximum socket send buffer of kernel in bytes. func WithSocketSendBuffer(sendBuf int) Option { return func(opts *Options) { opts.SocketSendBuffer = sendBuf } } -// WithTicker indicates that a ticker is set. +// WithTicker indicates whether a ticker is currently set. func WithTicker(ticker bool) Option { return func(opts *Options) { opts.Ticker = ticker } } -// WithLogPath is an option to set up the local path of log file. +// WithLogPath specifies a local path for logging file. func WithLogPath(fileName string) Option { return func(opts *Options) { opts.LogPath = fileName } } -// WithLogLevel is an option to set up the logging level. +// WithLogLevel specifies the logging level for the local logging file. func WithLogLevel(lvl logging.Level) Option { return func(opts *Options) { opts.LogLevel = lvl } } -// WithLogger sets up a customized logger. +// WithLogger specifies a customized logger. func WithLogger(logger logging.Logger) Option { return func(opts *Options) { opts.Logger = logger From 1ed4d08a988375011ad893690e7f18ba6d3065fa Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 3 Jul 2024 10:54:09 +0800 Subject: [PATCH 07/29] opt: close file descriptor after OnClose() (#622) Fixes #621 --- eventloop_unix.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/eventloop_unix.go b/eventloop_unix.go index 71361100d..617cf9162 100644 --- a/eventloop_unix.go +++ b/eventloop_unix.go @@ -244,6 +244,11 @@ func (el *eventloop) close(c *conn, err error) (rerr error) { return // ignore stale connections } + el.connections.delConn(c) + if el.eventHandler.OnClose(c, err) == Shutdown { + rerr = errorx.ErrEngineShutdown + } + // Send residual data in buffer back to the remote before actually closing the connection. for !c.outboundBuffer.IsEmpty() { iov, _ := c.outboundBuffer.Peek(0) @@ -258,22 +263,26 @@ func (el *eventloop) close(c *conn, err error) (rerr error) { } err0, err1 := el.poller.Delete(c.fd), unix.Close(c.fd) + var errStr strings.Builder if err0 != nil { - rerr = fmt.Errorf("failed to delete fd=%d from poller in event-loop(%d): %v", c.fd, el.idx, err0) + err0 = fmt.Errorf("failed to delete fd=%d from poller in event-loop(%d): %v", + c.fd, el.idx, os.NewSyscallError("delete", err0)) + errStr.WriteString(err0.Error()) + errStr.WriteString(" | ") } if err1 != nil { - err1 = fmt.Errorf("failed to close fd=%d in event-loop(%d): %v", c.fd, el.idx, os.NewSyscallError("close", err1)) + err1 = fmt.Errorf("failed to close fd=%d in event-loop(%d): %v", + c.fd, el.idx, os.NewSyscallError("close", err1)) + errStr.WriteString(err1.Error()) + } + if errStr.Len() > 0 { if rerr != nil { - rerr = errors.New(rerr.Error() + " & " + err1.Error()) + el.getLogger().Errorf(strings.TrimSuffix(errStr.String(), " | ")) } else { - rerr = err1 + rerr = errors.New(strings.TrimSuffix(errStr.String(), " | ")) } } - el.connections.delConn(c) - if el.eventHandler.OnClose(c, err) == Shutdown { - rerr = errorx.ErrEngineShutdown - } c.release() return From 70472bf1e73eb7460a47243aa509f5d368e427be Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 3 Jul 2024 20:36:47 +0800 Subject: [PATCH 08/29] opt: close file descriptor after OnClose() for UDP (#624) Updates #621 Follows up #622 This PR also refines `eventloop.close()` and fixes the potential UDP socket leaks for Windows clients. --- client_windows.go | 2 +- connection_windows.go | 1 - eventloop_unix.go | 61 +++++++++++++++---------------------------- eventloop_windows.go | 29 +++++++------------- 4 files changed, 32 insertions(+), 61 deletions(-) diff --git a/client_windows.go b/client_windows.go index 33e0566ff..d9cbbde4b 100644 --- a/client_windows.go +++ b/client_windows.go @@ -205,7 +205,7 @@ func (cli *Client) EnrollContext(nc net.Conn, ctx interface{}) (gc Conn, err err c := newUDPConn(cli.el, nil, nc.LocalAddr(), nc.RemoteAddr()) c.SetContext(ctx) c.rawConn = nc - cli.el.ch <- &openConn{c: c, isDatagram: true, cb: func() { close(connOpened) }} + cli.el.ch <- &openConn{c: c, cb: func() { close(connOpened) }} go func(uc net.Conn, el *eventloop) { var buffer [0x10000]byte for { diff --git a/connection_windows.go b/connection_windows.go index e46498f9d..efdfc22c9 100644 --- a/connection_windows.go +++ b/connection_windows.go @@ -45,7 +45,6 @@ type udpConn struct { type openConn struct { c *conn cb func() - isDatagram bool } type conn struct { diff --git a/eventloop_unix.go b/eventloop_unix.go index 617cf9162..97475302c 100644 --- a/eventloop_unix.go +++ b/eventloop_unix.go @@ -226,28 +226,13 @@ loop: return nil } -func (el *eventloop) close(c *conn, err error) (rerr error) { - if addr := c.localAddr; addr != nil && strings.HasPrefix(c.localAddr.Network(), "udp") { - rerr = el.poller.Delete(c.fd) - if _, ok := el.listeners[c.fd]; !ok { - rerr = unix.Close(c.fd) - el.connections.delConn(c) - } - if el.eventHandler.OnClose(c, err) == Shutdown { - return errorx.ErrEngineShutdown - } - c.release() - return - } - +func (el *eventloop) close(c *conn, err error) error { if !c.opened || el.connections.getConn(c.fd) == nil { - return // ignore stale connections + return nil // ignore stale connections } el.connections.delConn(c) - if el.eventHandler.OnClose(c, err) == Shutdown { - rerr = errorx.ErrEngineShutdown - } + action := el.eventHandler.OnClose(c, err) // Send residual data in buffer back to the remote before actually closing the connection. for !c.outboundBuffer.IsEmpty() { @@ -262,8 +247,10 @@ func (el *eventloop) close(c *conn, err error) (rerr error) { } } - err0, err1 := el.poller.Delete(c.fd), unix.Close(c.fd) + c.release() + var errStr strings.Builder + err0, err1 := el.poller.Delete(c.fd), unix.Close(c.fd) if err0 != nil { err0 = fmt.Errorf("failed to delete fd=%d from poller in event-loop(%d): %v", c.fd, el.idx, os.NewSyscallError("delete", err0)) @@ -276,16 +263,10 @@ func (el *eventloop) close(c *conn, err error) (rerr error) { errStr.WriteString(err1.Error()) } if errStr.Len() > 0 { - if rerr != nil { - el.getLogger().Errorf(strings.TrimSuffix(errStr.String(), " | ")) - } else { - rerr = errors.New(strings.TrimSuffix(errStr.String(), " | ")) - } + return errors.New(strings.TrimSuffix(errStr.String(), " | ")) } - c.release() - - return + return el.handleAction(c, action) } func (el *eventloop) wake(c *conn) error { @@ -333,19 +314,6 @@ func (el *eventloop) ticker(ctx context.Context) { } } -func (el *eventloop) handleAction(c *conn, action Action) error { - switch action { - case None: - return nil - case Close: - return el.close(c, nil) - case Shutdown: - return errorx.ErrEngineShutdown - default: - return nil - } -} - func (el *eventloop) readUDP(fd int, _ netpoll.IOEvent, _ netpoll.IOFlags) error { n, sa, err := unix.Recvfrom(fd, el.buffer, 0) if err != nil { @@ -372,6 +340,19 @@ func (el *eventloop) readUDP(fd int, _ netpoll.IOEvent, _ netpoll.IOFlags) error return nil } +func (el *eventloop) handleAction(c *conn, action Action) error { + switch action { + case None: + return nil + case Close: + return el.close(c, nil) + case Shutdown: + return errorx.ErrEngineShutdown + default: + return nil + } +} + /* func (el *eventloop) execCmd(itf interface{}) (err error) { cmd := itf.(*asyncCmd) diff --git a/eventloop_windows.go b/eventloop_windows.go index ea0f87377..93cadabf3 100644 --- a/eventloop_windows.go +++ b/eventloop_windows.go @@ -19,7 +19,6 @@ import ( "context" "errors" "runtime" - "strings" "sync/atomic" "time" @@ -97,10 +96,8 @@ func (el *eventloop) open(oc *openConn) error { } c := oc.c - if !oc.isDatagram { - el.connections[c] = struct{}{} - el.incConn(1) - } + el.connections[c] = struct{}{} + el.incConn(1) out, action := el.eventHandler.OnOpen(c) if out != nil { @@ -188,28 +185,22 @@ func (el *eventloop) wake(c *conn) error { } func (el *eventloop) close(c *conn, err error) error { - if addr := c.localAddr; addr != nil && strings.HasPrefix(addr.Network(), "udp") { - action := el.eventHandler.OnClose(c, err) - if c.rawConn != nil { - if err := c.rawConn.Close(); err != nil { - el.getLogger().Errorf("failed to close connection(%s), error:%v", c.remoteAddr.String(), err) - } - } - c.release() - return el.handleAction(c, action) - } - - if _, ok := el.connections[c]; !ok { + if _, ok := el.connections[c]; c.localAddr == nil || !ok { return nil // ignore stale wakes. } delete(el.connections, c) el.incConn(-1) action := el.eventHandler.OnClose(c, err) - if err := c.rawConn.Close(); err != nil { - el.getLogger().Errorf("failed to close connection(%s), error:%v", c.remoteAddr.String(), err) + err = nil + + if c.rawConn != nil { + err = c.rawConn.Close() } c.release() + if err != nil { + return err + } return el.handleAction(c, action) } From 42cc9a80607e27216f310457cff3fee643f10b61 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 3 Jul 2024 20:44:17 +0800 Subject: [PATCH 09/29] opt: prevent server-side UDP sockets in eventloop.close() on Windows Follows up #624 --- eventloop_windows.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/eventloop_windows.go b/eventloop_windows.go index 93cadabf3..74bea1a46 100644 --- a/eventloop_windows.go +++ b/eventloop_windows.go @@ -185,7 +185,7 @@ func (el *eventloop) wake(c *conn) error { } func (el *eventloop) close(c *conn, err error) error { - if _, ok := el.connections[c]; c.localAddr == nil || !ok { + if _, ok := el.connections[c]; c.rawConn == nil || !ok { return nil // ignore stale wakes. } @@ -194,9 +194,7 @@ func (el *eventloop) close(c *conn, err error) error { action := el.eventHandler.OnClose(c, err) err = nil - if c.rawConn != nil { - err = c.rawConn.Close() - } + err = c.rawConn.Close() c.release() if err != nil { return err From e91e9b344b10f8eea9696697ef2a63a8ec700e90 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 3 Jul 2024 21:50:10 +0800 Subject: [PATCH 10/29] windows: eliminate the redundant assignment in eventloop.close() --- eventloop_windows.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/eventloop_windows.go b/eventloop_windows.go index 74bea1a46..ea053a87d 100644 --- a/eventloop_windows.go +++ b/eventloop_windows.go @@ -192,8 +192,6 @@ func (el *eventloop) close(c *conn, err error) error { delete(el.connections, c) el.incConn(-1) action := el.eventHandler.OnClose(c, err) - err = nil - err = c.rawConn.Close() c.release() if err != nil { From c7fa1458463ab421f5dd93bbc77f4269faa32283 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 3 Jul 2024 22:00:47 +0800 Subject: [PATCH 11/29] windows: wrap the error with context info in eventloop.close() --- eventloop_windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eventloop_windows.go b/eventloop_windows.go index ea053a87d..565f3ef30 100644 --- a/eventloop_windows.go +++ b/eventloop_windows.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "errors" + "fmt" "runtime" "sync/atomic" "time" @@ -195,7 +196,7 @@ func (el *eventloop) close(c *conn, err error) error { err = c.rawConn.Close() c.release() if err != nil { - return err + return fmt.Errorf("failed to close connection=%s in event-loop(%d): %v", c.remoteAddr, el.idx, err) } return el.handleAction(c, action) From a0d1ed75bf6bb8da01a36e9426b5118761b1a46e Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 4 Jul 2024 17:17:22 +0800 Subject: [PATCH 12/29] doc: update the roadmap For #318 --- README.md | 8 +++++++- README_ZH.md | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e7ebf4f95..e90ef78b0 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ English | [中文](README_ZH.md) # 🚀 Features +## 🦖 Milestone + - [x] [High-performance](#-performance) event-driven looping based on a networking model of multiple threads/goroutines - [x] Built-in goroutine pool powered by the library [ants](https://github.com/panjf2000/ants) - [x] Lock-free during the entire runtime @@ -43,8 +45,12 @@ English | [中文](README_ZH.md) - [x] Running on `Linux`, `macOS`, `Windows`, and *BSD: `Darwin`/`DragonFlyBSD`/`FreeBSD`/`NetBSD`/`OpenBSD` - [x] **Edge-triggered** I/O support - [x] Multiple network addresses binding + +## 🕊 Roadmap + - [ ] **TLS** support -- [ ] [io_uring](https://kernel.dk/io_uring.pdf) support +- [ ] [io_uring](https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023) support +- [ ] **KCP** support ***Windows version of `gnet` should only be used in development for developing and testing, it shouldn't be used in production.*** diff --git a/README_ZH.md b/README_ZH.md index a6a2b2e7d..aec90e2cd 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -31,6 +31,8 @@ # 🚀 功能 +## 🦖 当前支持 + - [x] 基于多线程/协程网络模型的[高性能](#-性能测试)事件驱动循环 - [x] 内置 goroutine 池,由开源库 [ants](https://github.com/panjf2000/ants) 提供支持 - [x] 整个生命周期是无锁的 @@ -43,8 +45,12 @@ - [x] 支持 `Linux`, `macOS`, `Windows` 和 *BSD 操作系统: `Darwin`/`DragonFlyBSD`/`FreeBSD`/`NetBSD`/`OpenBSD` - [x] **Edge-triggered** I/O 支持 - [x] 多网络地址绑定 -- [ ] **TLS** 支持 -- [ ] [io_uring](https://kernel.dk/io_uring.pdf) 支持 + +## 🕊 未来计划 + +- [ ] 支持 **TLS** +- [ ] 支持 [io_uring](https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023) +- [ ] 支持 **KCP** ***`gnet` 的 Windows 版本应该仅用于开发阶段的开发和测试,切勿用于生产环境***。 From 7162941088af404fb71b3a9452cfd072a581ee03 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Fri, 5 Jul 2024 14:22:51 +0800 Subject: [PATCH 13/29] doc: add new use case --- README.md | 23 ++++++++++++++--------- README_ZH.md | 23 ++++++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e90ef78b0..1466ef73b 100644 --- a/README.md +++ b/README.md @@ -78,27 +78,27 @@ The following companies/organizations use `gnet` as the underlying network servi - - + + - + - - + + - - - + + + @@ -109,11 +109,16 @@ The following companies/organizations use `gnet` as the underlying network servi + + + + + -If you have `gnet` integrated into projects, feel free to open a pull request refreshing this list. +If your company is also using `gnet` in production, please help us enrich this list by opening a pull request. # 📊 Performance diff --git a/README_ZH.md b/README_ZH.md index aec90e2cd..0ac69c15e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -78,27 +78,27 @@ go get -u github.com/panjf2000/gnet - - + + - + - - + + - - - + + + @@ -109,11 +109,16 @@ go get -u github.com/panjf2000/gnet + + + + + -如果你的项目也在使用 `gnet`,欢迎给我提 Pull Request 来更新这份列表。 +如果你的公司也在生产环境上使用 `gnet`,欢迎提 Pull Request 来丰富这份列表。 # 📊 性能测试 From 31cbb950833d3d02af0363dc56cb7efc517e16e9 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Fri, 5 Jul 2024 15:16:55 +0800 Subject: [PATCH 14/29] Fix a few broken image links --- README.md | 2 +- README_ZH.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1466ef73b..3cf81cf2c 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ The following companies/organizations use `gnet` as the underlying network servi - + diff --git a/README_ZH.md b/README_ZH.md index 0ac69c15e..7be4f5236 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -111,7 +111,7 @@ go get -u github.com/panjf2000/gnet - + From 66ec945d617e85d65e1debaf1e51ad1d674b3d62 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 11 Jul 2024 09:27:28 +0800 Subject: [PATCH 15/29] doc: add new use case --- README.md | 5 +++++ README_ZH.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 3cf81cf2c..01fd29412 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ The following companies/organizations use `gnet` as the underlying network servi + + + + + diff --git a/README_ZH.md b/README_ZH.md index 7be4f5236..347ed58bf 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -114,6 +114,11 @@ go get -u github.com/panjf2000/gnet + + + + + From ca1713031a1032154f32b90cce516740805769eb Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 23 Jul 2024 07:06:57 +0800 Subject: [PATCH 16/29] chore: update READMEs --- README.md | 4 ++-- README_ZH.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 01fd29412..b64b68bf5 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ go get -u github.com/panjf2000/gnet # 🎡 Use cases -The following companies/organizations use `gnet` as the underlying network service in production. +The following corporations/organizations use `gnet` as the underlying network service in production. @@ -123,7 +123,7 @@ The following companies/organizations use `gnet` as the underlying network servi
-If your company is also using `gnet` in production, please help us enrich this list by opening a pull request. +If you're also using `gnet` in production, please help us enrich this list by opening a pull request. # 📊 Performance diff --git a/README_ZH.md b/README_ZH.md index 347ed58bf..96143a775 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -123,7 +123,7 @@ go get -u github.com/panjf2000/gnet -如果你的公司也在生产环境上使用 `gnet`,欢迎提 Pull Request 来丰富这份列表。 +如果你也正在生产环境上使用 `gnet`,欢迎提 Pull Request 来丰富这份列表。 # 📊 性能测试 From 2750d402b7df42e81805aa2e6ed965be3a6d6df3 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Tue, 30 Jul 2024 12:13:09 +0800 Subject: [PATCH 17/29] chore: update READMEs --- README.md | 16 ++++++++-------- README_ZH.md | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b64b68bf5..439338a05 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ The following corporations/organizations use `gnet` as the underlying network se + + + + + @@ -92,30 +97,25 @@ The following corporations/organizations use `gnet` as the underlying network se + + - - - - - - - - + diff --git a/README_ZH.md b/README_ZH.md index 96143a775..f5f22c867 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -82,6 +82,11 @@ go get -u github.com/panjf2000/gnet + + + + + @@ -92,30 +97,25 @@ go get -u github.com/panjf2000/gnet + + - - - - - - - - + From 7b23091b035d0d6a2ea15732ed271023d56302ca Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sun, 18 Aug 2024 09:55:03 +0800 Subject: [PATCH 18/29] chore: clean up needless test code --- gnet_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gnet_test.go b/gnet_test.go index 3147bb70f..50abb6e0c 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -501,9 +501,8 @@ func (s *testServer) OnClose(c Conn, err error) (action Action) { if err != nil { logging.Debugf("error occurred on closed, %v\n", err) } - if c.LocalAddr().Network() != "udp" { - require.Equal(s.tester, c.Context(), c, "invalid context") - } + + require.Equal(s.tester, c.Context(), c, "invalid context") atomic.AddInt32(&s.disconnected, 1) return From b210186c29032b3c42f9920c3e987252163ae31a Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Mon, 19 Aug 2024 11:16:10 +0800 Subject: [PATCH 19/29] Move the list of patrons elsewhere Relocated to https://andypan.me/donation/#-patrons --- README.md | 44 -------------------------------------------- README_ZH.md | 44 -------------------------------------------- 2 files changed, 88 deletions(-) diff --git a/README.md b/README.md index 439338a05..bdba1a6f3 100644 --- a/README.md +++ b/README.md @@ -244,50 +244,6 @@ Become a bronze sponsor with a monthly donation of $10 and get your logo on our       -# 💴 Patrons - - - - - - - - - - - - - -
- - Patrick Othmer - - - - Jimmy - - - - ChenZhen - - - - Mai Yang - - - - 王开帅 - - - - Unger Alejandro - - - - Weng Wei - -
- # 🔑 JetBrains OS licenses `gnet` had been being developed with `GoLand` IDE under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here. diff --git a/README_ZH.md b/README_ZH.md index f5f22c867..fe5a4f28f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -244,50 +244,6 @@ Test duration : 15s       -# 💴 资助者 - - - - - - - - - - - - - -
- - Patrick Othmer - - - - Jimmy - - - - ChenZhen - - - - Mai Yang - - - - 王开帅 - - - - Unger Alejandro - - - - Weng Wei - -
- # 🔑 JetBrains 开源证书支持 `gnet` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。 From 8c129aeb226d1758e6f85d33a6f1b289652e1356 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sat, 21 Sep 2024 20:21:21 +0800 Subject: [PATCH 20/29] chore: clean up unused code and deprecated function calls --- eventloop_unix_test.go | 2 +- gnet.go | 2 -- gnet_test.go | 12 +++++++++--- pkg/errors/errors.go | 2 -- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/eventloop_unix_test.go b/eventloop_unix_test.go index e21bb83f1..c166dde0a 100644 --- a/eventloop_unix_test.go +++ b/eventloop_unix_test.go @@ -275,7 +275,7 @@ func (s *testServerGC) GC(secs int) { runtime.GC() gcTime := time.Since(now) gcAllTime += gcTime - s.tester.Log(s.tester.Name(), s.network, " server gc:", gcTime, ", average gc time:", gcAllTime/gcAllCount) + s.tester.Log(s.tester.Name(), s.network, "server gc:", gcTime, "average gc time:", gcAllTime/gcAllCount) if time.Since(gcStart) >= time.Second*time.Duration(secs) { break } diff --git a/gnet.go b/gnet.go index 3e2d9ab9a..99f6f4038 100644 --- a/gnet.go +++ b/gnet.go @@ -293,8 +293,6 @@ type Socket interface { // algorithm). // The default is true (no delay), meaning that data is sent as soon as possible after a Write. SetNoDelay(noDelay bool) error - // CloseRead() error - // CloseWrite() error } // Conn is an interface of underlying connection. diff --git a/gnet_test.go b/gnet_test.go index 50abb6e0c..e26f085fb 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -1206,9 +1206,15 @@ type testStopServer struct { *BuiltinEventEngine tester *testing.T network, addr, protoAddr string + eng Engine action bool } +func (t *testStopServer) OnBoot(eng Engine) (action Action) { + t.eng = eng + return +} + func (t *testStopServer) OnClose(Conn, error) (action Action) { logging.Debugf("closing connection...") return @@ -1237,7 +1243,7 @@ func (t *testStopServer) OnTick() (delay time.Duration, action Action) { go func() { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - logging.Debugf("stop engine...", Stop(ctx, t.protoAddr)) + logging.Debugf("stop engine...", t.eng.Stop(ctx)) }() // waiting the engine shutdown. @@ -1365,7 +1371,7 @@ type testClosedWakeUpServer struct { clientClosed chan struct{} } -func (s *testClosedWakeUpServer) OnBoot(_ Engine) (action Action) { +func (s *testClosedWakeUpServer) OnBoot(eng Engine) (action Action) { go func() { c, err := net.Dial(s.network, s.addr) require.NoError(s.tester, err) @@ -1380,7 +1386,7 @@ func (s *testClosedWakeUpServer) OnBoot(_ Engine) (action Action) { close(s.clientClosed) <-s.serverClosed - logging.Debugf("stop engine...", Stop(context.TODO(), s.protoAddr)) + logging.Debugf("stop engine...", eng.Stop(context.TODO())) }() return None diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 835ddedf0..3aaf1b6ba 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -35,8 +35,6 @@ var ( ErrUnsupportedUDPProtocol = errors.New("gnet: only udp/udp4/udp6 are supported") // ErrUnsupportedUDSProtocol occurs when trying to use an unsupported Unix protocol. ErrUnsupportedUDSProtocol = errors.New("gnet: only unix is supported") - // ErrUnsupportedPlatform occurs when running gnet on an unsupported platform. - ErrUnsupportedPlatform = errors.New("gnet: unsupported platform in gnet") // ErrUnsupportedOp occurs when calling some methods that has not been implemented yet. ErrUnsupportedOp = errors.New("gnet: unsupported operation") // ErrNegativeSize occurs when trying to pass a negative size to a buffer. From 894263821665ed6dfe3cb38ab9e461823be15f0b Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sat, 21 Sep 2024 20:48:17 +0800 Subject: [PATCH 21/29] test: deprecate math/rand.Seed and math/rand.Read --- .github/workflows/test.yml | 4 +-- .github/workflows/test_gc_opt.yml | 4 +-- .github/workflows/test_poll_opt.yml | 4 +-- .github/workflows/test_poll_opt_gc_opt.yml | 4 +-- client_test.go | 4 +-- gnet_test.go | 7 ++-- os_unix_test.go | 4 +-- pkg/buffer/elastic/elastic_buffer_test.go | 23 +++++++------ pkg/buffer/linkedlist/llbuffer_test.go | 29 +++++++++------- pkg/buffer/ring/ring_buffer_test.go | 40 +++++++++++++--------- 10 files changed, 68 insertions(+), 55 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 202ac0c89..752c62135 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,9 +47,9 @@ jobs: cache: false - name: Setup and run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.61.0 args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot --timeout 5m test: needs: lint diff --git a/.github/workflows/test_gc_opt.yml b/.github/workflows/test_gc_opt.yml index 36e592aa5..a494a1589 100644 --- a/.github/workflows/test_gc_opt.yml +++ b/.github/workflows/test_gc_opt.yml @@ -47,9 +47,9 @@ jobs: cache: false - name: Setup and run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.61.0 args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot --timeout 5m test: needs: lint diff --git a/.github/workflows/test_poll_opt.yml b/.github/workflows/test_poll_opt.yml index 91092992a..22991b967 100644 --- a/.github/workflows/test_poll_opt.yml +++ b/.github/workflows/test_poll_opt.yml @@ -46,9 +46,9 @@ jobs: cache: false - name: Setup and run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.61.0 args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot test: needs: lint diff --git a/.github/workflows/test_poll_opt_gc_opt.yml b/.github/workflows/test_poll_opt_gc_opt.yml index 3cefb09a5..1d658f449 100644 --- a/.github/workflows/test_poll_opt_gc_opt.yml +++ b/.github/workflows/test_poll_opt_gc_opt.yml @@ -46,9 +46,9 @@ jobs: cache: false - name: Setup and run golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: - version: v1.56.2 + version: v1.61.0 args: -v -E gofumpt -E gocritic -E misspell -E revive -E godot test: needs: lint diff --git a/client_test.go b/client_test.go index 1f446501c..3f270ba0e 100644 --- a/client_test.go +++ b/client_test.go @@ -5,6 +5,7 @@ package gnet import ( "bytes" + crand "crypto/rand" "io" "math/rand" "net" @@ -462,7 +463,6 @@ func runClient(t *testing.T, network, addr string, et, reuseport, multicore, asy } func startGnetClient(t *testing.T, cli *Client, network, addr string, multicore, async, netDial bool) { - rand.Seed(time.Now().UnixNano()) var ( c Conn err error @@ -492,7 +492,7 @@ func startGnetClient(t *testing.T, cli *Client, network, addr string, multicore, if network == "udp" { reqData = reqData[:datagramLen] } - _, err = rand.Read(reqData) + _, err = crand.Read(reqData) require.NoError(t, err) err = c.AsyncWrite(reqData, nil) require.NoError(t, err) diff --git a/gnet_test.go b/gnet_test.go index e26f085fb..3e20b0c00 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + crand "crypto/rand" "encoding/binary" "errors" "io" @@ -660,7 +661,6 @@ func runServer(t *testing.T, addrs []string, et, reuseport, multicore, async, wr } func startClient(t *testing.T, network, addr string, multicore, async bool) { - rand.Seed(time.Now().UnixNano()) c, err := net.Dial(network, addr) require.NoError(t, err) defer c.Close() @@ -678,7 +678,7 @@ func startClient(t *testing.T, network, addr string, multicore, async bool) { if network == "udp" { reqData = reqData[:datagramLen] } - _, err = rand.Read(reqData) + _, err = crand.Read(reqData) require.NoError(t, err) _, err = c.Write(reqData) require.NoError(t, err) @@ -1659,7 +1659,6 @@ func runSimServer(t *testing.T, addr string, et bool, nclients, packetSize, pack } func runSimClient(t *testing.T, network, addr string, packetSize, batch int) { - rand.Seed(time.Now().UnixNano()) c, err := net.Dial(network, addr) require.NoError(t, err) defer c.Close() @@ -1695,7 +1694,7 @@ func batchSendAndRecv(t *testing.T, c net.Conn, rd *bufio.Reader, packetSize, ba ) for i := 0; i < batch; i++ { req := make([]byte, packetSize) - _, err := rand.Read(req) + _, err := crand.Read(req) require.NoError(t, err) requests = append(requests, req) packet, _ := codec.Encode(req) diff --git a/os_unix_test.go b/os_unix_test.go index 0612cfd06..696ec1514 100644 --- a/os_unix_test.go +++ b/os_unix_test.go @@ -5,6 +5,7 @@ package gnet import ( "context" + crand "crypto/rand" "errors" "fmt" "math/rand" @@ -104,7 +105,6 @@ type testMcastServer struct { } func (s *testMcastServer) startMcastClient() { - rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() c, err := net.Dial("udp", s.addr) @@ -117,7 +117,7 @@ func (s *testMcastServer) startMcastClient() { start := time.Now() for time.Since(start) < duration { reqData := make([]byte, 1024) - _, err = rand.Read(reqData) + _, err = crand.Read(reqData) require.NoError(s.t, err) _, err = c.Write(reqData) require.NoError(s.t, err) diff --git a/pkg/buffer/elastic/elastic_buffer_test.go b/pkg/buffer/elastic/elastic_buffer_test.go index f000ac00d..80c219fac 100644 --- a/pkg/buffer/elastic/elastic_buffer_test.go +++ b/pkg/buffer/elastic/elastic_buffer_test.go @@ -2,10 +2,10 @@ package elastic import ( "bytes" + crand "crypto/rand" "math/rand" "runtime" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -15,8 +15,8 @@ func TestMixedBuffer_Basic(t *testing.T) { mb, _ := New(maxStaticSize) const dataLen = 5 * 1024 data := make([]byte, dataLen) - rand.Seed(time.Now().Unix()) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) n, err := mb.Write(data) require.NoError(t, err) require.EqualValues(t, dataLen, n) @@ -27,7 +27,8 @@ func TestMixedBuffer_Basic(t *testing.T) { mb.Reset(-1) newDataLen := rbn + 2*1024 data = make([]byte, newDataLen) - rand.Read(data) + _, err = crand.Read(data) + require.NoError(t, err) n, err = mb.Write(data) require.NoError(t, err) require.EqualValues(t, newDataLen, n) @@ -71,7 +72,8 @@ func TestMixedBuffer_Basic(t *testing.T) { n := rand.Intn(512) + 128 cum += n data := make([]byte, n) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) buf.Write(data) if i < 3 { headCum += n @@ -107,15 +109,16 @@ func TestMixedBuffer_ReadFrom(t *testing.T) { mb, _ := New(maxStaticSize) const dataLen = 2 * 1024 data := make([]byte, dataLen) - rand.Seed(time.Now().Unix()) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) r := bytes.NewReader(data) n, err := mb.ReadFrom(r) require.NoError(t, err) require.EqualValues(t, dataLen, n) require.EqualValues(t, dataLen, mb.Buffered()) newData := make([]byte, dataLen) - rand.Read(newData) + _, err = crand.Read(newData) + require.NoError(t, err) r.Reset(newData) n, err = mb.ReadFrom(r) require.NoError(t, err) @@ -155,12 +158,12 @@ func TestMixedBuffer_WriteTo(t *testing.T) { buf bytes.Buffer ) - rand.Seed(time.Now().Unix()) for i := 0; i < maxBlocks; i++ { n := rand.Intn(512) + 128 cum += n data := make([]byte, n) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) buf.Write(data) if i < 3 { headCum += n diff --git a/pkg/buffer/linkedlist/llbuffer_test.go b/pkg/buffer/linkedlist/llbuffer_test.go index 1a755ca0d..4caabaa20 100644 --- a/pkg/buffer/linkedlist/llbuffer_test.go +++ b/pkg/buffer/linkedlist/llbuffer_test.go @@ -2,9 +2,9 @@ package linkedlist import ( "bytes" + crand "crypto/rand" "math/rand" "testing" - "time" "github.com/stretchr/testify/require" ) @@ -16,12 +16,12 @@ func TestLinkedListBuffer_Basic(t *testing.T) { cum int buf bytes.Buffer ) - rand.Seed(time.Now().Unix()) for i := 0; i < maxBlocks; i++ { n := rand.Intn(1024) + 128 cum += n data := make([]byte, n) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) llb.PushBack(data) buf.Write(data) } @@ -39,8 +39,10 @@ func TestLinkedListBuffer_Basic(t *testing.T) { require.EqualValues(t, buf.Bytes()[:pn], p) tmpA := make([]byte, cum/16) tmpB := make([]byte, cum/16) - rand.Read(tmpA) - rand.Read(tmpB) + _, err = crand.Read(tmpA) + require.NoError(t, err) + _, err = crand.Read(tmpB) + require.NoError(t, err) bs, err = llb.PeekWithBytes(cum/4, tmpA, tmpB) require.NoError(t, err) p = p[:0] @@ -69,8 +71,8 @@ func TestLinkedListBuffer_ReadFrom(t *testing.T) { var llb Buffer const dataLen = 4 * 1024 data := make([]byte, dataLen) - rand.Seed(time.Now().Unix()) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) r := bytes.NewReader(data) n, err := llb.ReadFrom(r) require.NoError(t, err) @@ -80,9 +82,11 @@ func TestLinkedListBuffer_ReadFrom(t *testing.T) { llb.Reset() const headLen = 256 head := make([]byte, headLen) - rand.Read(head) + _, err = crand.Read(head) + require.NoError(t, err) llb.PushBack(head) - rand.Read(data) + _, err = crand.Read(data) + require.NoError(t, err) r.Reset(data) n, err = llb.ReadFrom(r) require.NoError(t, err) @@ -104,12 +108,12 @@ func TestLinkedListBuffer_WriteTo(t *testing.T) { cum int buf bytes.Buffer ) - rand.Seed(time.Now().Unix()) for i := 0; i < maxBlocks; i++ { n := rand.Intn(1024) + 128 cum += n data := make([]byte, n) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) llb.PushBack(data) buf.Write(data) } @@ -130,7 +134,8 @@ func TestLinkedListBuffer_WriteTo(t *testing.T) { n := rand.Intn(1024) + 128 cum += n data := make([]byte, n) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) llb.PushBack(data) buf.Write(data) } diff --git a/pkg/buffer/ring/ring_buffer_test.go b/pkg/buffer/ring/ring_buffer_test.go index e323342b1..2efb15509 100644 --- a/pkg/buffer/ring/ring_buffer_test.go +++ b/pkg/buffer/ring/ring_buffer_test.go @@ -2,10 +2,9 @@ package ring import ( "bytes" - "math/rand" + crand "crypto/rand" "strings" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -125,7 +124,7 @@ func TestRingBufferGrow(t *testing.T) { assert.Empty(t, head, "head should be empty") assert.Empty(t, tail, "tail should be empty") data := make([]byte, DefaultBufferSize+1) - n, err := rand.Read(data) + n, err := crand.Read(data) assert.NoError(t, err, "failed to generate random data") assert.EqualValuesf(t, DefaultBufferSize+1, n, "expect random data length is %d but got %d", DefaultBufferSize+1, n) n, err = rb.Write(data) @@ -139,7 +138,7 @@ func TestRingBufferGrow(t *testing.T) { rb = New(DefaultBufferSize) newData := make([]byte, 3*512) - n, err = rand.Read(newData) + n, err = crand.Read(newData) assert.NoError(t, err, "failed to generate random data") assert.EqualValuesf(t, 3*512, n, "expect random data length is %d but got %d", 3*512, n) n, err = rb.Write(newData) @@ -153,7 +152,7 @@ func TestRingBufferGrow(t *testing.T) { rb.Reset() data = make([]byte, bufferGrowThreshold) - n, err = rand.Read(data) + n, err = crand.Read(data) assert.NoError(t, err, "failed to generate random data") assert.EqualValuesf(t, bufferGrowThreshold, n, "expect random data length is %d but got %d", bufferGrowThreshold, n) n, err = rb.Write(data) @@ -165,7 +164,7 @@ func TestRingBufferGrow(t *testing.T) { assert.EqualValues(t, 0, rb.Available()) assert.EqualValues(t, data, rb.Bytes()) newData = make([]byte, bufferGrowThreshold/2+1) - n, err = rand.Read(newData) + n, err = crand.Read(newData) assert.NoError(t, err, "failed to generate random data") assert.EqualValuesf(t, bufferGrowThreshold/2+1, n, "expect random data length is %d but got %d", bufferGrowThreshold, n) n, err = rb.Write(newData) @@ -307,8 +306,8 @@ func TestRingBuffer_ReadFrom(t *testing.T) { rb := New(0) const dataLen = 4 * 1024 data := make([]byte, dataLen) - rand.Seed(time.Now().Unix()) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) r := bytes.NewReader(data) n, err := rb.ReadFrom(r) require.NoError(t, err) @@ -329,8 +328,10 @@ func TestRingBuffer_ReadFrom(t *testing.T) { rb = New(0) const prefixLen = 2 * 1024 prefix := make([]byte, prefixLen) - rand.Read(prefix) - rand.Read(data) + _, err = crand.Read(prefix) + require.NoError(t, err) + _, err = crand.Read(data) + require.NoError(t, err) r.Reset(data) m, err = rb.Write(prefix) require.NoError(t, err) @@ -354,8 +355,10 @@ func TestRingBuffer_ReadFrom(t *testing.T) { const initLen = 5 * 1024 rb = New(initLen) - rand.Read(prefix) - rand.Read(data) + _, err = crand.Read(prefix) + require.NoError(t, err) + _, err = crand.Read(data) + require.NoError(t, err) r.Reset(data) m, err = rb.Write(prefix) require.NoError(t, err) @@ -380,8 +383,8 @@ func TestRingBuffer_WriteTo(t *testing.T) { rb := New(5 * 1024) const dataLen = 4 * 1024 data := make([]byte, dataLen) - rand.Seed(time.Now().Unix()) - rand.Read(data) + _, err := crand.Read(data) + require.NoError(t, err) n, err := rb.Write(data) require.NoError(t, err) require.EqualValuesf(t, dataLen, n, "ringbuffer should write %d bytes, but got %d", dataLen, n) @@ -394,7 +397,8 @@ func TestRingBuffer_WriteTo(t *testing.T) { require.EqualValues(t, data, buf.Bytes()) buf.Reset() - rand.Read(data) + _, err = crand.Read(data) + require.NoError(t, err) rb = New(dataLen) n, err = rb.Write(data) require.NoError(t, err) @@ -408,7 +412,8 @@ func TestRingBuffer_WriteTo(t *testing.T) { buf.Reset() rb.Reset() - rand.Read(data) + _, err = crand.Read(data) + require.NoError(t, err) n, err = rb.Write(data) require.NoError(t, err) require.EqualValuesf(t, dataLen, n, "ringbuffer should write %d bytes, but got %d", dataLen, n) @@ -419,7 +424,8 @@ func TestRingBuffer_WriteTo(t *testing.T) { require.EqualValues(t, data[:partLen], head) _, _ = rb.Discard(partLen) partData := make([]byte, partLen/2) - rand.Read(partData) + _, err = crand.Read(partData) + require.NoError(t, err) n, err = rb.Write(partData) require.NoError(t, err) require.EqualValuesf(t, partLen/2, n, "ringbuffer should write %d bytes, but got %d", dataLen, n) From 18f54e311551af7e2f01603f3a0758111e826e70 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Sat, 21 Sep 2024 21:52:03 +0800 Subject: [PATCH 22/29] lint: disable lint checks for string<->bytes conversion temporarily --- internal/bs/bs.go | 8 ++++++-- pkg/pool/byteslice/byteslice.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/bs/bs.go b/internal/bs/bs.go index d23cb34a4..16b3861f7 100644 --- a/internal/bs/bs.go +++ b/internal/bs/bs.go @@ -19,6 +19,10 @@ import ( "unsafe" ) +// TODO(panjf2000): rework the implementation of BytesToString and StringToBytes by +// using unsafe.String/unsafe.StringData and unsafe.Slice/unsafe.SliceData when we +// bump up the minimum required Go version to 1.20. + // BytesToString converts byte slice to a string without memory allocation. // // Note it may break if the implementation of string or slice header changes in the future go versions. @@ -32,9 +36,9 @@ func BytesToString(b []byte) string { // Note it may break if the implementation of string or slice header changes in the future go versions. func StringToBytes(s string) (b []byte) { /* #nosec G103 */ - sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) //nolint:staticcheck /* #nosec G103 */ - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) //nolint:staticcheck bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len return b diff --git a/pkg/pool/byteslice/byteslice.go b/pkg/pool/byteslice/byteslice.go index 3fd9ac89b..efd063375 100644 --- a/pkg/pool/byteslice/byteslice.go +++ b/pkg/pool/byteslice/byteslice.go @@ -53,7 +53,7 @@ func (p *Pool) Get(size int) (buf []byte) { if ptr == nil { return make([]byte, 1< Date: Sun, 22 Sep 2024 00:00:24 +0800 Subject: [PATCH 23/29] opt: bump up minimum required Go version to 1.20 (#638) Employ the standard approach of string<->bytes conversion --- .github/workflows/cross-compile-bsd.yml | 2 +- .github/workflows/test.yml | 4 ++-- .github/workflows/test_gc_opt.yml | 4 ++-- .github/workflows/test_poll_opt.yml | 6 ++--- .github/workflows/test_poll_opt_gc_opt.yml | 6 ++--- README.md | 2 +- README_ZH.md | 2 +- go.mod | 6 ++--- go.sum | 10 ++++----- internal/bs/bs.go | 26 +++++----------------- pkg/pool/byteslice/byteslice.go | 20 ++++++----------- 11 files changed, 32 insertions(+), 56 deletions(-) diff --git a/.github/workflows/cross-compile-bsd.yml b/.github/workflows/cross-compile-bsd.yml index 0e782a704..e91a22672 100644 --- a/.github/workflows/cross-compile-bsd.yml +++ b/.github/workflows/cross-compile-bsd.yml @@ -31,7 +31,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.17', '1.22'] + go: ['1.20', '1.23'] os: - ubuntu-latest name: Go ${{ matrix.go }} @ ${{ matrix.os }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 752c62135..3e964a415 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' cache: false - name: Setup and run golangci-lint @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.17', '1.22'] + go: ['1.20', '1.23'] os: - ubuntu-latest - macos-latest diff --git a/.github/workflows/test_gc_opt.yml b/.github/workflows/test_gc_opt.yml index a494a1589..df99ae8e2 100644 --- a/.github/workflows/test_gc_opt.yml +++ b/.github/workflows/test_gc_opt.yml @@ -43,7 +43,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' cache: false - name: Setup and run golangci-lint @@ -56,7 +56,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.17', '1.22'] + go: ['1.20', '1.23'] os: - ubuntu-latest - macos-latest diff --git a/.github/workflows/test_poll_opt.yml b/.github/workflows/test_poll_opt.yml index 22991b967..5d5ae19fc 100644 --- a/.github/workflows/test_poll_opt.yml +++ b/.github/workflows/test_poll_opt.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' cache: false - name: Setup and run golangci-lint @@ -55,7 +55,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.17', '1.22'] + go: ['1.20', '1.23'] os: [ubuntu-latest, macos-latest] name: Go ${{ matrix.go }} @ ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -68,7 +68,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' - name: Print Go environment id: go-env diff --git a/.github/workflows/test_poll_opt_gc_opt.yml b/.github/workflows/test_poll_opt_gc_opt.yml index 1d658f449..a5aef4062 100644 --- a/.github/workflows/test_poll_opt_gc_opt.yml +++ b/.github/workflows/test_poll_opt_gc_opt.yml @@ -42,7 +42,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' cache: false - name: Setup and run golangci-lint @@ -55,7 +55,7 @@ jobs: strategy: fail-fast: false matrix: - go: ['1.17', '1.22'] + go: ['1.20', '1.23'] os: [ubuntu-latest, macos-latest] name: Go ${{ matrix.go }} @ ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -68,7 +68,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '^1.17' + go-version: '^1.20' - name: Print Go environment id: go-env diff --git a/README.md b/README.md index bdba1a6f3..8c85967b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - +
diff --git a/README_ZH.md b/README_ZH.md index fe5a4f28f..d166d1b76 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -4,7 +4,7 @@ - +
diff --git a/go.mod b/go.mod index 178d229c9..0ab63f761 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ require ( github.com/stretchr/testify v1.9.0 github.com/valyala/bytebufferpool v1.0.0 go.uber.org/zap v1.21.0 // don't upgrade this one - golang.org/x/sync v0.7.0 - golang.org/x/sys v0.21.0 + golang.org/x/sync v0.8.0 + golang.org/x/sys v0.25.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -18,4 +18,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.17 +go 1.20 diff --git a/go.sum b/go.sum index 7ba46041c..6c36aa9be 100644 --- a/go.sum +++ b/go.sum @@ -17,13 +17,11 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -48,15 +46,15 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/bs/bs.go b/internal/bs/bs.go index 16b3861f7..7afb5136e 100644 --- a/internal/bs/bs.go +++ b/internal/bs/bs.go @@ -15,31 +15,15 @@ package bs import ( - "reflect" "unsafe" ) -// TODO(panjf2000): rework the implementation of BytesToString and StringToBytes by -// using unsafe.String/unsafe.StringData and unsafe.Slice/unsafe.SliceData when we -// bump up the minimum required Go version to 1.20. - -// BytesToString converts byte slice to a string without memory allocation. -// -// Note it may break if the implementation of string or slice header changes in the future go versions. +// BytesToString converts byte slice to a string without any memory allocation. func BytesToString(b []byte) string { - /* #nosec G103 */ - return *(*string)(unsafe.Pointer(&b)) + return unsafe.String(unsafe.SliceData(b), len(b)) } -// StringToBytes converts string to a byte slice without memory allocation. -// -// Note it may break if the implementation of string or slice header changes in the future go versions. -func StringToBytes(s string) (b []byte) { - /* #nosec G103 */ - sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) //nolint:staticcheck - /* #nosec G103 */ - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) //nolint:staticcheck - - bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len - return b +// StringToBytes converts string to a byte slice without any memory allocation. +func StringToBytes(s string) []byte { + return unsafe.Slice(unsafe.StringData(s), len(s)) } diff --git a/pkg/pool/byteslice/byteslice.go b/pkg/pool/byteslice/byteslice.go index efd063375..53397dac6 100644 --- a/pkg/pool/byteslice/byteslice.go +++ b/pkg/pool/byteslice/byteslice.go @@ -17,8 +17,6 @@ package byteslice import ( "math" "math/bits" - "reflect" - "runtime" "sync" "unsafe" ) @@ -41,7 +39,7 @@ func Put(buf []byte) { } // Get retrieves a byte slice of the length requested by the caller from pool or allocates a new one. -func (p *Pool) Get(size int) (buf []byte) { +func (p *Pool) Get(size int) []byte { if size <= 0 { return nil } @@ -49,16 +47,11 @@ func (p *Pool) Get(size int) (buf []byte) { return make([]byte, size) } idx := index(uint32(size)) - ptr, _ := p.pools[idx].Get().(unsafe.Pointer) + ptr, _ := p.pools[idx].Get().(*byte) if ptr == nil { - return make([]byte, 1< Date: Tue, 24 Sep 2024 17:12:58 +0800 Subject: [PATCH 24/29] chore: update the JetBrains logo --- README.md | 4 ++-- README_ZH.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8c85967b3..fd9b0f4d9 100644 --- a/README.md +++ b/README.md @@ -246,9 +246,9 @@ Become a bronze sponsor with a monthly donation of $10 and get your logo on our # 🔑 JetBrains OS licenses -`gnet` had been being developed with `GoLand` IDE under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here. +`gnet` has been being developed with `GoLand` IDE under the ***free JetBrains Open Source license(s)*** granted by JetBrains s.r.o., hence I would like to express my thanks here. - +JetBrains logo. # 🔋 Sponsorship diff --git a/README_ZH.md b/README_ZH.md index d166d1b76..04fae4bee 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -246,9 +246,9 @@ Test duration : 15s # 🔑 JetBrains 开源证书支持 -`gnet` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。 +`gnet` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 ***free JetBrains Open Source license(s)*** 正版免费授权,在此表达我的谢意。 - +JetBrains logo. # 🔋 赞助商 From e52039928c53495d6b5d3e30ae54d3d6331c0c5c Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 26 Sep 2024 14:00:16 +0800 Subject: [PATCH 25/29] chore: use errors.Is and errors.Join for customized errors (#640) * chore: use errors.Join of go1.20 on Windows * chore: use errors.Is for customized errors --- acceptor_windows.go | 10 ++------- engine_unix.go | 9 ++++---- internal/netpoll/poller_epoll_default.go | 24 ++++++++-------------- internal/netpoll/poller_epoll_ultimate.go | 24 ++++++++-------------- internal/netpoll/poller_kqueue_default.go | 24 ++++++++-------------- internal/netpoll/poller_kqueue_ultimate.go | 24 ++++++++-------------- 6 files changed, 39 insertions(+), 76 deletions(-) diff --git a/acceptor_windows.go b/acceptor_windows.go index ce6005c0e..d9d6339cd 100644 --- a/acceptor_windows.go +++ b/acceptor_windows.go @@ -39,11 +39,7 @@ func (eng *engine) listenStream(ln net.Listener) (err error) { if atomic.LoadInt32(&eng.beingShutdown) == 0 { eng.opts.Logger.Errorf("Accept() fails due to error: %v", err) } else if errors.Is(err, net.ErrClosed) { - err = errorx.ErrEngineShutdown - // TODO: errors.Join() is not supported until Go 1.20, - // we will uncomment this line after we bump up the - // minimal supported go version to 1.20. - // err = errors.Join(err, errorx.ErrEngineShutdown) + err = errors.Join(err, errorx.ErrEngineShutdown) } return } @@ -81,9 +77,7 @@ func (eng *engine) ListenUDP(pc net.PacketConn) (err error) { if atomic.LoadInt32(&eng.beingShutdown) == 0 { eng.opts.Logger.Errorf("failed to receive data from UDP fd due to error:%v", err) } else if errors.Is(err, net.ErrClosed) { - err = errorx.ErrEngineShutdown - // TODO: ditto. - // err = errors.Join(err, errorx.ErrEngineShutdown) + err = errors.Join(err, errorx.ErrEngineShutdown) } return } diff --git a/engine_unix.go b/engine_unix.go index 83a1ae864..3bc488f95 100644 --- a/engine_unix.go +++ b/engine_unix.go @@ -19,6 +19,7 @@ package gnet import ( "context" + "errors" "runtime" "strings" "sync" @@ -29,7 +30,7 @@ import ( "github.com/panjf2000/gnet/v2/internal/gfd" "github.com/panjf2000/gnet/v2/internal/netpoll" "github.com/panjf2000/gnet/v2/internal/queue" - "github.com/panjf2000/gnet/v2/pkg/errors" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) @@ -59,7 +60,7 @@ func (eng *engine) isInShutdown() bool { // shutdown signals the engine to shut down. func (eng *engine) shutdown(err error) { - if err != nil && err != errors.ErrEngineShutdown { + if err != nil && !errors.Is(err, errorx.ErrEngineShutdown) { eng.opts.Logger.Errorf("engine is being shutdown with error: %v", err) } @@ -211,14 +212,14 @@ func (eng *engine) stop(s Engine) { // Notify all event-loops to exit. eng.eventLoops.iterate(func(i int, el *eventloop) bool { - err := el.poller.Trigger(queue.HighPriority, func(_ interface{}) error { return errors.ErrEngineShutdown }, nil) + err := el.poller.Trigger(queue.HighPriority, func(_ interface{}) error { return errorx.ErrEngineShutdown }, nil) if err != nil { eng.opts.Logger.Errorf("failed to enqueue shutdown signal of high-priority for event-loop(%d): %v", i, err) } return true }) if eng.ingress != nil { - err := eng.ingress.poller.Trigger(queue.HighPriority, func(_ interface{}) error { return errors.ErrEngineShutdown }, nil) + err := eng.ingress.poller.Trigger(queue.HighPriority, func(_ interface{}) error { return errorx.ErrEngineShutdown }, nil) if err != nil { eng.opts.Logger.Errorf("failed to enqueue shutdown signal of high-priority for main event-loop: %v", err) } diff --git a/internal/netpoll/poller_epoll_default.go b/internal/netpoll/poller_epoll_default.go index d5e4d4e48..ec47d0b46 100644 --- a/internal/netpoll/poller_epoll_default.go +++ b/internal/netpoll/poller_epoll_default.go @@ -19,6 +19,7 @@ package netpoll import ( + "errors" "os" "runtime" "sync/atomic" @@ -27,7 +28,7 @@ import ( "golang.org/x/sys/unix" "github.com/panjf2000/gnet/v2/internal/queue" - "github.com/panjf2000/gnet/v2/pkg/errors" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) @@ -133,12 +134,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { if fd := int(ev.Fd); fd == p.efd { // poller is awakened to run tasks in queues. doChores = true } else { - switch err = callback(fd, ev.Events, 0); err { - case nil: - case errors.ErrAcceptSocket, errors.ErrEngineShutdown: + err = callback(fd, ev.Events, 0) + if errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in event-loop: %v", err) } } } @@ -147,12 +145,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { doChores = false task := p.urgentAsyncTaskQueue.Dequeue() for ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() { - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } @@ -160,12 +155,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { if task = p.asyncTaskQueue.Dequeue(); task == nil { break } - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } diff --git a/internal/netpoll/poller_epoll_ultimate.go b/internal/netpoll/poller_epoll_ultimate.go index fec70097e..730e0485b 100644 --- a/internal/netpoll/poller_epoll_ultimate.go +++ b/internal/netpoll/poller_epoll_ultimate.go @@ -18,6 +18,7 @@ package netpoll import ( + "errors" "os" "runtime" "sync/atomic" @@ -26,7 +27,7 @@ import ( "golang.org/x/sys/unix" "github.com/panjf2000/gnet/v2/internal/queue" - "github.com/panjf2000/gnet/v2/pkg/errors" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) @@ -135,12 +136,9 @@ func (p *Poller) Polling() error { if pollAttachment.FD == p.epa.FD { // poller is awakened to run tasks in queues. doChores = true } else { - switch err = pollAttachment.Callback(pollAttachment.FD, ev.events, 0); err { - case nil: - case errors.ErrAcceptSocket, errors.ErrEngineShutdown: + err = pollAttachment.Callback(pollAttachment.FD, ev.events, 0) + if errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in event-loop: %v", err) } } } @@ -149,12 +147,9 @@ func (p *Poller) Polling() error { doChores = false task := p.urgentAsyncTaskQueue.Dequeue() for ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() { - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } @@ -162,12 +157,9 @@ func (p *Poller) Polling() error { if task = p.asyncTaskQueue.Dequeue(); task == nil { break } - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } diff --git a/internal/netpoll/poller_kqueue_default.go b/internal/netpoll/poller_kqueue_default.go index c44be9027..4fbcd8e60 100644 --- a/internal/netpoll/poller_kqueue_default.go +++ b/internal/netpoll/poller_kqueue_default.go @@ -19,6 +19,7 @@ package netpoll import ( + "errors" "os" "runtime" "sync/atomic" @@ -26,7 +27,7 @@ import ( "golang.org/x/sys/unix" "github.com/panjf2000/gnet/v2/internal/queue" - "github.com/panjf2000/gnet/v2/pkg/errors" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) @@ -118,12 +119,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { doChores = true p.drainWakeupEvent() } else { - switch err = callback(fd, ev.Filter, ev.Flags); err { - case nil: - case errors.ErrAcceptSocket, errors.ErrEngineShutdown: + err = callback(fd, ev.Filter, ev.Flags) + if errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in event-loop: %v", err) } } } @@ -132,12 +130,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { doChores = false task := p.urgentAsyncTaskQueue.Dequeue() for ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() { - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } @@ -145,12 +140,9 @@ func (p *Poller) Polling(callback PollEventHandler) error { if task = p.asyncTaskQueue.Dequeue(); task == nil { break } - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } diff --git a/internal/netpoll/poller_kqueue_ultimate.go b/internal/netpoll/poller_kqueue_ultimate.go index fc50c6521..e12caf0c1 100644 --- a/internal/netpoll/poller_kqueue_ultimate.go +++ b/internal/netpoll/poller_kqueue_ultimate.go @@ -19,6 +19,7 @@ package netpoll import ( + "errors" "os" "runtime" "sync/atomic" @@ -27,7 +28,7 @@ import ( "golang.org/x/sys/unix" "github.com/panjf2000/gnet/v2/internal/queue" - "github.com/panjf2000/gnet/v2/pkg/errors" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) @@ -120,12 +121,9 @@ func (p *Poller) Polling() error { p.drainWakeupEvent() } else { pollAttachment := restorePollAttachment(unsafe.Pointer(&ev.Udata)) - switch err = pollAttachment.Callback(int(ev.Ident), ev.Filter, ev.Flags); err { - case nil: - case errors.ErrAcceptSocket, errors.ErrEngineShutdown: + err = pollAttachment.Callback(int(ev.Ident), ev.Filter, ev.Flags) + if errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in event-loop: %v", err) } } } @@ -134,12 +132,9 @@ func (p *Poller) Polling() error { doChores = false task := p.urgentAsyncTaskQueue.Dequeue() for ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() { - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } @@ -147,12 +142,9 @@ func (p *Poller) Polling() error { if task = p.asyncTaskQueue.Dequeue(); task == nil { break } - switch err = task.Run(task.Arg); err { - case nil: - case errors.ErrEngineShutdown: + err = task.Run(task.Arg) + if errors.Is(err, errorx.ErrEngineShutdown) { return err - default: - logging.Warnf("error occurs in user-defined function, %v", err) } queue.PutTask(task) } From 2e6430c4ece7b146a6542fac703cf1acea2c02e8 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 26 Sep 2024 18:50:23 +0800 Subject: [PATCH 26/29] actions: add actions/stale --- .github/workflows/gh-translator.yml | 2 +- .github/workflows/stale-bot.yml | 46 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/stale-bot.yml diff --git a/.github/workflows/gh-translator.yml b/.github/workflows/gh-translator.yml index c3a0f6d92..60bfe048a 100644 --- a/.github/workflows/gh-translator.yml +++ b/.github/workflows/gh-translator.yml @@ -20,4 +20,4 @@ jobs: with: BOT_GITHUB_TOKEN: ${{ secrets.GH_TRANSLATOR_TOKEN }} IS_MODIFY_TITLE: true - CUSTOM_BOT_NOTE: 🤖 Non-English text detected, translating... + CUSTOM_BOT_NOTE: 🤖 Non-English text detected, translating ... diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml new file mode 100644 index 000000000..d9222d5d7 --- /dev/null +++ b/.github/workflows/stale-bot.yml @@ -0,0 +1,46 @@ +name: Monitor inactive issues and PRs +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + stale-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-issue-stale: 30 + days-before-issue-close: 7 + stale-issue-label: 'stale' + stale-issue-message: | + This issue is marked as stale because it has been open for 30 days with no activity. + + You should take one of the following actions: + - Manually close this issue if it is no longer relevant + - Comment if you have more information to share + + This issue will be automatically closed in 7 days if no further activity occurs. + close-issue-message: | + This issue was closed because it has been inactive for 7 days since being marked as stale. + + If you believe this is a false alarm, please leave a comment for it or open a new issue, you can also reopen this issue directly if you have permission. + days-before-pr-stale: 21 + days-before-pr-close: 7 + stale-pr-label: 'stale' + stale-pr-message: | + This PR is marked as stale because it has been open for 21 days with no activity. + + You should take one of the following actions: + - Manually close this PR if it is no longer relevant + - Push new commits or comment if you have more information to share + + This PR will be automatically closed in 7 days if no further activity occurs. + close-pr-message: | + This PR was closed because it has been inactive for 7 days since being marked as stale. + + If you believe this is a false alarm, feel free to reopen this PR or create a new one. + repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From d745cd79dce16da93b21a583ad326e801b7be855 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 26 Sep 2024 19:02:45 +0800 Subject: [PATCH 27/29] actions: bump up the operations-per-run for actions/stale --- .github/workflows/stale-bot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index d9222d5d7..8a67f37de 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -13,6 +13,7 @@ jobs: steps: - uses: actions/stale@v9 with: + operations-per-run: 100 days-before-issue-stale: 30 days-before-issue-close: 7 stale-issue-label: 'stale' From cccef2c355050d8dc07f318c2326f360d86e31a4 Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Thu, 26 Sep 2024 19:51:52 +0800 Subject: [PATCH 28/29] actions: add "actions: write" for actions/stale temporarily Fix the cache issue. See actions/stale#1133 for details. --- .github/workflows/stale-bot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 8a67f37de..762a5b316 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -8,6 +8,7 @@ jobs: stale-issues: runs-on: ubuntu-latest permissions: + actions: write issues: write pull-requests: write steps: From ab70394ced980e640a7ed9b8a9e8be79d5ed591e Mon Sep 17 00:00:00 2001 From: Andy Pan Date: Wed, 2 Oct 2024 18:51:25 +0800 Subject: [PATCH 29/29] chore: fix lint warnings of missing case in the `switch` statement --- eventloop_unix.go | 2 +- eventloop_windows.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eventloop_unix.go b/eventloop_unix.go index 97475302c..0897485d6 100644 --- a/eventloop_unix.go +++ b/eventloop_unix.go @@ -293,7 +293,7 @@ func (el *eventloop) ticker(ctx context.Context) { for { delay, action = el.eventHandler.OnTick() switch action { - case None: + case None, Close: case Shutdown: // It seems reasonable to mark this as low-priority, waiting for some tasks like asynchronous writes // to finish up before shutting down the service. diff --git a/eventloop_windows.go b/eventloop_windows.go index 565f3ef30..906d8924f 100644 --- a/eventloop_windows.go +++ b/eventloop_windows.go @@ -155,7 +155,7 @@ func (el *eventloop) ticker(ctx context.Context) { for { delay, action = el.eventHandler.OnTick() switch action { - case None: + case None, Close: case Shutdown: if !shutdown { shutdown = true