├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── pr-check.yml ├── .gitignore ├── .golangci.yaml ├── .licenserc.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CREDITS ├── LICENSE ├── NOTICE ├── README.md ├── README_CN.md ├── _typos.toml ├── connection.go ├── connection_errors.go ├── connection_errors_test.go ├── connection_impl.go ├── connection_lock.go ├── connection_onevent.go ├── connection_reactor.go ├── connection_test.go ├── docs ├── guide │ ├── guide_cn.md │ └── guide_en.md └── reference │ ├── design_cn.md │ ├── design_en.md │ └── explain.md ├── eventloop.go ├── fd_operator.go ├── fd_operator_cache.go ├── fd_operator_cache_test.go ├── go.mod ├── go.sum ├── internal └── runner │ ├── runner.go │ └── runner_test.go ├── lint.sh ├── mux ├── mux_test.go ├── shard_queue.go └── shard_queue_test.go ├── net_dialer.go ├── net_dialer_test.go ├── net_io.go ├── net_listener.go ├── net_listener_test.go ├── net_netfd.go ├── net_netfd_conn.go ├── net_polldesc.go ├── net_polldesc_test.go ├── net_sock.go ├── net_tcpsock.go ├── net_unixsock.go ├── netpoll_config.go ├── netpoll_options.go ├── netpoll_server.go ├── netpoll_unix.go ├── netpoll_unix_test.go ├── netpoll_windows.go ├── nocopy.go ├── nocopy_linkbuffer.go ├── nocopy_linkbuffer_norace.go ├── nocopy_linkbuffer_race.go ├── nocopy_linkbuffer_test.go ├── nocopy_readwriter.go ├── nocopy_readwriter_test.go ├── poll.go ├── poll_default.go ├── poll_default_bsd.go ├── poll_default_bsd_norace.go ├── poll_default_bsd_race.go ├── poll_default_linux.go ├── poll_default_linux_norace.go ├── poll_default_linux_race.go ├── poll_default_linux_test.go ├── poll_loadbalance.go ├── poll_manager.go ├── poll_manager_test.go ├── poll_test.go ├── sys_epoll_linux.go ├── sys_epoll_linux_arm64.go ├── sys_epoll_linux_loong64.go ├── sys_exec.go ├── sys_exec_test.go ├── sys_keepalive_darwin.go ├── sys_keepalive_openbsd.go ├── sys_keepalive_unix.go ├── sys_sendmsg_bsd.go ├── sys_sendmsg_linux.go ├── sys_sockopt_bsd.go ├── sys_sockopt_linux.go ├── sys_zerocopy_bsd.go ├── sys_zerocopy_linux.go └── test_conns.sh /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # For more information, please refer to https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | 3 | * @cloudwego/netpoll-reviewers @cloudwego/netpoll-approvers @cloudwego/netpoll-maintainers 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: Push and Pull Request Check 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | compatibility-test: 7 | strategy: 8 | matrix: 9 | go: [ 1.18, 1.23 ] 10 | os: [ X64, ARM64 ] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version: ${{ matrix.go }} 18 | cache: false 19 | - name: Unit Test 20 | run: go test -timeout=2m -race ./... 21 | - name: Benchmark 22 | run: go test -bench=. -benchmem -run=none ./... -benchtime=100ms 23 | 24 | windows-test: 25 | runs-on: windows-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version: stable 32 | - name: Build Test 33 | run: go vet ./... 34 | 35 | compliant: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Check License Header 41 | uses: apache/skywalking-eyes/header@v0.4.0 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | - name: Check Spell 46 | uses: crate-ci/typos@v1.13.14 47 | 48 | golangci-lint: 49 | runs-on: [ Linux, X64 ] 50 | steps: 51 | - uses: actions/checkout@v4 52 | - name: Set up Go 53 | uses: actions/setup-go@v5 54 | with: 55 | go-version: stable 56 | # for self-hosted, the cache path is shared across projects 57 | # and it works well without the cache of github actions 58 | # Enable it if we're going to use Github only 59 | cache: false 60 | 61 | - name: Golangci Lint 62 | # https://golangci-lint.run/ 63 | uses: golangci/golangci-lint-action@v6 64 | with: 65 | version: latest 66 | only-new-issues: true 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea/ -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | # Options for analysis running. 2 | run: 3 | timeout: 3m 4 | 5 | linters: # https://golangci-lint.run/usage/linters/ 6 | disable-all: true 7 | enable: 8 | - gosimple 9 | - govet 10 | - ineffassign 11 | - staticcheck 12 | - unused 13 | - unconvert 14 | - goimports 15 | - gofumpt 16 | 17 | # Refer to https://golangci-lint.run/usage/linters 18 | linters-settings: 19 | gofumpt: 20 | # Choose whether to use the extra rules. 21 | # Default: false 22 | extra-rules: true 23 | goimports: 24 | # Put imports beginning with prefix after 3rd-party packages. 25 | # It's a comma-separated list of prefixes. 26 | local-prefixes: github.com/cloudwego/netpoll 27 | 28 | issues: 29 | exclude-use-default: true -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: CloudWeGo Authors 5 | 6 | paths: 7 | - '**/*.go' 8 | - '**/*.s' 9 | 10 | paths-ignore: 11 | - 'net_netfd.go' 12 | - 'net_sock.go' 13 | - 'net_tcpsock.go' 14 | - 'net_unixsock.go' 15 | - 'sys_sockopt_bsd.go' 16 | - 'sys_sockopt_linux.go' 17 | 18 | comment: on-failure -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | conduct@cloudwego.io. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Your First Pull Request 4 | We use github for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 5 | 6 | ## Without Semantic Versioning 7 | We keep the stable code in branch `main` like `golang.org/x`. Development base on branch `develop`. And we promise the **Forward Compatibility** by adding new package directory with suffix `v2/v3` when code has break changes. 8 | 9 | ## Branch Organization 10 | We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development) 11 | 12 | ## Bugs 13 | ### 1. How to Find Known Issues 14 | We are using [Github Issues](https://github.com/cloudwego/netpoll/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 15 | 16 | ### 2. Reporting New Issues 17 | Providing a reduced test code is a recommended way for reporting issues. Then can placed in: 18 | - Just in issues 19 | - [Golang Playground](https://play.golang.org/) 20 | 21 | ### 3. Security Bugs 22 | Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:conduct@cloudwego.io) 23 | 24 | ## How to Get in Touch 25 | - [Email](mailto:conduct@cloudwego.io) 26 | 27 | ## Submit a Pull Request 28 | Before you submit your Pull Request (PR) consider the following guidelines: 29 | 1. Search [GitHub](https://github.com/cloudwego/netpoll/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 30 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. 31 | 3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego/netpoll repo. 32 | 4. In your forked repository, make your changes in a new git branch: 33 | ``` 34 | git checkout -b my-fix-branch develop 35 | ``` 36 | 5. Create your patch, including appropriate test cases. 37 | 6. Follow our [Style Guides](#code-style-guides). 38 | 7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit). 39 | Adherence to these conventions is necessary because release notes are automatically generated from these messages. 40 | 8. Push your branch to GitHub: 41 | ``` 42 | git push origin my-fix-branch 43 | ``` 44 | 9. In GitHub, send a pull request to `netpoll:develop` 45 | 46 | ## Contribution Prerequisites 47 | - Our development environment keeps up with [Go Official](https://golang.org/project/). 48 | - You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint) 49 | - You are familiar with [Github](https://github.com) 50 | - Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool). 51 | 52 | ## Code Style Guides 53 | Also see [Pingcap General advice](https://pingcap.github.io/style-guide/general.html). 54 | 55 | Good resources: 56 | - [Effective Go](https://golang.org/doc/effective_go) 57 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 58 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) 59 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll/5babe710b6303acdbdcde68c5e24b63d6711c40c/CREDITS -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | CloudWeGO 2 | Copyright 2022 CloudWeGO authors. 3 | 4 | Go 5 | Copyright (c) 2009 The Go Authors. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudWeGo-Netpoll 2 | 3 | [中文](README_CN.md) 4 | 5 | [![Release](https://img.shields.io/github/v/release/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/releases) 6 | [![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/) 7 | [![License](https://img.shields.io/github/license/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/blob/main/LICENSE) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/netpoll)](https://goreportcard.com/report/github.com/cloudwego/netpoll) 9 | [![OpenIssue](https://img.shields.io/github/issues/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/issues) 10 | [![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/issues?q=is%3Aissue+is%3Aclosed) 11 | ![Stars](https://img.shields.io/github/stars/cloudwego/netpoll) 12 | ![Forks](https://img.shields.io/github/forks/cloudwego/netpoll) 13 | 14 | ## Introduction 15 | 16 | [Netpoll][Netpoll] is a high-performance non-blocking I/O networking framework, which 17 | focused on RPC scenarios, developed by [ByteDance][ByteDance]. 18 | 19 | RPC is usually heavy on processing logic and therefore cannot handle I/O serially. But Go's standard 20 | library [net][net] is designed for blocking I/O APIs, so that the RPC framework can 21 | only follow the One Conn One Goroutine design. It will waste a lot of cost for context switching, due to a large number 22 | of goroutines under high concurrency. Besides, [net.Conn][net.Conn] has 23 | no API to check Alive, so it is difficult to make an efficient connection pool for RPC framework, because there may be a 24 | large number of failed connections in the pool. 25 | 26 | On the other hand, the open source community currently lacks Go network libraries that focus on RPC scenarios. Similar 27 | repositories such as: [evio][evio], [gnet][gnet], etc., are all 28 | focus on scenarios like [Redis][Redis], [HAProxy][HAProxy]. 29 | 30 | But now, [Netpoll][Netpoll] was born and solved the above problems. It draws inspiration 31 | from the design of [evio][evio] and [netty][netty], has 32 | excellent [Performance](#performance), and is more suitable for microservice architecture. 33 | Also [Netpoll][Netpoll] provides a number of [Features](#features), and it is recommended 34 | to replace [net][net] in some RPC scenarios. 35 | 36 | We developed the RPC framework [Kitex][Kitex] and HTTP framework [Hertz][Hertz] 37 | based on [Netpoll][Netpoll], both with industry-leading performance. 38 | 39 | [Examples][netpoll-examples] show how to build RPC client and server 40 | using [Netpoll][Netpoll]. 41 | 42 | For more information, please refer to [Document](#document). 43 | 44 | ## Features 45 | 46 | * **Already** 47 | - [LinkBuffer][LinkBuffer] provides nocopy API for streaming reading and writing 48 | - [gopool][gopool] provides high-performance goroutine pool 49 | - [mcache][mcache] provides efficient memory reuse 50 | - `IsActive` supports checking whether the connection is alive 51 | - `Dialer` supports building clients 52 | - `EventLoop` supports building a server 53 | - TCP, Unix Domain Socket 54 | - Linux, macOS (operating system) 55 | 56 | * **Future** 57 | - [io_uring][io_uring] 58 | - Shared Memory IPC 59 | - TLS 60 | - UDP 61 | 62 | * **Unsupported** 63 | - Windows (operating system) 64 | 65 | ## Performance 66 | 67 | Benchmark should meet the requirements of industrial use. 68 | In the RPC scenario, concurrency and timeout are necessary support items. 69 | 70 | We provide the [netpoll-benchmark][netpoll-benchmark] project to track and compare 71 | the performance of [Netpoll][Netpoll] and other frameworks under different conditions for reference. 72 | 73 | More benchmarks reference [kitex-benchmark][kitex-benchmark] and [hertz-benchmark][hertz-benchmark]. 74 | 75 | ## Reference 76 | 77 | * [Official Website](https://www.cloudwego.io) 78 | * [Getting Started](docs/guide/guide_en.md) 79 | * [Design](docs/reference/design_en.md) 80 | * [Why DATA RACE](docs/reference/explain.md) 81 | 82 | [Netpoll]: https://github.com/cloudwego/netpoll 83 | [net]: https://github.com/golang/go/tree/master/src/net 84 | [net.Conn]: https://github.com/golang/go/blob/master/src/net/net.go 85 | [evio]: https://github.com/tidwall/evio 86 | [gnet]: https://github.com/panjf2000/gnet 87 | [netty]: https://github.com/netty/netty 88 | [Kitex]: https://github.com/cloudwego/kitex 89 | [Hertz]: https://github.com/cloudwego/hertz 90 | 91 | [netpoll-benchmark]: https://github.com/cloudwego/netpoll-benchmark 92 | [kitex-benchmark]: https://github.com/cloudwego/kitex-benchmark 93 | [hertz-benchmark]: https://github.com/cloudwego/hertz-benchmark 94 | [netpoll-examples]:https://github.com/cloudwego/netpoll-examples 95 | 96 | [ByteDance]: https://www.bytedance.com 97 | [Redis]: https://redis.io 98 | [HAProxy]: http://www.haproxy.org 99 | 100 | [LinkBuffer]: nocopy_linkbuffer.go 101 | [gopool]: https://github.com/bytedance/gopkg/tree/develop/util/gopool 102 | [mcache]: https://github.com/bytedance/gopkg/tree/develop/lang/mcache 103 | [io_uring]: https://github.com/axboe/liburing 104 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # CloudWeGo-Netpoll 2 | 3 | [English](README.md) 4 | 5 | [![Release](https://img.shields.io/github/v/release/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/releases) 6 | [![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/) 7 | [![License](https://img.shields.io/github/license/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/blob/main/LICENSE) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/cloudwego/netpoll)](https://goreportcard.com/report/github.com/cloudwego/netpoll) 9 | [![OpenIssue](https://img.shields.io/github/issues/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/issues) 10 | [![ClosedIssue](https://img.shields.io/github/issues-closed/cloudwego/netpoll)](https://github.com/cloudwego/netpoll/issues?q=is%3Aissue+is%3Aclosed) 11 | ![Stars](https://img.shields.io/github/stars/cloudwego/netpoll) 12 | ![Forks](https://img.shields.io/github/forks/cloudwego/netpoll) 13 | 14 | ## 简介 15 | 16 | [Netpoll][Netpoll] 是由 [字节跳动][ByteDance] 开发的高性能 NIO(Non-blocking I/O) 17 | 网络库,专注于 RPC 场景。 18 | 19 | RPC 通常有较重的处理逻辑,因此无法串行处理 I/O。而 Go 的标准库 [net][net] 设计了 BIO(Blocking I/O) 模式的 20 | API,使得 RPC 框架设计上只能为每个连接都分配一个 goroutine。 这在高并发下,会产生大量的 21 | goroutine,大幅增加调度开销。此外,[net.Conn][net.Conn] 没有提供检查连接活性的 API,因此 RPC 22 | 框架很难设计出高效的连接池,池中的失效连接无法及时清理。 23 | 24 | 另一方面,开源社区目前缺少专注于 RPC 方案的 Go 网络库。类似的项目如:[evio][evio] 25 | , [gnet][gnet] 等,均面向 [Redis][Redis], [HAProxy][HAProxy] 这样的场景。 26 | 27 | 因此 [Netpoll][Netpoll] 应运而生,它借鉴了 [evio][evio] 28 | 和 [netty][netty] 的优秀设计,具有出色的 [性能](#性能),更适用于微服务架构。 29 | 同时,[Netpoll][Netpoll] 还提供了一些 [特性](#特性),推荐在 RPC 设计中替代 30 | [net][net] 。 31 | 32 | 基于 [Netpoll][Netpoll] 开发的 RPC 框架 [Kitex][Kitex] 和 HTTP 框架 [Hertz][Hertz],性能均业界领先。 33 | 34 | [范例][netpoll-examples] 展示了如何使用 [Netpoll][Netpoll] 35 | 构建 RPC Client 和 Server。 36 | 37 | 更多信息请参阅 [文档](#文档)。 38 | 39 | ## 特性 40 | 41 | * **已经支持** 42 | - [LinkBuffer][LinkBuffer] 提供可以流式读写的 nocopy API 43 | - [gopool][gopool] 提供高性能的 goroutine 池 44 | - [mcache][mcache] 提供高效的内存复用 45 | - `IsActive` 支持检查连接是否存活 46 | - `Dialer` 支持构建 client 47 | - `EventLoop` 支持构建 server 48 | - 支持 TCP,Unix Domain Socket 49 | - 支持 Linux,macOS(操作系统) 50 | 51 | * **即将开源** 52 | - [io_uring][io_uring] 53 | - Shared Memory IPC 54 | - 支持 TLS 55 | - 支持 UDP 56 | 57 | * **不被支持** 58 | - Windows(操作系统) 59 | 60 | ## 性能 61 | 62 | 性能测试应满足工业级使用要求,在 RPC 场景下,并发请求、等待超时是必要的支持项。 63 | 64 | 我们提供了 [netpoll-benchmark][netpoll-benchmark] 项目用来长期追踪和比较 [Netpoll][Netpoll] 与其他框架在不同情况下的性能数据以供参考。 65 | 66 | 更多测试参考 [kitex-benchmark][kitex-benchmark] 和 [hertz-benchmark][hertz-benchmark] 67 | 68 | ## 参考 69 | 70 | * [官方网站](https://www.cloudwego.io) 71 | * [使用文档](docs/guide/guide_cn.md) 72 | * [设计文档](docs/reference/design_cn.md) 73 | * [DATA RACE 说明](docs/reference/explain.md) 74 | 75 | [Netpoll]: https://github.com/cloudwego/netpoll 76 | [net]: https://github.com/golang/go/tree/master/src/net 77 | [net.Conn]: https://github.com/golang/go/blob/master/src/net/net.go 78 | [evio]: https://github.com/tidwall/evio 79 | [gnet]: https://github.com/panjf2000/gnet 80 | [netty]: https://github.com/netty/netty 81 | [Kitex]: https://github.com/cloudwego/kitex 82 | [Hertz]: https://github.com/cloudwego/hertz 83 | 84 | [netpoll-benchmark]: https://github.com/cloudwego/netpoll-benchmark 85 | [kitex-benchmark]: https://github.com/cloudwego/kitex-benchmark 86 | [hertz-benchmark]: https://github.com/cloudwego/hertz-benchmark 87 | [netpoll-examples]:https://github.com/cloudwego/netpoll-examples 88 | 89 | [ByteDance]: https://www.bytedance.com 90 | [Redis]: https://redis.io 91 | [HAProxy]: http://www.haproxy.org 92 | 93 | [LinkBuffer]: nocopy_linkbuffer.go 94 | [gopool]: https://github.com/bytedance/gopkg/tree/develop/util/gopool 95 | [mcache]: https://github.com/bytedance/gopkg/tree/develop/lang/mcache 96 | [io_uring]: https://github.com/axboe/liburing 97 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | # Typo check: https://github.com/crate-ci/typos 2 | 3 | [files] 4 | extend-exclude = ["go.mod", "go.sum"] 5 | 6 | [default.extend-identifiers] 7 | # *sigh* this just isn't worth the cost of fixing 8 | nd = "nd" 9 | paniced = "paniced" 10 | write_datas = "write_datas" 11 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "net" 19 | "time" 20 | ) 21 | 22 | // CloseCallback will be called after the connection is closed. 23 | // Return: error is unused which will be ignored directly. 24 | type CloseCallback func(connection Connection) error 25 | 26 | // Connection supports reading and writing simultaneously, 27 | // but does not support simultaneous reading or writing by multiple goroutines. 28 | // It maintains its own input/output buffer, and provides nocopy API for reading and writing. 29 | type Connection interface { 30 | // Connection extends net.Conn, just for interface compatibility. 31 | // It's not recommended to use net.Conn API except for io.Closer. 32 | net.Conn 33 | 34 | // The recommended API for nocopy reading and writing. 35 | // Reader will return nocopy buffer data, or error after timeout which set by SetReadTimeout. 36 | Reader() Reader 37 | // Writer will write data to the connection by NIO mode, 38 | // so it will return an error only when the connection isn't Active. 39 | Writer() Writer 40 | 41 | // IsActive checks whether the connection is active or not. 42 | IsActive() bool 43 | 44 | // SetReadTimeout sets the timeout for future Read calls wait. 45 | // A zero value for timeout means Reader will not timeout. 46 | SetReadTimeout(timeout time.Duration) error 47 | 48 | // SetWriteTimeout sets the timeout for future Write calls wait. 49 | // A zero value for timeout means Writer will not timeout. 50 | SetWriteTimeout(timeout time.Duration) error 51 | 52 | // SetIdleTimeout sets the idle timeout of connections. 53 | // Idle connections that exceed the set timeout are no longer guaranteed to be active, 54 | // but can be checked by calling IsActive. 55 | SetIdleTimeout(timeout time.Duration) error 56 | 57 | // SetOnRequest can set or replace the OnRequest method for a connection, but can't be set to nil. 58 | // Although SetOnRequest avoids data race, it should still be used before transmitting data. 59 | // Replacing OnRequest while processing data may cause unexpected behavior and results. 60 | // Generally, the server side should uniformly set the OnRequest method for each connection via NewEventLoop, 61 | // which is set when the connection is initialized. 62 | // On the client side, if necessary, make sure that OnRequest is set before sending data. 63 | SetOnRequest(on OnRequest) error 64 | 65 | // AddCloseCallback can add hangup callback for a connection, which will be called when connection closing. 66 | // This is very useful for cleaning up idle connections. For instance, you can use callbacks to clean up 67 | // the local resources, which bound to the idle connection, when hangup by the peer. No need another goroutine 68 | // to polling check connection status. 69 | AddCloseCallback(callback CloseCallback) error 70 | } 71 | 72 | // Conn extends net.Conn, but supports getting the conn's fd. 73 | type Conn interface { 74 | net.Conn 75 | 76 | // Fd return conn's fd, used by poll 77 | Fd() (fd int) 78 | } 79 | 80 | // Listener extends net.Listener, but supports getting the listener's fd. 81 | type Listener interface { 82 | net.Listener 83 | 84 | // Fd return listener's fd, used by poll. 85 | Fd() (fd int) 86 | } 87 | 88 | // Dialer extends net.Dialer's API, just for interface compatibility. 89 | // DialConnection is recommended, but of course all functions are practically the same. 90 | // The returned net.Conn can be directly asserted as Connection if error is nil. 91 | type Dialer interface { 92 | DialConnection(network, address string, timeout time.Duration) (connection Connection, err error) 93 | 94 | DialTimeout(network, address string, timeout time.Duration) (conn net.Conn, err error) 95 | } 96 | -------------------------------------------------------------------------------- /connection_errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "syscall" 21 | ) 22 | 23 | // extends syscall.Errno, the range is set to 0x100-0x1FF 24 | const ( 25 | // The connection closed when in use. 26 | ErrConnClosed = syscall.Errno(0x101) 27 | // Read I/O buffer timeout, calling by Connection.Reader 28 | ErrReadTimeout = syscall.Errno(0x102) 29 | // Dial timeout 30 | ErrDialTimeout = syscall.Errno(0x103) 31 | // Calling dialer without timeout. 32 | ErrDialNoDeadline = syscall.Errno(0x104) // TODO: no-deadline support in future 33 | // The calling function not support. 34 | ErrUnsupported = syscall.Errno(0x105) 35 | // Same as io.EOF 36 | ErrEOF = syscall.Errno(0x106) 37 | // Write I/O buffer timeout, calling by Connection.Writer 38 | ErrWriteTimeout = syscall.Errno(0x107) 39 | // Concurrent connection access error 40 | ErrConcurrentAccess = syscall.Errno(0x108) 41 | ) 42 | 43 | const ErrnoMask = 0xFF 44 | 45 | // wrap Errno, implement xerrors.Wrapper 46 | func Exception(err error, suffix string) error { 47 | no, ok := err.(syscall.Errno) 48 | if !ok { 49 | if suffix == "" { 50 | return err 51 | } 52 | return fmt.Errorf("%s %s", err.Error(), suffix) 53 | } 54 | return &exception{no: no, suffix: suffix} 55 | } 56 | 57 | var _ net.Error = (*exception)(nil) 58 | 59 | type exception struct { 60 | no syscall.Errno 61 | suffix string 62 | } 63 | 64 | func (e *exception) Error() string { 65 | var s string 66 | if int(e.no)&0x100 != 0 { 67 | s = errnos[int(e.no)&ErrnoMask] 68 | } 69 | if s == "" { 70 | s = e.no.Error() 71 | } 72 | if e.suffix != "" { 73 | s += " " + e.suffix 74 | } 75 | return s 76 | } 77 | 78 | func (e *exception) Is(target error) bool { 79 | if e == target { 80 | return true 81 | } 82 | if e.no == target { 83 | return true 84 | } 85 | // TODO: ErrConnClosed contains ErrEOF 86 | if e.no == ErrEOF && target == ErrConnClosed { 87 | return true 88 | } 89 | return e.no.Is(target) 90 | } 91 | 92 | func (e *exception) Unwrap() error { 93 | return e.no 94 | } 95 | 96 | func (e *exception) Timeout() bool { 97 | switch e.no { 98 | case ErrDialTimeout, ErrReadTimeout, ErrWriteTimeout: 99 | return true 100 | } 101 | return e.no.Timeout() 102 | } 103 | 104 | func (e *exception) Temporary() bool { 105 | return e.no.Temporary() 106 | } 107 | 108 | // Errors defined in netpoll 109 | var errnos = [...]string{ 110 | ErrnoMask & ErrConnClosed: "connection has been closed", 111 | ErrnoMask & ErrReadTimeout: "connection read timeout", 112 | ErrnoMask & ErrDialTimeout: "dial wait timeout", 113 | ErrnoMask & ErrDialNoDeadline: "dial no deadline", 114 | ErrnoMask & ErrUnsupported: "netpoll does not support", 115 | ErrnoMask & ErrEOF: "EOF", 116 | ErrnoMask & ErrWriteTimeout: "connection write timeout", 117 | ErrnoMask & ErrConcurrentAccess: "concurrent connection access", 118 | } 119 | -------------------------------------------------------------------------------- /connection_errors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "errors" 22 | "syscall" 23 | "testing" 24 | ) 25 | 26 | func TestErrno(t *testing.T) { 27 | var err1 error = Exception(ErrConnClosed, "when next") 28 | MustTrue(t, errors.Is(err1, ErrConnClosed)) 29 | Equal(t, err1.Error(), "connection has been closed when next") 30 | t.Logf("error1=%s", err1) 31 | 32 | var err2 error = Exception(syscall.EPIPE, "when flush") 33 | MustTrue(t, errors.Is(err2, syscall.EPIPE)) 34 | Equal(t, err2.Error(), "broken pipe when flush") 35 | t.Logf("error2=%s", err2) 36 | } 37 | -------------------------------------------------------------------------------- /connection_lock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | ) 21 | 22 | type who = int32 23 | 24 | const ( 25 | none who = iota 26 | user 27 | poller 28 | ) 29 | 30 | type key int32 31 | 32 | /* State Diagram 33 | +--------------+ +--------------+ 34 | | processing |-------->| flushing | 35 | +-------+------+ +-------+------+ 36 | | 37 | | +--------------+ 38 | +--------------->| closing | 39 | +--------------+ 40 | 41 | - "processing" locks onRequest handler, and doesn't exist in dialer. 42 | - "flushing" locks outputBuffer 43 | - "closing" should wait for flushing finished and call the closeCallback after that. 44 | */ 45 | 46 | const ( 47 | closing key = iota 48 | connecting 49 | processing 50 | flushing 51 | // total must be at the bottom. 52 | total 53 | ) 54 | 55 | type locker struct { 56 | // keychain use for lock/unlock/stop operation by who. 57 | // 0 means unlock, 1 means locked, 2 means stop. 58 | keychain [total]int32 59 | } 60 | 61 | func (l *locker) closeBy(w who) (success bool) { 62 | return atomic.CompareAndSwapInt32(&l.keychain[closing], 0, int32(w)) 63 | } 64 | 65 | func (l *locker) isCloseBy(w who) (yes bool) { 66 | return atomic.LoadInt32(&l.keychain[closing]) == int32(w) 67 | } 68 | 69 | func (l *locker) status(k key) int32 { 70 | return atomic.LoadInt32(&l.keychain[k]) 71 | } 72 | 73 | func (l *locker) force(k key, v int32) { 74 | atomic.StoreInt32(&l.keychain[k], v) 75 | } 76 | 77 | func (l *locker) lock(k key) (success bool) { 78 | return atomic.CompareAndSwapInt32(&l.keychain[k], 0, 1) 79 | } 80 | 81 | func (l *locker) unlock(k key) { 82 | atomic.StoreInt32(&l.keychain[k], 0) 83 | } 84 | 85 | func (l *locker) stop(k key) { 86 | for !atomic.CompareAndSwapInt32(&l.keychain[k], 0, 2) && atomic.LoadInt32(&l.keychain[k]) != 2 { 87 | runtime.Gosched() 88 | } 89 | } 90 | 91 | func (l *locker) isUnlock(k key) bool { 92 | return atomic.LoadInt32(&l.keychain[k]) == 0 93 | } 94 | -------------------------------------------------------------------------------- /connection_reactor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "sync/atomic" 22 | ) 23 | 24 | // ------------------------------------------ implement FDOperator ------------------------------------------ 25 | 26 | // onHup means close by poller. 27 | func (c *connection) onHup(p Poll) error { 28 | if !c.closeBy(poller) { 29 | return nil 30 | } 31 | c.triggerRead(Exception(ErrEOF, "peer close")) 32 | c.triggerWrite(Exception(ErrConnClosed, "peer close")) 33 | 34 | // call Disconnect callback first 35 | c.onDisconnect() 36 | 37 | // It depends on closing by user if OnConnect and OnRequest is nil, otherwise it needs to be released actively. 38 | // It can be confirmed that the OnRequest goroutine has been exited before closeCallback executing, 39 | // and it is safe to close the buffer at this time. 40 | onConnect := c.onConnectCallback.Load() 41 | onRequest := c.onRequestCallback.Load() 42 | needCloseByUser := onConnect == nil && onRequest == nil 43 | if !needCloseByUser { 44 | // already PollDetach when call OnHup 45 | c.closeCallback(true, false) 46 | } 47 | return nil 48 | } 49 | 50 | // onClose means close by user. 51 | func (c *connection) onClose() error { 52 | // user code close the connection 53 | if c.closeBy(user) { 54 | c.triggerRead(Exception(ErrConnClosed, "self close")) 55 | c.triggerWrite(Exception(ErrConnClosed, "self close")) 56 | // Detach from poller when processing finished, otherwise it will cause race 57 | c.closeCallback(true, true) 58 | return nil 59 | } 60 | 61 | // closed by poller 62 | // still need to change closing status to `user` since OnProcess should not be processed again 63 | c.force(closing, user) 64 | 65 | // user code should actively close the connection to recycle resources. 66 | // poller already detached operator 67 | return c.closeCallback(true, false) 68 | } 69 | 70 | // closeBuffer recycle input & output LinkBuffer. 71 | func (c *connection) closeBuffer() { 72 | onConnect, _ := c.onConnectCallback.Load().(OnConnect) 73 | onRequest, _ := c.onRequestCallback.Load().(OnRequest) 74 | // if client close the connection, we cannot ensure that the poller is not process the buffer, 75 | // so we need to check the buffer length, and if it's an "unclean" close operation, let's give up to reuse the buffer 76 | if c.inputBuffer.Len() == 0 || onConnect != nil || onRequest != nil { 77 | c.inputBuffer.Close() 78 | } 79 | if c.outputBuffer.Len() == 0 || onConnect != nil || onRequest != nil { 80 | c.outputBuffer.Close() 81 | barrierPool.Put(c.outputBarrier) 82 | } 83 | } 84 | 85 | // inputs implements FDOperator. 86 | func (c *connection) inputs(vs [][]byte) (rs [][]byte) { 87 | vs[0] = c.inputBuffer.book(c.bookSize, c.maxSize) 88 | return vs[:1] 89 | } 90 | 91 | // inputAck implements FDOperator. 92 | func (c *connection) inputAck(n int) (err error) { 93 | if n <= 0 { 94 | c.inputBuffer.bookAck(0) 95 | return nil 96 | } 97 | 98 | // Auto size bookSize. 99 | if n == c.bookSize && c.bookSize < mallocMax { 100 | c.bookSize <<= 1 101 | } 102 | 103 | length, _ := c.inputBuffer.bookAck(n) 104 | if c.maxSize < length { 105 | c.maxSize = length 106 | } 107 | if c.maxSize > mallocMax { 108 | c.maxSize = mallocMax 109 | } 110 | 111 | needTrigger := true 112 | if length == n { // first start onRequest 113 | needTrigger = c.onRequest() 114 | } 115 | if needTrigger && length >= int(atomic.LoadInt64(&c.waitReadSize)) { 116 | c.triggerRead(nil) 117 | } 118 | return nil 119 | } 120 | 121 | // outputs implements FDOperator. 122 | func (c *connection) outputs(vs [][]byte) (rs [][]byte, supportZeroCopy bool) { 123 | if c.outputBuffer.IsEmpty() { 124 | c.rw2r() 125 | return rs, c.supportZeroCopy 126 | } 127 | rs = c.outputBuffer.GetBytes(vs) 128 | return rs, c.supportZeroCopy 129 | } 130 | 131 | // outputAck implements FDOperator. 132 | func (c *connection) outputAck(n int) (err error) { 133 | if n > 0 { 134 | c.outputBuffer.Skip(n) 135 | c.outputBuffer.Release() 136 | } 137 | if c.outputBuffer.IsEmpty() { 138 | c.rw2r() 139 | } 140 | return nil 141 | } 142 | 143 | // rw2r removed the monitoring of write events. 144 | func (c *connection) rw2r() { 145 | c.operator.Control(PollRW2R) 146 | c.triggerWrite(nil) 147 | } 148 | -------------------------------------------------------------------------------- /docs/reference/design_cn.md: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /docs/reference/design_en.md: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /docs/reference/explain.md: -------------------------------------------------------------------------------- 1 | # DATA RACE EXPLAIN 2 | `Netpoll` declare different files by `//+build !race` and `//+build race` to avoid `DATA RACE` detection in some code. 3 | 4 | The reason is that the `epoll` uses `unsafe.Pointer` to access the struct pointer, in order 5 | to improve performance. This operation is beyond the detection range of the `race detector`, 6 | so it is mistaken for data race, but not code bug actually. 7 | -------------------------------------------------------------------------------- /eventloop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "context" 19 | "net" 20 | ) 21 | 22 | // A EventLoop is a network server. 23 | type EventLoop interface { 24 | // Serve registers a listener and runs blockingly to provide services, including listening to ports, 25 | // accepting connections and processing trans data. When an exception occurs or Shutdown is invoked, 26 | // Serve will return an error which describes the specific reason. 27 | Serve(ln net.Listener) error 28 | 29 | // Shutdown is used to graceful exit. 30 | // It will close all idle connections on the server, but will not change the underlying pollers. 31 | // 32 | // Argument: ctx set the waiting deadline, after which an error will be returned, 33 | // but will not force the closing of connections in progress. 34 | Shutdown(ctx context.Context) error 35 | } 36 | 37 | /* The Connection Callback Sequence Diagram 38 | | Connection State | Callback Function | Notes 39 | | Connected but not initialized | OnPrepare | Conn is not registered into poller 40 | | Connected and initialized | OnConnect | Conn is ready for read or write 41 | | Read first byte | OnRequest | Conn is ready for read or write 42 | | Peer closed but conn is active | OnDisconnect | Conn access will race with OnRequest function 43 | | Self closed and conn is closed | CloseCallback | Conn is destroyed 44 | 45 | Execution Order: 46 | OnPrepare => OnConnect => OnRequest => CloseCallback 47 | OnDisconnect 48 | Note: only OnRequest and OnDisconnect will be executed in parallel 49 | */ 50 | 51 | // OnPrepare is used to inject custom preparation at connection initialization, 52 | // which is optional but important in some scenarios. For example, a qps limiter 53 | // can be set by closing overloaded connections directly in OnPrepare. 54 | // 55 | // Return: 56 | // context will become the argument of OnRequest. 57 | // Usually, custom resources can be initialized in OnPrepare and used in OnRequest. 58 | // 59 | // PLEASE NOTE: 60 | // OnPrepare is executed without any data in the connection, 61 | // so Reader() or Writer() cannot be used here, but may be supported in the future. 62 | type OnPrepare func(connection Connection) context.Context 63 | 64 | // OnConnect is called once connection created. 65 | // It supports read/write/close connection, and could return a ctx which will be passed to OnRequest. 66 | // OnConnect will not block the poller since it's executed asynchronously. 67 | // Only after OnConnect finished the OnRequest could be executed. 68 | // 69 | // An example usage in TCP Proxy scenario: 70 | // 71 | // func onConnect(ctx context.Context, upstream netpoll.Connection) context.Context { 72 | // downstream, _ := netpoll.DialConnection("tcp", downstreamAddr, time.Second) 73 | // return context.WithValue(ctx, downstreamKey, downstream) 74 | // } 75 | // 76 | // func onRequest(ctx context.Context, upstream netpoll.Connection) error { 77 | // downstream := ctx.Value(downstreamKey).(netpoll.Connection) 78 | // } 79 | type OnConnect func(ctx context.Context, connection Connection) context.Context 80 | 81 | // OnDisconnect is called once connection is going to be closed. 82 | // OnDisconnect must return as quick as possible because it will block poller. 83 | // OnDisconnect is different from CloseCallback, you could check with "The Connection Callback Sequence Diagram" section. 84 | type OnDisconnect func(ctx context.Context, connection Connection) 85 | 86 | // OnRequest defines the function for handling connection. When data is sent from the connection peer, 87 | // netpoll actively reads the data in LT mode and places it in the connection's input buffer. 88 | // Generally, OnRequest starts handling the data in the following way: 89 | // 90 | // func OnRequest(ctx context, connection Connection) error { 91 | // input := connection.Reader().Next(n) 92 | // handling input data... 93 | // send, _ := connection.Writer().Malloc(l) 94 | // copy(send, output) 95 | // connection.Flush() 96 | // return nil 97 | // } 98 | // 99 | // OnRequest will run in a separate goroutine and 100 | // it is guaranteed that there is one and only one OnRequest running at the same time. 101 | // The underlying logic is similar to: 102 | // 103 | // go func() { 104 | // for !connection.Reader().IsEmpty() { 105 | // OnRequest(ctx, connection) 106 | // } 107 | // }() 108 | // 109 | // PLEASE NOTE: 110 | // OnRequest must either eventually read all the input data or actively Close the connection, 111 | // otherwise the goroutine will fall into a dead loop. 112 | // 113 | // Return: error is unused which will be ignored directly. 114 | type OnRequest func(ctx context.Context, connection Connection) error 115 | -------------------------------------------------------------------------------- /fd_operator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | ) 21 | 22 | // FDOperator is a collection of operations on file descriptors. 23 | type FDOperator struct { 24 | // FD is file descriptor, poll will bind when register. 25 | FD int 26 | 27 | // The FDOperator provides three operations of reading, writing, and hanging. 28 | // The poll actively fire the FDOperator when fd changes, no check the return value of FDOperator. 29 | OnRead func(p Poll) error 30 | OnWrite func(p Poll) error 31 | OnHup func(p Poll) error 32 | 33 | // The following is the required fn, which must exist when used, or directly panic. 34 | // Fns are only called by the poll when handles connection events. 35 | Inputs func(vs [][]byte) (rs [][]byte) 36 | InputAck func(n int) (err error) 37 | 38 | // Outputs will locked if len(rs) > 0, which need unlocked by OutputAck. 39 | Outputs func(vs [][]byte) (rs [][]byte, supportZeroCopy bool) 40 | OutputAck func(n int) (err error) 41 | 42 | // poll is the registered location of the file descriptor. 43 | poll Poll 44 | 45 | // protect only detach once 46 | detached int32 47 | 48 | // private, used by operatorCache 49 | next *FDOperator 50 | state int32 // CAS: 0(unused) 1(inuse) 2(do-done) 51 | index int32 // index in operatorCache 52 | } 53 | 54 | func (op *FDOperator) Control(event PollEvent) error { 55 | if event == PollDetach && atomic.AddInt32(&op.detached, 1) > 1 { 56 | return nil 57 | } 58 | return op.poll.Control(op, event) 59 | } 60 | 61 | func (op *FDOperator) Free() { 62 | op.poll.Free(op) 63 | } 64 | 65 | func (op *FDOperator) do() (can bool) { 66 | return atomic.CompareAndSwapInt32(&op.state, 1, 2) 67 | } 68 | 69 | func (op *FDOperator) done() { 70 | atomic.StoreInt32(&op.state, 1) 71 | } 72 | 73 | func (op *FDOperator) inuse() { 74 | for !atomic.CompareAndSwapInt32(&op.state, 0, 1) { 75 | if atomic.LoadInt32(&op.state) == 1 { 76 | return 77 | } 78 | runtime.Gosched() 79 | } 80 | } 81 | 82 | func (op *FDOperator) unused() { 83 | for !atomic.CompareAndSwapInt32(&op.state, 1, 0) { 84 | if atomic.LoadInt32(&op.state) == 0 { 85 | return 86 | } 87 | runtime.Gosched() 88 | } 89 | } 90 | 91 | func (op *FDOperator) isUnused() bool { 92 | return atomic.LoadInt32(&op.state) == 0 93 | } 94 | 95 | func (op *FDOperator) reset() { 96 | op.FD = 0 97 | op.OnRead, op.OnWrite, op.OnHup = nil, nil, nil 98 | op.Inputs, op.InputAck = nil, nil 99 | op.Outputs, op.OutputAck = nil, nil 100 | op.poll = nil 101 | op.detached = 0 102 | } 103 | -------------------------------------------------------------------------------- /fd_operator_cache.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | "unsafe" 21 | ) 22 | 23 | func newOperatorCache() *operatorCache { 24 | return &operatorCache{ 25 | cache: make([]*FDOperator, 0, 1024), 26 | freelist: make([]int32, 0, 1024), 27 | } 28 | } 29 | 30 | type operatorCache struct { 31 | first *FDOperator 32 | cache []*FDOperator 33 | locked int32 34 | // freelist store the freeable operator 35 | // to reduce GC pressure, we only store op index here 36 | freelocked int32 37 | freelist []int32 38 | } 39 | 40 | func (c *operatorCache) alloc() *FDOperator { 41 | lock(&c.locked) 42 | if c.first == nil { 43 | const opSize = unsafe.Sizeof(FDOperator{}) 44 | n := block4k / opSize 45 | if n == 0 { 46 | n = 1 47 | } 48 | index := int32(len(c.cache)) 49 | for i := uintptr(0); i < n; i++ { 50 | pd := &FDOperator{index: index} 51 | c.cache = append(c.cache, pd) 52 | pd.next = c.first 53 | c.first = pd 54 | index++ 55 | } 56 | } 57 | op := c.first 58 | c.first = op.next 59 | unlock(&c.locked) 60 | return op 61 | } 62 | 63 | // freeable mark the operator that could be freed 64 | // only poller could do the real free action 65 | func (c *operatorCache) freeable(op *FDOperator) { 66 | // reset all state 67 | op.unused() 68 | op.reset() 69 | lock(&c.freelocked) 70 | c.freelist = append(c.freelist, op.index) 71 | unlock(&c.freelocked) 72 | } 73 | 74 | func (c *operatorCache) free() { 75 | lock(&c.freelocked) 76 | defer unlock(&c.freelocked) 77 | if len(c.freelist) == 0 { 78 | return 79 | } 80 | 81 | lock(&c.locked) 82 | for _, idx := range c.freelist { 83 | op := c.cache[idx] 84 | op.next = c.first 85 | c.first = op 86 | } 87 | c.freelist = c.freelist[:0] 88 | unlock(&c.locked) 89 | } 90 | 91 | func lock(locked *int32) { 92 | for !atomic.CompareAndSwapInt32(locked, 0, 1) { 93 | runtime.Gosched() 94 | } 95 | } 96 | 97 | func unlock(locked *int32) { 98 | atomic.StoreInt32(locked, 0) 99 | } 100 | -------------------------------------------------------------------------------- /fd_operator_cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "runtime" 22 | "testing" 23 | ) 24 | 25 | // go test -v -gcflags=-d=checkptr -run=TestPersistFDOperator 26 | func TestPersistFDOperator(t *testing.T) { 27 | opcache := newOperatorCache() 28 | // init 29 | size := 2048 30 | ops := make([]*FDOperator, size) 31 | for i := 0; i < size; i++ { 32 | op := opcache.alloc() 33 | op.FD = i 34 | ops[i] = op 35 | } 36 | Equal(t, len(opcache.freelist), 0) 37 | // gc 38 | for i := 0; i < 4; i++ { 39 | runtime.GC() 40 | } 41 | // check alloc 42 | for i := range ops { 43 | Equal(t, ops[i].FD, i) 44 | opcache.freeable(ops[i]) 45 | Equal(t, len(opcache.freelist), i+1) 46 | } 47 | Equal(t, len(opcache.freelist), size) 48 | opcache.free() 49 | Equal(t, len(opcache.freelist), 0) 50 | Assert(t, len(opcache.cache) >= size) 51 | } 52 | 53 | func BenchmarkPersistFDOperator1(b *testing.B) { 54 | b.ReportAllocs() 55 | b.ResetTimer() 56 | opcache := newOperatorCache() 57 | for i := 0; i < b.N; i++ { 58 | op := opcache.alloc() 59 | opcache.freeable(op) 60 | opcache.free() 61 | } 62 | } 63 | 64 | func BenchmarkPersistFDOperator2(b *testing.B) { 65 | // benchmark 66 | b.ReportAllocs() 67 | b.SetParallelism(128) 68 | b.ResetTimer() 69 | opcache := newOperatorCache() 70 | b.RunParallel(func(pb *testing.PB) { 71 | for pb.Next() { 72 | op := opcache.alloc() 73 | opcache.freeable(op) 74 | opcache.free() 75 | } 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudwego/netpoll 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/bytedance/gopkg v0.1.1 7 | github.com/cloudwego/gopkg v0.1.4 8 | golang.org/x/sys v0.19.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE= 2 | github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= 3 | github.com/cloudwego/gopkg v0.1.4 h1:EoQiCG4sTonTPHxOGE0VlQs+sQR+Hsi2uN0qqwu8O50= 4 | github.com/cloudwego/gopkg v0.1.4/go.mod h1:FQuXsRWRsSqJLsMVd5SYzp8/Z1y5gXKnVvRrWUOsCMI= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 8 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 10 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 13 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 18 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 19 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 20 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 22 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 23 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 24 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 25 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 26 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 27 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 28 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 29 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 30 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 31 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 32 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 33 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 34 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 35 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 36 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 37 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 38 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 39 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 51 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 52 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 53 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 54 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 55 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 56 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 57 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 58 | golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= 59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 60 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 61 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 62 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 63 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 64 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 67 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 68 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 69 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 71 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 72 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 73 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 74 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 75 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 76 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "context" 21 | "os" 22 | "strconv" 23 | 24 | bgopool "github.com/bytedance/gopkg/util/gopool" 25 | cgopool "github.com/cloudwego/gopkg/concurrency/gopool" 26 | ) 27 | 28 | // RunTask runs the `f` in background, and `ctx` is optional. 29 | // `ctx` is used to pass to underlying implementation 30 | var RunTask func(ctx context.Context, f func()) 31 | 32 | func goRunTask(ctx context.Context, f func()) { 33 | go f() 34 | } 35 | 36 | func init() { 37 | // netpoll uses github.com/bytedance/gopkg/util/gopool by default 38 | // if the env is set, change it to cloudwego/gopkg 39 | // for most users, using the 'go' keyword directly is more suitable. 40 | if yes, _ := strconv.ParseBool(os.Getenv("USE_CLOUDWEGO_GOPOOL")); yes { 41 | RunTask = cgopool.CtxGo 42 | } else { 43 | RunTask = bgopool.CtxGo 44 | } 45 | } 46 | 47 | // UseGoRunTask updates RunTask with goRunTask which creates 48 | // a new goroutine for the given func, basically `go f()` 49 | func UseGoRunTask() { 50 | RunTask = goRunTask 51 | } 52 | -------------------------------------------------------------------------------- /internal/runner/runner_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "context" 21 | "sync" 22 | "testing" 23 | ) 24 | 25 | func TestRunTask(t *testing.T) { 26 | var wg sync.WaitGroup 27 | wg.Add(2) 28 | ctx := context.Background() 29 | RunTask(ctx, func() { 30 | wg.Done() 31 | }) 32 | UseGoRunTask() 33 | RunTask(ctx, func() { 34 | wg.Done() 35 | }) 36 | wg.Wait() 37 | } 38 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | golangci-lint run 4 | -------------------------------------------------------------------------------- /mux/mux_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package mux 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | func MustNil(t *testing.T, val interface{}) { 25 | t.Helper() 26 | Assert(t, val == nil, val) 27 | if val != nil { 28 | t.Fatal("assertion nil failed, val=", val) 29 | } 30 | } 31 | 32 | func MustTrue(t *testing.T, cond bool) { 33 | t.Helper() 34 | if !cond { 35 | t.Fatal("assertion true failed.") 36 | } 37 | } 38 | 39 | func Equal(t *testing.T, got, expect interface{}) { 40 | t.Helper() 41 | if got != expect { 42 | t.Fatalf("assertion equal failed, got=[%v], expect=[%v]", got, expect) 43 | } 44 | } 45 | 46 | func Assert(t *testing.T, cond bool, val ...interface{}) { 47 | t.Helper() 48 | if !cond { 49 | if len(val) > 0 { 50 | val = append([]interface{}{"assertion failed:"}, val...) 51 | t.Fatal(val...) 52 | } else { 53 | t.Fatal("assertion failed") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mux/shard_queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mux 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | "sync" 21 | "sync/atomic" 22 | 23 | "github.com/cloudwego/netpoll" 24 | "github.com/cloudwego/netpoll/internal/runner" 25 | ) 26 | 27 | /* DOC: 28 | * ShardQueue uses the netpoll's nocopy API to merge and send data. 29 | * The Data Flush is passively triggered by ShardQueue.Add and does not require user operations. 30 | * If there is an error in the data transmission, the connection will be closed. 31 | * 32 | * ShardQueue.Add: add the data to be sent. 33 | * NewShardQueue: create a queue with netpoll.Connection. 34 | * ShardSize: the recommended number of shards is 32. 35 | */ 36 | var ShardSize int 37 | 38 | func init() { 39 | ShardSize = runtime.GOMAXPROCS(0) 40 | } 41 | 42 | // NewShardQueue . 43 | func NewShardQueue(size int, conn netpoll.Connection) (queue *ShardQueue) { 44 | queue = &ShardQueue{ 45 | conn: conn, 46 | size: int32(size), 47 | getters: make([][]WriterGetter, size), 48 | swap: make([]WriterGetter, 0, 64), 49 | locks: make([]int32, size), 50 | } 51 | for i := range queue.getters { 52 | queue.getters[i] = make([]WriterGetter, 0, 64) 53 | } 54 | queue.list = make([]int32, size) 55 | return queue 56 | } 57 | 58 | // WriterGetter is used to get a netpoll.Writer. 59 | type WriterGetter func() (buf netpoll.Writer, isNil bool) 60 | 61 | // ShardQueue uses the netpoll's nocopy API to merge and send data. 62 | // The Data Flush is passively triggered by ShardQueue.Add and does not require user operations. 63 | // If there is an error in the data transmission, the connection will be closed. 64 | // ShardQueue.Add: add the data to be sent. 65 | type ShardQueue struct { 66 | conn netpoll.Connection 67 | idx, size int32 68 | getters [][]WriterGetter // len(getters) = size 69 | swap []WriterGetter // use for swap 70 | locks []int32 // len(locks) = size 71 | queueTrigger 72 | } 73 | 74 | const ( 75 | // queueTrigger state 76 | active = 0 77 | closing = 1 78 | closed = 2 79 | ) 80 | 81 | // here for trigger 82 | type queueTrigger struct { 83 | trigger int32 84 | state int32 // 0: active, 1: closing, 2: closed 85 | runNum int32 86 | w, r int32 // ptr of list 87 | list []int32 // record the triggered shard 88 | listLock sync.Mutex // list total lock 89 | } 90 | 91 | // Add adds to q.getters[shard] 92 | func (q *ShardQueue) Add(gts ...WriterGetter) { 93 | if atomic.LoadInt32(&q.state) != active { 94 | return 95 | } 96 | shard := atomic.AddInt32(&q.idx, 1) % q.size 97 | q.lock(shard) 98 | trigger := len(q.getters[shard]) == 0 99 | q.getters[shard] = append(q.getters[shard], gts...) 100 | q.unlock(shard) 101 | if trigger { 102 | q.triggering(shard) 103 | } 104 | } 105 | 106 | func (q *ShardQueue) Close() error { 107 | if !atomic.CompareAndSwapInt32(&q.state, active, closing) { 108 | return fmt.Errorf("shardQueue has been closed") 109 | } 110 | // wait for all tasks finished 111 | for atomic.LoadInt32(&q.state) != closed { 112 | if atomic.LoadInt32(&q.trigger) == 0 { 113 | atomic.StoreInt32(&q.state, closed) 114 | return nil 115 | } 116 | runtime.Gosched() 117 | } 118 | return nil 119 | } 120 | 121 | // triggering shard. 122 | func (q *ShardQueue) triggering(shard int32) { 123 | q.listLock.Lock() 124 | q.w = (q.w + 1) % q.size 125 | q.list[q.w] = shard 126 | q.listLock.Unlock() 127 | 128 | if atomic.AddInt32(&q.trigger, 1) > 1 { 129 | return 130 | } 131 | q.foreach() 132 | } 133 | 134 | // foreach swap r & w. It's not concurrency safe. 135 | func (q *ShardQueue) foreach() { 136 | if atomic.AddInt32(&q.runNum, 1) > 1 { 137 | return 138 | } 139 | runner.RunTask(nil, func() { 140 | var negNum int32 // is negative number of triggerNum 141 | for triggerNum := atomic.LoadInt32(&q.trigger); triggerNum > 0; { 142 | q.r = (q.r + 1) % q.size 143 | shared := q.list[q.r] 144 | 145 | // lock & swap 146 | q.lock(shared) 147 | tmp := q.getters[shared] 148 | q.getters[shared] = q.swap[:0] 149 | q.swap = tmp 150 | q.unlock(shared) 151 | 152 | // deal 153 | q.deal(q.swap) 154 | negNum-- 155 | if triggerNum+negNum == 0 { 156 | triggerNum = atomic.AddInt32(&q.trigger, negNum) 157 | negNum = 0 158 | } 159 | } 160 | q.flush() 161 | 162 | // quit & check again 163 | atomic.StoreInt32(&q.runNum, 0) 164 | if atomic.LoadInt32(&q.trigger) > 0 { 165 | q.foreach() 166 | return 167 | } 168 | // if state is closing, change it to closed 169 | atomic.CompareAndSwapInt32(&q.state, closing, closed) 170 | }) 171 | } 172 | 173 | // deal is used to get deal of netpoll.Writer. 174 | func (q *ShardQueue) deal(gts []WriterGetter) { 175 | writer := q.conn.Writer() 176 | for _, gt := range gts { 177 | buf, isNil := gt() 178 | if !isNil { 179 | err := writer.Append(buf) 180 | if err != nil { 181 | q.conn.Close() 182 | return 183 | } 184 | } 185 | } 186 | } 187 | 188 | // flush is used to flush netpoll.Writer. 189 | func (q *ShardQueue) flush() { 190 | err := q.conn.Writer().Flush() 191 | if err != nil { 192 | q.conn.Close() 193 | return 194 | } 195 | } 196 | 197 | // lock shard. 198 | func (q *ShardQueue) lock(shard int32) { 199 | for !atomic.CompareAndSwapInt32(&q.locks[shard], 0, 1) { 200 | runtime.Gosched() 201 | } 202 | } 203 | 204 | // unlock shard. 205 | func (q *ShardQueue) unlock(shard int32) { 206 | atomic.StoreInt32(&q.locks[shard], 0) 207 | } 208 | -------------------------------------------------------------------------------- /mux/shard_queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package mux 19 | 20 | import ( 21 | "net" 22 | "testing" 23 | "time" 24 | 25 | "github.com/cloudwego/netpoll" 26 | ) 27 | 28 | func TestShardQueue(t *testing.T) { 29 | var svrConn net.Conn 30 | accepted := make(chan struct{}) 31 | 32 | network, address := "tcp", "localhost:12345" 33 | ln, err := net.Listen("tcp", address) 34 | MustNil(t, err) 35 | stop := make(chan int, 1) 36 | defer close(stop) 37 | go func() { 38 | var err error 39 | for { 40 | select { 41 | case <-stop: 42 | err = ln.Close() 43 | MustNil(t, err) 44 | return 45 | default: 46 | } 47 | svrConn, err = ln.Accept() 48 | MustNil(t, err) 49 | accepted <- struct{}{} 50 | } 51 | }() 52 | 53 | conn, err := netpoll.DialConnection(network, address, time.Second) 54 | MustNil(t, err) 55 | <-accepted 56 | 57 | // test 58 | queue := NewShardQueue(4, conn) 59 | count, pkgsize := 16, 11 60 | for i := 0; i < int(count); i++ { 61 | var getter WriterGetter = func() (buf netpoll.Writer, isNil bool) { 62 | buf = netpoll.NewLinkBuffer(pkgsize) 63 | buf.Malloc(pkgsize) 64 | return buf, false 65 | } 66 | queue.Add(getter) 67 | } 68 | 69 | err = queue.Close() 70 | MustNil(t, err) 71 | total := count * pkgsize 72 | recv := make([]byte, total) 73 | rn, err := svrConn.Read(recv) 74 | MustNil(t, err) 75 | Equal(t, rn, total) 76 | } 77 | 78 | // TODO: need mock flush 79 | func BenchmarkShardQueue(b *testing.B) { 80 | b.Skip() 81 | } 82 | -------------------------------------------------------------------------------- /net_dialer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | "net" 23 | "time" 24 | ) 25 | 26 | // DialConnection is a default implementation of Dialer. 27 | func DialConnection(network, address string, timeout time.Duration) (connection Connection, err error) { 28 | return defaultDialer.DialConnection(network, address, timeout) 29 | } 30 | 31 | // NewFDConnection create a Connection initialed by any fd 32 | // It's useful for write unit test for functions have args with the type of netpoll.Connection 33 | // The typical usage like: 34 | // 35 | // rfd, wfd := netpoll.GetSysFdPairs() 36 | // rconn, _ = netpoll.NewFDConnection(rfd) 37 | // wconn, _ = netpoll.NewFDConnection(wfd) 38 | func NewFDConnection(fd int) (Connection, error) { 39 | conn := new(connection) 40 | err := conn.init(&netFD{fd: fd}, nil) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return conn, nil 45 | } 46 | 47 | // NewDialer only support TCP and unix socket now. 48 | func NewDialer() Dialer { 49 | return &dialer{} 50 | } 51 | 52 | var defaultDialer = NewDialer() 53 | 54 | type dialer struct{} 55 | 56 | // DialTimeout implements Dialer. 57 | func (d *dialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { 58 | return d.DialConnection(network, address, timeout) 59 | } 60 | 61 | // DialConnection implements Dialer. 62 | func (d *dialer) DialConnection(network, address string, timeout time.Duration) (connection Connection, err error) { 63 | ctx := context.Background() 64 | if timeout > 0 { 65 | subCtx, cancel := context.WithTimeout(ctx, timeout) 66 | defer cancel() 67 | ctx = subCtx 68 | } 69 | 70 | switch network { 71 | case "tcp", "tcp4", "tcp6": 72 | return d.dialTCP(ctx, network, address) 73 | // case "udp", "udp4", "udp6": // TODO: unsupported now 74 | case "unix", "unixgram", "unixpacket": 75 | raddr := &UnixAddr{ 76 | UnixAddr: net.UnixAddr{Name: address, Net: network}, 77 | } 78 | return DialUnix(network, nil, raddr) 79 | default: 80 | return nil, net.UnknownNetworkError(network) 81 | } 82 | } 83 | 84 | func (d *dialer) dialTCP(ctx context.Context, network, address string) (connection *TCPConnection, err error) { 85 | host, port, err := net.SplitHostPort(address) 86 | if err != nil { 87 | return nil, err 88 | } 89 | var portnum int 90 | if portnum, err = net.DefaultResolver.LookupPort(ctx, network, port); err != nil { 91 | return nil, err 92 | } 93 | var ipaddrs []net.IPAddr 94 | // host maybe empty if address is :12345 95 | if host == "" { 96 | ipaddrs = []net.IPAddr{{}} 97 | } else { 98 | ipaddrs, err = net.DefaultResolver.LookupIPAddr(ctx, host) 99 | if err != nil { 100 | return nil, err 101 | } 102 | if len(ipaddrs) == 0 { 103 | return nil, &net.DNSError{Err: "no such host", Name: host, IsNotFound: true} 104 | } 105 | } 106 | 107 | var firstErr error // The error from the first address is most relevant. 108 | tcpAddr := &TCPAddr{} 109 | for _, ipaddr := range ipaddrs { 110 | tcpAddr.IP = ipaddr.IP 111 | tcpAddr.Port = portnum 112 | tcpAddr.Zone = ipaddr.Zone 113 | if ipaddr.IP != nil && ipaddr.IP.To4() == nil { 114 | connection, err = DialTCP(ctx, "tcp6", nil, tcpAddr) 115 | } else { 116 | connection, err = DialTCP(ctx, "tcp", nil, tcpAddr) 117 | } 118 | if err == nil { 119 | return connection, nil 120 | } 121 | select { 122 | case <-ctx.Done(): // check timeout error 123 | return nil, err 124 | default: 125 | } 126 | if firstErr == nil { 127 | firstErr = err 128 | } 129 | } 130 | 131 | if firstErr == nil { 132 | firstErr = &net.OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: errMissingAddress} 133 | } 134 | return nil, firstErr 135 | } 136 | 137 | // sysDialer contains a Dial's parameters and configuration. 138 | type sysDialer struct { 139 | net.Dialer 140 | network, address string 141 | } 142 | -------------------------------------------------------------------------------- /net_dialer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | "fmt" 23 | "runtime" 24 | "strconv" 25 | "strings" 26 | "sync" 27 | "syscall" 28 | "testing" 29 | "time" 30 | ) 31 | 32 | func TestDialerTCP(t *testing.T) { 33 | dialer := NewDialer() 34 | address := getTestAddress() 35 | conn, err := dialer.DialTimeout("tcp", address, time.Second) 36 | MustTrue(t, err != nil) 37 | MustTrue(t, conn.(*TCPConnection) == nil) 38 | 39 | ln, err := CreateListener("tcp", address) 40 | MustNil(t, err) 41 | 42 | stop := make(chan int, 1) 43 | defer close(stop) 44 | 45 | go func() { 46 | for { 47 | select { 48 | case <-stop: 49 | err := ln.Close() 50 | MustNil(t, err) 51 | return 52 | default: 53 | } 54 | conn, err := ln.Accept() 55 | if conn == nil && err == nil { 56 | continue 57 | } 58 | } 59 | }() 60 | 61 | conn, err = dialer.DialTimeout("tcp", address, time.Second) 62 | MustNil(t, err) 63 | MustTrue(t, strings.HasPrefix(conn.LocalAddr().String(), "127.0.0.1:")) 64 | Equal(t, conn.RemoteAddr().String(), address) 65 | } 66 | 67 | func TestDialerUnix(t *testing.T) { 68 | dialer := NewDialer() 69 | conn, err := dialer.DialTimeout("unix", "tmp.sock", time.Second) 70 | MustTrue(t, err != nil) 71 | MustTrue(t, conn.(*UnixConnection) == nil) 72 | 73 | ln, err := CreateListener("unix", "tmp.sock") 74 | MustNil(t, err) 75 | defer ln.Close() 76 | 77 | stop := make(chan int, 1) 78 | defer func() { 79 | close(stop) 80 | time.Sleep(time.Millisecond) 81 | }() 82 | 83 | go func() { 84 | for { 85 | select { 86 | case <-stop: 87 | err := ln.Close() 88 | MustNil(t, err) 89 | return 90 | default: 91 | } 92 | conn, err := ln.Accept() 93 | if conn == nil && err == nil { 94 | continue 95 | } 96 | } 97 | }() 98 | 99 | conn, err = dialer.DialTimeout("unix", "tmp.sock", time.Second) 100 | MustNil(t, err) 101 | if runtime.GOOS == "linux" { 102 | Equal(t, conn.LocalAddr().String(), "@") 103 | } else { 104 | Equal(t, conn.LocalAddr().String(), "") 105 | } 106 | Equal(t, conn.RemoteAddr().String(), "tmp.sock") 107 | } 108 | 109 | func TestDialerFdAlloc(t *testing.T) { 110 | address := getTestAddress() 111 | ln, err := CreateListener("tcp", address) 112 | MustNil(t, err) 113 | defer ln.Close() 114 | el1, _ := NewEventLoop(func(ctx context.Context, connection Connection) error { 115 | connection.Close() 116 | return nil 117 | }) 118 | go func() { 119 | el1.Serve(ln) 120 | }() 121 | ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) 122 | defer cancel1() 123 | defer el1.Shutdown(ctx1) 124 | 125 | for i := 0; i < 100; i++ { 126 | conn, err := DialConnection("tcp", address, time.Second) 127 | MustNil(t, err) 128 | fd := conn.(*TCPConnection).fd 129 | conn.Write([]byte("hello world")) 130 | for conn.IsActive() { 131 | runtime.Gosched() 132 | } 133 | time.Sleep(time.Millisecond) 134 | syscall.SetNonblock(fd, true) 135 | } 136 | } 137 | 138 | func TestFDClose(t *testing.T) { 139 | address := getTestAddress() 140 | ln, err := CreateListener("tcp", address) 141 | MustNil(t, err) 142 | defer ln.Close() 143 | el1, _ := NewEventLoop(func(ctx context.Context, connection Connection) error { 144 | connection.Close() 145 | return nil 146 | }) 147 | go func() { 148 | el1.Serve(ln) 149 | }() 150 | ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) 151 | defer cancel1() 152 | defer el1.Shutdown(ctx1) 153 | 154 | var fd int 155 | var conn Connection 156 | conn, err = DialConnection("tcp", address, time.Second) 157 | MustNil(t, err) 158 | fd = conn.(*TCPConnection).fd 159 | syscall.SetNonblock(fd, true) 160 | conn.Close() 161 | 162 | conn, err = DialConnection("tcp", address, time.Second) 163 | MustNil(t, err) 164 | fd = conn.(*TCPConnection).fd 165 | syscall.SetNonblock(fd, true) 166 | time.Sleep(time.Second) 167 | conn.Close() 168 | } 169 | 170 | // fd data package race test, use two servers and two dialers. 171 | func TestDialerThenClose(t *testing.T) { 172 | address1 := getTestAddress() 173 | address2 := getTestAddress() 174 | // server 1 175 | ln1, _ := createTestListener("tcp", address1) 176 | el1 := mockDialerEventLoop(1) 177 | go func() { 178 | el1.Serve(ln1) 179 | }() 180 | ctx1, cancel1 := context.WithTimeout(context.Background(), time.Second) 181 | defer cancel1() 182 | defer el1.Shutdown(ctx1) 183 | 184 | // server 2 185 | ln2, _ := createTestListener("tcp", address2) 186 | el2 := mockDialerEventLoop(2) 187 | go func() { 188 | el2.Serve(ln2) 189 | }() 190 | ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second) 191 | defer cancel2() 192 | defer el2.Shutdown(ctx2) 193 | 194 | size := 20 195 | var wg sync.WaitGroup 196 | wg.Add(size) 197 | for i := 0; i < size; i++ { 198 | go func() { 199 | defer wg.Done() 200 | for i := 0; i < 50; i++ { 201 | // send server 1 202 | conn, err := DialConnection("tcp", address1, time.Second) 203 | if err == nil { 204 | mockDialerSend(1, &conn.(*TCPConnection).connection) 205 | } 206 | // send server 2 207 | conn, err = DialConnection("tcp", address2, time.Second) 208 | if err == nil { 209 | mockDialerSend(2, &conn.(*TCPConnection).connection) 210 | } 211 | } 212 | }() 213 | } 214 | wg.Wait() 215 | } 216 | 217 | func TestNewFDConnection(t *testing.T) { 218 | r, w := GetSysFdPairs() 219 | rconn, err := NewFDConnection(r) 220 | MustNil(t, err) 221 | wconn, err := NewFDConnection(w) 222 | MustNil(t, err) 223 | _, err = rconn.Writer().WriteString("hello") 224 | MustNil(t, err) 225 | err = rconn.Writer().Flush() 226 | MustNil(t, err) 227 | buf, err := wconn.Reader().Next(5) 228 | MustNil(t, err) 229 | Equal(t, string(buf), "hello") 230 | } 231 | 232 | func mockDialerEventLoop(idx int) EventLoop { 233 | el, _ := NewEventLoop(func(ctx context.Context, conn Connection) (err error) { 234 | defer func() { 235 | if err != nil { 236 | fmt.Printf("Error: server%d conn closed: %s", idx, err.Error()) 237 | conn.Close() 238 | } 239 | }() 240 | operator := conn.(*connection) 241 | fd := operator.fd 242 | msg := make([]byte, 15) 243 | n, err := operator.Read(msg) 244 | if err != nil { 245 | fmt.Printf("Error: conn[%d] server%d-read fail: %s", operator.fd, idx, err.Error()) 246 | return err 247 | } 248 | if n < 1 { 249 | return nil 250 | } 251 | if string(msg[0]) != strconv.Itoa(idx) { 252 | panic(fmt.Sprintf("msg[%s] != [%d-xxx]", msg, idx)) 253 | } 254 | 255 | ss := strings.Split(string(msg[:n]), "-") 256 | rfd, _ := strconv.Atoi(ss[1]) 257 | _, err = operator.Write([]byte(fmt.Sprintf("%d-%d", idx, fd))) 258 | if err != nil { 259 | fmt.Printf("Error: conn[%d] rfd[%d] server%d-write fail: %s", operator.fd, rfd, idx, err.Error()) 260 | return err 261 | } 262 | return nil 263 | }) 264 | return el 265 | } 266 | 267 | func mockDialerSend(idx int, conn *connection) { 268 | defer func() { 269 | conn.Close() 270 | }() 271 | randID1 := []byte(fmt.Sprintf("%d-%d", idx, conn.fd)) 272 | _, err := conn.Write(randID1) 273 | if err != nil { 274 | fmt.Printf("Error: conn[%d] client%d write fail: %s", conn.fd, idx, err.Error()) 275 | } 276 | msg := make([]byte, 15) 277 | _, err = conn.Read(msg) 278 | if err != nil { 279 | fmt.Printf("Error: conn[%d] client%d Next fail: %s", conn.fd, idx, err.Error()) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /net_io.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import "syscall" 21 | 22 | // return value: 23 | // - n: n == 0 but err == nil, retry syscall 24 | // - err: if not nil, connection should be closed. 25 | func ioread(fd int, bs [][]byte, ivs []syscall.Iovec) (n int, err error) { 26 | n, err = readv(fd, bs, ivs) 27 | if n == 0 && err == nil { // means EOF 28 | return 0, Exception(ErrEOF, "") 29 | } 30 | if err == syscall.EINTR || err == syscall.EAGAIN { 31 | return 0, nil 32 | } 33 | return n, err 34 | } 35 | 36 | // return value: 37 | // - n: n == 0 but err == nil, retry syscall 38 | // - err: if not nil, connection should be closed. 39 | func iosend(fd int, bs [][]byte, ivs []syscall.Iovec, zerocopy bool) (n int, err error) { 40 | n, err = sendmsg(fd, bs, ivs, zerocopy) 41 | if err == syscall.EAGAIN { 42 | return 0, nil 43 | } 44 | return n, err 45 | } 46 | -------------------------------------------------------------------------------- /net_listener.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import ( 21 | "errors" 22 | "net" 23 | "os" 24 | "syscall" 25 | ) 26 | 27 | // CreateListener return a new Listener. 28 | func CreateListener(network, addr string) (l Listener, err error) { 29 | if network == "udp" { 30 | // TODO: udp listener. 31 | return udpListener(network, addr) 32 | } 33 | // tcp, tcp4, tcp6, unix 34 | ln, err := net.Listen(network, addr) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return ConvertListener(ln) 39 | } 40 | 41 | // ConvertListener converts net.Listener to Listener 42 | func ConvertListener(l net.Listener) (nl Listener, err error) { 43 | if tmp, ok := l.(Listener); ok { 44 | return tmp, nil 45 | } 46 | ln := &listener{} 47 | ln.ln = l 48 | ln.addr = l.Addr() 49 | err = ln.parseFD() 50 | if err != nil { 51 | return nil, err 52 | } 53 | return ln, syscall.SetNonblock(ln.fd, true) 54 | } 55 | 56 | // TODO: udpListener does not work now. 57 | func udpListener(network, addr string) (l Listener, err error) { 58 | ln := &listener{} 59 | ln.pconn, err = net.ListenPacket(network, addr) 60 | if err != nil { 61 | return nil, err 62 | } 63 | ln.addr = ln.pconn.LocalAddr() 64 | switch pconn := ln.pconn.(type) { 65 | case *net.UDPConn: 66 | ln.file, err = pconn.File() 67 | } 68 | if err != nil { 69 | return nil, err 70 | } 71 | ln.fd = int(ln.file.Fd()) 72 | return ln, syscall.SetNonblock(ln.fd, true) 73 | } 74 | 75 | var _ net.Listener = &listener{} 76 | 77 | type listener struct { 78 | fd int 79 | addr net.Addr // listener's local addr 80 | ln net.Listener // tcp|unix listener 81 | pconn net.PacketConn // udp listener 82 | file *os.File 83 | } 84 | 85 | // Accept implements Listener. 86 | func (ln *listener) Accept() (net.Conn, error) { 87 | // udp 88 | if ln.pconn != nil { 89 | return ln.UDPAccept() 90 | } 91 | // tcp 92 | fd, sa, err := syscall.Accept(ln.fd) 93 | if err != nil { 94 | /* https://man7.org/linux/man-pages/man2/accept.2.html 95 | EAGAIN or EWOULDBLOCK 96 | The socket is marked nonblocking and no connections are 97 | present to be accepted. POSIX.1-2001 and POSIX.1-2008 98 | allow either error to be returned for this case, and do 99 | not require these constants to have the same value, so a 100 | portable application should check for both possibilities. 101 | */ 102 | if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK { 103 | return nil, nil 104 | } 105 | return nil, err 106 | } 107 | nfd := &netFD{} 108 | nfd.fd = fd 109 | nfd.localAddr = ln.addr 110 | nfd.network = ln.addr.Network() 111 | nfd.remoteAddr = sockaddrToAddr(sa) 112 | return nfd, nil 113 | } 114 | 115 | // TODO: UDPAccept Not implemented. 116 | func (ln *listener) UDPAccept() (net.Conn, error) { 117 | return nil, Exception(ErrUnsupported, "UDP") 118 | } 119 | 120 | // Close implements Listener. 121 | func (ln *listener) Close() error { 122 | if ln.fd != 0 { 123 | syscall.Close(ln.fd) 124 | } 125 | if ln.file != nil { 126 | ln.file.Close() 127 | } 128 | if ln.ln != nil { 129 | ln.ln.Close() 130 | } 131 | if ln.pconn != nil { 132 | ln.pconn.Close() 133 | } 134 | return nil 135 | } 136 | 137 | // Addr implements Listener. 138 | func (ln *listener) Addr() net.Addr { 139 | return ln.addr 140 | } 141 | 142 | // Fd implements Listener. 143 | func (ln *listener) Fd() (fd int) { 144 | return ln.fd 145 | } 146 | 147 | func (ln *listener) parseFD() (err error) { 148 | switch netln := ln.ln.(type) { 149 | case *net.TCPListener: 150 | ln.file, err = netln.File() 151 | case *net.UnixListener: 152 | ln.file, err = netln.File() 153 | default: 154 | return errors.New("listener type can't support") 155 | } 156 | if err != nil { 157 | return err 158 | } 159 | ln.fd = int(ln.file.Fd()) 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /net_listener_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | "net" 23 | "sync/atomic" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func TestListenerDialer(t *testing.T) { 29 | network := "tcp" 30 | addr := getTestAddress() 31 | ln, err := CreateListener(network, addr) 32 | MustNil(t, err) 33 | defer ln.Close() 34 | trigger := make(chan int) 35 | msg := []byte("0123456789") 36 | 37 | go func() { 38 | for { 39 | conn, err := ln.Accept() 40 | if conn == nil && err == nil { 41 | continue 42 | } 43 | if err != nil { 44 | return 45 | } 46 | go func(conn net.Conn) { 47 | <-trigger 48 | buf := make([]byte, 10) 49 | n, err := conn.Read(buf) 50 | MustNil(t, err) 51 | Equal(t, n, len(msg)) 52 | Equal(t, string(buf[:n]), string(msg)) 53 | n, err = conn.Write(buf) 54 | MustNil(t, err) 55 | Equal(t, n, len(msg)) 56 | }(conn) 57 | } 58 | }() 59 | 60 | // trigger 61 | var closed, read int32 62 | 63 | dialer := NewDialer() 64 | callback := func(connection Connection) error { 65 | atomic.StoreInt32(&closed, 1) 66 | return nil 67 | } 68 | onRequest := func(ctx context.Context, connection Connection) error { 69 | atomic.StoreInt32(&read, 1) 70 | err := connection.Close() 71 | MustNil(t, err) 72 | return err 73 | } 74 | for i := 0; i < 10; i++ { 75 | conn, err := dialer.DialConnection(network, addr, time.Second) 76 | if err != nil { 77 | continue 78 | } 79 | conn.AddCloseCallback(callback) 80 | conn.SetOnRequest(onRequest) 81 | 82 | MustNil(t, err) 83 | n, err := conn.Write([]byte(msg)) 84 | MustNil(t, err) 85 | Equal(t, n, len(msg)) 86 | time.Sleep(10 * time.Millisecond) 87 | trigger <- 1 88 | time.Sleep(10 * time.Millisecond) 89 | Equal(t, atomic.LoadInt32(&read), int32(1)) 90 | Equal(t, atomic.LoadInt32(&closed), int32(1)) 91 | } 92 | } 93 | 94 | func TestConvertListener(t *testing.T) { 95 | network, address := "unix", "mock.test.sock" 96 | ln, err := net.Listen(network, address) 97 | if err != nil { 98 | panic(err) 99 | } 100 | udsln, _ := ln.(*net.UnixListener) 101 | // udsln.SetUnlinkOnClose(false) 102 | 103 | nln, err := ConvertListener(udsln) 104 | if err != nil { 105 | panic(err) 106 | } 107 | err = nln.Close() 108 | if err != nil { 109 | panic(err) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /net_netfd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file may have been modified by CloudWeGo authors. (“CloudWeGo Modifications”). 6 | // All CloudWeGo Modifications are Copyright 2022 CloudWeGo authors. 7 | 8 | //go:build aix || darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris 9 | // +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris 10 | 11 | package netpoll 12 | 13 | import ( 14 | "context" 15 | "errors" 16 | "net" 17 | "os" 18 | "runtime" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | // nonDeadline and noCancel are just zero values for 24 | // readability with functions taking too many parameters. 25 | var noDeadline = time.Time{} 26 | 27 | type netFD struct { 28 | // file descriptor 29 | fd int 30 | // When calling netFD.dial(), fd will be registered into poll in some scenarios, such as dialing tcp socket, 31 | // but not in other scenarios, such as dialing unix socket. 32 | // This leads to a different behavior in register poller at after, so use this field to mark it. 33 | pd *pollDesc 34 | // closed marks whether fd has expired 35 | closed uint32 36 | // Whether this is a streaming descriptor, as opposed to a 37 | // packet-based descriptor like a UDP socket. Immutable. 38 | isStream bool 39 | // Whether a zero byte read indicates EOF. This is false for a 40 | // message based socket connection. 41 | zeroReadIsEOF bool 42 | family int // AF_INET, AF_INET6, syscall.AF_UNIX 43 | sotype int // syscall.SOCK_STREAM, syscall.SOCK_DGRAM, syscall.SOCK_RAW 44 | isConnected bool // handshake completed or use of association with peer 45 | network string // tcp tcp4 tcp6, udp, udp4, udp6, ip, ip4, ip6, unix, unixgram, unixpacket 46 | localAddr net.Addr 47 | remoteAddr net.Addr 48 | // for detaching conn from poller 49 | detaching bool 50 | } 51 | 52 | func newNetFD(fd, family, sotype int, net string) *netFD { 53 | ret := &netFD{} 54 | ret.fd = fd 55 | ret.network = net 56 | ret.family = family 57 | ret.sotype = sotype 58 | ret.isStream = sotype == syscall.SOCK_STREAM 59 | ret.zeroReadIsEOF = sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW 60 | return ret 61 | } 62 | 63 | // if dial connection error, you need exec netFD.Close actively 64 | func (c *netFD) dial(ctx context.Context, laddr, raddr sockaddr) (err error) { 65 | var lsa syscall.Sockaddr 66 | if laddr != nil { 67 | if lsa, err = laddr.sockaddr(c.family); err != nil { 68 | return err 69 | } else if lsa != nil { 70 | // bind local address 71 | if err = syscall.Bind(c.fd, lsa); err != nil { 72 | return os.NewSyscallError("bind", err) 73 | } 74 | } 75 | } 76 | var rsa syscall.Sockaddr // remote address from the user 77 | var crsa syscall.Sockaddr // remote address we actually connected to 78 | if raddr != nil { 79 | if rsa, err = raddr.sockaddr(c.family); err != nil { 80 | return err 81 | } 82 | } 83 | // remote address we actually connected to 84 | if crsa, err = c.connect(ctx, lsa, rsa); err != nil { 85 | return err 86 | } 87 | c.isConnected = true 88 | 89 | // Record the local and remote addresses from the actual socket. 90 | // Get the local address by calling Getsockname. 91 | // For the remote address, use 92 | // 1) the one returned by the connect method, if any; or 93 | // 2) the one from Getpeername, if it succeeds; or 94 | // 3) the one passed to us as the raddr parameter. 95 | lsa, _ = syscall.Getsockname(c.fd) 96 | c.localAddr = sockaddrToAddr(lsa) 97 | if crsa != nil { 98 | c.remoteAddr = sockaddrToAddr(crsa) 99 | } else if crsa, _ = syscall.Getpeername(c.fd); crsa != nil { 100 | c.remoteAddr = sockaddrToAddr(crsa) 101 | } else { 102 | c.remoteAddr = sockaddrToAddr(rsa) 103 | } 104 | return nil 105 | } 106 | 107 | func (c *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, retErr error) { 108 | // Do not need to call c.writing here, 109 | // because c is not yet accessible to user, 110 | // so no concurrent operations are possible. 111 | switch err := syscall.Connect(c.fd, ra); err { 112 | case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: 113 | case nil, syscall.EISCONN: 114 | select { 115 | case <-ctx.Done(): 116 | return nil, mapErr(ctx.Err()) 117 | default: 118 | } 119 | return nil, nil 120 | case syscall.EINVAL: 121 | // On Solaris we can see EINVAL if the socket has 122 | // already been accepted and closed by the server. 123 | // Treat this as a successful connection--writes to 124 | // the socket will see EOF. For details and a test 125 | // case in C see https://golang.org/issue/6828. 126 | if runtime.GOOS == "solaris" { 127 | return nil, nil 128 | } 129 | fallthrough 130 | default: 131 | return nil, os.NewSyscallError("connect", err) 132 | } 133 | 134 | c.pd = newPollDesc(c.fd) 135 | defer func() { 136 | // free operator to avoid leak 137 | c.pd.operator.Free() 138 | c.pd = nil 139 | }() 140 | for { 141 | // Performing multiple connect system calls on a 142 | // non-blocking socket under Unix variants does not 143 | // necessarily result in earlier errors being 144 | // returned. Instead, once runtime-integrated network 145 | // poller tells us that the socket is ready, get the 146 | // SO_ERROR socket option to see if the connection 147 | // succeeded or failed. See issue 7474 for further 148 | // details. 149 | if err := c.pd.WaitWrite(ctx); err != nil { 150 | return nil, err 151 | } 152 | nerr, err := syscall.GetsockoptInt(c.fd, syscall.SOL_SOCKET, syscall.SO_ERROR) 153 | if err != nil { 154 | return nil, os.NewSyscallError("getsockopt", err) 155 | } 156 | switch err := syscall.Errno(nerr); err { 157 | case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: 158 | case syscall.EISCONN: 159 | return nil, nil 160 | case syscall.Errno(0): 161 | // The runtime poller can wake us up spuriously; 162 | // see issues 14548 and 19289. Check that we are 163 | // really connected; if not, wait again. 164 | if rsa, err := syscall.Getpeername(c.fd); err == nil { 165 | return rsa, nil 166 | } 167 | default: 168 | return nil, os.NewSyscallError("connect", err) 169 | } 170 | } 171 | } 172 | 173 | // Various errors contained in OpError. 174 | var ( 175 | errMissingAddress = errors.New("missing address") 176 | errCanceled = errors.New("operation was canceled") 177 | errIOTimeout = errors.New("i/o timeout") 178 | ) 179 | 180 | // mapErr maps from the context errors to the historical internal net 181 | // error values. 182 | // 183 | // TODO(bradfitz): get rid of this after adjusting tests and making 184 | // context.DeadlineExceeded implement net.Error? 185 | func mapErr(err error) error { 186 | switch err { 187 | case context.Canceled: 188 | return errCanceled 189 | case context.DeadlineExceeded: 190 | return errIOTimeout 191 | default: 192 | return err 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /net_netfd_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import ( 21 | "net" 22 | "strings" 23 | "sync/atomic" 24 | "syscall" 25 | "time" 26 | ) 27 | 28 | var _ Conn = &netFD{} 29 | 30 | // Fd implements Conn. 31 | func (c *netFD) Fd() (fd int) { 32 | return c.fd 33 | } 34 | 35 | // Read implements Conn. 36 | func (c *netFD) Read(b []byte) (n int, err error) { 37 | n, err = syscall.Read(c.fd, b) 38 | if err != nil { 39 | if err == syscall.EAGAIN || err == syscall.EINTR { 40 | return 0, nil 41 | } 42 | } 43 | return n, err 44 | } 45 | 46 | // Write implements Conn. 47 | func (c *netFD) Write(b []byte) (n int, err error) { 48 | n, err = syscall.Write(c.fd, b) 49 | if err != nil { 50 | if err == syscall.EAGAIN { 51 | return 0, nil 52 | } 53 | } 54 | return n, err 55 | } 56 | 57 | // Close will be executed only once. 58 | func (c *netFD) Close() (err error) { 59 | if atomic.AddUint32(&c.closed, 1) != 1 { 60 | return nil 61 | } 62 | if !c.detaching && c.fd > 2 { 63 | err = syscall.Close(c.fd) 64 | if err != nil { 65 | logger.Printf("NETPOLL: netFD[%d] close error: %s", c.fd, err.Error()) 66 | } 67 | } 68 | return err 69 | } 70 | 71 | // LocalAddr implements Conn. 72 | func (c *netFD) LocalAddr() (addr net.Addr) { 73 | return c.localAddr 74 | } 75 | 76 | // RemoteAddr implements Conn. 77 | func (c *netFD) RemoteAddr() (addr net.Addr) { 78 | return c.remoteAddr 79 | } 80 | 81 | // SetKeepAlive implements Conn. 82 | // TODO: only tcp conn is ok. 83 | func (c *netFD) SetKeepAlive(second int) error { 84 | if !strings.HasPrefix(c.network, "tcp") { 85 | return nil 86 | } 87 | if second > 0 { 88 | return SetKeepAlive(c.fd, second) 89 | } 90 | return nil 91 | } 92 | 93 | // SetDeadline implements Conn. 94 | func (c *netFD) SetDeadline(t time.Time) error { 95 | return Exception(ErrUnsupported, "SetDeadline") 96 | } 97 | 98 | // SetReadDeadline implements Conn. 99 | func (c *netFD) SetReadDeadline(t time.Time) error { 100 | return Exception(ErrUnsupported, "SetReadDeadline") 101 | } 102 | 103 | // SetWriteDeadline implements Conn. 104 | func (c *netFD) SetWriteDeadline(t time.Time) error { 105 | return Exception(ErrUnsupported, "SetWriteDeadline") 106 | } 107 | -------------------------------------------------------------------------------- /net_polldesc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | ) 23 | 24 | func newPollDesc(fd int) *pollDesc { 25 | pd := &pollDesc{} 26 | poll := pollmanager.Pick() 27 | pd.operator = poll.Alloc() 28 | pd.operator.poll = poll 29 | pd.operator.FD = fd 30 | pd.operator.OnWrite = pd.onwrite 31 | pd.operator.OnHup = pd.onhup 32 | pd.writeTrigger = make(chan struct{}) 33 | pd.closeTrigger = make(chan struct{}) 34 | return pd 35 | } 36 | 37 | type pollDesc struct { 38 | operator *FDOperator 39 | // The write event is OneShot, then mark the writable to skip duplicate calling. 40 | writeTrigger chan struct{} 41 | closeTrigger chan struct{} 42 | } 43 | 44 | // WaitWrite . 45 | func (pd *pollDesc) WaitWrite(ctx context.Context) (err error) { 46 | if pd.operator.isUnused() { 47 | // add ET|Write|Hup 48 | if err = pd.operator.Control(PollWritable); err != nil { 49 | logger.Printf("NETPOLL: pollDesc register operator failed: %v", err) 50 | return err 51 | } 52 | } 53 | 54 | select { 55 | case <-pd.closeTrigger: // triggered by poller 56 | // no need to detach, since poller has done it in OnHup. 57 | return Exception(ErrConnClosed, "by peer") 58 | case <-pd.writeTrigger: // triggered by poller 59 | err = nil 60 | case <-ctx.Done(): // triggered by ctx 61 | // deregister from poller, upper caller function will close fd 62 | pd.detach() 63 | err = mapErr(ctx.Err()) 64 | } 65 | // double check close trigger 66 | select { 67 | case <-pd.closeTrigger: 68 | return Exception(ErrConnClosed, "by peer") 69 | default: 70 | return err 71 | } 72 | } 73 | 74 | func (pd *pollDesc) onwrite(p Poll) error { 75 | select { 76 | case <-pd.writeTrigger: 77 | default: 78 | pd.detach() 79 | close(pd.writeTrigger) 80 | } 81 | return nil 82 | } 83 | 84 | func (pd *pollDesc) onhup(p Poll) error { 85 | select { 86 | case <-pd.closeTrigger: 87 | default: 88 | close(pd.closeTrigger) 89 | } 90 | return nil 91 | } 92 | 93 | func (pd *pollDesc) detach() { 94 | if err := pd.operator.Control(PollDetach); err != nil { 95 | logger.Printf("NETPOLL: pollDesc detach operator failed: %v", err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /net_polldesc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestZeroTimer(t *testing.T) { 26 | MustTrue(t, noDeadline.IsZero()) 27 | } 28 | 29 | func TestRuntimePoll(t *testing.T) { 30 | address := getTestAddress() 31 | ln, err := CreateListener("tcp", address) 32 | MustNil(t, err) 33 | 34 | stop := make(chan int, 1) 35 | defer close(stop) 36 | 37 | go func() { 38 | for { 39 | select { 40 | case <-stop: 41 | err := ln.Close() 42 | MustNil(t, err) 43 | return 44 | default: 45 | } 46 | conn, err := ln.Accept() 47 | if conn == nil && err == nil { 48 | continue 49 | } 50 | } 51 | }() 52 | 53 | for i := 0; i < 10; i++ { 54 | conn, err := DialConnection("tcp", address, time.Second) 55 | MustNil(t, err) 56 | conn.Close() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /net_sock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file may have been modified by CloudWeGo authors. (“CloudWeGo Modifications”). 6 | // All CloudWeGo Modifications are Copyright 2022 CloudWeGo authors. 7 | 8 | //go:build !windows 9 | // +build !windows 10 | 11 | package netpoll 12 | 13 | import ( 14 | "context" 15 | "net" 16 | "runtime" 17 | "syscall" 18 | ) 19 | 20 | // A sockaddr represents a TCP, UDP, IP or Unix network endpoint 21 | // address that can be converted into a syscall.Sockaddr. 22 | type sockaddr interface { 23 | net.Addr 24 | 25 | // family returns the platform-dependent address family 26 | // identifier. 27 | family() int 28 | 29 | // isWildcard reports whether the address is a wildcard 30 | // address. 31 | isWildcard() bool 32 | 33 | // sockaddr returns the address converted into a syscall 34 | // sockaddr type that implements syscall.Sockaddr 35 | // interface. It returns a nil interface when the address is nil. 36 | sockaddr(family int) (syscall.Sockaddr, error) 37 | 38 | // toLocal maps the zero address to a local system address (127.0.0.1 or ::1) 39 | toLocal(net string) sockaddr 40 | } 41 | 42 | func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string) (conn *netFD, err error) { 43 | if (runtime.GOOS == "aix" || runtime.GOOS == "windows" || runtime.GOOS == "openbsd" || runtime.GOOS == "nacl") && raddr.isWildcard() { 44 | raddr = raddr.toLocal(net) 45 | } 46 | family, ipv6only := favoriteAddrFamily(net, laddr, raddr) 47 | return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr) 48 | } 49 | 50 | // favoriteAddrFamily returns the appropriate address family for the 51 | // given network, laddr, raddr and mode. 52 | // 53 | // If mode indicates "listen" and laddr is a wildcard, we assume that 54 | // the user wants to make a passive-open connection with a wildcard 55 | // address family, both AF_INET and AF_INET6, and a wildcard address 56 | // like the following: 57 | // 58 | // - A listen for a wildcard communication domain, "tcp" or 59 | // "udp", with a wildcard address: If the platform supports 60 | // both IPv6 and IPv4-mapped IPv6 communication capabilities, 61 | // or does not support IPv4, we use a dual stack, AF_INET6 and 62 | // IPV6_V6ONLY=0, wildcard address listen. The dual stack 63 | // wildcard address listen may fall back to an IPv6-only, 64 | // AF_INET6 and IPV6_V6ONLY=1, wildcard address listen. 65 | // Otherwise we prefer an IPv4-only, AF_INET, wildcard address 66 | // listen. 67 | // 68 | // - A listen for a wildcard communication domain, "tcp" or 69 | // "udp", with an IPv4 wildcard address: same as above. 70 | // 71 | // - A listen for a wildcard communication domain, "tcp" or 72 | // "udp", with an IPv6 wildcard address: same as above. 73 | // 74 | // - A listen for an IPv4 communication domain, "tcp4" or "udp4", 75 | // with an IPv4 wildcard address: We use an IPv4-only, AF_INET, 76 | // wildcard address listen. 77 | // 78 | // - A listen for an IPv6 communication domain, "tcp6" or "udp6", 79 | // with an IPv6 wildcard address: We use an IPv6-only, AF_INET6 80 | // and IPV6_V6ONLY=1, wildcard address listen. 81 | // 82 | // Otherwise guess: If the addresses are IPv4 then returns AF_INET, 83 | // or else returns AF_INET6. It also returns a boolean value what 84 | // designates IPV6_V6ONLY option. 85 | // 86 | // Note that the latest DragonFly BSD and OpenBSD kernels allow 87 | // neither "net.inet6.ip6.v6only=1" change nor IPPROTO_IPV6 level 88 | // IPV6_V6ONLY socket option setting. 89 | func favoriteAddrFamily(network string, laddr, raddr sockaddr) (family int, ipv6only bool) { 90 | switch network[len(network)-1] { 91 | case '4': 92 | return syscall.AF_INET, false 93 | case '6': 94 | return syscall.AF_INET6, true 95 | } 96 | if (laddr == nil || laddr.family() == syscall.AF_INET) && 97 | (raddr == nil || raddr.family() == syscall.AF_INET) { 98 | return syscall.AF_INET, false 99 | } 100 | return syscall.AF_INET6, false 101 | } 102 | 103 | // socket returns a network file descriptor that is ready for 104 | // asynchronous I/O using the network poller. 105 | func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr) (netfd *netFD, err error) { 106 | // syscall.Socket & set socket options 107 | var fd int 108 | fd, err = sysSocket(family, sotype, proto) 109 | if err != nil { 110 | return nil, err 111 | } 112 | err = setDefaultSockopts(fd, family, sotype, ipv6only) 113 | if err != nil { 114 | syscall.Close(fd) 115 | return nil, err 116 | } 117 | 118 | netfd = newNetFD(fd, family, sotype, net) 119 | err = netfd.dial(ctx, laddr, raddr) 120 | if err != nil { 121 | netfd.Close() 122 | return nil, err 123 | } 124 | return netfd, nil 125 | } 126 | 127 | // sockaddrToAddr returns a go/net friendly address 128 | func sockaddrToAddr(sa syscall.Sockaddr) net.Addr { 129 | var a net.Addr 130 | switch sa := sa.(type) { 131 | case *syscall.SockaddrInet4: 132 | a = &net.TCPAddr{ 133 | IP: sa.Addr[0:], 134 | Port: sa.Port, 135 | } 136 | case *syscall.SockaddrInet6: 137 | var zone string 138 | if sa.ZoneId != 0 { 139 | if ifi, err := net.InterfaceByIndex(int(sa.ZoneId)); err == nil { 140 | zone = ifi.Name 141 | } 142 | } 143 | // if zone == "" && sa.ZoneId != 0 { 144 | // } 145 | a = &net.TCPAddr{ 146 | IP: sa.Addr[0:], 147 | Port: sa.Port, 148 | Zone: zone, 149 | } 150 | case *syscall.SockaddrUnix: 151 | a = &net.UnixAddr{Net: "unix", Name: sa.Name} 152 | } 153 | return a 154 | } 155 | -------------------------------------------------------------------------------- /net_unixsock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file may have been modified by CloudWeGo authors. (“CloudWeGo Modifications”). 6 | // All CloudWeGo Modifications are Copyright 2022 CloudWeGo authors. 7 | 8 | //go:build !windows 9 | // +build !windows 10 | 11 | package netpoll 12 | 13 | import ( 14 | "context" 15 | "errors" 16 | "net" 17 | "syscall" 18 | ) 19 | 20 | // BUG(mikio): On JS, NaCl and Plan 9, methods and functions related 21 | // to UnixConn and UnixListener are not implemented. 22 | 23 | // BUG(mikio): On Windows, methods and functions related to UnixConn 24 | // and UnixListener don't work for "unixgram" and "unixpacket". 25 | 26 | // UnixAddr represents the address of a Unix domain socket end point. 27 | type UnixAddr struct { 28 | net.UnixAddr 29 | } 30 | 31 | func (a *UnixAddr) isWildcard() bool { 32 | return a == nil || a.Name == "" 33 | } 34 | 35 | func (a *UnixAddr) opAddr() net.Addr { 36 | if a == nil { 37 | return nil 38 | } 39 | return a 40 | } 41 | 42 | func (a *UnixAddr) family() int { 43 | return syscall.AF_UNIX 44 | } 45 | 46 | func (a *UnixAddr) sockaddr(family int) (syscall.Sockaddr, error) { 47 | if a == nil { 48 | return nil, nil 49 | } 50 | return &syscall.SockaddrUnix{Name: a.Name}, nil 51 | } 52 | 53 | func (a *UnixAddr) toLocal(net string) sockaddr { 54 | return a 55 | } 56 | 57 | // ResolveUnixAddr returns an address of Unix domain socket end point. 58 | // 59 | // The network must be a Unix network name. 60 | // 61 | // See func Dial for a description of the network and address 62 | // parameters. 63 | func ResolveUnixAddr(network, address string) (*UnixAddr, error) { 64 | addr, err := net.ResolveUnixAddr(network, address) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return &UnixAddr{*addr}, nil 69 | } 70 | 71 | // UnixConnection implements Connection. 72 | type UnixConnection struct { 73 | connection 74 | } 75 | 76 | // newUnixConnection wraps UnixConnection. 77 | func newUnixConnection(conn Conn) (connection *UnixConnection, err error) { 78 | connection = &UnixConnection{} 79 | err = connection.init(conn, nil) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return connection, nil 84 | } 85 | 86 | // DialUnix acts like Dial for Unix networks. 87 | // 88 | // The network must be a Unix network name; see func Dial for details. 89 | // 90 | // If laddr is non-nil, it is used as the local address for the 91 | // connection. 92 | func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConnection, error) { 93 | switch network { 94 | case "unix", "unixgram", "unixpacket": 95 | default: 96 | return nil, &net.OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: net.UnknownNetworkError(network)} 97 | } 98 | sd := &sysDialer{network: network, address: raddr.String()} 99 | c, err := sd.dialUnix(context.Background(), laddr, raddr) 100 | if err != nil { 101 | return nil, &net.OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err} 102 | } 103 | return c, nil 104 | } 105 | 106 | func (sd *sysDialer) dialUnix(ctx context.Context, laddr, raddr *UnixAddr) (*UnixConnection, error) { 107 | conn, err := unixSocket(ctx, sd.network, laddr, raddr, "dial") 108 | if err != nil { 109 | return nil, err 110 | } 111 | return newUnixConnection(conn) 112 | } 113 | 114 | func unixSocket(ctx context.Context, network string, laddr, raddr sockaddr, mode string) (conn *netFD, err error) { 115 | var sotype int 116 | switch network { 117 | case "unix": 118 | sotype = syscall.SOCK_STREAM 119 | case "unixgram": 120 | sotype = syscall.SOCK_DGRAM 121 | case "unixpacket": 122 | sotype = syscall.SOCK_SEQPACKET 123 | default: 124 | return nil, net.UnknownNetworkError(network) 125 | } 126 | 127 | switch mode { 128 | case "dial": 129 | if laddr != nil && laddr.isWildcard() { 130 | laddr = nil 131 | } 132 | if raddr != nil && raddr.isWildcard() { 133 | raddr = nil 134 | } 135 | if raddr == nil && (sotype != syscall.SOCK_DGRAM || laddr == nil) { 136 | return nil, errMissingAddress 137 | } 138 | case "listen": 139 | default: 140 | return nil, errors.New("unknown mode: " + mode) 141 | } 142 | 143 | return socket(ctx, network, syscall.AF_UNIX, sotype, 0, false, laddr, raddr) 144 | } 145 | -------------------------------------------------------------------------------- /netpoll_config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "context" 19 | "io" 20 | ) 21 | 22 | // global config 23 | var ( 24 | defaultLinkBufferSize = pagesize 25 | featureAlwaysNoCopyRead = false 26 | ) 27 | 28 | // Config expose some tuning parameters to control the internal behaviors of netpoll. 29 | // Every parameter with the default zero value should keep the default behavior of netpoll. 30 | type Config struct { 31 | PollerNum int // number of pollers 32 | BufferSize int // default size of a new connection's LinkBuffer 33 | Runner func(ctx context.Context, f func()) // runner for event handler, most of the time use a goroutine pool. 34 | LoggerOutput io.Writer // logger output 35 | LoadBalance LoadBalance // load balance for poller picker 36 | Feature // define all features that not enable by default 37 | } 38 | 39 | // Feature expose some new features maybe promoted as a default behavior but not yet. 40 | type Feature struct { 41 | // AlwaysNoCopyRead allows some copy Read functions like ReadBinary/ReadString 42 | // will use NoCopy read and will not reuse the underlying buffer. 43 | // It gains more performance benefits when need read much big string/bytes in codec. 44 | AlwaysNoCopyRead bool 45 | } 46 | -------------------------------------------------------------------------------- /netpoll_options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import "time" 18 | 19 | // Option . 20 | type Option struct { 21 | f func(*options) 22 | } 23 | 24 | type options struct { 25 | onPrepare OnPrepare 26 | onConnect OnConnect 27 | onDisconnect OnDisconnect 28 | onRequest OnRequest 29 | readTimeout time.Duration 30 | writeTimeout time.Duration 31 | idleTimeout time.Duration 32 | } 33 | 34 | // WithOnPrepare registers the OnPrepare method to EventLoop. 35 | func WithOnPrepare(onPrepare OnPrepare) Option { 36 | return Option{func(op *options) { 37 | op.onPrepare = onPrepare 38 | }} 39 | } 40 | 41 | // WithOnConnect registers the OnConnect method to EventLoop. 42 | func WithOnConnect(onConnect OnConnect) Option { 43 | return Option{func(op *options) { 44 | op.onConnect = onConnect 45 | }} 46 | } 47 | 48 | // WithOnDisconnect registers the OnDisconnect method to EventLoop. 49 | func WithOnDisconnect(onDisconnect OnDisconnect) Option { 50 | return Option{func(op *options) { 51 | op.onDisconnect = onDisconnect 52 | }} 53 | } 54 | 55 | // WithReadTimeout sets the read timeout of connections. 56 | func WithReadTimeout(timeout time.Duration) Option { 57 | return Option{func(op *options) { 58 | op.readTimeout = timeout 59 | }} 60 | } 61 | 62 | // WithWriteTimeout sets the write timeout of connections. 63 | func WithWriteTimeout(timeout time.Duration) Option { 64 | return Option{func(op *options) { 65 | op.writeTimeout = timeout 66 | }} 67 | } 68 | 69 | // WithIdleTimeout sets the idle timeout of connections. 70 | func WithIdleTimeout(timeout time.Duration) Option { 71 | return Option{func(op *options) { 72 | op.idleTimeout = timeout 73 | }} 74 | } 75 | -------------------------------------------------------------------------------- /netpoll_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | "errors" 23 | "strings" 24 | "sync" 25 | "syscall" 26 | "time" 27 | ) 28 | 29 | // newServer wrap listener into server, quit will be invoked when server exit. 30 | func newServer(ln Listener, opts *options, onQuit func(err error)) *server { 31 | return &server{ 32 | ln: ln, 33 | opts: opts, 34 | onQuit: onQuit, 35 | } 36 | } 37 | 38 | type server struct { 39 | operator FDOperator 40 | ln Listener 41 | opts *options 42 | onQuit func(err error) 43 | connections sync.Map // key=fd, value=connection 44 | } 45 | 46 | // Run this server. 47 | func (s *server) Run() (err error) { 48 | s.operator = FDOperator{ 49 | FD: s.ln.Fd(), 50 | OnRead: s.OnRead, 51 | OnHup: s.OnHup, 52 | } 53 | s.operator.poll = pollmanager.Pick() 54 | err = s.operator.Control(PollReadable) 55 | if err != nil { 56 | s.onQuit(err) 57 | } 58 | return err 59 | } 60 | 61 | // Close this server with deadline. 62 | func (s *server) Close(ctx context.Context) error { 63 | s.operator.Control(PollDetach) 64 | s.ln.Close() 65 | 66 | for { 67 | activeConn := 0 68 | s.connections.Range(func(key, value interface{}) bool { 69 | conn, ok := value.(gracefulExit) 70 | if !ok || conn.isIdle() { 71 | value.(Connection).Close() 72 | } else { 73 | activeConn++ 74 | } 75 | return true 76 | }) 77 | if activeConn == 0 { // all connections have been closed 78 | return nil 79 | } 80 | 81 | // smart control graceful shutdown check internal 82 | // we should wait for more time if there are more active connections 83 | waitTime := time.Millisecond * time.Duration(activeConn) 84 | if waitTime > time.Second { // max wait time is 1000 ms 85 | waitTime = time.Millisecond * 1000 86 | } else if waitTime < time.Millisecond*50 { // min wait time is 50 ms 87 | waitTime = time.Millisecond * 50 88 | } 89 | select { 90 | case <-ctx.Done(): 91 | return ctx.Err() 92 | case <-time.After(waitTime): 93 | continue 94 | } 95 | } 96 | } 97 | 98 | // OnRead implements FDOperator. 99 | func (s *server) OnRead(p Poll) error { 100 | // accept socket 101 | conn, err := s.ln.Accept() 102 | if err == nil { 103 | if conn != nil { 104 | s.onAccept(conn.(Conn)) 105 | } 106 | // EAGAIN | EWOULDBLOCK if conn and err both nil 107 | return nil 108 | } 109 | logger.Printf("NETPOLL: accept conn failed: %v", err) 110 | 111 | // delay accept when too many open files 112 | if isOutOfFdErr(err) { 113 | // since we use Epoll LT, we have to detach listener fd from epoll first 114 | // and re-register it when accept successfully or there is no available connection 115 | cerr := s.operator.Control(PollDetach) 116 | if cerr != nil { 117 | logger.Printf("NETPOLL: detach listener fd failed: %v", cerr) 118 | return err 119 | } 120 | go func() { 121 | retryTimes := []time.Duration{0, 10, 50, 100, 200, 500, 1000} // ms 122 | retryTimeIndex := 0 123 | for { 124 | if retryTimeIndex > 0 { 125 | time.Sleep(retryTimes[retryTimeIndex] * time.Millisecond) 126 | } 127 | conn, err := s.ln.Accept() 128 | if err == nil { 129 | if conn == nil { 130 | // recovery accept poll loop 131 | s.operator.Control(PollReadable) 132 | return 133 | } 134 | s.onAccept(conn.(Conn)) 135 | logger.Println("NETPOLL: re-accept conn success:", conn.RemoteAddr()) 136 | retryTimeIndex = 0 137 | continue 138 | } 139 | if retryTimeIndex+1 < len(retryTimes) { 140 | retryTimeIndex++ 141 | } 142 | logger.Printf("NETPOLL: re-accept conn failed, err=[%s] and next retrytime=%dms", err.Error(), retryTimes[retryTimeIndex]) 143 | } 144 | }() 145 | } 146 | 147 | // shut down 148 | if strings.Contains(err.Error(), "closed") { 149 | s.operator.Control(PollDetach) 150 | s.onQuit(err) 151 | return err 152 | } 153 | 154 | return err 155 | } 156 | 157 | // OnHup implements FDOperator. 158 | func (s *server) OnHup(p Poll) error { 159 | s.onQuit(errors.New("listener close")) 160 | return nil 161 | } 162 | 163 | func (s *server) onAccept(conn Conn) { 164 | // store & register connection 165 | nconn := new(connection) 166 | nconn.init(conn, s.opts) 167 | if !nconn.IsActive() { 168 | return 169 | } 170 | fd := conn.Fd() 171 | nconn.AddCloseCallback(func(connection Connection) error { 172 | s.connections.Delete(fd) 173 | return nil 174 | }) 175 | s.connections.Store(fd, nconn) 176 | 177 | // trigger onConnect asynchronously 178 | nconn.onConnect() 179 | } 180 | 181 | func isOutOfFdErr(err error) bool { 182 | se, ok := err.(syscall.Errno) 183 | return ok && (se == syscall.EMFILE || se == syscall.ENFILE) 184 | } 185 | -------------------------------------------------------------------------------- /netpoll_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import ( 21 | "context" 22 | "io" 23 | "log" 24 | "net" 25 | "os" 26 | "runtime" 27 | "sync" 28 | 29 | "github.com/cloudwego/netpoll/internal/runner" 30 | ) 31 | 32 | var ( 33 | pollmanager = newManager(runtime.GOMAXPROCS(0)/20 + 1) // pollmanager manage all pollers 34 | logger = log.New(os.Stderr, "", log.LstdFlags) 35 | ) 36 | 37 | // Initialize the pollers actively. By default, it's lazy initialized. 38 | // It's safe to call it multi times. 39 | func Initialize() { 40 | // The first call of Pick() will init pollers 41 | _ = pollmanager.Pick() 42 | } 43 | 44 | // Configure the internal behaviors of netpoll. 45 | // Configure must called in init() function, because the poller will read some global variable after init() finished 46 | func Configure(config Config) (err error) { 47 | if config.PollerNum > 0 { 48 | if err = pollmanager.SetNumLoops(config.PollerNum); err != nil { 49 | return err 50 | } 51 | } 52 | if config.BufferSize > 0 { 53 | defaultLinkBufferSize = config.BufferSize 54 | } 55 | 56 | if config.Runner != nil { 57 | runner.RunTask = config.Runner 58 | } 59 | if config.LoggerOutput != nil { 60 | logger = log.New(config.LoggerOutput, "", log.LstdFlags) 61 | } 62 | if config.LoadBalance >= 0 { 63 | if err = pollmanager.SetLoadBalance(config.LoadBalance); err != nil { 64 | return err 65 | } 66 | } 67 | 68 | featureAlwaysNoCopyRead = config.AlwaysNoCopyRead 69 | return nil 70 | } 71 | 72 | // SetNumLoops is used to set the number of pollers, generally do not need to actively set. 73 | // By default, the number of pollers is equal to runtime.GOMAXPROCS(0)/20+1. 74 | // If the number of cores in your service process is less than 20c, theoretically only one poller is needed. 75 | // Otherwise, you may need to adjust the number of pollers to achieve the best results. 76 | // Experience recommends assigning a poller every 20c. 77 | // 78 | // You can only use SetNumLoops before any connection is created. An example usage: 79 | // 80 | // func init() { 81 | // netpoll.SetNumLoops(...) 82 | // } 83 | // 84 | // Deprecated: use Configure instead. 85 | func SetNumLoops(numLoops int) error { 86 | return pollmanager.SetNumLoops(numLoops) 87 | } 88 | 89 | // SetLoadBalance sets the load balancing method. Load balancing is always a best effort to attempt 90 | // to distribute the incoming connections between multiple polls. 91 | // This option only works when numLoops is set. 92 | // Deprecated: use Configure instead. 93 | func SetLoadBalance(lb LoadBalance) error { 94 | return pollmanager.SetLoadBalance(lb) 95 | } 96 | 97 | // SetLoggerOutput sets the logger output target. 98 | // Deprecated: use Configure instead. 99 | func SetLoggerOutput(w io.Writer) { 100 | logger = log.New(w, "", log.LstdFlags) 101 | } 102 | 103 | // SetRunner set the runner function for every OnRequest/OnConnect callback 104 | // 105 | // Deprecated: use Configure and specify config.Runner instead. 106 | func SetRunner(f func(ctx context.Context, f func())) { 107 | runner.RunTask = f 108 | } 109 | 110 | // DisableGopool will remove gopool(the goroutine pool used to run OnRequest), 111 | // which means that OnRequest will be run via `go OnRequest(...)`. 112 | // Usually, OnRequest will cause stack expansion, which can be solved by reusing goroutine. 113 | // But if you can confirm that the OnRequest will not cause stack expansion, 114 | // it is recommended to use DisableGopool to reduce redundancy and improve performance. 115 | // 116 | // Deprecated: use Configure() and specify config.Runner instead. 117 | func DisableGopool() error { 118 | runner.UseGoRunTask() 119 | return nil 120 | } 121 | 122 | // NewEventLoop . 123 | func NewEventLoop(onRequest OnRequest, ops ...Option) (EventLoop, error) { 124 | opts := &options{ 125 | onRequest: onRequest, 126 | } 127 | for _, do := range ops { 128 | do.f(opts) 129 | } 130 | return &eventLoop{ 131 | opts: opts, 132 | stop: make(chan error, 1), 133 | }, nil 134 | } 135 | 136 | type eventLoop struct { 137 | sync.Mutex 138 | opts *options 139 | svr *server 140 | stop chan error 141 | } 142 | 143 | // Serve implements EventLoop. 144 | func (evl *eventLoop) Serve(ln net.Listener) error { 145 | npln, err := ConvertListener(ln) 146 | if err != nil { 147 | return err 148 | } 149 | evl.Lock() 150 | evl.svr = newServer(npln, evl.opts, evl.quit) 151 | evl.svr.Run() 152 | evl.Unlock() 153 | 154 | err = evl.waitQuit() 155 | // ensure evl will not be finalized until Serve returns 156 | runtime.SetFinalizer(evl, nil) 157 | return err 158 | } 159 | 160 | // Shutdown signals a shutdown a begins server closing. 161 | func (evl *eventLoop) Shutdown(ctx context.Context) error { 162 | evl.Lock() 163 | svr := evl.svr 164 | evl.svr = nil 165 | evl.Unlock() 166 | 167 | if svr == nil { 168 | return nil 169 | } 170 | evl.quit(nil) 171 | return svr.Close(ctx) 172 | } 173 | 174 | // waitQuit waits for a quit signal 175 | func (evl *eventLoop) waitQuit() error { 176 | return <-evl.stop 177 | } 178 | 179 | func (evl *eventLoop) quit(err error) { 180 | select { 181 | case evl.stop <- err: 182 | default: 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /netpoll_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build windows 16 | // +build windows 17 | 18 | // The following methods would not be used, but are intended to compile on Windows. 19 | package netpoll 20 | 21 | import ( 22 | "net" 23 | ) 24 | 25 | // Configure the internal behaviors of netpoll. 26 | func Configure(config Config) (err error) { 27 | return nil 28 | } 29 | 30 | // NewDialer only support TCP and unix socket now. 31 | func NewDialer() Dialer { 32 | return nil 33 | } 34 | 35 | // NewEventLoop . 36 | func NewEventLoop(onRequest OnRequest, ops ...Option) (EventLoop, error) { 37 | return nil, nil 38 | } 39 | 40 | // ConvertListener converts net.Listener to Listener 41 | func ConvertListener(l net.Listener) (nl Listener, err error) { 42 | return nil, nil 43 | } 44 | 45 | // CreateListener return a new Listener. 46 | func CreateListener(network, addr string) (l Listener, err error) { 47 | return nil, nil 48 | } 49 | -------------------------------------------------------------------------------- /nocopy_linkbuffer_norace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !race 16 | // +build !race 17 | 18 | package netpoll 19 | 20 | type LinkBuffer = UnsafeLinkBuffer 21 | -------------------------------------------------------------------------------- /nocopy_linkbuffer_race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build race 16 | // +build race 17 | 18 | package netpoll 19 | 20 | import ( 21 | "sync" 22 | ) 23 | 24 | type LinkBuffer = SafeLinkBuffer 25 | 26 | // SafeLinkBuffer only used to in go tests with -race 27 | type SafeLinkBuffer struct { 28 | sync.Mutex 29 | UnsafeLinkBuffer 30 | } 31 | 32 | // ------------------------------------------ implement zero-copy reader ------------------------------------------ 33 | 34 | // Next implements Reader. 35 | func (b *SafeLinkBuffer) Next(n int) (p []byte, err error) { 36 | b.Lock() 37 | defer b.Unlock() 38 | return b.UnsafeLinkBuffer.Next(n) 39 | } 40 | 41 | // Peek implements Reader. 42 | func (b *SafeLinkBuffer) Peek(n int) (p []byte, err error) { 43 | b.Lock() 44 | defer b.Unlock() 45 | return b.UnsafeLinkBuffer.Peek(n) 46 | } 47 | 48 | // Skip implements Reader. 49 | func (b *SafeLinkBuffer) Skip(n int) (err error) { 50 | b.Lock() 51 | defer b.Unlock() 52 | return b.UnsafeLinkBuffer.Skip(n) 53 | } 54 | 55 | // Until implements Reader. 56 | func (b *SafeLinkBuffer) Until(delim byte) (line []byte, err error) { 57 | b.Lock() 58 | defer b.Unlock() 59 | return b.UnsafeLinkBuffer.Until(delim) 60 | } 61 | 62 | // Release implements Reader. 63 | func (b *SafeLinkBuffer) Release() (err error) { 64 | b.Lock() 65 | defer b.Unlock() 66 | return b.UnsafeLinkBuffer.Release() 67 | } 68 | 69 | // ReadString implements Reader. 70 | func (b *SafeLinkBuffer) ReadString(n int) (s string, err error) { 71 | b.Lock() 72 | defer b.Unlock() 73 | return b.UnsafeLinkBuffer.ReadString(n) 74 | } 75 | 76 | // ReadBinary implements Reader. 77 | func (b *SafeLinkBuffer) ReadBinary(n int) (p []byte, err error) { 78 | b.Lock() 79 | defer b.Unlock() 80 | return b.UnsafeLinkBuffer.ReadBinary(n) 81 | } 82 | 83 | // ReadByte implements Reader. 84 | func (b *SafeLinkBuffer) ReadByte() (p byte, err error) { 85 | b.Lock() 86 | defer b.Unlock() 87 | return b.UnsafeLinkBuffer.ReadByte() 88 | } 89 | 90 | // Slice implements Reader. 91 | func (b *SafeLinkBuffer) Slice(n int) (r Reader, err error) { 92 | b.Lock() 93 | defer b.Unlock() 94 | return b.UnsafeLinkBuffer.Slice(n) 95 | } 96 | 97 | // ------------------------------------------ implement zero-copy writer ------------------------------------------ 98 | 99 | // Malloc implements Writer. 100 | func (b *SafeLinkBuffer) Malloc(n int) (buf []byte, err error) { 101 | b.Lock() 102 | defer b.Unlock() 103 | return b.UnsafeLinkBuffer.Malloc(n) 104 | } 105 | 106 | // MallocLen implements Writer. 107 | func (b *SafeLinkBuffer) MallocLen() (length int) { 108 | b.Lock() 109 | defer b.Unlock() 110 | return b.UnsafeLinkBuffer.MallocLen() 111 | } 112 | 113 | // MallocAck implements Writer. 114 | func (b *SafeLinkBuffer) MallocAck(n int) (err error) { 115 | b.Lock() 116 | defer b.Unlock() 117 | return b.UnsafeLinkBuffer.MallocAck(n) 118 | } 119 | 120 | // Flush implements Writer. 121 | func (b *SafeLinkBuffer) Flush() (err error) { 122 | b.Lock() 123 | defer b.Unlock() 124 | return b.UnsafeLinkBuffer.Flush() 125 | } 126 | 127 | // Append implements Writer. 128 | func (b *SafeLinkBuffer) Append(w Writer) (err error) { 129 | b.Lock() 130 | defer b.Unlock() 131 | return b.UnsafeLinkBuffer.Append(w) 132 | } 133 | 134 | // WriteBuffer implements Writer. 135 | func (b *SafeLinkBuffer) WriteBuffer(buf *LinkBuffer) (err error) { 136 | b.Lock() 137 | defer b.Unlock() 138 | return b.UnsafeLinkBuffer.WriteBuffer(buf) 139 | } 140 | 141 | // WriteString implements Writer. 142 | func (b *SafeLinkBuffer) WriteString(s string) (n int, err error) { 143 | b.Lock() 144 | defer b.Unlock() 145 | return b.UnsafeLinkBuffer.WriteString(s) 146 | } 147 | 148 | // WriteBinary implements Writer. 149 | func (b *SafeLinkBuffer) WriteBinary(p []byte) (n int, err error) { 150 | b.Lock() 151 | defer b.Unlock() 152 | return b.UnsafeLinkBuffer.WriteBinary(p) 153 | } 154 | 155 | // WriteDirect cannot be mixed with WriteString or WriteBinary functions. 156 | func (b *SafeLinkBuffer) WriteDirect(p []byte, remainLen int) error { 157 | b.Lock() 158 | defer b.Unlock() 159 | return b.UnsafeLinkBuffer.WriteDirect(p, remainLen) 160 | } 161 | 162 | // WriteByte implements Writer. 163 | func (b *SafeLinkBuffer) WriteByte(p byte) (err error) { 164 | b.Lock() 165 | defer b.Unlock() 166 | return b.UnsafeLinkBuffer.WriteByte(p) 167 | } 168 | 169 | // Close will recycle all buffer. 170 | func (b *SafeLinkBuffer) Close() (err error) { 171 | b.Lock() 172 | defer b.Unlock() 173 | return b.UnsafeLinkBuffer.Close() 174 | } 175 | 176 | // ------------------------------------------ implement connection interface ------------------------------------------ 177 | 178 | // Bytes returns all the readable bytes of this SafeLinkBuffer. 179 | func (b *SafeLinkBuffer) Bytes() []byte { 180 | b.Lock() 181 | defer b.Unlock() 182 | return b.UnsafeLinkBuffer.Bytes() 183 | } 184 | 185 | // GetBytes will read and fill the slice p as much as possible. 186 | func (b *SafeLinkBuffer) GetBytes(p [][]byte) (vs [][]byte) { 187 | b.Lock() 188 | defer b.Unlock() 189 | return b.UnsafeLinkBuffer.GetBytes(p) 190 | } 191 | 192 | // book will grow and malloc buffer to hold data. 193 | // 194 | // bookSize: The size of data that can be read at once. 195 | // maxSize: The maximum size of data between two Release(). In some cases, this can 196 | // 197 | // guarantee all data allocated in one node to reduce copy. 198 | func (b *SafeLinkBuffer) book(bookSize, maxSize int) (p []byte) { 199 | b.Lock() 200 | defer b.Unlock() 201 | return b.UnsafeLinkBuffer.book(bookSize, maxSize) 202 | } 203 | 204 | // bookAck will ack the first n malloc bytes and discard the rest. 205 | // 206 | // length: The size of data in inputBuffer. It is used to calculate the maxSize 207 | func (b *SafeLinkBuffer) bookAck(n int) (length int, err error) { 208 | b.Lock() 209 | defer b.Unlock() 210 | return b.UnsafeLinkBuffer.bookAck(n) 211 | } 212 | 213 | // calcMaxSize will calculate the data size between two Release() 214 | func (b *SafeLinkBuffer) calcMaxSize() (sum int) { 215 | b.Lock() 216 | defer b.Unlock() 217 | return b.UnsafeLinkBuffer.calcMaxSize() 218 | } 219 | 220 | func (b *SafeLinkBuffer) resetTail(maxSize int) { 221 | b.Lock() 222 | defer b.Unlock() 223 | b.UnsafeLinkBuffer.resetTail(maxSize) 224 | } 225 | 226 | func (b *SafeLinkBuffer) indexByte(c byte, skip int) int { 227 | b.Lock() 228 | defer b.Unlock() 229 | return b.UnsafeLinkBuffer.indexByte(c, skip) 230 | } 231 | -------------------------------------------------------------------------------- /nocopy_readwriter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | ) 21 | 22 | const maxReadCycle = 16 23 | 24 | func newZCReader(r io.Reader) *zcReader { 25 | return &zcReader{ 26 | r: r, 27 | buf: NewLinkBuffer(), 28 | } 29 | } 30 | 31 | var _ Reader = &zcReader{} 32 | 33 | // zcReader implements Reader. 34 | type zcReader struct { 35 | r io.Reader 36 | buf *LinkBuffer 37 | } 38 | 39 | // Next implements Reader. 40 | func (r *zcReader) Next(n int) (p []byte, err error) { 41 | if err = r.waitRead(n); err != nil { 42 | return p, err 43 | } 44 | return r.buf.Next(n) 45 | } 46 | 47 | // Peek implements Reader. 48 | func (r *zcReader) Peek(n int) (buf []byte, err error) { 49 | if err = r.waitRead(n); err != nil { 50 | return buf, err 51 | } 52 | return r.buf.Peek(n) 53 | } 54 | 55 | // Skip implements Reader. 56 | func (r *zcReader) Skip(n int) (err error) { 57 | if err = r.waitRead(n); err != nil { 58 | return err 59 | } 60 | return r.buf.Skip(n) 61 | } 62 | 63 | // Release implements Reader. 64 | func (r *zcReader) Release() (err error) { 65 | return r.buf.Release() 66 | } 67 | 68 | // Slice implements Reader. 69 | func (r *zcReader) Slice(n int) (reader Reader, err error) { 70 | if err = r.waitRead(n); err != nil { 71 | return nil, err 72 | } 73 | return r.buf.Slice(n) 74 | } 75 | 76 | // Len implements Reader. 77 | func (r *zcReader) Len() (length int) { 78 | return r.buf.Len() 79 | } 80 | 81 | // ReadString implements Reader. 82 | func (r *zcReader) ReadString(n int) (s string, err error) { 83 | if err = r.waitRead(n); err != nil { 84 | return s, err 85 | } 86 | return r.buf.ReadString(n) 87 | } 88 | 89 | // ReadBinary implements Reader. 90 | func (r *zcReader) ReadBinary(n int) (p []byte, err error) { 91 | if err = r.waitRead(n); err != nil { 92 | return p, err 93 | } 94 | return r.buf.ReadBinary(n) 95 | } 96 | 97 | // ReadByte implements Reader. 98 | func (r *zcReader) ReadByte() (b byte, err error) { 99 | if err = r.waitRead(1); err != nil { 100 | return b, err 101 | } 102 | return r.buf.ReadByte() 103 | } 104 | 105 | func (r *zcReader) Until(delim byte) (line []byte, err error) { 106 | return r.buf.Until(delim) 107 | } 108 | 109 | func (r *zcReader) waitRead(n int) (err error) { 110 | for r.buf.Len() < n { 111 | err = r.fill(n) 112 | if err != nil { 113 | if err == io.EOF { 114 | err = Exception(ErrEOF, "") 115 | } 116 | return err 117 | } 118 | } 119 | return nil 120 | } 121 | 122 | // fill buffer to greater than n, range no more than 16 times. 123 | func (r *zcReader) fill(n int) (err error) { 124 | var buf []byte 125 | var num int 126 | for i := 0; i < maxReadCycle && r.buf.Len() < n && err == nil; i++ { 127 | buf, err = r.buf.Malloc(block4k) 128 | if err != nil { 129 | return err 130 | } 131 | num, err = r.r.Read(buf) 132 | if num < 0 { 133 | if err == nil { 134 | err = fmt.Errorf("zcReader fill negative count[%d]", num) 135 | } 136 | num = 0 137 | } 138 | r.buf.MallocAck(num) 139 | r.buf.Flush() 140 | if err != nil { 141 | return err 142 | } 143 | } 144 | return err 145 | } 146 | 147 | func newZCWriter(w io.Writer) *zcWriter { 148 | return &zcWriter{ 149 | w: w, 150 | buf: NewLinkBuffer(), 151 | } 152 | } 153 | 154 | var _ Writer = &zcWriter{} 155 | 156 | // zcWriter implements Writer. 157 | type zcWriter struct { 158 | w io.Writer 159 | buf *LinkBuffer 160 | } 161 | 162 | // Malloc implements Writer. 163 | func (w *zcWriter) Malloc(n int) (buf []byte, err error) { 164 | return w.buf.Malloc(n) 165 | } 166 | 167 | // MallocLen implements Writer. 168 | func (w *zcWriter) MallocLen() (length int) { 169 | return w.buf.MallocLen() 170 | } 171 | 172 | // Flush implements Writer. 173 | func (w *zcWriter) Flush() (err error) { 174 | w.buf.Flush() 175 | n, err := w.w.Write(w.buf.Bytes()) 176 | if n > 0 { 177 | w.buf.Skip(n) 178 | w.buf.Release() 179 | } 180 | return err 181 | } 182 | 183 | // MallocAck implements Writer. 184 | func (w *zcWriter) MallocAck(n int) (err error) { 185 | return w.buf.MallocAck(n) 186 | } 187 | 188 | // Append implements Writer. 189 | func (w *zcWriter) Append(w2 Writer) (err error) { 190 | return w.buf.Append(w2) 191 | } 192 | 193 | // WriteString implements Writer. 194 | func (w *zcWriter) WriteString(s string) (n int, err error) { 195 | return w.buf.WriteString(s) 196 | } 197 | 198 | // WriteBinary implements Writer. 199 | func (w *zcWriter) WriteBinary(b []byte) (n int, err error) { 200 | return w.buf.WriteBinary(b) 201 | } 202 | 203 | // WriteDirect implements Writer. 204 | func (w *zcWriter) WriteDirect(p []byte, remainCap int) error { 205 | return w.buf.WriteDirect(p, remainCap) 206 | } 207 | 208 | // WriteByte implements Writer. 209 | func (w *zcWriter) WriteByte(b byte) (err error) { 210 | return w.buf.WriteByte(b) 211 | } 212 | 213 | // zcWriter implements ReadWriter. 214 | type zcReadWriter struct { 215 | *zcReader 216 | *zcWriter 217 | } 218 | 219 | func newIOReader(r Reader) *ioReader { 220 | return &ioReader{ 221 | r: r, 222 | } 223 | } 224 | 225 | var _ io.Reader = &ioReader{} 226 | 227 | // ioReader implements io.Reader. 228 | type ioReader struct { 229 | r Reader 230 | } 231 | 232 | // Read implements io.Reader. 233 | func (r *ioReader) Read(p []byte) (n int, err error) { 234 | l := len(p) 235 | if l == 0 { 236 | return 0, nil 237 | } 238 | // read min(len(p), buffer.Len) 239 | if has := r.r.Len(); has < l { 240 | l = has 241 | } 242 | if l == 0 { 243 | return 0, io.EOF 244 | } 245 | src, err := r.r.Next(l) 246 | if err != nil { 247 | return 0, err 248 | } 249 | n = copy(p, src) 250 | err = r.r.Release() 251 | if err != nil { 252 | return 0, err 253 | } 254 | return n, nil 255 | } 256 | 257 | func newIOWriter(w Writer) *ioWriter { 258 | return &ioWriter{ 259 | w: w, 260 | } 261 | } 262 | 263 | var _ io.Writer = &ioWriter{} 264 | 265 | // ioWriter implements io.Writer. 266 | type ioWriter struct { 267 | w Writer 268 | } 269 | 270 | // Write implements io.Writer. 271 | func (w *ioWriter) Write(p []byte) (n int, err error) { 272 | dst, err := w.w.Malloc(len(p)) 273 | if err != nil { 274 | return 0, err 275 | } 276 | n = copy(dst, p) 277 | err = w.w.Flush() 278 | if err != nil { 279 | return 0, err 280 | } 281 | return n, nil 282 | } 283 | 284 | // ioReadWriter implements io.ReadWriter. 285 | type ioReadWriter struct { 286 | *ioReader 287 | *ioWriter 288 | } 289 | -------------------------------------------------------------------------------- /nocopy_readwriter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "errors" 22 | "io" 23 | "io/ioutil" 24 | "testing" 25 | ) 26 | 27 | func TestZCReader(t *testing.T) { 28 | reader := &MockIOReadWriter{ 29 | read: func(p []byte) (n int, err error) { 30 | return len(p), nil 31 | }, 32 | } 33 | r := newZCReader(reader) 34 | 35 | p, err := r.Next(block8k) 36 | MustNil(t, err) 37 | Equal(t, len(p), block8k) 38 | Equal(t, r.buf.Len(), 0) 39 | 40 | p, err = r.Peek(block4k) 41 | MustNil(t, err) 42 | Equal(t, len(p), block4k) 43 | Equal(t, r.buf.Len(), block4k) 44 | 45 | err = r.Skip(block4k) 46 | MustNil(t, err) 47 | Equal(t, r.buf.Len(), 0) 48 | 49 | err = r.Release() 50 | MustNil(t, err) 51 | } 52 | 53 | func TestZCWriter(t *testing.T) { 54 | writer := &MockIOReadWriter{ 55 | write: func(p []byte) (n int, err error) { 56 | return len(p), nil 57 | }, 58 | } 59 | w := newZCWriter(writer) 60 | 61 | p, err := w.Malloc(block1k) 62 | MustNil(t, err) 63 | Equal(t, len(p), block1k) 64 | Equal(t, w.buf.Len(), 0) 65 | 66 | err = w.Flush() 67 | MustNil(t, err) 68 | Equal(t, w.buf.Len(), 0) 69 | 70 | p, err = w.Malloc(block2k) 71 | MustNil(t, err) 72 | Equal(t, len(p), block2k) 73 | Equal(t, w.buf.Len(), 0) 74 | 75 | err = w.buf.Flush() 76 | MustNil(t, err) 77 | Equal(t, w.buf.Len(), block2k) 78 | 79 | err = w.Flush() 80 | MustNil(t, err) 81 | Equal(t, w.buf.Len(), 0) 82 | } 83 | 84 | func TestZCEOF(t *testing.T) { 85 | reader := &MockIOReadWriter{ 86 | read: func(p []byte) (n int, err error) { 87 | return 0, io.EOF 88 | }, 89 | } 90 | r := newZCReader(reader) 91 | 92 | _, err := r.Next(block8k) 93 | MustTrue(t, errors.Is(err, ErrEOF)) 94 | } 95 | 96 | type MockIOReadWriter struct { 97 | read func(p []byte) (n int, err error) 98 | write func(p []byte) (n int, err error) 99 | } 100 | 101 | func (rw *MockIOReadWriter) Read(p []byte) (n int, err error) { 102 | if rw.read != nil { 103 | return rw.read(p) 104 | } 105 | return 106 | } 107 | 108 | func (rw *MockIOReadWriter) Write(p []byte) (n int, err error) { 109 | if rw.write != nil { 110 | return rw.write(p) 111 | } 112 | return 113 | } 114 | 115 | func TestIOReadWriter(t *testing.T) { 116 | buf := NewLinkBuffer(block1k) 117 | reader, writer := newIOReader(buf), newIOWriter(buf) 118 | msg := []byte("hello world") 119 | n, err := writer.Write(msg) 120 | MustNil(t, err) 121 | Equal(t, n, len(msg)) 122 | 123 | p := make([]byte, block1k) 124 | n, err = reader.Read(p) 125 | MustNil(t, err) 126 | Equal(t, n, len(msg)) 127 | } 128 | 129 | func TestIOReadWriter2(t *testing.T) { 130 | buf := NewLinkBuffer(block1k) 131 | reader, writer := newIOReader(buf), newIOWriter(buf) 132 | msg := []byte("hello world") 133 | n, err := writer.Write(msg) 134 | MustNil(t, err) 135 | Equal(t, n, len(msg)) 136 | 137 | p, err := ioutil.ReadAll(reader) 138 | MustNil(t, err) 139 | Equal(t, len(p), len(msg)) 140 | } 141 | -------------------------------------------------------------------------------- /poll.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | // Poll monitors fd(file descriptor), calls the FDOperator to perform specific actions, 18 | // and shields underlying differences. On linux systems, poll uses epoll by default, 19 | // and kevent by default on bsd systems. 20 | type Poll interface { 21 | // Wait will poll all registered fds, and schedule processing based on the triggered event. 22 | // The call will block, so the usage can be like: 23 | // 24 | // go wait() 25 | // 26 | Wait() error 27 | 28 | // Close the poll and shutdown Wait(). 29 | Close() error 30 | 31 | // Trigger can be used to actively refresh the loop where Wait is located when no event is triggered. 32 | // On linux systems, eventfd is used by default, and kevent by default on bsd systems. 33 | Trigger() error 34 | 35 | // Control the event of file descriptor and the operations is defined by PollEvent. 36 | Control(operator *FDOperator, event PollEvent) error 37 | 38 | // Alloc the operator from cache. 39 | Alloc() (operator *FDOperator) 40 | 41 | // Free the operator from cache. 42 | Free(operator *FDOperator) 43 | } 44 | 45 | // PollEvent defines the operation of poll.Control. 46 | type PollEvent int 47 | 48 | const ( 49 | // PollReadable is used to monitor whether the FDOperator registered by 50 | // listener and connection is readable or closed. 51 | PollReadable PollEvent = 0x1 52 | 53 | // PollWritable is used to monitor whether the FDOperator created by the dialer is writable or closed. 54 | // ET mode must be used (still need to poll hup after being writable) 55 | PollWritable PollEvent = 0x2 56 | 57 | // PollDetach is used to remove the FDOperator from poll. 58 | PollDetach PollEvent = 0x3 59 | 60 | // PollR2RW is used to monitor writable for FDOperator, 61 | // which is only called when the socket write buffer is full. 62 | PollR2RW PollEvent = 0x5 63 | 64 | // PollRW2R is used to remove the writable monitor of FDOperator, generally used with PollR2RW. 65 | PollRW2R PollEvent = 0x6 66 | ) 67 | -------------------------------------------------------------------------------- /poll_default.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly || linux 16 | // +build darwin netbsd freebsd openbsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | func (p *defaultPoll) Alloc() (operator *FDOperator) { 21 | op := p.opcache.alloc() 22 | op.poll = p 23 | return op 24 | } 25 | 26 | func (p *defaultPoll) Free(operator *FDOperator) { 27 | p.opcache.freeable(operator) 28 | } 29 | 30 | func (p *defaultPoll) appendHup(operator *FDOperator) { 31 | p.hups = append(p.hups, operator.OnHup) 32 | p.detach(operator) 33 | operator.done() 34 | } 35 | 36 | func (p *defaultPoll) detach(operator *FDOperator) { 37 | if err := operator.Control(PollDetach); err != nil { 38 | logger.Printf("NETPOLL: poller detach operator failed: %v", err) 39 | } 40 | } 41 | 42 | func (p *defaultPoll) onhups() { 43 | if len(p.hups) == 0 { 44 | return 45 | } 46 | hups := p.hups 47 | p.hups = nil 48 | go func(onhups []func(p Poll) error) { 49 | for i := range onhups { 50 | if onhups[i] != nil { 51 | onhups[i](p) 52 | } 53 | } 54 | }(hups) 55 | } 56 | 57 | // readall read all left data before close connection 58 | func readall(op *FDOperator, br barrier) (total int, err error) { 59 | ivs := br.ivs 60 | var n int 61 | for { 62 | bs := op.Inputs(br.bs) 63 | if len(bs) == 0 { 64 | return total, nil 65 | } 66 | 67 | TryRead: 68 | n, err = ioread(op.FD, bs, ivs) 69 | op.InputAck(n) 70 | total += n 71 | if err != nil { 72 | return total, err 73 | } 74 | if n == 0 { 75 | goto TryRead 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /poll_default_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly 16 | // +build darwin netbsd freebsd openbsd dragonfly 17 | 18 | package netpoll 19 | 20 | import ( 21 | "errors" 22 | "sync" 23 | "sync/atomic" 24 | "syscall" 25 | "unsafe" 26 | ) 27 | 28 | func openPoll() (Poll, error) { 29 | return openDefaultPoll() 30 | } 31 | 32 | func openDefaultPoll() (*defaultPoll, error) { 33 | l := new(defaultPoll) 34 | p, err := syscall.Kqueue() 35 | if err != nil { 36 | return nil, err 37 | } 38 | l.fd = p 39 | _, err = syscall.Kevent(l.fd, []syscall.Kevent_t{{ 40 | Ident: 0, 41 | Filter: syscall.EVFILT_USER, 42 | Flags: syscall.EV_ADD | syscall.EV_CLEAR, 43 | }}, nil, nil) 44 | if err != nil { 45 | syscall.Close(l.fd) 46 | return nil, err 47 | } 48 | l.opcache = newOperatorCache() 49 | return l, nil 50 | } 51 | 52 | type defaultPoll struct { 53 | fd int 54 | trigger uint32 55 | m sync.Map // only used in go:race 56 | opcache *operatorCache // operator cache 57 | hups []func(p Poll) error 58 | } 59 | 60 | // Wait implements Poll. 61 | func (p *defaultPoll) Wait() error { 62 | // init 63 | size, caps := 1024, barriercap 64 | events, barriers := make([]syscall.Kevent_t, size), make([]barrier, size) 65 | for i := range barriers { 66 | barriers[i].bs = make([][]byte, caps) 67 | barriers[i].ivs = make([]syscall.Iovec, caps) 68 | } 69 | // wait 70 | var triggerRead, triggerWrite, triggerHup bool 71 | for { 72 | n, err := syscall.Kevent(p.fd, nil, events, nil) 73 | if err != nil && err != syscall.EINTR { 74 | // exit gracefully 75 | if err == syscall.EBADF { 76 | return nil 77 | } 78 | return err 79 | } 80 | for i := 0; i < n; i++ { 81 | fd := int(events[i].Ident) 82 | // trigger 83 | if fd == 0 { 84 | // clean trigger 85 | atomic.StoreUint32(&p.trigger, 0) 86 | continue 87 | } 88 | operator := p.getOperator(fd, unsafe.Pointer(&events[i].Udata)) 89 | if operator == nil || !operator.do() { 90 | continue 91 | } 92 | 93 | var totalRead int 94 | evt := events[i] 95 | triggerRead = evt.Filter == syscall.EVFILT_READ && evt.Flags&syscall.EV_ENABLE != 0 96 | triggerWrite = evt.Filter == syscall.EVFILT_WRITE && evt.Flags&syscall.EV_ENABLE != 0 97 | triggerHup = evt.Flags&syscall.EV_EOF != 0 98 | 99 | if triggerRead { 100 | if operator.OnRead != nil { 101 | // for non-connection 102 | operator.OnRead(p) 103 | } else { 104 | // only for connection 105 | bs := operator.Inputs(barriers[i].bs) 106 | if len(bs) > 0 { 107 | n, err := ioread(operator.FD, bs, barriers[i].ivs) 108 | operator.InputAck(n) 109 | totalRead += n 110 | if err != nil { 111 | p.appendHup(operator) 112 | continue 113 | } 114 | } 115 | } 116 | } 117 | if triggerHup { 118 | if triggerRead && operator.Inputs != nil { 119 | var leftRead int 120 | // read all left data if peer send and close 121 | if leftRead, err = readall(operator, barriers[i]); err != nil && !errors.Is(err, ErrEOF) { 122 | logger.Printf("NETPOLL: readall(fd=%d)=%d before close: %s", operator.FD, total, err.Error()) 123 | } 124 | totalRead += leftRead 125 | } 126 | // only close connection if no further read bytes 127 | if totalRead == 0 { 128 | p.appendHup(operator) 129 | continue 130 | } 131 | } 132 | if triggerWrite { 133 | if operator.OnWrite != nil { 134 | // for non-connection 135 | operator.OnWrite(p) 136 | } else { 137 | // only for connection 138 | bs, supportZeroCopy := operator.Outputs(barriers[i].bs) 139 | if len(bs) > 0 { 140 | // TODO: Let the upper layer pass in whether to use ZeroCopy. 141 | n, err := iosend(operator.FD, bs, barriers[i].ivs, false && supportZeroCopy) 142 | operator.OutputAck(n) 143 | if err != nil { 144 | p.appendHup(operator) 145 | continue 146 | } 147 | } 148 | } 149 | } 150 | operator.done() 151 | } 152 | // hup conns together to avoid blocking the poll. 153 | p.onhups() 154 | p.opcache.free() 155 | } 156 | } 157 | 158 | // TODO: Close will bad file descriptor here 159 | func (p *defaultPoll) Close() error { 160 | err := syscall.Close(p.fd) 161 | return err 162 | } 163 | 164 | // Trigger implements Poll. 165 | func (p *defaultPoll) Trigger() error { 166 | if atomic.AddUint32(&p.trigger, 1) > 1 { 167 | return nil 168 | } 169 | _, err := syscall.Kevent(p.fd, []syscall.Kevent_t{{ 170 | Ident: 0, 171 | Filter: syscall.EVFILT_USER, 172 | Fflags: syscall.NOTE_TRIGGER, 173 | }}, nil, nil) 174 | return err 175 | } 176 | 177 | // Control implements Poll. 178 | func (p *defaultPoll) Control(operator *FDOperator, event PollEvent) error { 179 | evs := make([]syscall.Kevent_t, 1) 180 | evs[0].Ident = uint64(operator.FD) 181 | p.setOperator(unsafe.Pointer(&evs[0].Udata), operator) 182 | switch event { 183 | case PollReadable: 184 | operator.inuse() 185 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_READ, syscall.EV_ADD|syscall.EV_ENABLE 186 | case PollWritable: 187 | operator.inuse() 188 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_WRITE, syscall.EV_ADD|syscall.EV_ENABLE 189 | case PollDetach: 190 | if operator.OnWrite != nil { // means WaitWrite finished 191 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_WRITE, syscall.EV_DELETE 192 | } else { 193 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_READ, syscall.EV_DELETE 194 | } 195 | p.delOperator(operator) 196 | case PollR2RW: 197 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_WRITE, syscall.EV_ADD|syscall.EV_ENABLE 198 | case PollRW2R: 199 | evs[0].Filter, evs[0].Flags = syscall.EVFILT_WRITE, syscall.EV_DELETE 200 | } 201 | _, err := syscall.Kevent(p.fd, evs, nil, nil) 202 | return err 203 | } 204 | -------------------------------------------------------------------------------- /poll_default_bsd_norace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build (darwin || netbsd || freebsd || openbsd || dragonfly) && !race 16 | // +build darwin netbsd freebsd openbsd dragonfly 17 | // +build !race 18 | 19 | package netpoll 20 | 21 | import "unsafe" 22 | 23 | func (p *defaultPoll) getOperator(fd int, ptr unsafe.Pointer) *FDOperator { 24 | return *(**FDOperator)(ptr) 25 | } 26 | 27 | func (p *defaultPoll) setOperator(ptr unsafe.Pointer, operator *FDOperator) { 28 | *(**FDOperator)(ptr) = operator 29 | } 30 | 31 | func (p *defaultPoll) delOperator(operator *FDOperator) { 32 | } 33 | -------------------------------------------------------------------------------- /poll_default_bsd_race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build (darwin || netbsd || freebsd || openbsd || dragonfly) && race 16 | // +build darwin netbsd freebsd openbsd dragonfly 17 | // +build race 18 | 19 | package netpoll 20 | 21 | import "unsafe" 22 | 23 | func (p *defaultPoll) getOperator(fd int, ptr unsafe.Pointer) *FDOperator { 24 | tmp, _ := p.m.Load(fd) 25 | if tmp == nil { 26 | return nil 27 | } 28 | return tmp.(*FDOperator) 29 | } 30 | 31 | func (p *defaultPoll) setOperator(ptr unsafe.Pointer, operator *FDOperator) { 32 | p.m.Store(operator.FD, operator) 33 | } 34 | 35 | func (p *defaultPoll) delOperator(operator *FDOperator) { 36 | p.m.Delete(operator.FD) 37 | } 38 | -------------------------------------------------------------------------------- /poll_default_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "errors" 19 | "runtime" 20 | "sync" 21 | "sync/atomic" 22 | "syscall" 23 | "unsafe" 24 | ) 25 | 26 | func openPoll() (Poll, error) { 27 | return openDefaultPoll() 28 | } 29 | 30 | func openDefaultPoll() (*defaultPoll, error) { 31 | poll := new(defaultPoll) 32 | 33 | poll.buf = make([]byte, 8) 34 | p, err := EpollCreate(0) 35 | if err != nil { 36 | return nil, err 37 | } 38 | poll.fd = p 39 | 40 | r0, _, e0 := syscall.Syscall(syscall.SYS_EVENTFD2, 0, 0, 0) 41 | if e0 != 0 { 42 | _ = syscall.Close(poll.fd) 43 | return nil, e0 44 | } 45 | 46 | poll.Reset = poll.reset 47 | poll.Handler = poll.handler 48 | poll.wop = &FDOperator{FD: int(r0)} 49 | 50 | if err = poll.Control(poll.wop, PollReadable); err != nil { 51 | _ = syscall.Close(poll.wop.FD) 52 | _ = syscall.Close(poll.fd) 53 | return nil, err 54 | } 55 | 56 | poll.opcache = newOperatorCache() 57 | return poll, nil 58 | } 59 | 60 | type defaultPoll struct { 61 | pollArgs 62 | fd int // epoll fd 63 | wop *FDOperator // eventfd, wake epoll_wait 64 | buf []byte // read wfd trigger msg 65 | trigger uint32 // trigger flag 66 | m sync.Map //nolint:unused // only used in go:race 67 | opcache *operatorCache // operator cache 68 | // fns for handle events 69 | Reset func(size, caps int) 70 | Handler func(events []epollevent) (closed bool) 71 | } 72 | 73 | type pollArgs struct { 74 | size int 75 | caps int 76 | events []epollevent 77 | barriers []barrier 78 | hups []func(p Poll) error 79 | } 80 | 81 | func (a *pollArgs) reset(size, caps int) { 82 | a.size, a.caps = size, caps 83 | a.events, a.barriers = make([]epollevent, size), make([]barrier, size) 84 | for i := range a.barriers { 85 | a.barriers[i].bs = make([][]byte, a.caps) 86 | a.barriers[i].ivs = make([]syscall.Iovec, a.caps) 87 | } 88 | } 89 | 90 | // Wait implements Poll. 91 | func (p *defaultPoll) Wait() (err error) { 92 | // init 93 | caps, msec, n := barriercap, -1, 0 94 | p.Reset(128, caps) 95 | // wait 96 | for { 97 | if n == p.size && p.size < 128*1024 { 98 | p.Reset(p.size<<1, caps) 99 | } 100 | n, err = EpollWait(p.fd, p.events, msec) 101 | if err != nil && err != syscall.EINTR { 102 | return err 103 | } 104 | if n <= 0 { 105 | msec = -1 106 | runtime.Gosched() 107 | continue 108 | } 109 | msec = 0 110 | if p.Handler(p.events[:n]) { 111 | return nil 112 | } 113 | // we can make sure that there is no op remaining if Handler finished 114 | p.opcache.free() 115 | } 116 | } 117 | 118 | func (p *defaultPoll) handler(events []epollevent) (closed bool) { 119 | var triggerRead, triggerWrite, triggerHup, triggerError bool 120 | var err error 121 | for i := range events { 122 | operator := p.getOperator(0, unsafe.Pointer(&events[i].data)) 123 | if operator == nil || !operator.do() { 124 | continue 125 | } 126 | 127 | var totalRead int 128 | evt := events[i].events 129 | triggerRead = evt&syscall.EPOLLIN != 0 130 | triggerWrite = evt&syscall.EPOLLOUT != 0 131 | triggerHup = evt&(syscall.EPOLLHUP|syscall.EPOLLRDHUP) != 0 132 | triggerError = evt&syscall.EPOLLERR != 0 133 | 134 | // trigger or exit gracefully 135 | if operator.FD == p.wop.FD { 136 | // must clean trigger first 137 | syscall.Read(p.wop.FD, p.buf) 138 | atomic.StoreUint32(&p.trigger, 0) 139 | // if closed & exit 140 | if p.buf[0] > 0 { 141 | syscall.Close(p.wop.FD) 142 | syscall.Close(p.fd) 143 | operator.done() 144 | return true 145 | } 146 | operator.done() 147 | continue 148 | } 149 | 150 | if triggerRead { 151 | if operator.OnRead != nil { 152 | // for non-connection 153 | operator.OnRead(p) 154 | } else if operator.Inputs != nil { 155 | // for connection 156 | bs := operator.Inputs(p.barriers[i].bs) 157 | if len(bs) > 0 { 158 | n, err := ioread(operator.FD, bs, p.barriers[i].ivs) 159 | operator.InputAck(n) 160 | totalRead += n 161 | if err != nil { 162 | p.appendHup(operator) 163 | continue 164 | } 165 | } 166 | } else { 167 | logger.Printf("NETPOLL: operator has critical problem! event=%d operator=%v", evt, operator) 168 | } 169 | } 170 | if triggerHup { 171 | if triggerRead && operator.Inputs != nil { 172 | // read all left data if peer send and close 173 | var leftRead int 174 | // read all left data if peer send and close 175 | if leftRead, err = readall(operator, p.barriers[i]); err != nil && !errors.Is(err, ErrEOF) { 176 | logger.Printf("NETPOLL: readall(fd=%d)=%d before close: %s", operator.FD, total, err.Error()) 177 | } 178 | totalRead += leftRead 179 | } 180 | // only close connection if no further read bytes 181 | if totalRead == 0 { 182 | p.appendHup(operator) 183 | continue 184 | } 185 | } 186 | if triggerError { 187 | // Under block-zerocopy, the kernel may give an error callback, which is not a real error, just an EAGAIN. 188 | // So here we need to check this error, if it is EAGAIN then do nothing, otherwise still mark as hup. 189 | if _, _, _, _, err := syscall.Recvmsg(operator.FD, nil, nil, syscall.MSG_ERRQUEUE); err != syscall.EAGAIN { 190 | p.appendHup(operator) 191 | } else { 192 | operator.done() 193 | } 194 | continue 195 | } 196 | if triggerWrite { 197 | if operator.OnWrite != nil { 198 | // for non-connection 199 | operator.OnWrite(p) 200 | } else if operator.Outputs != nil { 201 | // for connection 202 | bs, supportZeroCopy := operator.Outputs(p.barriers[i].bs) 203 | if len(bs) > 0 { 204 | // TODO: Let the upper layer pass in whether to use ZeroCopy. 205 | n, err := iosend(operator.FD, bs, p.barriers[i].ivs, false && supportZeroCopy) 206 | operator.OutputAck(n) 207 | if err != nil { 208 | p.appendHup(operator) 209 | continue 210 | } 211 | } 212 | } else { 213 | logger.Printf("NETPOLL: operator has critical problem! event=%d operator=%v", evt, operator) 214 | } 215 | } 216 | operator.done() 217 | } 218 | // hup conns together to avoid blocking the poll. 219 | p.onhups() 220 | return false 221 | } 222 | 223 | // Close will write 10000000 224 | func (p *defaultPoll) Close() error { 225 | _, err := syscall.Write(p.wop.FD, []byte{1, 0, 0, 0, 0, 0, 0, 0}) 226 | return err 227 | } 228 | 229 | // Trigger implements Poll. 230 | func (p *defaultPoll) Trigger() error { 231 | if atomic.AddUint32(&p.trigger, 1) > 1 { 232 | return nil 233 | } 234 | // MAX(eventfd) = 0xfffffffffffffffe 235 | _, err := syscall.Write(p.wop.FD, []byte{0, 0, 0, 0, 0, 0, 0, 1}) 236 | return err 237 | } 238 | 239 | // Control implements Poll. 240 | func (p *defaultPoll) Control(operator *FDOperator, event PollEvent) error { 241 | // DON'T move `fd=operator.FD` behind inuse() call, we can only access operator before op.inuse() for avoid race 242 | // G1: G2: 243 | // op.inuse() op.unused() 244 | // op.FD -- T1 op.FD = 0 -- T2 245 | // T1 and T2 may happen together 246 | fd := operator.FD 247 | var op int 248 | var evt epollevent 249 | p.setOperator(unsafe.Pointer(&evt.data), operator) 250 | switch event { 251 | case PollReadable: // server accept a new connection and wait read 252 | operator.inuse() 253 | op, evt.events = syscall.EPOLL_CTL_ADD, syscall.EPOLLIN|syscall.EPOLLRDHUP|syscall.EPOLLERR 254 | case PollWritable: // client create a new connection and wait connect finished 255 | operator.inuse() 256 | op, evt.events = syscall.EPOLL_CTL_ADD, EPOLLET|syscall.EPOLLOUT|syscall.EPOLLRDHUP|syscall.EPOLLERR 257 | case PollDetach: // deregister 258 | p.delOperator(operator) 259 | op, evt.events = syscall.EPOLL_CTL_DEL, syscall.EPOLLIN|syscall.EPOLLOUT|syscall.EPOLLRDHUP|syscall.EPOLLERR 260 | case PollR2RW: // connection wait read/write 261 | op, evt.events = syscall.EPOLL_CTL_MOD, syscall.EPOLLIN|syscall.EPOLLOUT|syscall.EPOLLRDHUP|syscall.EPOLLERR 262 | case PollRW2R: // connection wait read 263 | op, evt.events = syscall.EPOLL_CTL_MOD, syscall.EPOLLIN|syscall.EPOLLRDHUP|syscall.EPOLLERR 264 | } 265 | return EpollCtl(p.fd, op, fd, &evt) 266 | } 267 | -------------------------------------------------------------------------------- /poll_default_linux_norace.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux && !race 16 | // +build linux,!race 17 | 18 | package netpoll 19 | 20 | import "unsafe" 21 | 22 | func (p *defaultPoll) getOperator(fd int, ptr unsafe.Pointer) *FDOperator { 23 | return *(**FDOperator)(ptr) 24 | } 25 | 26 | func (p *defaultPoll) setOperator(ptr unsafe.Pointer, operator *FDOperator) { 27 | *(**FDOperator)(ptr) = operator 28 | } 29 | 30 | func (p *defaultPoll) delOperator(operator *FDOperator) { 31 | } 32 | -------------------------------------------------------------------------------- /poll_default_linux_race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux && race 16 | // +build linux,race 17 | 18 | package netpoll 19 | 20 | import "unsafe" 21 | 22 | type eventdata struct { 23 | fd int32 24 | pad int32 25 | } 26 | 27 | func (p *defaultPoll) getOperator(fd int, ptr unsafe.Pointer) *FDOperator { 28 | data := *(*eventdata)(ptr) 29 | tmp, _ := p.m.Load(int(data.fd)) 30 | if tmp == nil { 31 | return nil 32 | } 33 | return tmp.(*FDOperator) 34 | } 35 | 36 | func (p *defaultPoll) setOperator(ptr unsafe.Pointer, operator *FDOperator) { 37 | *(*eventdata)(ptr) = eventdata{fd: int32(operator.FD)} 38 | p.m.Store(operator.FD, operator) 39 | } 40 | 41 | func (p *defaultPoll) delOperator(operator *FDOperator) { 42 | p.m.Delete(operator.FD) 43 | } 44 | -------------------------------------------------------------------------------- /poll_loadbalance.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "sync/atomic" 19 | 20 | "github.com/bytedance/gopkg/lang/fastrand" 21 | ) 22 | 23 | // LoadBalance sets the load balancing method. 24 | type LoadBalance int 25 | 26 | const ( 27 | // RoundRobin requests that connections are distributed to a Poll 28 | // in a round-robin fashion. 29 | RoundRobin LoadBalance = iota 30 | // Random requests that connections are randomly distributed. 31 | Random 32 | ) 33 | 34 | // loadbalance sets the load balancing method for []*polls 35 | type loadbalance interface { 36 | LoadBalance() LoadBalance 37 | // Pick choose the most qualified Poll 38 | Pick() (poll Poll) 39 | 40 | Rebalance(polls []Poll) 41 | } 42 | 43 | func newLoadbalance(lb LoadBalance, polls []Poll) loadbalance { 44 | switch lb { 45 | case RoundRobin: 46 | return newRoundRobinLB(polls) 47 | case Random: 48 | return newRandomLB(polls) 49 | } 50 | return newRoundRobinLB(polls) 51 | } 52 | 53 | func newRandomLB(polls []Poll) loadbalance { 54 | return &randomLB{polls: polls, pollSize: len(polls)} 55 | } 56 | 57 | type randomLB struct { 58 | polls []Poll 59 | pollSize int 60 | } 61 | 62 | func (b *randomLB) LoadBalance() LoadBalance { 63 | return Random 64 | } 65 | 66 | func (b *randomLB) Pick() (poll Poll) { 67 | idx := fastrand.Intn(b.pollSize) 68 | return b.polls[idx] 69 | } 70 | 71 | func (b *randomLB) Rebalance(polls []Poll) { 72 | b.polls, b.pollSize = polls, len(polls) 73 | } 74 | 75 | func newRoundRobinLB(polls []Poll) loadbalance { 76 | return &roundRobinLB{polls: polls, pollSize: len(polls)} 77 | } 78 | 79 | type roundRobinLB struct { 80 | polls []Poll 81 | accepted uintptr // accept counter 82 | pollSize int 83 | } 84 | 85 | func (b *roundRobinLB) LoadBalance() LoadBalance { 86 | return RoundRobin 87 | } 88 | 89 | func (b *roundRobinLB) Pick() (poll Poll) { 90 | idx := int(atomic.AddUintptr(&b.accepted, 1)) % b.pollSize 91 | return b.polls[idx] 92 | } 93 | 94 | func (b *roundRobinLB) Rebalance(polls []Poll) { 95 | b.polls, b.pollSize = polls, len(polls) 96 | } 97 | -------------------------------------------------------------------------------- /poll_manager.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "fmt" 22 | "runtime" 23 | "sync/atomic" 24 | ) 25 | 26 | const ( 27 | managerUninitialized = iota 28 | managerInitializing 29 | managerInitialized 30 | ) 31 | 32 | func newManager(numLoops int) *manager { 33 | m := new(manager) 34 | m.SetLoadBalance(RoundRobin) 35 | m.SetNumLoops(numLoops) 36 | return m 37 | } 38 | 39 | // LoadBalance is used to do load balancing among multiple pollers. 40 | // a single poller may not be optimal if the number of cores is large (40C+). 41 | type manager struct { 42 | numLoops int32 43 | status int32 // 0: uninitialized, 1: initializing, 2: initialized 44 | balance loadbalance // load balancing method 45 | polls []Poll // all the polls 46 | } 47 | 48 | // SetNumLoops will return error when set numLoops < 1 49 | func (m *manager) SetNumLoops(numLoops int) (err error) { 50 | if numLoops < 1 { 51 | return fmt.Errorf("set invalid numLoops[%d]", numLoops) 52 | } 53 | // note: set new numLoops first and then change the status 54 | atomic.StoreInt32(&m.numLoops, int32(numLoops)) 55 | atomic.StoreInt32(&m.status, managerUninitialized) 56 | return nil 57 | } 58 | 59 | // SetLoadBalance set load balance. 60 | func (m *manager) SetLoadBalance(lb LoadBalance) error { 61 | if m.balance != nil && m.balance.LoadBalance() == lb { 62 | return nil 63 | } 64 | m.balance = newLoadbalance(lb, m.polls) 65 | return nil 66 | } 67 | 68 | // Close release all resources. 69 | func (m *manager) Close() (err error) { 70 | for _, poll := range m.polls { 71 | err = poll.Close() 72 | } 73 | m.numLoops = 0 74 | m.balance = nil 75 | m.polls = nil 76 | return err 77 | } 78 | 79 | // Run all pollers. 80 | func (m *manager) Run() (err error) { 81 | defer func() { 82 | if err != nil { 83 | _ = m.Close() 84 | } 85 | }() 86 | 87 | numLoops := int(atomic.LoadInt32(&m.numLoops)) 88 | if numLoops == len(m.polls) { 89 | return nil 90 | } 91 | polls := make([]Poll, numLoops) 92 | if numLoops < len(m.polls) { 93 | // shrink polls 94 | copy(polls, m.polls[:numLoops]) 95 | for idx := numLoops; idx < len(m.polls); idx++ { 96 | // close redundant polls 97 | if err = m.polls[idx].Close(); err != nil { 98 | logger.Printf("NETPOLL: poller close failed: %v\n", err) 99 | } 100 | } 101 | } else { 102 | // growth polls 103 | copy(polls, m.polls) 104 | for idx := len(m.polls); idx < numLoops; idx++ { 105 | var poll Poll 106 | poll, err = openPoll() 107 | if err != nil { 108 | return err 109 | } 110 | polls[idx] = poll 111 | go poll.Wait() 112 | } 113 | } 114 | m.polls = polls 115 | 116 | // LoadBalance must be set before calling Run, otherwise it will panic. 117 | m.balance.Rebalance(m.polls) 118 | return nil 119 | } 120 | 121 | // Reset pollers, this operation is very dangerous, please make sure to do this when calling ! 122 | func (m *manager) Reset() error { 123 | for _, poll := range m.polls { 124 | poll.Close() 125 | } 126 | m.polls = nil 127 | return m.Run() 128 | } 129 | 130 | // Pick will select the poller for use each time based on the LoadBalance. 131 | func (m *manager) Pick() Poll { 132 | START: 133 | // fast path 134 | if atomic.LoadInt32(&m.status) == managerInitialized { 135 | return m.balance.Pick() 136 | } 137 | // slow path 138 | // try to get initializing lock failed, wait others finished the init work, and try again 139 | if !atomic.CompareAndSwapInt32(&m.status, managerUninitialized, managerInitializing) { 140 | runtime.Gosched() 141 | goto START 142 | } 143 | // adjust polls 144 | // m.Run() will finish very quickly, so will not many goroutines block on Pick. 145 | _ = m.Run() 146 | 147 | if !atomic.CompareAndSwapInt32(&m.status, managerInitializing, managerInitialized) { 148 | // SetNumLoops called during m.Run() which cause CAS failed 149 | // The polls will be adjusted next Pick 150 | } 151 | return m.balance.Pick() 152 | } 153 | -------------------------------------------------------------------------------- /poll_manager_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "runtime" 22 | "sync" 23 | "testing" 24 | ) 25 | 26 | func TestPollManager(t *testing.T) { 27 | r, w := GetSysFdPairs() 28 | rconn, wconn := &connection{}, &connection{} 29 | err := rconn.init(&netFD{fd: r}, nil) 30 | MustNil(t, err) 31 | err = wconn.init(&netFD{fd: w}, nil) 32 | MustNil(t, err) 33 | 34 | msg := []byte("hello world") 35 | n, err := wconn.Write(msg) 36 | MustNil(t, err) 37 | Equal(t, n, len(msg)) 38 | 39 | p, err := rconn.Reader().Next(n) 40 | MustNil(t, err) 41 | Equal(t, string(p), string(msg)) 42 | 43 | err = wconn.Close() 44 | MustNil(t, err) 45 | for rconn.IsActive() || wconn.IsActive() { 46 | runtime.Gosched() 47 | } 48 | } 49 | 50 | func TestPollManagerReset(t *testing.T) { 51 | n := pollmanager.numLoops 52 | err := pollmanager.Reset() 53 | MustNil(t, err) 54 | Equal(t, len(pollmanager.polls), int(n)) 55 | } 56 | 57 | func TestPollManagerSetNumLoops(t *testing.T) { 58 | pm := newManager(1) 59 | 60 | startGs := runtime.NumGoroutine() 61 | poll := pm.Pick() 62 | newGs := runtime.NumGoroutine() 63 | Assert(t, poll != nil) 64 | Assert(t, newGs-startGs >= 1, newGs, startGs) 65 | t.Logf("old=%d, new=%d", startGs, newGs) 66 | 67 | // change pollers 68 | oldGs := newGs 69 | err := pm.SetNumLoops(100) 70 | MustNil(t, err) 71 | newGs = runtime.NumGoroutine() 72 | t.Logf("old=%d, new=%d", oldGs, newGs) 73 | Assert(t, newGs == oldGs) 74 | 75 | // trigger polls adjustment 76 | var wg sync.WaitGroup 77 | finish := make(chan struct{}) 78 | for i := 0; i < 32; i++ { 79 | wg.Add(1) 80 | go func() { 81 | poll := pm.Pick() 82 | Assert(t, poll != nil) 83 | Assert(t, len(pm.polls) == 100) 84 | wg.Done() 85 | <-finish // hold goroutines 86 | }() 87 | } 88 | wg.Wait() 89 | close(finish) 90 | } 91 | -------------------------------------------------------------------------------- /poll_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "runtime" 22 | "sync" 23 | "sync/atomic" 24 | "syscall" 25 | "testing" 26 | "time" 27 | ) 28 | 29 | // Trigger has been validated, but no usage for now. 30 | func TestPollTrigger(t *testing.T) { 31 | t.Skip() 32 | var trigger int 33 | stop := make(chan error) 34 | p, err := openDefaultPoll() 35 | MustNil(t, err) 36 | 37 | go func() { 38 | stop <- p.Wait() 39 | }() 40 | 41 | time.Sleep(time.Millisecond) 42 | Equal(t, trigger, 0) 43 | p.Trigger() 44 | time.Sleep(time.Millisecond) 45 | Equal(t, trigger, 1) 46 | p.Trigger() 47 | time.Sleep(time.Millisecond) 48 | Equal(t, trigger, 2) 49 | 50 | p.Close() 51 | err = <-stop 52 | MustNil(t, err) 53 | } 54 | 55 | func TestPollMod(t *testing.T) { 56 | var rn, wn, hn int32 57 | read := func(p Poll) error { 58 | atomic.AddInt32(&rn, 1) 59 | return nil 60 | } 61 | write := func(p Poll) error { 62 | atomic.AddInt32(&wn, 1) 63 | return nil 64 | } 65 | hup := func(p Poll) error { 66 | atomic.AddInt32(&hn, 1) 67 | return nil 68 | } 69 | stop := make(chan error) 70 | p, err := openDefaultPoll() 71 | MustNil(t, err) 72 | go func() { 73 | stop <- p.Wait() 74 | }() 75 | 76 | rfd, wfd := GetSysFdPairs() 77 | rop := &FDOperator{FD: rfd, OnRead: read, OnWrite: write, OnHup: hup, poll: p} 78 | wop := &FDOperator{FD: wfd, OnRead: read, OnWrite: write, OnHup: hup, poll: p} 79 | var r, w, h int32 80 | r, w, h = atomic.LoadInt32(&rn), atomic.LoadInt32(&wn), atomic.LoadInt32(&hn) 81 | Assert(t, r == 0 && w == 0 && h == 0, r, w, h) 82 | err = p.Control(rop, PollReadable) 83 | MustNil(t, err) 84 | r, w, h = atomic.LoadInt32(&rn), atomic.LoadInt32(&wn), atomic.LoadInt32(&hn) 85 | Assert(t, r == 0 && w == 0 && h == 0, r, w, h) 86 | 87 | err = p.Control(wop, PollWritable) // trigger one shot 88 | MustNil(t, err) 89 | for atomic.LoadInt32(&wn) == 0 { 90 | runtime.Gosched() 91 | } 92 | r, w, h = atomic.LoadInt32(&rn), atomic.LoadInt32(&wn), atomic.LoadInt32(&hn) 93 | Assert(t, r == 0 && w >= 1 && h == 0, r, w, h) 94 | 95 | err = p.Control(rop, PollR2RW) // trigger write 96 | MustNil(t, err) 97 | for atomic.LoadInt32(&wn) <= 1 { 98 | runtime.Gosched() 99 | } 100 | r, w, h = atomic.LoadInt32(&rn), atomic.LoadInt32(&wn), atomic.LoadInt32(&hn) 101 | Assert(t, r == 0 && w >= 2 && h == 0, r, w, h) 102 | 103 | // close wfd, then trigger hup rfd 104 | err = syscall.Close(wfd) // trigger hup 105 | MustNil(t, err) 106 | for atomic.LoadInt32(&hn) == 0 { 107 | runtime.Gosched() 108 | } 109 | w, h = atomic.LoadInt32(&wn), atomic.LoadInt32(&hn) 110 | Assert(t, w >= 2 && h >= 1, r, w, h) 111 | 112 | p.Close() 113 | err = <-stop 114 | MustNil(t, err) 115 | } 116 | 117 | func TestPollClose(t *testing.T) { 118 | p, err := openDefaultPoll() 119 | MustNil(t, err) 120 | var wg sync.WaitGroup 121 | wg.Add(1) 122 | go func() { 123 | p.Wait() 124 | wg.Done() 125 | }() 126 | p.Close() 127 | wg.Wait() 128 | } 129 | 130 | func BenchmarkPollMod(b *testing.B) { 131 | b.StopTimer() 132 | p, _ := openDefaultPoll() 133 | r, _ := GetSysFdPairs() 134 | operator := &FDOperator{FD: r} 135 | p.Control(operator, PollReadable) 136 | 137 | // benchmark 138 | b.ReportAllocs() 139 | b.StartTimer() 140 | for i := 0; i < b.N; i++ { 141 | p.Control(operator, PollR2RW) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /sys_epoll_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !arm64 && !loong64 16 | // +build !arm64,!loong64 17 | 18 | package netpoll 19 | 20 | import ( 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | const EPOLLET = -syscall.EPOLLET 26 | 27 | type epollevent struct { 28 | events uint32 29 | data [8]byte // unaligned uintptr 30 | } 31 | 32 | // EpollCreate implements epoll_create1. 33 | func EpollCreate(flag int) (fd int, err error) { 34 | var r0 uintptr 35 | r0, _, err = syscall.RawSyscall(syscall.SYS_EPOLL_CREATE1, uintptr(flag), 0, 0) 36 | if err == syscall.Errno(0) { 37 | err = nil 38 | } 39 | return int(r0), err 40 | } 41 | 42 | // EpollCtl implements epoll_ctl. 43 | func EpollCtl(epfd, op, fd int, event *epollevent) (err error) { 44 | _, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0) 45 | if err == syscall.Errno(0) { 46 | err = nil 47 | } 48 | return err 49 | } 50 | 51 | // EpollWait implements epoll_wait. 52 | func EpollWait(epfd int, events []epollevent, msec int) (n int, err error) { 53 | var r0 uintptr 54 | _p0 := unsafe.Pointer(&events[0]) 55 | if msec == 0 { 56 | r0, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), 0, 0, 0) 57 | } else { 58 | r0, _, err = syscall.Syscall6(syscall.SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) 59 | } 60 | if err == syscall.Errno(0) { 61 | err = nil 62 | } 63 | return int(r0), err 64 | } 65 | -------------------------------------------------------------------------------- /sys_epoll_linux_arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "syscall" 19 | "unsafe" 20 | ) 21 | 22 | const EPOLLET = syscall.EPOLLET 23 | 24 | type epollevent struct { 25 | events uint32 26 | _ int32 27 | data [8]byte // unaligned uintptr 28 | } 29 | 30 | // EpollCreate implements epoll_create1. 31 | func EpollCreate(flag int) (fd int, err error) { 32 | var r0 uintptr 33 | r0, _, err = syscall.RawSyscall(syscall.SYS_EPOLL_CREATE1, uintptr(flag), 0, 0) 34 | if err == syscall.Errno(0) { 35 | err = nil 36 | } 37 | return int(r0), err 38 | } 39 | 40 | // EpollCtl implements epoll_ctl. 41 | func EpollCtl(epfd int, op int, fd int, event *epollevent) (err error) { 42 | _, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0) 43 | if err == syscall.Errno(0) { 44 | err = nil 45 | } 46 | return err 47 | } 48 | 49 | // EpollWait implements epoll_wait. 50 | func EpollWait(epfd int, events []epollevent, msec int) (n int, err error) { 51 | var r0 uintptr 52 | _p0 := unsafe.Pointer(&events[0]) 53 | if msec == 0 { 54 | r0, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), 0, 0, 0) 55 | } else { 56 | r0, _, err = syscall.Syscall6(syscall.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) 57 | } 58 | if err == syscall.Errno(0) { 59 | err = nil 60 | } 61 | return int(r0), err 62 | } 63 | -------------------------------------------------------------------------------- /sys_epoll_linux_loong64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build linux && loong64 16 | // +build linux,loong64 17 | 18 | package netpoll 19 | 20 | import ( 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | const EPOLLET = syscall.EPOLLET 26 | 27 | type epollevent struct { 28 | events uint32 29 | _ int32 30 | data [8]byte // unaligned uintptr 31 | } 32 | 33 | // EpollCreate implements epoll_create1. 34 | func EpollCreate(flag int) (fd int, err error) { 35 | var r0 uintptr 36 | r0, _, err = syscall.RawSyscall(syscall.SYS_EPOLL_CREATE1, uintptr(flag), 0, 0) 37 | if err == syscall.Errno(0) { 38 | err = nil 39 | } 40 | return int(r0), err 41 | } 42 | 43 | // EpollCtl implements epoll_ctl. 44 | func EpollCtl(epfd int, op int, fd int, event *epollevent) (err error) { 45 | _, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0) 46 | if err == syscall.Errno(0) { 47 | err = nil 48 | } 49 | return err 50 | } 51 | 52 | // EpollWait implements epoll_wait. 53 | func EpollWait(epfd int, events []epollevent, msec int) (n int, err error) { 54 | var r0 uintptr 55 | _p0 := unsafe.Pointer(&events[0]) 56 | if msec == 0 { 57 | r0, _, err = syscall.RawSyscall6(syscall.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), 0, 0, 0) 58 | } else { 59 | r0, _, err = syscall.Syscall6(syscall.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0) 60 | } 61 | if err == syscall.Errno(0) { 62 | err = nil 63 | } 64 | return int(r0), err 65 | } 66 | -------------------------------------------------------------------------------- /sys_exec.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "math" 22 | "os" 23 | "syscall" 24 | "unsafe" 25 | ) 26 | 27 | // GetSysFdPairs creates and returns the fds of a pair of sockets. 28 | func GetSysFdPairs() (r, w int) { 29 | fds, _ := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0) 30 | return fds[0], fds[1] 31 | } 32 | 33 | // setTCPNoDelay set the TCP_NODELAY flag on socket 34 | func setTCPNoDelay(fd int, b bool) (err error) { 35 | return syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, boolint(b)) 36 | } 37 | 38 | // Wrapper around the socket system call that marks the returned file 39 | // descriptor as nonblocking and close-on-exec. 40 | func sysSocket(family, sotype, proto int) (int, error) { 41 | // See ../syscall/exec_unix.go for description of ForkLock. 42 | syscall.ForkLock.RLock() 43 | s, err := syscall.Socket(family, sotype, proto) 44 | if err == nil { 45 | syscall.CloseOnExec(s) 46 | } 47 | syscall.ForkLock.RUnlock() 48 | if err != nil { 49 | return -1, os.NewSyscallError("socket", err) 50 | } 51 | if err = syscall.SetNonblock(s, true); err != nil { 52 | syscall.Close(s) 53 | return -1, os.NewSyscallError("setnonblock", err) 54 | } 55 | return s, nil 56 | } 57 | 58 | const barriercap = 32 59 | 60 | type barrier struct { 61 | bs [][]byte 62 | ivs []syscall.Iovec 63 | } 64 | 65 | // writev wraps the writev system call. 66 | func writev(fd int, bs [][]byte, ivs []syscall.Iovec) (n int, err error) { 67 | iovLen := iovecs(bs, ivs) 68 | if iovLen == 0 { 69 | return 0, nil 70 | } 71 | // syscall 72 | r, _, e := syscall.RawSyscall(syscall.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&ivs[0])), uintptr(iovLen)) 73 | resetIovecs(bs, ivs[:iovLen]) 74 | if e != 0 { 75 | return int(r), syscall.Errno(e) 76 | } 77 | return int(r), nil 78 | } 79 | 80 | // readv wraps the readv system call. 81 | // return 0, nil means EOF. 82 | func readv(fd int, bs [][]byte, ivs []syscall.Iovec) (n int, err error) { 83 | iovLen := iovecs(bs, ivs) 84 | if iovLen == 0 { 85 | return 0, nil 86 | } 87 | // syscall 88 | r, _, e := syscall.RawSyscall(syscall.SYS_READV, uintptr(fd), uintptr(unsafe.Pointer(&ivs[0])), uintptr(iovLen)) 89 | resetIovecs(bs, ivs[:iovLen]) 90 | if e != 0 { 91 | return int(r), syscall.Errno(e) 92 | } 93 | return int(r), nil 94 | } 95 | 96 | // TODO: read from sysconf(_SC_IOV_MAX)? The Linux default is 97 | // 98 | // 1024 and this seems conservative enough for now. Darwin's 99 | // UIO_MAXIOV also seems to be 1024. 100 | // 101 | // iovecs limit length to 2GB(2^31) 102 | func iovecs(bs [][]byte, ivs []syscall.Iovec) (iovLen int) { 103 | totalLen := 0 104 | for i := 0; i < len(bs); i++ { 105 | chunk := bs[i] 106 | l := len(chunk) 107 | if l == 0 { 108 | continue 109 | } 110 | ivs[iovLen].Base = &chunk[0] 111 | totalLen += l 112 | if totalLen < math.MaxInt32 { 113 | ivs[iovLen].SetLen(l) 114 | iovLen++ 115 | } else { 116 | newLen := math.MaxInt32 - totalLen + l 117 | ivs[iovLen].SetLen(newLen) 118 | iovLen++ 119 | return iovLen 120 | } 121 | } 122 | 123 | return iovLen 124 | } 125 | 126 | func resetIovecs(bs [][]byte, ivs []syscall.Iovec) { 127 | for i := 0; i < len(bs); i++ { 128 | bs[i] = nil 129 | } 130 | for i := 0; i < len(ivs); i++ { 131 | ivs[i].Base = nil 132 | } 133 | } 134 | 135 | // Boolean to int. 136 | func boolint(b bool) int { 137 | if b { 138 | return 1 139 | } 140 | return 0 141 | } 142 | -------------------------------------------------------------------------------- /sys_exec_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !windows 16 | // +build !windows 17 | 18 | package netpoll 19 | 20 | import ( 21 | "math" 22 | "syscall" 23 | "testing" 24 | ) 25 | 26 | func TestIovecs(t *testing.T) { 27 | var got int 28 | var bs [][]byte 29 | ivs := make([]syscall.Iovec, 4) 30 | 31 | // case 1 32 | bs = [][]byte{ 33 | make([]byte, 10), 34 | make([]byte, 20), 35 | make([]byte, 30), 36 | make([]byte, 40), 37 | } 38 | got = iovecs(bs, ivs) 39 | Equal(t, got, 4) 40 | Equal(t, int(ivs[0].Len), 10) 41 | Equal(t, int(ivs[1].Len), 20) 42 | Equal(t, int(ivs[2].Len), 30) 43 | Equal(t, int(ivs[3].Len), 40) 44 | 45 | // case 2 46 | resetIovecs(bs, ivs) 47 | bs = [][]byte{ 48 | make([]byte, math.MaxInt32+100), 49 | make([]byte, 20), 50 | make([]byte, 30), 51 | make([]byte, 40), 52 | } 53 | got = iovecs(bs, ivs) 54 | Equal(t, got, 1) 55 | Equal(t, int(ivs[0].Len), math.MaxInt32) 56 | Assert(t, ivs[1].Base == nil) 57 | Assert(t, ivs[2].Base == nil) 58 | Assert(t, ivs[3].Base == nil) 59 | 60 | // case 3 61 | resetIovecs(bs, ivs) 62 | bs = [][]byte{ 63 | make([]byte, 10), 64 | make([]byte, 20), 65 | make([]byte, math.MaxInt32+100), 66 | make([]byte, 40), 67 | } 68 | got = iovecs(bs, ivs) 69 | Equal(t, got, 3) 70 | Equal(t, int(ivs[0].Len), 10) 71 | Equal(t, int(ivs[1].Len), 20) 72 | Equal(t, int(ivs[2].Len), math.MaxInt32-30) 73 | Assert(t, ivs[3].Base == nil) 74 | } 75 | 76 | func TestWritev(t *testing.T) { 77 | r, w := GetSysFdPairs() 78 | barrier := barrier{} 79 | barrier.bs = [][]byte{ 80 | []byte(""), // len=0 81 | []byte("first line"), // len=10 82 | []byte("second line"), // len=11 83 | []byte("third line"), // len=10 84 | } 85 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 86 | wn, err := writev(w, barrier.bs, barrier.ivs) 87 | MustNil(t, err) 88 | Equal(t, wn, 31) 89 | p := make([]byte, 50) 90 | rn, err := syscall.Read(r, p) 91 | MustNil(t, err) 92 | Equal(t, rn, 31) 93 | t.Logf("READ %s", p[:rn]) 94 | } 95 | 96 | func TestReadv(t *testing.T) { 97 | r, w := GetSysFdPairs() 98 | vs := [][]byte{ 99 | []byte("first line"), // len=10 100 | []byte("second line"), // len=11 101 | []byte("third line"), // len=10 102 | } 103 | w1, _ := syscall.Write(w, vs[0]) 104 | w2, _ := syscall.Write(w, vs[1]) 105 | w3, _ := syscall.Write(w, vs[2]) 106 | Equal(t, w1+w2+w3, 31) 107 | 108 | barrier := barrier{ 109 | bs: make([][]byte, 4), 110 | } 111 | res := [][]byte{ 112 | make([]byte, 0), 113 | make([]byte, 10), 114 | make([]byte, 11), 115 | make([]byte, 10), 116 | } 117 | copy(barrier.bs, res) 118 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 119 | rn, err := readv(r, barrier.bs, barrier.ivs) 120 | MustNil(t, err) 121 | Equal(t, rn, 31) 122 | for i, v := range res { 123 | t.Logf("READ [%d] %s", i, v) 124 | } 125 | } 126 | 127 | func TestSendmsg(t *testing.T) { 128 | r, w := GetSysFdPairs() 129 | barrier := barrier{} 130 | barrier.bs = [][]byte{ 131 | []byte(""), // len=0 132 | []byte("first line"), // len=10 133 | []byte("second line"), // len=11 134 | []byte("third line"), // len=10 135 | } 136 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 137 | wn, err := sendmsg(w, barrier.bs, barrier.ivs, false) 138 | MustNil(t, err) 139 | Equal(t, wn, 31) 140 | p := make([]byte, 50) 141 | rn, err := syscall.Read(r, p) 142 | MustNil(t, err) 143 | Equal(t, rn, 31) 144 | t.Logf("READ %s", p[:rn]) 145 | } 146 | 147 | func BenchmarkWrite(b *testing.B) { 148 | b.StopTimer() 149 | r, w := GetSysFdPairs() 150 | message := "hello, world!" 151 | size := 5 152 | 153 | go func() { 154 | buffer := make([]byte, 13) 155 | for { 156 | syscall.Read(r, buffer) 157 | } 158 | }() 159 | 160 | // benchmark 161 | b.ReportAllocs() 162 | b.StartTimer() 163 | for i := 0; i < b.N; i++ { 164 | wmsg := make([]byte, len(message)*5) 165 | var n int 166 | for j := 0; j < size; j++ { 167 | n += copy(wmsg[n:], message) 168 | } 169 | syscall.Write(w, wmsg) 170 | } 171 | } 172 | 173 | func BenchmarkWritev(b *testing.B) { 174 | b.StopTimer() 175 | r, w := GetSysFdPairs() 176 | message := "hello, world!" 177 | size := 5 178 | barrier := barrier{} 179 | barrier.bs = make([][]byte, size) 180 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 181 | for i := range barrier.bs { 182 | barrier.bs[i] = make([]byte, len(message)) 183 | } 184 | 185 | go func() { 186 | buffer := make([]byte, 13) 187 | for { 188 | syscall.Read(r, buffer) 189 | } 190 | }() 191 | 192 | // benchmark 193 | b.ReportAllocs() 194 | b.StartTimer() 195 | for i := 0; i < b.N; i++ { 196 | writev(w, barrier.bs, barrier.ivs) 197 | } 198 | } 199 | 200 | func BenchmarkSendmsg(b *testing.B) { 201 | b.StopTimer() 202 | r, w := GetSysFdPairs() 203 | message := "hello, world!" 204 | size := 5 205 | barrier := barrier{} 206 | barrier.bs = make([][]byte, size) 207 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 208 | for i := range barrier.bs { 209 | barrier.bs[i] = make([]byte, len(message)) 210 | } 211 | 212 | go func() { 213 | buffer := make([]byte, 13) 214 | for { 215 | syscall.Read(r, buffer) 216 | } 217 | }() 218 | 219 | // benchmark 220 | b.ReportAllocs() 221 | b.StartTimer() 222 | for i := 0; i < b.N; i++ { 223 | sendmsg(w, barrier.bs, barrier.ivs, false) 224 | } 225 | } 226 | 227 | func BenchmarkRead(b *testing.B) { 228 | b.StopTimer() 229 | r, w := GetSysFdPairs() 230 | message := "hello, world!" 231 | size := 5 232 | wmsg := make([]byte, size*len(message)) 233 | var n int 234 | for j := 0; j < size; j++ { 235 | n += copy(wmsg[n:], message) 236 | } 237 | 238 | go func() { 239 | for { 240 | syscall.Write(w, wmsg) 241 | } 242 | }() 243 | 244 | // benchmark 245 | b.ReportAllocs() 246 | b.StartTimer() 247 | for i := 0; i < b.N; i++ { 248 | buffer := make([]byte, size*len(message)) 249 | syscall.Read(r, buffer) 250 | } 251 | } 252 | 253 | func BenchmarkReadv(b *testing.B) { 254 | b.StopTimer() 255 | r, w := GetSysFdPairs() 256 | message := "hello, world!" 257 | size := 5 258 | barrier := barrier{} 259 | barrier.bs = make([][]byte, size) 260 | barrier.ivs = make([]syscall.Iovec, len(barrier.bs)) 261 | for i := range barrier.bs { 262 | barrier.bs[i] = make([]byte, len(message)) 263 | } 264 | 265 | go func() { 266 | for { 267 | writeAll(w, []byte(message)) 268 | } 269 | }() 270 | 271 | // benchmark 272 | b.ReportAllocs() 273 | b.StartTimer() 274 | for i := 0; i < b.N; i++ { 275 | readv(r, barrier.bs, barrier.ivs) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /sys_keepalive_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import "syscall" 18 | 19 | // SetKeepAlive sets the keepalive for the connection 20 | func SetKeepAlive(fd, secs int) error { 21 | if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { 22 | return err 23 | } 24 | switch err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, 0x101, secs); err { 25 | case nil, syscall.ENOPROTOOPT: // OS X 10.7 and earlier don't support this option 26 | default: 27 | return err 28 | } 29 | return syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPALIVE, secs) 30 | } 31 | -------------------------------------------------------------------------------- /sys_keepalive_openbsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | // SetKeepAlive sets the keepalive for the connection 18 | func SetKeepAlive(fd, secs int) error { 19 | // OpenBSD has no user-settable per-socket TCP keepalive options. 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /sys_keepalive_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build netbsd || freebsd || dragonfly || linux 16 | // +build netbsd freebsd dragonfly linux 17 | 18 | package netpoll 19 | 20 | import "syscall" 21 | 22 | // just support ipv4 23 | func SetKeepAlive(fd, secs int) error { 24 | // open keep-alive 25 | if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE, 1); err != nil { 26 | return err 27 | } 28 | // tcp_keepalive_intvl 29 | if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil { 30 | return err 31 | } 32 | // tcp_keepalive_probes 33 | // if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPCNT, 1); err != nil { 34 | // return err 35 | // } 36 | // tcp_keepalive_time 37 | return syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs) 38 | } 39 | -------------------------------------------------------------------------------- /sys_sendmsg_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 16 | // +build darwin dragonfly freebsd netbsd openbsd 17 | 18 | package netpoll 19 | 20 | import ( 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | var supportZeroCopySend bool 26 | 27 | // sendmsg wraps the sendmsg system call. 28 | // Must len(iovs) >= len(vs) 29 | func sendmsg(fd int, bs [][]byte, ivs []syscall.Iovec, zerocopy bool) (n int, err error) { 30 | iovLen := iovecs(bs, ivs) 31 | if iovLen == 0 { 32 | return 0, nil 33 | } 34 | msghdr := syscall.Msghdr{ 35 | Iov: &ivs[0], 36 | Iovlen: int32(iovLen), 37 | } 38 | // flags = syscall.MSG_DONTWAIT 39 | r, _, e := syscall.RawSyscall(syscall.SYS_SENDMSG, uintptr(fd), uintptr(unsafe.Pointer(&msghdr)), uintptr(0)) 40 | resetIovecs(bs, ivs[:iovLen]) 41 | if e != 0 { 42 | return int(r), syscall.Errno(e) 43 | } 44 | return int(r), nil 45 | } 46 | -------------------------------------------------------------------------------- /sys_sendmsg_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "syscall" 19 | "unsafe" 20 | ) 21 | 22 | //func init() { 23 | // err := syscall.Setrlimit(8, &syscall.Rlimit{ 24 | // Cur: 0xffffffff, 25 | // Max: 0xffffffff, 26 | // }) 27 | // if err != nil { 28 | // panic(err) 29 | // } 30 | //} 31 | 32 | // sendmsg wraps the sendmsg system call. 33 | // Must len(iovs) >= len(vs) 34 | func sendmsg(fd int, bs [][]byte, ivs []syscall.Iovec, zerocopy bool) (n int, err error) { 35 | iovLen := iovecs(bs, ivs) 36 | if iovLen == 0 { 37 | return 0, nil 38 | } 39 | msghdr := syscall.Msghdr{ 40 | Iov: &ivs[0], 41 | Iovlen: uint64(iovLen), 42 | } 43 | var flags uintptr 44 | if zerocopy { 45 | flags = MSG_ZEROCOPY 46 | } 47 | r, _, e := syscall.RawSyscall(syscall.SYS_SENDMSG, uintptr(fd), uintptr(unsafe.Pointer(&msghdr)), flags) 48 | resetIovecs(bs, ivs[:iovLen]) 49 | if e != 0 { 50 | return int(r), syscall.Errno(e) 51 | } 52 | return int(r), nil 53 | } 54 | -------------------------------------------------------------------------------- /sys_sockopt_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file may have been modified by CloudWeGo authors. (“CloudWeGo Modifications”). 6 | // All CloudWeGo Modifications are Copyright 2022 CloudWeGo authors. 7 | 8 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 9 | // +build darwin dragonfly freebsd netbsd openbsd 10 | 11 | package netpoll 12 | 13 | import ( 14 | "os" 15 | "runtime" 16 | "syscall" 17 | ) 18 | 19 | func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { 20 | if runtime.GOOS == "dragonfly" && sotype != syscall.SOCK_RAW { 21 | // On DragonFly BSD, we adjust the ephemeral port 22 | // range because unlike other BSD systems its default 23 | // port range doesn't conform to IANA recommendation 24 | // as described in RFC 6056 and is pretty narrow. 25 | switch family { 26 | case syscall.AF_INET: 27 | syscall.SetsockoptInt(s, syscall.IPPROTO_IP, syscall.IP_PORTRANGE, syscall.IP_PORTRANGE_HIGH) 28 | case syscall.AF_INET6: 29 | syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_PORTRANGE, syscall.IPV6_PORTRANGE_HIGH) 30 | } 31 | } 32 | // Allow broadcast. 33 | return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)) 34 | } 35 | -------------------------------------------------------------------------------- /sys_sockopt_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // This file may have been modified by CloudWeGo authors. (“CloudWeGo Modifications”). 6 | // All CloudWeGo Modifications are Copyright 2022 CloudWeGo authors. 7 | 8 | package netpoll 9 | 10 | import ( 11 | "os" 12 | "syscall" 13 | ) 14 | 15 | func setDefaultSockopts(s, family, sotype int, ipv6only bool) error { 16 | if family == syscall.AF_INET6 && sotype != syscall.SOCK_RAW { 17 | // Allow both IP versions even if the OS default 18 | // is otherwise. Note that some operating systems 19 | // never admit this option. 20 | syscall.SetsockoptInt(s, syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY, boolint(ipv6only)) 21 | } 22 | 23 | // Allow broadcast. 24 | return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)) 25 | } 26 | -------------------------------------------------------------------------------- /sys_zerocopy_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 16 | // +build darwin dragonfly freebsd netbsd openbsd 17 | 18 | package netpoll 19 | 20 | import "syscall" 21 | 22 | func setZeroCopy(fd int) error { 23 | return syscall.EINVAL 24 | } 25 | 26 | func setBlockZeroCopySend(fd int, sec, usec int64) error { 27 | return syscall.EINVAL 28 | } 29 | -------------------------------------------------------------------------------- /sys_zerocopy_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package netpoll 16 | 17 | import ( 18 | "syscall" 19 | ) 20 | 21 | const ( 22 | SO_ZEROCOPY = 60 23 | SO_ZEROBLOCKTIMEO = 69 24 | MSG_ZEROCOPY = 0x4000000 25 | ) 26 | 27 | func setZeroCopy(fd int) error { 28 | return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, SO_ZEROCOPY, 1) 29 | } 30 | 31 | func setBlockZeroCopySend(fd int, sec, usec int64) error { 32 | return syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, SO_ZEROBLOCKTIMEO, &syscall.Timeval{ 33 | Sec: sec, 34 | Usec: usec, 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /test_conns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ip="$1" 4 | port="$2" 5 | conns="$3" 6 | timeout="$4" 7 | 8 | for i in $(seq 1 $conns); 9 | do 10 | nc -v -w $timeout $ip $port < /dev/null & 11 | done 12 | 13 | wait 14 | --------------------------------------------------------------------------------