diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d4613378d..1d33490ef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ Was this change discussed in an issue first? That can help save time in case the It is not uncommon for pull requests to go through several, iterative reviews. Please be patient with us! Every reviewer is a volunteer, and each has their own style. --> -## 1. Are you opening this pull request for bug-fixes, optimizations or new feature? +## 1. Are you opening this pull request for bug-fix, optimization or new feature? @@ -20,7 +20,7 @@ It is not uncommon for pull requests to go through several, iterative reviews. P -## 4. Which documentation changes (if any) need to be made/updated because of this PR? +## 4. What documentation changes (if any) need to be made/updated because of this PR? diff --git a/README.md b/README.md index fd9b0f4d9..9cdcd599a 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ English | [中文](README_ZH.md) `gnet` is an event-driven networking framework that is ultra-fast and lightweight. It is built from scratch by exploiting [epoll](https://man7.org/linux/man-pages/man7/epoll.7.html) and [kqueue](https://en.wikipedia.org/wiki/Kqueue) and it can achieve much higher performance with lower memory consumption than Go [net](https://golang.org/pkg/net/) in many specific scenarios. -`gnet` and [net](https://golang.org/pkg/net/) don't share the same philosophy about network programming. Thus, building network applications with `gnet` can be significantly different from building them with [net](https://golang.org/pkg/net/), and the philosophies can't be reconciled. There are other similar products written in other programming languages in the community, such as [libevent](https://github.com/libevent/libevent), [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado), etc. which work in a similar pattern as `gnet` under the hood. +`gnet` and [net](https://golang.org/pkg/net/) don't share the same philosophy about network programming. Thus, building network applications with `gnet` can be significantly different from building them with [net](https://golang.org/pkg/net/), and the philosophies can't be reconciled. There are other similar products written in other programming languages in the community, such as [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado), etc. which work in a similar pattern as `gnet` under the hood. -`gnet` is not designed to displace the Go [net](https://golang.org/pkg/net/), but to create an alternative in the Go ecosystem for building performance-critical network services. As a result of which, `gnet` is not as comprehensive as Go [net](https://golang.org/pkg/net/), it provides only the core functionalities (in a concise API set) required by a network application and it doesn't plan on being a coverall networking framework, as I think Go [net](https://golang.org/pkg/net/) has done a good enough job in that area. +`gnet` is not designed to displace the Go [net](https://golang.org/pkg/net/), but to create an alternative in the Go ecosystem for building performance-critical network services. As a result of which, `gnet` is not as comprehensive as Go [net](https://golang.org/pkg/net/), it provides only the core functionality (via a concise set of APIs) required by a network application and it doesn't plan on becoming a coverall networking framework, as I think Go [net](https://golang.org/pkg/net/) has done a good enough job in that area. `gnet` sells itself as a high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go which works on the transport layer with TCP/UDP protocols and Unix Domain Socket. It enables developers to implement their own protocols(HTTP, RPC, WebSocket, Redis, etc.) of application layer upon `gnet` for building diversified network services. For instance, you get an HTTP Server if you implement HTTP protocol upon `gnet` while you have a Redis Server done with the implementation of Redis protocol upon `gnet` and so on. diff --git a/README_ZH.md b/README_ZH.md index 04fae4bee..533543275 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -21,7 +21,7 @@ `gnet` 是一个基于事件驱动的高性能和轻量级网络框架。这个框架是基于 [epoll](https://en.wikipedia.org/wiki/Epoll) 和 [kqueue](https://en.wikipedia.org/wiki/Kqueue) 从零开发的,而且相比 Go [net](https://golang.org/pkg/net/),它能以更低的内存占用实现更高的性能。 -`gnet` 和 [net](https://golang.org/pkg/net/) 有着不一样的网络编程模式。因此,用 `gnet` 开发网络应用和用 [net](https://golang.org/pkg/net/) 开发区别很大,而且两者之间不可调和。社区里有其他同类的产品像是 [libevent](https://github.com/libevent/libevent), [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado),`gnet` 的底层工作原理和这些框架非常类似。 +`gnet` 和 [net](https://golang.org/pkg/net/) 有着不一样的网络编程模式。因此,用 `gnet` 开发网络应用和用 [net](https://golang.org/pkg/net/) 开发区别很大,而且两者之间不可调和。社区里有其他同类的产品像是 [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado),`gnet` 的底层工作原理和这些框架非常类似。 `gnet` 不是为了取代 [net](https://golang.org/pkg/net/) 而生的,而是在 Go 生态中为开发者提供一个开发性能敏感的网络服务的替代品。也正因如此,`gnet` 在功能全面性上比不了 Go [net](https://golang.org/pkg/net/),它只会提供网络应用所需的最核心的功能和最精简的 APIs,而且 `gnet` 也并没有打算变成一个无所不包的网络框架,因为我觉得 Go [net](https://golang.org/pkg/net/) 在这方面已经做得足够好了。 diff --git a/client_test.go b/client_test.go index 3f270ba0e..47a454a99 100644 --- a/client_test.go +++ b/client_test.go @@ -100,50 +100,50 @@ func TestClient(t *testing.T) { t.Run("poll-LT", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", false, false, false, false, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", false, false, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{false, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", false, false, false, true, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", false, false, true, true, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{false, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", false, false, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", false, false, true, false, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{false, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", false, false, false, true, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", false, false, true, true, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{false, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", false, false, false, false, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", false, false, true, false, 10, SourceAddrHash) + runClient(t, "unix", "gnet2.sock", &testConf{false, 0, false, true, false, false, 10, SourceAddrHash}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", false, false, false, true, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", false, false, true, true, 10, SourceAddrHash) + runClient(t, "unix", "gnet2.sock", &testConf{false, 0, false, true, true, false, 10, SourceAddrHash}) }) }) }) @@ -151,50 +151,101 @@ func TestClient(t *testing.T) { t.Run("poll-ET", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", true, false, false, false, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{true, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", true, false, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{true, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", true, false, false, true, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{true, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", true, false, true, true, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{true, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", true, false, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{true, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", true, false, true, false, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{true, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", true, false, false, true, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{true, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", true, false, true, true, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{true, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", true, false, false, false, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{true, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", true, false, true, false, 10, SourceAddrHash) + runClient(t, "unix", "gnet2.sock", &testConf{true, 0, false, true, false, false, 10, SourceAddrHash}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", true, false, false, true, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{true, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", true, false, true, true, 10, SourceAddrHash) + runClient(t, "unix", "gnet2.sock", &testConf{true, 0, false, true, true, false, 10, SourceAddrHash}) + }) + }) + }) + + t.Run("poll-ET-chunk", func(t *testing.T) { + t.Run("tcp", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "tcp", ":9991", &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "tcp", ":9992", &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections}) + }) + }) + t.Run("tcp-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "tcp", ":9991", &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "tcp", ":9992", &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections}) + }) + }) + t.Run("udp", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "udp", ":9991", &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "udp", ":9992", &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections}) + }) + }) + t.Run("udp-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "udp", ":9991", &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "udp", ":9992", &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections}) + }) + }) + t.Run("unix", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "unix", "gnet1.sock", &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "unix", "gnet2.sock", &testConf{true, 1 << 19, false, true, false, false, 10, SourceAddrHash}) + }) + }) + t.Run("unix-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runClient(t, "unix", "gnet1.sock", &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runClient(t, "unix", "gnet2.sock", &testConf{true, 1 << 19, false, true, true, false, 10, SourceAddrHash}) }) }) }) @@ -202,50 +253,50 @@ func TestClient(t *testing.T) { t.Run("poll-reuseport-LT", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", false, true, false, false, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", false, true, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", false, true, false, true, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", false, true, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", false, true, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", false, true, true, false, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", false, true, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", false, true, true, true, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", false, true, false, false, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", false, true, true, false, 10, LeastConnections) + runClient(t, "unix", "gnet2.sock", &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", false, true, false, true, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", false, true, true, true, 10, LeastConnections) + runClient(t, "unix", "gnet2.sock", &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) }) @@ -253,50 +304,50 @@ func TestClient(t *testing.T) { t.Run("poll-reuseport-ET", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", true, true, false, false, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", true, true, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "tcp", ":9991", true, true, false, true, 10, RoundRobin) + runClient(t, "tcp", ":9991", &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "tcp", ":9992", true, true, true, false, 10, LeastConnections) + runClient(t, "tcp", ":9992", &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", true, true, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", true, true, true, false, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "udp", ":9991", true, true, false, false, 10, RoundRobin) + runClient(t, "udp", ":9991", &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "udp", ":9992", true, true, true, true, 10, LeastConnections) + runClient(t, "udp", ":9992", &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", true, true, false, false, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", true, true, true, false, 10, LeastConnections) + runClient(t, "unix", "gnet2.sock", &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runClient(t, "unix", "gnet1.sock", true, true, false, true, 10, RoundRobin) + runClient(t, "unix", "gnet1.sock", &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runClient(t, "unix", "gnet2.sock", true, true, true, true, 10, LeastConnections) + runClient(t, "unix", "gnet2.sock", &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) }) @@ -426,20 +477,22 @@ func (s *testClient) OnTick() (delay time.Duration, action Action) { return } -func runClient(t *testing.T, network, addr string, et, reuseport, multicore, async bool, nclients int, lb LoadBalancing) { +func runClient(t *testing.T, network, addr string, conf *testConf) { ts := &testClient{ tester: t, network: network, addr: addr, - multicore: multicore, - async: async, - nclients: nclients, + multicore: conf.multicore, + async: conf.async, + nclients: conf.clients, workerPool: goPool.Default(), } var err error clientEV := &clientEvents{tester: t, packetLen: streamLen, svr: ts} ts.client, err = NewClient( clientEV, + WithEdgeTriggeredIO(conf.et), + WithEdgeTriggeredIOChunk(conf.etChunk), WithTCPNoDelay(TCPNoDelay), WithLockOSThread(true), WithTicker(true), @@ -452,13 +505,14 @@ func runClient(t *testing.T, network, addr string, et, reuseport, multicore, asy err = Run(ts, network+"://"+addr, - WithEdgeTriggeredIO(et), - WithLockOSThread(async), - WithMulticore(multicore), - WithReusePort(reuseport), + WithEdgeTriggeredIO(conf.et), + WithEdgeTriggeredIOChunk(conf.etChunk), + WithLockOSThread(conf.async), + WithMulticore(conf.multicore), + WithReusePort(conf.reuseport), WithTicker(true), WithTCPKeepAlive(time.Minute*1), - WithLoadBalancing(lb)) + WithLoadBalancing(conf.lb)) assert.NoError(t, err) } @@ -473,7 +527,7 @@ func startGnetClient(t *testing.T, cli *Client, network, addr string, multicore, } if netDial { var netConn net.Conn - netConn, err = NetDial(network, addr) + netConn, err = stdDial(network, addr) require.NoError(t, err) c, err = cli.EnrollContext(netConn, handler) } else { diff --git a/client_unix.go b/client_unix.go index 2d82f8e98..709a09ef2 100644 --- a/client_unix.go +++ b/client_unix.go @@ -87,6 +87,13 @@ func NewClient(eh EventHandler, opts ...Option) (cli *Client, err error) { poller: p, } + if options.EdgeTriggeredIOChunk > 0 { + options.EdgeTriggeredIO = true + options.EdgeTriggeredIOChunk = math.CeilToPowerOfTwo(options.EdgeTriggeredIOChunk) + } else if options.EdgeTriggeredIO { + options.EdgeTriggeredIOChunk = 1 << 20 // 1MB + } + rbc := options.ReadBufferCap switch { case rbc <= 0: diff --git a/connection_unix.go b/connection_unix.go index cfce3a928..3ccac4ab1 100644 --- a/connection_unix.go +++ b/connection_unix.go @@ -325,11 +325,11 @@ 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 { - return head[:n], err - } c.loop.cache.Reset() c.loop.cache.Write(head) + if len(head) >= n { + return c.loop.cache.Bytes(), err + } c.loop.cache.Write(tail) if inBufferLen >= n { return c.loop.cache.Bytes(), err diff --git a/connection_windows.go b/connection_windows.go index efdfc22c9..745028434 100644 --- a/connection_windows.go +++ b/connection_windows.go @@ -43,8 +43,8 @@ type udpConn struct { } type openConn struct { - c *conn - cb func() + c *conn + cb func() } type conn struct { @@ -164,11 +164,11 @@ 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 { - return head[:n], err - } c.loop.cache.Reset() c.loop.cache.Write(head) + if len(head) >= n { + return c.loop.cache.Bytes(), err + } c.loop.cache.Write(tail) if inBufferLen >= n { return c.loop.cache.Bytes(), err diff --git a/eventloop_unix.go b/eventloop_unix.go index 97475302c..6602e78eb 100644 --- a/eventloop_unix.go +++ b/eventloop_unix.go @@ -118,8 +118,6 @@ func (el *eventloop) read0(itf interface{}) error { return el.read(itf.(*conn)) } -const maxBytesTransferET = 1 << 20 - func (el *eventloop) read(c *conn) error { if !c.opened { return nil @@ -127,6 +125,7 @@ func (el *eventloop) read(c *conn) error { var recv int isET := el.engine.opts.EdgeTriggeredIO + chunk := el.engine.opts.EdgeTriggeredIOChunk loop: n, err := unix.Read(c.fd, el.buffer) if err != nil || n == 0 { @@ -152,7 +151,7 @@ loop: _, _ = c.inboundBuffer.Write(c.buffer) c.buffer = c.buffer[:0] - if c.isEOF || (isET && recv < maxBytesTransferET) { + if c.isEOF || (isET && recv < chunk) { goto loop } @@ -180,6 +179,7 @@ func (el *eventloop) write(c *conn) error { } isET := el.engine.opts.EdgeTriggeredIO + chunk := el.engine.opts.EdgeTriggeredIOChunk var ( n int sent int @@ -205,7 +205,7 @@ loop: } sent += n - if isET && !c.outboundBuffer.IsEmpty() && sent < maxBytesTransferET { + if isET && !c.outboundBuffer.IsEmpty() && sent < chunk { goto loop } @@ -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 diff --git a/gnet.go b/gnet.go index 99f6f4038..f05197e72 100644 --- a/gnet.go +++ b/gnet.go @@ -443,6 +443,13 @@ func createListeners(addrs []string, opts ...Option) ([]*listener, *Options, err return nil, nil, errors.ErrTooManyEventLoopThreads } + if options.EdgeTriggeredIOChunk > 0 { + options.EdgeTriggeredIO = true + options.EdgeTriggeredIOChunk = math.CeilToPowerOfTwo(options.EdgeTriggeredIOChunk) + } else if options.EdgeTriggeredIO { + options.EdgeTriggeredIOChunk = 1 << 20 // 1MB + } + rbc := options.ReadBufferCap switch { case rbc <= 0: diff --git a/gnet_test.go b/gnet_test.go index 3e20b0c00..201c6b9de 100644 --- a/gnet_test.go +++ b/gnet_test.go @@ -33,6 +33,17 @@ var ( streamLen = 1024 * 1024 ) +type testConf struct { + et bool + etChunk int + reuseport bool + multicore bool + async bool + writev bool + clients int + lb LoadBalancing +} + func TestServer(t *testing.T) { // start an engine // connect 10 clients @@ -43,66 +54,66 @@ func TestServer(t *testing.T) { t.Run("poll-LT", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("tcp-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, false, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, false, true, true, true, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, false, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, false, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{false, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, false, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, false, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{false, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, false, true, false, false, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, false, true, false, false, 10, SourceAddrHash}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, false, true, true, false, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, false, true, true, false, 10, SourceAddrHash}) }) }) t.Run("unix-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, false, true, true, true, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, false, true, true, true, 10, SourceAddrHash}) }) }) }) @@ -110,66 +121,133 @@ func TestServer(t *testing.T) { t.Run("poll-ET", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, false, true, false, false, 10, LeastConnections}) + }) + }) + t.Run("tcp-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, false, true, true, false, 10, LeastConnections}) + }) + }) + t.Run("tcp-async-writev", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, false, false, true, true, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, false, true, true, true, 10, LeastConnections}) + }) + }) + t.Run("udp", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"udp://:9991"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"udp://:9992"}, &testConf{true, 0, false, true, false, false, 10, LeastConnections}) + }) + }) + t.Run("udp-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"udp://:9991"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"udp://:9992"}, &testConf{true, 0, false, true, true, false, 10, LeastConnections}) + }) + }) + t.Run("unix", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, false, true, false, false, 10, SourceAddrHash}) + }) + }) + t.Run("unix-async", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, false, true, true, false, 10, SourceAddrHash}) + }) + }) + t.Run("unix-async-writev", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, false, false, true, true, 10, RoundRobin}) + }) + t.Run("N-loop", func(t *testing.T) { + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, false, true, true, true, 10, SourceAddrHash}) + }) + }) + }) + + t.Run("poll-ET-chunk", func(t *testing.T) { + t.Run("tcp", func(t *testing.T) { + t.Run("1-loop", func(t *testing.T) { + runServer(t, []string{"tcp://:9991"}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections}) }) }) t.Run("tcp-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 1 << 18, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, false, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 1 << 19, false, true, true, true, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, true, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, true, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, true, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, true, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, false, true, false, false, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 1 << 19, false, true, false, false, 10, SourceAddrHash}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, false, true, true, false, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 1 << 19, false, true, true, false, 10, SourceAddrHash}) }) }) t.Run("unix-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 1 << 18, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, false, true, true, true, 10, SourceAddrHash) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 1 << 19, false, true, true, true, 10, SourceAddrHash}) }) }) }) @@ -177,66 +255,66 @@ func TestServer(t *testing.T) { t.Run("poll-reuseport-LT", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("tcp-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, false, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{false, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, false, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{false, 0, true, true, true, true, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, false, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, false, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, false, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, false, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, false, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{false, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, false, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{false, 0, true, true, true, true, 10, LeastConnections}) }) }) }) @@ -244,66 +322,66 @@ func TestServer(t *testing.T) { t.Run("poll-reuseport-ET", func(t *testing.T) { t.Run("tcp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("tcp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("tcp-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991"}, true, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991"}, &testConf{true, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9992"}, true, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9992"}, &testConf{true, 0, true, true, true, true, 10, LeastConnections}) }) }) t.Run("udp", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, true, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, true, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("udp-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9991"}, true, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"udp://:9991"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"udp://:9992"}, true, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"udp://:9992"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("unix-async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("unix-async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet1.sock"}, true, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"unix://gnet1.sock"}, &testConf{true, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"unix://gnet2.sock"}, true, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"unix://gnet2.sock"}, &testConf{true, 0, true, true, true, true, 10, LeastConnections}) }) }) }) @@ -311,34 +389,34 @@ func TestServer(t *testing.T) { t.Run("poll-multi-addrs-LT", func(t *testing.T) { t.Run("sync", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("sync-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, false, false, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, false, false, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, true, false, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, true, false, true, 10, LeastConnections}) }) }) t.Run("async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, false, false, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, false, true, true, true, 10, LeastConnections}) }) }) }) @@ -346,34 +424,34 @@ func TestServer(t *testing.T) { t.Run("poll-multi-addrs-reuseport-LT", func(t *testing.T) { t.Run("sync", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("sync-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, false, false, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, false, false, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, true, false, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, true, false, true, 10, LeastConnections}) }) }) t.Run("async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, false, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{false, 0, true, true, true, true, 10, LeastConnections}) }) }) }) @@ -381,34 +459,34 @@ func TestServer(t *testing.T) { t.Run("poll-multi-addrs-ET", func(t *testing.T) { t.Run("sync", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, true, false, false, 10, LeastConnections}) }) }) t.Run("sync-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, false, false, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, false, false, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, true, false, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, true, false, true, 10, LeastConnections}) }) }) t.Run("async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, true, true, false, 10, LeastConnections}) }) }) t.Run("async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, true, false, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, false, true, true, true, 10, LeastConnections}) }) }) }) @@ -416,34 +494,34 @@ func TestServer(t *testing.T) { t.Run("poll-multi-addrs-reuseport-ET", func(t *testing.T) { t.Run("sync", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, false, false, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, true, false, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections}) }) }) t.Run("sync-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, false, false, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, false, false, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, true, false, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, true, false, true, 10, LeastConnections}) }) }) t.Run("async", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, false, true, false, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "udp://:9993", "udp://:9994", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, true, true, false, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "udp://:9997", "udp://:9998", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections}) }) }) t.Run("async-writev", func(t *testing.T) { t.Run("1-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, false, true, true, 10, RoundRobin) + runServer(t, []string{"tcp://:9991", "tcp://:9992", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, false, true, true, 10, RoundRobin}) }) t.Run("N-loop", func(t *testing.T) { - runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, true, true, true, true, true, 10, LeastConnections) + runServer(t, []string{"tcp://:9995", "tcp://:9996", "unix://gnet1.sock", "unix://gnet2.sock"}, &testConf{true, 0, true, true, true, true, 10, LeastConnections}) }) }) }) @@ -623,39 +701,41 @@ func (s *testServer) OnTick() (delay time.Duration, action Action) { return } -func runServer(t *testing.T, addrs []string, et, reuseport, multicore, async, writev bool, nclients int, lb LoadBalancing) { +func runServer(t *testing.T, addrs []string, conf *testConf) { ts := &testServer{ tester: t, addrs: addrs, - multicore: multicore, - async: async, - writev: writev, - nclients: nclients, + multicore: conf.multicore, + async: conf.async, + writev: conf.writev, + nclients: conf.clients, workerPool: goPool.Default(), } var err error if len(addrs) > 1 { err = Rotate(ts, addrs, - WithEdgeTriggeredIO(et), - WithLockOSThread(async), - WithMulticore(multicore), - WithReusePort(reuseport), + WithEdgeTriggeredIO(conf.et), + WithEdgeTriggeredIOChunk(conf.etChunk), + WithLockOSThread(conf.async), + WithMulticore(conf.multicore), + WithReusePort(conf.reuseport), WithTicker(true), WithTCPKeepAlive(time.Minute), WithTCPNoDelay(TCPNoDelay), - WithLoadBalancing(lb)) + WithLoadBalancing(conf.lb)) } else { err = Run(ts, addrs[0], - WithEdgeTriggeredIO(et), - WithLockOSThread(async), - WithMulticore(multicore), - WithReusePort(reuseport), + WithEdgeTriggeredIO(conf.et), + WithEdgeTriggeredIOChunk(conf.etChunk), + WithLockOSThread(conf.async), + WithMulticore(conf.multicore), + WithReusePort(conf.reuseport), WithTicker(true), WithTCPKeepAlive(time.Minute), WithTCPNoDelay(TCPDelay), - WithLoadBalancing(lb)) + WithLoadBalancing(conf.lb)) } assert.NoError(t, err) } diff --git a/internal/socket/socket.go b/internal/socket/socket.go index a6d9a9448..9ddce28a0 100644 --- a/internal/socket/socket.go +++ b/internal/socket/socket.go @@ -27,24 +27,33 @@ import ( ) // Option is used for setting an option on socket. -type Option struct { - SetSockOpt func(int, int) error - Opt int +type Option[T int | string] struct { + SetSockOpt func(int, T) error + Opt T +} + +func execSockOpts[T int | string](fd int, opts []Option[T]) error { + for _, opt := range opts { + if err := opt.SetSockOpt(fd, opt.Opt); err != nil { + return err + } + } + return nil } // TCPSocket calls the internal tcpSocket. -func TCPSocket(proto, addr string, passive bool, sockOpts ...Option) (int, net.Addr, error) { - return tcpSocket(proto, addr, passive, sockOpts...) +func TCPSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) { + return tcpSocket(proto, addr, passive, sockOptInts, sockOptStrs) } // UDPSocket calls the internal udpSocket. -func UDPSocket(proto, addr string, connect bool, sockOpts ...Option) (int, net.Addr, error) { - return udpSocket(proto, addr, connect, sockOpts...) +func UDPSocket(proto, addr string, connect bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) { + return udpSocket(proto, addr, connect, sockOptInts, sockOptStrs) } // UnixSocket calls the internal udsSocket. -func UnixSocket(proto, addr string, passive bool, sockOpts ...Option) (int, net.Addr, error) { - return udsSocket(proto, addr, passive, sockOpts...) +func UnixSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) { + return udsSocket(proto, addr, passive, sockOptInts, sockOptStrs) } // Accept accepts the next incoming socket along with setting diff --git a/internal/socket/sockopts_bsd.go b/internal/socket/sockopts_bsd.go new file mode 100644 index 000000000..92b1b79ec --- /dev/null +++ b/internal/socket/sockopts_bsd.go @@ -0,0 +1,26 @@ +// Copyright (c) 2024 The Gnet Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build dragonfly || freebsd || netbsd || openbsd +// +build dragonfly freebsd netbsd openbsd + +package socket + +import errorx "github.com/panjf2000/gnet/v2/pkg/errors" + +// SetBindToDevice is not implemented on *BSD because there is +// no equivalent of Linux's SO_BINDTODEVICE. +func SetBindToDevice(_ int, _ string) error { + return errorx.ErrUnsupportedOp +} diff --git a/internal/socket/sockopts_darwin.go b/internal/socket/sockopts_darwin.go index 338857cc5..5a2f69217 100644 --- a/internal/socket/sockopts_darwin.go +++ b/internal/socket/sockopts_darwin.go @@ -19,6 +19,8 @@ import ( "os" "golang.org/x/sys/unix" + + errorx "github.com/panjf2000/gnet/v2/pkg/errors" ) // SetKeepAlivePeriod sets whether the operating system should send @@ -52,3 +54,9 @@ func SetKeepAlivePeriod(fd, secs int) error { return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPCNT, 5)) } + +// SetBindToDevice is not implemented on macOS because there is +// no equivalent of Linux's SO_BINDTODEVICE. +func SetBindToDevice(_ int, _ string) error { + return errorx.ErrUnsupportedOp +} diff --git a/internal/socket/sockopts_linux.go b/internal/socket/sockopts_linux.go new file mode 100644 index 000000000..f110f16b8 --- /dev/null +++ b/internal/socket/sockopts_linux.go @@ -0,0 +1,30 @@ +// Copyright (c) 2024 The Gnet Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package socket + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// SetBindToDevice binds the socket to a specific network interface. +// +// SO_BINDTODEVICE on Linux works in both directions: only process packets +// received from the particular interface along with sending them through +// that interface, instead of following the default route. +func SetBindToDevice(fd int, ifname string) error { + return os.NewSyscallError("setsockopt", unix.BindToDevice(fd, ifname)) +} diff --git a/internal/socket/sockopts_openbsd.go b/internal/socket/sockopts_openbsd.go index 8670f2af0..47820e6a0 100644 --- a/internal/socket/sockopts_openbsd.go +++ b/internal/socket/sockopts_openbsd.go @@ -14,11 +14,11 @@ package socket -import "golang.org/x/sys/unix" +import errorx "github.com/panjf2000/gnet/v2/pkg/errors" // SetKeepAlivePeriod sets whether the operating system should send // keep-alive messages on the connection and sets period between TCP keep-alive probes. func SetKeepAlivePeriod(_, _ int) error { // OpenBSD has no user-settable per-socket TCP keepalive options. - return unix.ENOPROTOOPT + return errorx.ErrUnsupportedOp } diff --git a/internal/socket/sockopts_posix.go b/internal/socket/sockopts_posix.go index 61fc8d31f..5da7a7206 100644 --- a/internal/socket/sockopts_posix.go +++ b/internal/socket/sockopts_posix.go @@ -39,13 +39,13 @@ func SetNoDelay(fd, noDelay int) error { // SetRecvBuffer sets the size of the operating system's // receive buffer associated with the connection. func SetRecvBuffer(fd, size int) error { - return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, size) + return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, size)) } // SetSendBuffer sets the size of the operating system's // transmit buffer associated with the connection. func SetSendBuffer(fd, size int) error { - return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, size) + return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, size)) } // SetReuseAddr enables SO_REUSEADDR option on socket. @@ -55,7 +55,7 @@ func SetReuseAddr(fd, reuseAddr int) error { // SetIPv6Only restricts a IPv6 socket to only process IPv6 requests or both IPv4 and IPv6 requests. func SetIPv6Only(fd, ipv6only int) error { - return unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, ipv6only) + return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, ipv6only)) } // SetLinger sets the behavior of Close on a connection which still @@ -79,7 +79,7 @@ func SetLinger(fd, sec int) error { l.Onoff = 0 l.Linger = 0 } - return unix.SetsockoptLinger(fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &l) + return os.NewSyscallError("setsockopt", unix.SetsockoptLinger(fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &l)) } // SetMulticastMembership returns with a socket option function based on the IP diff --git a/internal/socket/tcp_socket.go b/internal/socket/tcp_socket.go index 21d4af32e..e086469f2 100644 --- a/internal/socket/tcp_socket.go +++ b/internal/socket/tcp_socket.go @@ -83,7 +83,7 @@ func determineTCPProto(proto string, addr *net.TCPAddr) (string, error) { // tcpSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint. // Argument `reusePort` indicates whether the SO_REUSEPORT flag will be assigned. -func tcpSocket(proto, addr string, passive bool, sockOpts ...Option) (fd int, netAddr net.Addr, err error) { +func tcpSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) { var ( family int ipv6only bool @@ -114,10 +114,11 @@ func tcpSocket(proto, addr string, passive bool, sockOpts ...Option) (fd int, ne } } - for _, sockOpt := range sockOpts { - if err = sockOpt.SetSockOpt(fd, sockOpt.Opt); err != nil { - return - } + if err = execSockOpts(fd, sockOptInts); err != nil { + return + } + if err = execSockOpts(fd, sockOptStrs); err != nil { + return } if passive { diff --git a/internal/socket/udp_socket.go b/internal/socket/udp_socket.go index 6205c986b..0e524457b 100644 --- a/internal/socket/udp_socket.go +++ b/internal/socket/udp_socket.go @@ -81,7 +81,7 @@ func determineUDPProto(proto string, addr *net.UDPAddr) (string, error) { // udpSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint. // Argument `reusePort` indicates whether the SO_REUSEPORT flag will be assigned. -func udpSocket(proto, addr string, connect bool, sockOpts ...Option) (fd int, netAddr net.Addr, err error) { +func udpSocket(proto, addr string, connect bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) { var ( family int ipv6only bool @@ -117,10 +117,11 @@ func udpSocket(proto, addr string, connect bool, sockOpts ...Option) (fd int, ne return } - for _, sockOpt := range sockOpts { - if err = sockOpt.SetSockOpt(fd, sockOpt.Opt); err != nil { - return - } + if err = execSockOpts(fd, sockOptInts); err != nil { + return + } + if err = execSockOpts(fd, sockOptStrs); err != nil { + return } if connect { diff --git a/internal/socket/unix_socket.go b/internal/socket/unix_socket.go index 688672d09..87cc889b4 100644 --- a/internal/socket/unix_socket.go +++ b/internal/socket/unix_socket.go @@ -45,7 +45,7 @@ func GetUnixSockAddr(proto, addr string) (sa unix.Sockaddr, family int, unixAddr // udsSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint. // Argument `reusePort` indicates whether the SO_REUSEPORT flag will be assigned. -func udsSocket(proto, addr string, passive bool, sockOpts ...Option) (fd int, netAddr net.Addr, err error) { +func udsSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) { var ( family int sa unix.Sockaddr @@ -70,10 +70,11 @@ func udsSocket(proto, addr string, passive bool, sockOpts ...Option) (fd int, ne } }() - for _, sockOpt := range sockOpts { - if err = sockOpt.SetSockOpt(fd, sockOpt.Opt); err != nil { - return - } + if err = execSockOpts(fd, sockOptInts); err != nil { + return + } + if err = execSockOpts(fd, sockOptStrs); err != nil { + return } if passive { diff --git a/listener_unix.go b/listener_unix.go index 18fde857a..2e2711df8 100644 --- a/listener_unix.go +++ b/listener_unix.go @@ -36,7 +36,8 @@ type listener struct { fd int addr net.Addr address, network string - sockOpts []socket.Option + sockOptInts []socket.Option[int] + sockOptStrs []socket.Option[string] pollAttachment *netpoll.PollAttachment // listener attachment for poller } @@ -52,14 +53,14 @@ func (ln *listener) dup() (int, error) { func (ln *listener) normalize() (err error) { switch ln.network { case "tcp", "tcp4", "tcp6": - ln.fd, ln.addr, err = socket.TCPSocket(ln.network, ln.address, true, ln.sockOpts...) + ln.fd, ln.addr, err = socket.TCPSocket(ln.network, ln.address, true, ln.sockOptInts, ln.sockOptStrs) ln.network = "tcp" case "udp", "udp4", "udp6": - ln.fd, ln.addr, err = socket.UDPSocket(ln.network, ln.address, false, ln.sockOpts...) + ln.fd, ln.addr, err = socket.UDPSocket(ln.network, ln.address, false, ln.sockOptInts, ln.sockOptStrs) ln.network = "udp" case "unix": _ = os.RemoveAll(ln.address) - ln.fd, ln.addr, err = socket.UnixSocket(ln.network, ln.address, true, ln.sockOpts...) + ln.fd, ln.addr, err = socket.UnixSocket(ln.network, ln.address, true, ln.sockOptInts, ln.sockOptStrs) default: err = errors.ErrUnsupportedProtocol } @@ -79,37 +80,44 @@ func (ln *listener) close() { } func initListener(network, addr string, options *Options) (l *listener, err error) { - var sockOpts []socket.Option + var ( + sockOptInts []socket.Option[int] + sockOptStrs []socket.Option[string] + ) if options.ReusePort || strings.HasPrefix(network, "udp") { - sockOpt := socket.Option{SetSockOpt: socket.SetReuseport, Opt: 1} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: socket.SetReuseport, Opt: 1} + sockOptInts = append(sockOptInts, sockOpt) } if options.ReuseAddr { - sockOpt := socket.Option{SetSockOpt: socket.SetReuseAddr, Opt: 1} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: socket.SetReuseAddr, Opt: 1} + sockOptInts = append(sockOptInts, sockOpt) } if options.TCPNoDelay == TCPNoDelay && strings.HasPrefix(network, "tcp") { - sockOpt := socket.Option{SetSockOpt: socket.SetNoDelay, Opt: 1} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: socket.SetNoDelay, Opt: 1} + sockOptInts = append(sockOptInts, sockOpt) } if options.SocketRecvBuffer > 0 { - sockOpt := socket.Option{SetSockOpt: socket.SetRecvBuffer, Opt: options.SocketRecvBuffer} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: socket.SetRecvBuffer, Opt: options.SocketRecvBuffer} + sockOptInts = append(sockOptInts, sockOpt) } if options.SocketSendBuffer > 0 { - sockOpt := socket.Option{SetSockOpt: socket.SetSendBuffer, Opt: options.SocketSendBuffer} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: socket.SetSendBuffer, Opt: options.SocketSendBuffer} + sockOptInts = append(sockOptInts, sockOpt) } if strings.HasPrefix(network, "udp") { udpAddr, err := net.ResolveUDPAddr(network, addr) if err == nil && udpAddr.IP.IsMulticast() { if sockoptFn := socket.SetMulticastMembership(network, udpAddr); sockoptFn != nil { - sockOpt := socket.Option{SetSockOpt: sockoptFn, Opt: options.MulticastInterfaceIndex} - sockOpts = append(sockOpts, sockOpt) + sockOpt := socket.Option[int]{SetSockOpt: sockoptFn, Opt: options.MulticastInterfaceIndex} + sockOptInts = append(sockOptInts, sockOpt) } } } - l = &listener{network: network, address: addr, sockOpts: sockOpts} + if options.BindToDevice != "" { + sockOpt := socket.Option[string]{SetSockOpt: socket.SetBindToDevice, Opt: options.BindToDevice} + sockOptStrs = append(sockOptStrs, sockOpt) + } + l = &listener{network: network, address: addr, sockOptInts: sockOptInts, sockOptStrs: sockOptStrs} err = l.normalize() return } diff --git a/options.go b/options.go index ed1f8e2ec..86d5883af 100644 --- a/options.go +++ b/options.go @@ -68,6 +68,12 @@ type Options struct { // MulticastInterfaceIndex is the index of the interface name where the multicast UDP addresses will be bound to. MulticastInterfaceIndex int + // BindToDevice is the name of the interface to which the listening socket will be bound. + // + // It is only available on Linux at the moment, an error will therefore be returned when + // setting this option on non-linux platforms. + BindToDevice string + // ============================= Options for both server-side and client-side ============================= // ReadBufferCap is the maximum number of bytes that can be read from the remote when the readable event comes. @@ -95,7 +101,7 @@ type Options struct { // Ticker indicates whether the ticker has been set up. Ticker bool - // TCPKeepAlive enable the TCP keep-alive mechanism (SO_KEEPALIVE) and set its value + // TCPKeepAlive enables 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 @@ -134,6 +140,14 @@ type Options struct { // Don't enable it unless you are 100% sure what you are doing. // Note that this option is only available for stream-oriented protocol. EdgeTriggeredIO bool + + // EdgeTriggeredIOChunk specifies the number of bytes that `gnet` can + // read/write up to in one event loop of ET. This option implies + // EdgeTriggeredIO when it is set to a value greater than 0. + // If EdgeTriggeredIO is set to true and EdgeTriggeredIOChunk is not set, + // 1MB is used. The value of EdgeTriggeredIOChunk must be a power of 2, + // otherwise, it will be rounded up to the nearest power of 2. + EdgeTriggeredIOChunk int } // WithOptions sets up all options. @@ -262,9 +276,27 @@ func WithMulticastInterfaceIndex(idx int) Option { } } +// WithBindToDevice sets the name of the interface to which the listening socket will be bound. +// +// It is only available on Linux at the moment, an error will therefore be returned when +// setting this option on non-linux platforms. +func WithBindToDevice(iface string) Option { + return func(opts *Options) { + opts.BindToDevice = iface + } +} + // WithEdgeTriggeredIO enables the edge-triggered I/O for the underlying epoll/kqueue event-loop. func WithEdgeTriggeredIO(et bool) Option { return func(opts *Options) { opts.EdgeTriggeredIO = et } } + +// WithEdgeTriggeredIOChunk sets the number of bytes that `gnet` can +// read/write up to in one event loop of ET. +func WithEdgeTriggeredIOChunk(chunk int) Option { + return func(opts *Options) { + opts.EdgeTriggeredIOChunk = chunk + } +} diff --git a/os_unix_test.go b/os_unix_test.go index 696ec1514..2ff97ed11 100644 --- a/os_unix_test.go +++ b/os_unix_test.go @@ -10,6 +10,9 @@ import ( "fmt" "math/rand" "net" + "regexp" + "runtime" + "strings" "sync" "sync/atomic" "testing" @@ -19,15 +22,16 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sys/unix" + errorx "github.com/panjf2000/gnet/v2/pkg/errors" "github.com/panjf2000/gnet/v2/pkg/logging" ) var ( SysClose = unix.Close - NetDial = net.Dial + stdDial = net.Dial ) -// NOTE: TestServeMulticast can fail with "write: no buffer space available" on wifi interface. +// NOTE: TestServeMulticast can fail with "write: no buffer space available" on Wi-Fi interface. func TestServeMulticast(t *testing.T) { t.Run("IPv4", func(t *testing.T) { // 224.0.0.169 is an unassigned address from the Local Network Control Block @@ -191,6 +195,217 @@ func TestMulticastBindIPv6(t *testing.T) { assert.NoError(t, err) } +func detectLinuxEthernetInterfaceName() (string, error) { + ifaces, err := net.Interfaces() + if err != nil { + return "", err + } + // Traditionally, network interfaces were named as eth0, eth1, etc., for Ethernet interfaces. + // However, with the introduction of predictable network interface names. Meanwhile, modern + // convention commonly uses patterns like eno[1-N], ens[1-N], enps, etc., + // for Ethernet interfaces. + // Check out https://www.thomas-krenn.com/en/wiki/Predictable_Network_Interface_Names and + // https://en.wikipedia.org/wiki/Consistent_Network_Device_Naming for more details. + regex := regexp.MustCompile(`e(no|ns|np|th)\d+s*\d*$`) + for _, iface := range ifaces { + if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagRunning == 0 { + continue + } + if regex.MatchString(iface.Name) { + return iface.Name, nil + } + } + return "", errors.New("no Ethernet interface found") +} + +func getInterfaceIP(ifname string, ipv4 bool) (net.IP, error) { + iface, err := net.InterfaceByName(ifname) + if err != nil { + return nil, err + } + // Get all unicast addresses for this interface + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + // Loop through the addresses and find the first IPv4 address + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + // Check if the IP is IPv4. + if ip != nil && (ip.To4() != nil) == ipv4 { + return ip, nil + } + } + return nil, errors.New("no valid IP address found") +} + +type testBindToDeviceServer[T interface{ *net.TCPAddr | *net.UDPAddr }] struct { + BuiltinEventEngine + tester *testing.T + data []byte + packets atomic.Int32 + expectedPackets int32 + network string + loopBackAddr T + eth0Addr T + broadcastAddr T +} + +func netDial[T *net.TCPAddr | *net.UDPAddr](network string, a T) (net.Conn, error) { + addr := any(a) + switch v := addr.(type) { + case *net.TCPAddr: + return net.DialTCP(network, nil, v) + case *net.UDPAddr: + return net.DialUDP(network, nil, v) + default: + return nil, errors.New("unsupported address type") + } +} + +func (s *testBindToDeviceServer[T]) OnTraffic(c Conn) (action Action) { + b, err := c.Next(-1) + assert.NoError(s.tester, err) + assert.EqualValues(s.tester, s.data, b) + _, err = c.Write(b) + assert.NoError(s.tester, err) + s.packets.Add(1) + return +} + +func (s *testBindToDeviceServer[T]) OnShutdown(_ Engine) { + assert.EqualValues(s.tester, s.expectedPackets, s.packets.Load()) +} + +func (s *testBindToDeviceServer[T]) OnTick() (delay time.Duration, action Action) { + // Send a packet to the loopback interface, it should never make its way to the server + // because we've bound the server to eth0. + c, err := netDial(s.network, s.loopBackAddr) + if strings.HasPrefix(s.network, "tcp") { + assert.ErrorContains(s.tester, err, "connection refused") + } else { + assert.NoError(s.tester, err) + defer c.Close() + _, err = c.Write(s.data) + assert.NoError(s.tester, err) + } + + if s.broadcastAddr != nil { + // Send a packet to the broadcast address, it should reach the server. + c6, err := netDial(s.network, s.broadcastAddr) + assert.NoError(s.tester, err) + defer c6.Close() + _, err = c6.Write(s.data) + assert.NoError(s.tester, err) + } + + // Send a packet to the eth0 interface, it should reach the server. + c4, err := netDial(s.network, s.eth0Addr) + assert.NoError(s.tester, err) + defer c4.Close() + _, err = c4.Write(s.data) + assert.NoError(s.tester, err) + buf := make([]byte, len(s.data)) + _, err = c4.Read(buf) + assert.NoError(s.tester, err) + assert.EqualValues(s.tester, s.data, buf, len(s.data), len(buf)) + + return time.Second, Shutdown +} + +func TestBindToDevice(t *testing.T) { + if runtime.GOOS != "linux" { + err := Run(&testBindToDeviceServer[*net.UDPAddr]{}, "tcp://:9999", WithBindToDevice("eth0")) + assert.ErrorIs(t, err, errorx.ErrUnsupportedOp) + return + } + + lp, err := findLoopbackInterface() + assert.NoError(t, err) + dev, err := detectLinuxEthernetInterfaceName() + assert.NoErrorf(t, err, "no testable Ethernet interface found") + t.Logf("detected Ethernet interface: %s", dev) + data := []byte("hello") + t.Run("IPv4", func(t *testing.T) { + ip, err := getInterfaceIP(dev, true) + assert.NoError(t, err) + t.Run("TCP", func(t *testing.T) { + ts := &testBindToDeviceServer[*net.TCPAddr]{ + tester: t, + data: data, + expectedPackets: 1, + network: "tcp", + loopBackAddr: &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9999, Zone: ""}, + eth0Addr: &net.TCPAddr{IP: ip, Port: 9999, Zone: ""}, + } + require.NoError(t, err) + err = Run(ts, "tcp://0.0.0.0:9999", + WithTicker(true), + WithBindToDevice(dev)) + assert.NoError(t, err) + }) + t.Run("UDP", func(t *testing.T) { + ts := &testBindToDeviceServer[*net.UDPAddr]{ + tester: t, + data: data, + expectedPackets: 2, + network: "udp", + loopBackAddr: &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9999, Zone: ""}, + eth0Addr: &net.UDPAddr{IP: ip, Port: 9999, Zone: ""}, + broadcastAddr: &net.UDPAddr{IP: net.IPv4bcast, Port: 9999, Zone: ""}, + } + require.NoError(t, err) + err = Run(ts, "udp://0.0.0.0:9999", + WithTicker(true), + WithBindToDevice(dev)) + assert.NoError(t, err) + }) + }) + t.Run("IPv6", func(t *testing.T) { + t.Run("TCP", func(t *testing.T) { + ip, err := getInterfaceIP(dev, false) + assert.NoError(t, err) + ts := &testBindToDeviceServer[*net.TCPAddr]{ + tester: t, + data: data, + expectedPackets: 1, + network: "tcp6", + loopBackAddr: &net.TCPAddr{IP: net.IPv6loopback, Port: 9999, Zone: lp.Name}, + eth0Addr: &net.TCPAddr{IP: ip, Port: 9999, Zone: dev}, + } + require.NoError(t, err) + err = Run(ts, "tcp6://[::]:9999", + WithTicker(true), + WithBindToDevice(dev)) + assert.NoError(t, err) + }) + t.Run("UDP", func(t *testing.T) { + ip, err := getInterfaceIP(dev, false) + assert.NoError(t, err) + ts := &testBindToDeviceServer[*net.UDPAddr]{ + tester: t, + data: data, + expectedPackets: 2, + network: "udp6", + loopBackAddr: &net.UDPAddr{IP: net.IPv6loopback, Port: 9999, Zone: lp.Name}, + eth0Addr: &net.UDPAddr{IP: ip, Port: 9999, Zone: dev}, + broadcastAddr: &net.UDPAddr{IP: net.IPv6linklocalallnodes, Port: 9999, Zone: dev}, + } + require.NoError(t, err) + err = Run(ts, "udp6://[::]:9999", + WithTicker(true), + WithBindToDevice(dev)) + assert.NoError(t, err) + }) + }) +} + /* func TestEngineAsyncWrite(t *testing.T) { t.Run("tcp", func(t *testing.T) { diff --git a/os_windows_test.go b/os_windows_test.go index c9e8fa9b0..99ec4eeb3 100644 --- a/os_windows_test.go +++ b/os_windows_test.go @@ -12,7 +12,7 @@ func SysClose(fd int) error { return syscall.CloseHandle(syscall.Handle(fd)) } -func NetDial(network, addr string) (net.Conn, error) { +func stdDial(network, addr string) (net.Conn, error) { if network == "unix" { laddr, _ := net.ResolveUnixAddr(network, unixAddr(addr)) raddr, _ := net.ResolveUnixAddr(network, addr)