[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [panjf2000]\npatreon: panjf2000\nopen_collective: panjf2000\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yaml",
    "content": "name: Bug Report\ndescription: Oops!..., it's a bug.\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nassignees:\n  - panjf2000\nbody:\n  - type: markdown\n    id: tips\n    attributes:\n      value: |\n        ## Before you go any further\n        - Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.\n        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/UyKD7NZcfH).\n        - First of all, visit our [website](https://gnet.host) and [Github Org](https://github.com/gnet-io) for docs and examples.\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Actions I've taken before I'm here\n      description: Make sure you have tried the following ways but still the problem has not been solved.\n      options:\n        - label: I've thoroughly read the documentations on this issue but still have no clue.\n          required: true\n        - label: I've searched the Github Issues but didn't find any duplicate issues that have been resolved.\n          required: true\n        - label: I've searched the internet for this issue but didn't find anything helpful.\n          required: true\n    validations:\n      required: true\n  - type: textarea\n    id: bug-report\n    attributes:\n      label: What happened?\n      description: Describe (and illustrate) the bug that you encountered precisely.\n      placeholder: Please describe what happened and how it happened, the more details you provide, the faster the bug gets fixed.\n    validations:\n      required: true\n  - type: dropdown\n    id: major-version\n    attributes:\n      label: Major version of gnet\n      description: What major version of gnet are you running?\n      options:\n        - v2\n        - v1\n    validations:\n      required: true\n  - type: input\n    id: specific-version\n    attributes:\n      label: Specific version of gnet\n      description: What's the specific version of gnet?\n      placeholder: \"For example: v2.1.2\"\n    validations:\n      required: true\n  - type: dropdown\n    id: os\n    attributes:\n      label: Operating system\n      multiple: true\n      options:\n        - Linux\n        - macOS\n        - Windows\n        - BSD\n    validations:\n      required: true\n  - type: input\n    id: os-version\n    attributes:\n      label: OS version\n      description: What's the specific version of OS?\n      placeholder: \"Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64\"\n    validations:\n      required: true\n  - type: input\n    id: go-version\n    attributes:\n      label: Go version\n      description: What's the specific version of Go?\n      placeholder: \"Run `go version` command to get the info, for example: go1.20.5 linux/amd64\"\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output.\n      render: shell\n    validations:\n      required: true\n  - type: textarea\n    id: code\n    attributes:\n      label: Reproducer\n      description: Please provide the minimal code to reproduce the bug.\n      render: go\n  - type: textarea\n    id: how-to-reproduce\n    attributes:\n      label: How to Reproduce\n      description: Steps to reproduce the result.\n      placeholder: Tell us step by step how we can replicate this bug and what we should see in the end.\n      value: |\n        Steps to reproduce the behavior:\n        1. Go to '....'\n        2. Click on '....'\n        3. Do '....'\n        4. See '....'\n    validations:\n      required: true\n  - type: dropdown\n    id: bug-in-latest-code\n    attributes:\n      label: Does this issue reproduce with the latest release?\n      description: Is this bug still present in the latest version?\n      options:\n        - It can reproduce with the latest release\n        - It gets fixed in the latest release\n        - I haven't verified it with the latest release\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yaml",
    "content": "name: Feature Request\ndescription: Propose an idea to make gnet even better.\ntitle: \"[Feature]: \"\nlabels: [\"proposal\", \"enhancement\"]\nassignees:\n  - panjf2000\nbody:\n  - type: markdown\n    id: tips\n    attributes:\n      value: |\n        ## Before you go any further\n        - Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.\n        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/UyKD7NZcfH).\n        - First of all, visit our [website](https://gnet.host) and [Github Org](https://github.com/gnet-io) for docs and examples.\n  - type: textarea\n    id: feature-request\n    attributes:\n      label: Description of new feature\n      description: Make a concise but clear description about this new feature.\n      placeholder: Describe this new feature with critical details.\n    validations:\n      required: true\n  - type: textarea\n    id: feature-scenario\n    attributes:\n      label: Scenarios for new feature\n      description: Explain why you need this feature and what scenarios can benefit from it.\n      placeholder: Please try to convince us that this new feature makes sense, also it will improve gnet.\n    validations:\n      required: true\n  - type: dropdown\n    id: breaking-changes\n    attributes:\n      label: Breaking changes or not?\n      description: Is this new feature going to break the backward compatibility of the existing APIs?\n      options:\n        - \"Yes\"\n        - \"No\"\n    validations:\n      required: true\n  - type: textarea\n    id: code\n    attributes:\n      label: Code snippets (optional)\n      description: Illustrate this new feature with source code, what it looks like in code.\n      render: go\n  - type: textarea\n    id: feature-alternative\n    attributes:\n      label: Alternatives for new feature\n      description: Alternatives in your mind in case that the feature can't be done for some reasons.\n      placeholder: A concise but clear description of any alternative solutions or features you had in mind.\n      value: None.\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Additional context (optional)\n      description: Any additional context about this new feature we should know.\n      placeholder: What else should we know before we start discussing this new feature?\n      value: None.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.yaml",
    "content": "name: Question\ndescription: Ask questions about gnet.\ntitle: \"[Question]: \"\nlabels: [\"question\", \"help wanted\"]\nbody:\n  - type: markdown\n    id: tips\n    attributes:\n      value: |\n        ## Before you go any further\n        - Please read [<u>*How To Ask Questions The Smart Way*</u>](http://www.catb.org/~esr/faqs/smart-questions.html) ( Chinese translation: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)) before you file an issue formally.\n        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/UyKD7NZcfH).\n        - First of all, visit our [website](https://gnet.host) and [Github Org](https://github.com/gnet-io) for docs and examples.\n        - Make sure what you're looking for here is an issue rather than a [discussion](https://github.com/panjf2000/gnet/discussions/new/choose).\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Actions I've taken before I'm here\n      description: Make sure you have tried the following ways to get the answer of your problem.\n      options:\n        - label: I've thoroughly read the documentations about this problem but still have no answer.\n          required: true\n        - label: I've searched the Github Issues/Discussions but didn't find any similar problems that have been solved.\n          required: true\n        - label: I've searched the internet for this problem but didn't find anything helpful.\n          required: true\n    validations:\n      required: true\n  - type: textarea\n    id: question\n    attributes:\n      label: Questions with details\n      description: What do you want to know?\n      placeholder: Describe your question with critical details here.\n    validations:\n      required: true\n  - type: textarea\n    id: code\n    attributes:\n      label: Code snippets (optional)\n      description: Illustrate your question with source code if needed.\n      render: go\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nThank you for contributing to `gnet`! Please fill this out to help us review your pull request more efficiently.\n\nWas this change discussed in an issue first? That can help save time in case the change is not a good fit for the project. Not all pull requests get merged.\n\nIt is not uncommon for pull requests to go through several, iterative reviews. Please be patient with us! Every reviewer is a volunteer, and each has their own style.\n-->\n\n## 1. Are you opening this pull request for bug-fix, optimization or new feature?\n\n\n\n## 2. Please describe how these code changes achieve your intention.\n<!-- Please be specific. Motivate the problem, and justify why this is the best solution. -->\n\n\n\n## 3. Please link to the relevant issues (if any).\n<!-- This adds crucial context to your change. -->\n\n\n\n## 4. What documentation changes (if any) need to be made/updated because of this PR?\n<!-- Reviewers will often reference this first in order to know what to expect from the change. Please be specific enough so that they can paste your wording into the documentation directly. -->\n\n\n\n## 4. Checklist\n\n- [ ] I have squashed all insignificant commits.\n- [ ] I have commented my code for explaining package types, values, functions, and non-obvious lines.\n- [ ] I have written unit tests and verified that all tests passes (if needed).\n- [ ] I have documented feature info on the README (only when this PR is adding a new feature).\n- [ ] (optional) I am willing to help maintain this change if there are issues with it later.\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: Gnet v$RESOLVED_VERSION\ntag-template: v$RESOLVED_VERSION\ncategories:\n    - title: 🧨 Breaking changes\n      labels:\n          - breaking changes\n    - title: 🚀 Features\n      labels:\n          - proposal\n          - new feature\n    - title: 🛩 Enhancements\n      labels:\n          - enhancements\n          - optimization\n    - title: 🐛 Bugfixes\n      labels:\n          - bug\n    - title: 📚 Documentation\n      labels:\n          - docs\n    - title: 🗃 Misc\n      labels:\n          - chores\nchange-template: '- $TITLE (#$NUMBER)'\nchange-title-escapes: '\\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.\nversion-resolver:\n  major:\n    labels:\n      - major\n  minor:\n    labels:\n      - minor\n      - new feature\n      - proposal\n  patch:\n    labels:\n      - patch\n      - bug\n      - dependencies\n  default: patch\nautolabeler:\n  - label: bug\n    title:\n      - /fix/i\n      - /bug/i\n      - /resolve/i\n  - label: docs\n    files:\n      - '*.md'\n    title:\n      - /doc/i\n      - /README/i\n  - label: enhancement\n    title:\n      - /opt:/i\n      - /refactor/i\n      - /optimize/i\n      - /improve/i\n      - /update/i\n      - /remove/i\n      - /delete/i\n  - label: optimization\n    title:\n      - /opt:/i\n      - /refactor/i\n      - /optimize/i\n      - /improve/i\n      - /update/i\n      - /remove/i\n      - /delete/i\n  - label: new feature\n    title:\n      - /feat:/i\n      - /feature/i\n      - /implement/i\n      - /add/i\n      - /minor/i\n  - label: dependencies\n    title:\n      - /dep:/i\n      - /dependencies/i\n      - /upgrade/i\n      - /bump up/i\n  - label: chores\n    title:\n      - /chore/i\n      - /misc/i\n      - /cleanup/i\n      - /clean up/i\n  - label: major\n    title:\n      - /major:/i\n  - label: minor\n    title:\n      - /minor:/i\n  - label: patch\n    title:\n      - /patch:/i\ntemplate: |\n    ## Changelogs\n    \n    $CHANGES\n\n    **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION\n  \n    Thanks to all these contributors: $CONTRIBUTORS for making this release possible.\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "name: \"Code Scanning\"\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/codeql.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/codeql.yml'\n  schedule:\n    #        ┌───────────── minute (0 - 59)\n    #        │  ┌───────────── hour (0 - 23)\n    #        │  │ ┌───────────── day of the month (1 - 31)\n    #        │  │ │ ┌───────────── month (1 - 12 or JAN-DEC)\n    #        │  │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)\n    #        │  │ │ │ │\n    #        │  │ │ │ │\n    #        │  │ │ │ │\n    #        *  * * * *\n    - cron: '30 4 * * 0'\n\njobs:\n  CodeQL-Build:\n    # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest\n    runs-on: ubuntu-latest\n\n    permissions:\n      # required for all workflows\n      security-events: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 2\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: go\n\n      # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below).\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v3\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n\n      # ✏️ If the Autobuild fails above, remove it and uncomment the following\n      #    three lines and modify them (or add more) to build your code if your\n      #    project uses a compiled language\n\n      #- run: |\n      #     make bootstrap\n      #     make release\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3"
  },
  {
    "path": ".github/workflows/cross-compile-bsd.yml",
    "content": "name: Cross-compile for *BSD\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/cross-compile-bsd.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/cross-compile-bsd.yml'\n\nenv:\n  GO111MODULE: on\n  GOPROXY: \"https://proxy.golang.org\"\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.20', '1.25']\n        os:\n          - ubuntu-latest\n    name: Go ${{ matrix.go }} @ ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref }}\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Print Go environment\n        id: go-env\n        run: |\n          printf \"Using go at: $(which go)\\n\"\n          printf \"Go version: $(go version)\\n\"\n          printf \"\\n\\nGo environment:\\n\\n\"\n          go env\n          printf \"\\n\\nSystem environment:\\n\\n\"\n          env\n          # Calculate the short SHA1 hash of the git commit\n          echo \"SHORT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_OUTPUT\n          echo \"GO_CACHE=$(go env GOCACHE)\" >> $GITHUB_OUTPUT\n\n      - name: Cross-compiling for DragonFlyBSD\n        run: GOOS=dragonfly GOARCH=amd64 go build\n\n      - name: Cross-compiling for DragonFlyBSD -tags=poll_opt,gc_opt\n        run: GOOS=dragonfly GOARCH=amd64 go build -tags=poll_opt,gc_opt\n\n      - name: Cross-compiling for FreeBSD\n        run: GOOS=freebsd GOARCH=amd64 go build\n\n      - name: Cross-compiling for FreeBSD -tags=poll_opt,gc_opt\n        run: GOOS=freebsd GOARCH=amd64 go build -tags=poll_opt,gc_opt\n\n      - name: Cross-compiling for NetBSD\n        run: GOOS=netbsd GOARCH=amd64 go build\n\n      - name: Cross-compiling for NetBSD -tags=poll_opt,gc_opt\n        run: GOOS=netbsd GOARCH=amd64 go build -tags=poll_opt,gc_opt\n\n      - name: Cross-compiling for OpenBSD\n        run: GOOS=openbsd GOARCH=amd64 go build\n\n      - name: Cross-compiling for OpenBSD -tags=poll_opt,gc_opt\n        run: GOOS=openbsd GOARCH=amd64 go build -tags=poll_opt,gc_opt\n"
  },
  {
    "path": ".github/workflows/gh-translator.yml",
    "content": "name: 'gh-translator'\n\non:\n  issues:\n    types: [opened]\n  pull_request:\n    types: [opened]\n  issue_comment:\n    types: [created, edited]\n  discussion:\n    types: [created, edited, answered]\n  discussion_comment:\n    types: [created, edited]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: usthe/issues-translate-action@v2.7\n        with:\n          BOT_GITHUB_TOKEN: ${{ secrets.GH_TRANSLATOR_TOKEN }}\n          IS_MODIFY_TITLE: true\n          CUSTOM_BOT_NOTE: 🤖 Non-English text detected, translating ...\n"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "content": "name: Check pull request target\non:\n  pull_request:\n    types:\n      - opened\n      - reopened\n      - synchronize\n    branches:\n      - master\njobs:\n  check-branches:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check target branch\n        run: |\n          if [ ${{ github.head_ref }} != \"dev\" ]; then\n            echo \"Only pull requests from dev branch are only allowed to be merged into master branch.\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n      - master\n  # pull_request event is required only for autolabeler\n  pull_request:\n    # Only following types are handled by the action, but one can default to all as well\n    types: [opened, reopened, synchronize]\n  # pull_request_target event is required for autolabeler to support PRs from forks\n  # pull_request_target:\n  #   types: [opened, reopened, synchronize]\n\npermissions:\n  contents: read\n\njobs:\n  update_release_draft:\n    permissions:\n      # write permission is required to create a github release\n      contents: write\n      # write permission is required for autolabeler\n      # otherwise, read permission is required at least\n      pull-requests: write\n    runs-on: ubuntu-latest\n    steps:\n      # (Optional) GitHub Enterprise requires GHE_HOST variable set\n      #- name: Set GHE_HOST\n      #  run: |\n      #    echo \"GHE_HOST=${GITHUB_SERVER_URL##https:\\/\\/}\" >> $GITHUB_ENV\n\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v6\n        # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml\n        # with:\n        #   config-name: my-config.yml\n        #   disable-autolabeler: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/stale-bot.yml",
    "content": "name: Monitor inactive issues and PRs\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\njobs:\n  stale-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v9\n        with:\n          operations-per-run: 100\n          days-before-issue-stale: 30\n          days-before-issue-close: 7\n          stale-issue-label: 'stale'\n          stale-issue-message: |\n            This issue is marked as stale because it has been open for 30 days with no activity.\n            \n            You should take one of the following actions:\n            - Manually close this issue if it is no longer relevant\n            - Comment if you have more information to share\n            \n            This issue will be automatically closed in 7 days if no further activity occurs.\n          close-issue-message: |\n            This issue was closed because it has been inactive for 7 days since being marked as stale.\n\n            If you believe this is a false alarm, please leave a comment for it or open a new issue, you can also reopen this issue directly if you have permission.\n          days-before-pr-stale: 21\n          days-before-pr-close: 7\n          stale-pr-label: 'stale'\n          stale-pr-message: |\n            This PR is marked as stale because it has been open for 21 days with no activity.\n            \n            You should take one of the following actions:\n            - Manually close this PR if it is no longer relevant\n            - Push new commits or comment if you have more information to share\n            \n            This PR will be automatically closed in 7 days if no further activity occurs.\n          close-pr-message: |\n            This PR was closed because it has been inactive for 7 days since being marked as stale.\n\n            If you believe this is a false alarm, feel free to reopen this PR or create a new one.\n          repo-token: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Run tests\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test.yml'\n\nenv:\n  GO111MODULE: on\n  GOPROXY: \"https://proxy.golang.org\"\n\njobs:\n  lint:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n          #- windows-latest\n    name: Run golangci-lint\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '^1.20'\n          cache: false\n\n      - name: Setup and run golangci-lint\n        uses: golangci/golangci-lint-action@v7\n        with:\n          version: v2.1.6\n          args: -v -E gocritic -E misspell -E revive -E godot --timeout 5m\n  test:\n    needs: lint\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.20', '1.26']\n        os:\n          - ubuntu-latest\n          - macos-14 # check out this issue: https://github.com/actions/runner-images/issues/10924\n          - windows-latest\n    name: Go ${{ matrix.go }} @ ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref }}\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Print Go environment\n        id: go-env\n        run: |\n          printf \"Using go at: $(which go)\\n\"\n          printf \"Go version: $(go version)\\n\"\n          printf \"\\n\\nGo environment:\\n\\n\"\n          go env\n          printf \"\\n\\nSystem environment:\\n\\n\"\n          env\n          # Calculate the short SHA1 hash of the git commit\n          echo \"SHORT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_OUTPUT\n          echo \"GO_CACHE=$(go env GOCACHE)\" >> $GITHUB_OUTPUT\n\n      - name: Run unit tests for packages\n        run: go test $(go list ./... | tail -n +2)\n\n      # NOTE: -race is conditionally disabled for Windows with Go 1.26 due to\n      # known instability of the Go race detector on this platform/version\n      # combination (intermittent crashes and timeouts observed in CI).\n      # This is likely a regression in Go 1.26 on Windows. Check out #752 and #754 for details.\n      # Once the underlying issue is resolved and CI is stable, this\n      # condition should be revisited so that race testing can be re-enabled.\n      - name: Run integration tests\n        run: go test -v ${{ !(runner.os == 'Windows' && startsWith(matrix.go, '1.26')) && '-race' || '' }} -coverprofile=\"codecov.report\" -covermode=atomic -timeout 15m -failfast\n\n      - name: Upload the code coverage report to codecov.io\n        uses: codecov/codecov-action@v5\n        with:\n          files: ./codecov.report\n          flags: unittests\n          name: codecov-gnet\n          fail_ci_if_error: true\n          verbose: true\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test_gc_opt.yml",
    "content": "name: Run tests with -tags=gc_opt\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_gc_opt.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_gc_opt.yml'\n\nenv:\n  GO111MODULE: on\n  GOPROXY: \"https://proxy.golang.org\"\n\njobs:\n  lint:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n          #- windows-latest\n    name: Run golangci-lint\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '^1.20'\n          cache: false\n\n      - name: Setup and run golangci-lint\n        uses: golangci/golangci-lint-action@v7\n        with:\n          version: v2.1.6\n          args: -v -E gocritic -E misspell -E revive -E godot --timeout 5m\n  test:\n    needs: lint\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.20', '1.26']\n        os:\n          - ubuntu-latest\n          - macos-14 # check out this issue: https://github.com/actions/runner-images/issues/10924\n    name: Go ${{ matrix.go }} @ ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref }}\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Print Go environment\n        id: go-env\n        run: |\n          printf \"Using go at: $(which go)\\n\"\n          printf \"Go version: $(go version)\\n\"\n          printf \"\\n\\nGo environment:\\n\\n\"\n          go env\n          printf \"\\n\\nSystem environment:\\n\\n\"\n          env\n          # Calculate the short SHA1 hash of the git commit\n          echo \"SHORT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_OUTPUT\n          echo \"GO_CACHE=$(go env GOCACHE)\" >> $GITHUB_OUTPUT\n\n      - name: Run unit tests for packages\n        run: go test $(go list ./... | tail -n +2)\n\n      - name: Run integration tests\n        run: go test -v -race -tags=gc_opt -coverprofile=\"codecov.report\" -covermode=atomic -timeout 15m -failfast\n\n      - name: Upload the code coverage report to codecov.io\n        uses: codecov/codecov-action@v5\n        with:\n          files: ./codecov.report\n          flags: unittests\n          name: codecov-gnet\n          fail_ci_if_error: true\n          verbose: true\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test_poll_opt.yml",
    "content": "name: Run tests with -tags=poll_opt\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_poll_opt.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_poll_opt.yml'\n\nenv:\n  GO111MODULE: on\n  GOPROXY: \"https://proxy.golang.org\"\n\njobs:\n  lint:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n    name: Run golangci-lint\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '^1.20'\n          cache: false\n\n      - name: Setup and run golangci-lint\n        uses: golangci/golangci-lint-action@v7\n        with:\n          version: v2.1.6\n          args: -v -E gocritic -E misspell -E revive -E godot\n  test:\n    needs: lint\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.20', '1.26']\n        os:\n          - ubuntu-latest\n          - macos-14 # check out this issue: https://github.com/actions/runner-images/issues/10924\n    name: Go ${{ matrix.go }} @ ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref }}\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Print Go environment\n        id: go-env\n        run: |\n          printf \"Using go at: $(which go)\\n\"\n          printf \"Go version: $(go version)\\n\"\n          printf \"\\n\\nGo environment:\\n\\n\"\n          go env\n          printf \"\\n\\nSystem environment:\\n\\n\"\n          env\n          # Calculate the short SHA1 hash of the git commit\n          echo \"SHORT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_OUTPUT\n          echo \"GO_CACHE=$(go env GOCACHE)\" >> $GITHUB_OUTPUT\n\n      - name: Run unit tests for packages\n        run: go test $(go list ./... | tail -n +2)\n\n      - name: Run integration tests with -tags=poll_opt\n        run: go test -v -tags=poll_opt -coverprofile=\"codecov.report\" -covermode=atomic -timeout 10m -failfast\n\n      - name: Upload the code coverage report to codecov.io\n        uses: codecov/codecov-action@v5\n        with:\n          files: ./codecov.report\n          flags: unittests\n          name: codecov-gnet-poll_opt\n          fail_ci_if_error: true\n          verbose: true\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/test_poll_opt_gc_opt.yml",
    "content": "name: Run tests with -tags=poll_opt,gc_opt\n\non:\n  push:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_poll_opt_gc_opt.yml'\n  pull_request:\n    branches:\n      - master\n      - dev\n      - 1.x\n    paths-ignore:\n      - '**.md'\n      - '**.yml'\n      - '**.yaml'\n      - '!.github/workflows/test_poll_opt_gc_opt.yml'\n\nenv:\n  GO111MODULE: on\n  GOPROXY: \"https://proxy.golang.org\"\n\njobs:\n  lint:\n    strategy:\n      matrix:\n        os:\n          - ubuntu-latest\n          - macos-latest\n    name: Run golangci-lint\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: '^1.20'\n          cache: false\n\n      - name: Setup and run golangci-lint\n        uses: golangci/golangci-lint-action@v7\n        with:\n          version: v2.1.6\n          args: -v -E gocritic -E misspell -E revive -E godot\n  test:\n    needs: lint\n    strategy:\n      fail-fast: false\n      matrix:\n        go: ['1.20', '1.26']\n        os:\n          - ubuntu-latest\n          - macos-14 # check out this issue: https://github.com/actions/runner-images/issues/10924\n    name: Go ${{ matrix.go }} @ ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref }}\n\n      - name: Setup Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: ${{ matrix.go }}\n\n      - name: Print Go environment\n        id: go-env\n        run: |\n          printf \"Using go at: $(which go)\\n\"\n          printf \"Go version: $(go version)\\n\"\n          printf \"\\n\\nGo environment:\\n\\n\"\n          go env\n          printf \"\\n\\nSystem environment:\\n\\n\"\n          env\n          # Calculate the short SHA1 hash of the git commit\n          echo \"SHORT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_OUTPUT\n          echo \"GO_CACHE=$(go env GOCACHE)\" >> $GITHUB_OUTPUT\n\n      - name: Run unit tests for packages\n        run: go test $(go list ./... | tail -n +2)\n\n      - name: Run integration tests with -tags=poll_opt\n        run: go test -v -tags=poll_opt,gc_opt -coverprofile=\"codecov.report\" -covermode=atomic -timeout 10m -failfast\n\n      - name: Upload the code coverage report to codecov.io\n        uses: codecov/codecov-action@v5\n        with:\n          files: ./codecov.report\n          flags: unittests\n          name: codecov-gnet-poll_opt\n          fail_ci_if_error: true\n          verbose: true\n        env:\n          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# IDEs\n.idea/\n.vscode/\n\n# dependencies\n/node_modules\n\n# production\n/build\n\n# generated files\n.docusaurus\n.docusaurus/\n.cache-loader\n\n# misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at panjf2000@gmail.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing \n\n## With issues:\n  - Use the search tool before opening a new issue.\n  - Please provide source code and commit sha if you found a bug.\n  - Review existing issues and provide feedback or react to them.\n\n## With pull requests:\n  - Open your pull request against `dev`.\n  - Open one pull request for only one feature/proposal, if you have several those, please put them into different PRs, whereas you are allowed to open one pull request with several bug-fixes.\n  - Your pull request should have no more than two commits, if not, you should squash them.\n  - It should pass all tests in the available continuous integrations systems such as TravisCI.\n  - You should add/modify tests to cover your proposed code changes.\n  - If your pull request contains a new feature, please document it on the README.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (c) 2019-present Andy Pan\n   Copyright (c) 2017 Joshua J Baker\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/panjf2000/logos/master/gnet/logo.png\" alt=\"gnet\" />\n<br />\n<a title=\"Build Status\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/actions?query=workflow%3ATests\"><img src=\"https://img.shields.io/github/actions/workflow/status/panjf2000/gnet/test.yml?branch=dev&style=flat-square&logo=github-actions\" /></a>\n<a title=\"Codecov\" target=\"_blank\" href=\"https://codecov.io/gh/panjf2000/gnet\"><img src=\"https://img.shields.io/codecov/c/github/panjf2000/gnet?style=flat-square&logo=codecov\" /></a>\n<a title=\"Supported Platforms\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet\"><img src=\"https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20*BSD%20%7C%20Windows-549688?style=flat-square&logo=launchpad\" /></a>\n<a title=\"Minimum Go Version\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet\"><img src=\"https://img.shields.io/badge/go-%3E%3D1.20-30dff3?style=flat-square&logo=go\" /></a>\n<br />\n<a title=\"Go Report Card\" target=\"_blank\" href=\"https://goreportcard.com/report/github.com/panjf2000/gnet\"><img src=\"https://goreportcard.com/badge/github.com/panjf2000/gnet?style=flat-square\" /></a>\n<a title=\"Doc for gnet\" target=\"_blank\" href=\"https://pkg.go.dev/github.com/panjf2000/gnet/v2#section-documentation\"><img src=\"https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs\" /></a>\n<a title=\"Mentioned in Awesome Go\" target=\"_blank\" href=\"https://github.com/avelino/awesome-go#networking\"><img src=\"https://awesome.re/mentioned-badge-flat.svg\" /></a>\n<a title=\"Release\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/releases\"><img src=\"https://img.shields.io/github/v/release/panjf2000/gnet.svg?color=161823&style=flat-square&logo=smartthings\" /></a>\n<a title=\"Tag\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/tags\"><img src=\"https://img.shields.io/github/v/tag/panjf2000/gnet?color=%23ff8936&logo=fitbit&style=flat-square\" /></a>\n</p>\n\n<p align=\"center\">\n<a href=\"https://trendshift.io/repositories/9602\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/9602\" alt=\"panjf2000%2Fgnet | Trendshift\" style=\"width: 250px; height: 55px;\" width=\"250\" height=\"55\"/></a>\n</p>\n\nEnglish | [中文](README_ZH.md)\n\n### 🎉🎉🎉 Feel free to join [the channels about `gnet` on the Discord Server](https://discord.gg/UyKD7NZcfH).\n\n# 📖 Introduction\n\n`gnet` is an event-driven networking framework that is ultra-fast and lightweight. It is built from scratch by exploiting [epoll](https://man7.org/linux/man-pages/man7/epoll.7.html) and [kqueue](https://en.wikipedia.org/wiki/Kqueue) and it can achieve much higher performance with lower memory consumption than Go [net](https://golang.org/pkg/net/) in many specific scenarios.\n\n`gnet` and [net](https://golang.org/pkg/net/) don't share the same philosophy in network programming. Thus, building network applications with `gnet` can be significantly different from building them with [net](https://golang.org/pkg/net/), and the philosophies can't be reconciled. There are other similar products written in other programming languages in the community, such as [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado), etc. which work in a similar pattern as `gnet` under the hood.\n\n`gnet` is not designed to displace the Go [net](https://golang.org/pkg/net/), but to create an alternative in the Go ecosystem for building performance-critical network services. As a result of which, `gnet` is not as comprehensive as Go [net](https://golang.org/pkg/net/), it provides only the core functionality (via a concise set of APIs) required by a network application and it doesn't plan on becoming a coverall networking framework, as I think Go [net](https://golang.org/pkg/net/) has done a good enough job in that area.\n\n`gnet` sells itself as a high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go which works on the transport layer with TCP/UDP protocols and Unix Domain Socket. It enables developers to implement their own protocols(HTTP, RPC, WebSocket, Redis, etc.) of application layer upon `gnet` for building diversified network services. For instance, you get an HTTP Server if you implement HTTP protocol upon `gnet` while you have a Redis Server done with the implementation of Redis protocol upon `gnet` and so on.\n\n**`gnet` derives from the project: `evio` with much higher performance and more features.**\n\n# 🚀 Features\n\n## 🦖 Milestones\n\n- [x] [High-performance](#-performance) event-driven looping based on a networking model of multiple threads/goroutines\n- [x] Built-in goroutine pool powered by the library [ants](https://github.com/panjf2000/ants)\n- [x] Lock-free during the entire runtime\n- [x] Concise and easy-to-use APIs\n- [x] Efficient, reusable, and elastic memory buffer: (Elastic-)Ring-Buffer, Linked-List-Buffer and Elastic-Mixed-Buffer\n- [x] Multiple protocols/IPC mechanisms: `TCP`, `UDP`, and `Unix Domain Socket`\n- [x] Multiple load-balancing algorithms: `Round-Robin`, `Source-Addr-Hash`, and `Least-Connections`\n- [x] Flexible ticker event\n- [x] `gnet` client\n- [x] Running on `Linux`, `macOS`, `Windows`, and *BSD: `Darwin`/`DragonFlyBSD`/`FreeBSD`/`NetBSD`/`OpenBSD`\n- [x] **Edge-triggered** I/O support\n- [x] Multiple network addresses binding\n- [x] Support registering new connections to event-loops\n\n## 🕊 Roadmap\n\n- [ ] **TLS** support\n- [ ] [io_uring](https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023) support\n- [ ] **KCP** support\n\n***Windows version of `gnet` should only be used in development for developing and testing, it shouldn't be used in production.***\n\n# 🎬 Getting started\n\n`gnet` is available as a Go module and we highly recommend that you use `gnet` via [Go Modules](https://go.dev/blog/using-go-modules), with Go 1.11 Modules enabled (Go 1.11+), you can just simply add `import \"github.com/panjf2000/gnet/v2\"` to the codebase and run `go mod download/go mod tidy` or `go [build|run|test]` to download the necessary dependencies automatically.\n\n## With v2\n\n```bash\ngo get -u github.com/panjf2000/gnet/v2\n```\n\n## With v1\n\n```bash\ngo get -u github.com/panjf2000/gnet\n```\n\n# 🎡 Use cases\n\nThe following corporations/organizations use `gnet` as the underlying network service in production.\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.tencent.com/\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/tencent_logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.tencentgames.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/tencent-games-logo.jpeg\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.iqiyi.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/iqiyi-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.mi.com/global/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/mi-logo.png\" width=\"200\" />\n        </a>\n      </td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.360.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/360-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://tieba.baidu.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.jd.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/jd-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.zuoyebang.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg\" width=\"200\" />\n        </a>\n      </td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.bytedance.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png\" width=\"250\" />\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\nIf you're also using `gnet` in production, please help us enrich this list by opening a pull request.\n\n# 📊 Performance\n\n## Benchmarks on TechEmpower\n\n```bash\n# Hardware Environment\n* 28 HT Cores Intel(R) Xeon(R) Gold 5120 CPU @ 3.20GHz\n* 32GB RAM\n* Dedicated Cisco 10-gigabit Ethernet switch\n* Debian 12 \"bookworm\"\n* Go1.19.x linux/amd64\n```\n\n![](https://raw.githubusercontent.com/panjf2000/illustrations/master/benchmark/techempower-plaintext-top50-light.jpg)\n\nThis is a leaderboard of the top ***50*** out of ***486*** frameworks that encompass various programming languages worldwide, in which `gnet` is ranked ***first***.\n\n![](https://raw.githubusercontent.com/panjf2000/illustrations/master/benchmark/techempower-plaintext-topN-go-light.png)\n\nThis is the full framework ranking of Go and `gnet` tops all the other frameworks, which makes `gnet` the ***fastest*** networking framework in Go.\n\nTo see the full ranking list, visit [TechEmpower Benchmark **Round 22**](https://www.techempower.com/benchmarks/#hw=ph&test=plaintext&section=data-r22).\n\n***Note that the HTTP implementation of gnet on TechEmpower is half-baked and fine-tuned for benchmark purposes only and far from production-ready.***\n\n## Contrasts to the similar networking libraries\n\n### On Linux (epoll)\n\n#### Environment\n\n```bash\n# Machine information\n        OS : Ubuntu 20.04/x86_64\n       CPU : 8 CPU cores, AMD EPYC 7K62 48-Core Processor\n    Memory : 16.0 GiB\n\n# Go version and settings\nGo Version : go1.17.2 linux/amd64\nGOMAXPROCS : 8\n\n# Benchmark parameters\nTCP connections : 1000/2000/5000/10000\nPacket size     : 512/1024/2048/4096/8192/16384/32768/65536 bytes\nTest duration   : 15s\n```\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_conn_linux.png)](https://github.com/gnet-io/gnet-benchmarks)\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_packet_linux.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n### On MacOS (kqueue)\n\n#### Environment\n\n```bash\n# Machine information\n        OS : MacOS Big Sur/x86_64\n       CPU : 6 CPU cores, Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n    Memory : 16.0 GiB\n\n# Go version and settings\nGo Version : go1.16.5 darwin/amd64\nGOMAXPROCS : 12\n\n# Benchmark parameters\nTCP connections : 300/400/500/600/700\nPacket size     : 512/1024/2048/4096/8192 bytes\nTest duration   : 15s\n```\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_conn_macos.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_packet_macos.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n### Combat with Rust\n\n[![](https://res.strikefreedom.top/static_res/blog/figures/Gjfx2GoXAAA5haW.jpeg)](https://www.youtube.com/watch?v=31R8Ef9A0iw)\n\n# ⚠️ License\n\nThe source code of `gnet` should be distributed under the Apache-2.0 license.\n\n# 👏 Contributors\n\nPlease read the [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `gnet`!\n\n<a href=\"https://github.com/panjf2000/gnet/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=panjf2000/gnet\" />\n</a>\n\n# ⚓ Relevant Articles\n\n- [A Million WebSockets and Go](https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/)\n- [Going Infinite, handling 1M websockets connections in Go](https://speakerdeck.com/eranyanay/going-infinite-handling-1m-websockets-connections-in-go)\n- [Go netpoller 原生网络模型之源码全面揭秘](https://strikefreedom.top/go-netpoll-io-multiplexing-reactor)\n- [gnet: 一个轻量级且高性能的 Golang 网络库](https://strikefreedom.top/go-event-loop-networking-library-gnet)\n- [最快的 Go 网络框架 gnet 来啦！](https://strikefreedom.top/releasing-gnet-v1-with-techempower)\n\n# ☕️ Buy me a coffee\n\n> Please be sure to leave your name, GitHub account, or other social media accounts when you donate by the following means so that I can add it to the list of donors as a token of my appreciation.\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://buymeacoffee.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/bmc_qr.png\" width=\"250\" alt=\"By me coffee\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://www.patreon.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/patreon_logo.png\" width=\"250\" alt=\"Patreon\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://opencollective.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/open-collective-logo.png\" width=\"250\" alt=\"OpenCollective\" />\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\n# 🔑 JetBrains OS licenses\n\n`gnet` has been being developed with `GoLand` IDE under the ***free JetBrains Open Source license(s)*** granted by JetBrains s.r.o., hence I would like to express my thanks here.\n\n<a href=\"https://www.jetbrains.com/?from=gnet\" target=\"_blank\"><img src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg\" alt=\"JetBrains logo.\"></a>\n\n# 🔋 Sponsorship\n\n<p>\n  <h3>This project is supported by:</h3>\n  <a href=\"https://www.digitalocean.com/\"><img src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg\" width=\"201px\" />\n  </a>\n</p>"
  },
  {
    "path": "README_ZH.md",
    "content": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/panjf2000/logos/master/gnet/logo.png\" alt=\"gnet\" />\n<br />\n<a title=\"Build Status\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/actions?query=workflow%3ATests\"><img src=\"https://img.shields.io/github/actions/workflow/status/panjf2000/gnet/test.yml?branch=dev&style=flat-square&logo=github-actions\" /></a>\n<a title=\"Codecov\" target=\"_blank\" href=\"https://codecov.io/gh/panjf2000/gnet\"><img src=\"https://img.shields.io/codecov/c/github/panjf2000/gnet?style=flat-square&logo=codecov\" /></a>\n<a title=\"Supported Platforms\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet\"><img src=\"https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20*BSD%20%7C%20Windows-549688?style=flat-square&logo=launchpad\" /></a>\n<a title=\"Minimum Go Version\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet\"><img src=\"https://img.shields.io/badge/go-%3E%3D1.20-30dff3?style=flat-square&logo=go\" /></a>\n<br />\n<a title=\"Go Report Card\" target=\"_blank\" href=\"https://goreportcard.com/report/github.com/panjf2000/gnet\"><img src=\"https://goreportcard.com/badge/github.com/panjf2000/gnet?style=flat-square\" /></a>\n<a title=\"Doc for gnet\" target=\"_blank\" href=\"https://pkg.go.dev/github.com/panjf2000/gnet/v2#section-documentation\"><img src=\"https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs\" /></a>\n<a title=\"Mentioned in Awesome Go\" target=\"_blank\" href=\"https://github.com/avelino/awesome-go#networking\"><img src=\"https://awesome.re/mentioned-badge-flat.svg\" /></a>\n<a title=\"Release\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/releases\"><img src=\"https://img.shields.io/github/v/release/panjf2000/gnet.svg?color=161823&style=flat-square&logo=smartthings\" /></a>\n<a title=\"Tag\" target=\"_blank\" href=\"https://github.com/panjf2000/gnet/tags\"><img src=\"https://img.shields.io/github/v/tag/panjf2000/gnet?color=%23ff8936&logo=fitbit&style=flat-square\" /></a>\n</p>\n\n<p align=\"center\">\n<a href=\"https://trendshift.io/repositories/9602\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/9602\" alt=\"panjf2000%2Fgnet | Trendshift\" style=\"width: 250px; height: 55px;\" width=\"250\" height=\"55\"/></a>\n</p>\n\n[英文](README.md) | 中文\n\n### 🎉🎉🎉 欢迎加入 `gnet` 在 [Discord 服务器上的频道](https://discord.gg/UyKD7NZcfH).\n\n# 📖 简介\n\n`gnet` 是一个基于事件驱动的高性能和轻量级网络框架。这个框架是基于 [epoll](https://en.wikipedia.org/wiki/Epoll) 和 [kqueue](https://en.wikipedia.org/wiki/Kqueue) 从零开发的，而且相比 Go [net](https://golang.org/pkg/net/)，它能以更低的内存占用实现更高的性能。\n\n`gnet` 和 [net](https://golang.org/pkg/net/) 有着不一样的网络编程范式。因此，用 `gnet` 开发网络应用和用 [net](https://golang.org/pkg/net/) 开发区别很大，而且两者之间不可调和。社区里有其他同类的产品像是 [libuv](https://github.com/libuv/libuv), [netty](https://github.com/netty/netty), [twisted](https://github.com/twisted/twisted), [tornado](https://github.com/tornadoweb/tornado)，`gnet` 的底层工作原理和这些框架非常类似。\n\n`gnet` 不是为了取代 [net](https://golang.org/pkg/net/) 而生的，而是在 Go 生态中为开发者提供一个开发性能敏感的网络服务的替代品。也正因如此，`gnet` 在功能全面性上比不了 Go [net](https://golang.org/pkg/net/)，它只会提供网络应用所需的最核心的功能和最精简的 APIs，而且 `gnet` 也并没有打算变成一个无所不包的网络框架，因为我觉得 Go [net](https://golang.org/pkg/net/) 在这方面已经做得足够好了。\n\n`gnet` 的卖点在于它是一个高性能、轻量级、非阻塞的纯 Go 语言实现的传输层（TCP/UDP/Unix Domain Socket）网络框架。开发者可以使用 `gnet` 来实现自己的应用层网络协议(HTTP、RPC、Redis、WebSocket 等等)，从而构建出自己的应用层网络服务。比如在 `gnet` 上实现 HTTP 协议就可以创建出一个 HTTP 服务器 或者 Web 开发框架，实现 Redis 协议就可以创建出自己的 Redis 服务器等等。\n\n**`gnet` 衍生自另一个项目：`evio`，但拥有更丰富的功能特性，且性能远胜之。**\n\n# 🚀 功能\n\n## 🦖 里程碑\n\n- [x] 基于多线程/协程网络模型的[高性能](#-性能测试)事件驱动循环\n- [x] 内置 goroutine 池，由开源库 [ants](https://github.com/panjf2000/ants) 提供支持\n- [x] 整个生命周期是无锁的\n- [x] 简单易用的 APIs\n- [x] 高效、可重用而且自动伸缩的内存 buffer：(Elastic-)Ring-Buffer, Linked-List-Buffer and Elastic-Mixed-Buffer\n- [x] 多种网络协议/IPC 机制：`TCP`、`UDP` 和 `Unix Domain Socket`\n- [x] 多种负载均衡算法：`Round-Robin(轮询)`、`Source-Addr-Hash(源地址哈希)` 和 `Least-Connections(最少连接数)`\n- [x] 灵活的事件定时器\n- [x] `gnet` 客户端支持\n- [x] 支持 `Linux`, `macOS`, `Windows` 和 *BSD 操作系统: `Darwin`/`DragonFlyBSD`/`FreeBSD`/`NetBSD`/`OpenBSD`\n- [x] **Edge-triggered** I/O 支持\n- [x] 多网络地址绑定\n- [x] 支持注册新的连接到事件循环\n\n## 🕊 蓝图\n\n- [ ] 支持 **TLS**\n- [ ] 支持 [io_uring](https://github.com/axboe/liburing/wiki/io_uring-and-networking-in-2023)\n- [ ] 支持 **KCP**\n\n***`gnet` 的 Windows 版本应该仅用于开发阶段的开发和测试，切勿用于生产环境***。\n\n# 🎬 开始\n\n`gnet` 是一个 Go module，而且我们也强烈推荐通过 [Go Modules](https://go.dev/blog/using-go-modules) 来使用 `gnet`，在开启 Go Modules 支持（Go 1.11+）之后可以通过简单地在代码中写 `import \"github.com/panjf2000/gnet/v2\"` 来引入 `gnet`，然后执行 `go mod download/go mod tidy` 或者 `go [build|run|test]` 这些命令来自动下载所依赖的包。\n\n## 使用 v2\n\n```bash\ngo get -u github.com/panjf2000/gnet/v2\n```\n\n## 使用 v1\n\n```bash\ngo get -u github.com/panjf2000/gnet\n```\n\n# 🎡 用户案例\n\n以下公司/组织在生产环境上使用了 `gnet` 作为底层网络服务。\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.tencent.com/\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/tencent_logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.tencentgames.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/tencent-games-logo.jpeg\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.iqiyi.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/iqiyi-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.mi.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/mi-logo.png\" width=\"200\" />\n        </a>\n      </td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.360.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/360-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://tieba.baidu.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.jd.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/jd-logo.png\" width=\"200\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.zuoyebang.com/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg\" width=\"200\" />\n        </a>\n      </td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a href=\"https://www.bytedance.com/zh/\" target=\"_blank\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png\" width=\"250\" />\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\n如果你也正在生产环境上使用 `gnet`，欢迎提 Pull Request 来丰富这份列表。\n\n# 📊 性能测试\n\n## TechEmpower 性能测试\n\n```bash\n# 硬件环境\n* 28 HT Cores Intel(R) Xeon(R) Gold 5120 CPU @ 3.20GHz\n* 32GB RAM\n* Dedicated Cisco 10-gigabit Ethernet switch\n* Debian 12 \"bookworm\"\n* Go1.19.x linux/amd64\n```\n\n![](https://raw.githubusercontent.com/panjf2000/illustrations/master/benchmark/techempower-plaintext-top50-light.jpg)\n\n这是包含全部编程语言框架的性能排名***前 50*** 的结果，总榜单包含了全世界共计 ***486*** 个框架，其中 `gnet` 排名***第一***。\n\n![](https://raw.githubusercontent.com/panjf2000/illustrations/master/benchmark/techempower-plaintext-topN-go-light.png)\n\n这是 Go 语言分类下的全部排名，`gnet` 超越了其他所有框架，位列第一，是***最快***的 Go 网络框架。\n\n完整的排行可以通过 [TechEmpower Benchmark **Round 22**](https://www.techempower.com/benchmarks/#hw=ph&test=plaintext&section=data-r22) 查看。\n\n***请注意，TechEmpower 上的 gnet 的 HTTP 实现是不完备且针对性调优的，仅仅是用于压测目的，不是生产可用的***。\n\n## 同类型的网络库性能对比\n\n### On Linux (epoll)\n\n#### Environment\n\n```bash\n# Machine information\n        OS : Ubuntu 20.04/x86_64\n       CPU : 8 CPU cores, AMD EPYC 7K62 48-Core Processor\n    Memory : 16.0 GiB\n\n# Go version and settings\nGo Version : go1.17.2 linux/amd64\nGOMAXPROCS : 8\n\n# Benchmark parameters\nTCP connections : 1000/2000/5000/10000\nPacket size     : 512/1024/2048/4096/8192/16384/32768/65536 bytes\nTest duration   : 15s\n```\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_conn_linux.png)](https://github.com/gnet-io/gnet-benchmarks)\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_packet_linux.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n### On MacOS (kqueue)\n\n#### Environment\n\n```bash\n# Machine information\n        OS : MacOS Big Sur/x86_64\n       CPU : 6 CPU cores, Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\n    Memory : 16.0 GiB\n\n# Go version and settings\nGo Version : go1.16.5 darwin/amd64\nGOMAXPROCS : 12\n\n# Benchmark parameters\nTCP connections : 300/400/500/600/700\nPacket size     : 512/1024/2048/4096/8192 bytes\nTest duration   : 15s\n```\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_conn_macos.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n[![](https://github.com/panjf2000/gnet_benchmarks/raw/master/results/echo_packet_macos.png)]((https://github.com/gnet-io/gnet-benchmarks))\n\n### \"硬刚\" Rust\n\n[![](https://res.strikefreedom.top/static_res/blog/figures/Gjfx2GoXAAA5haW.jpeg)](https://www.youtube.com/watch?v=31R8Ef9A0iw)\n\n# ⚠️ 证书\n\n`gnet` 的源码需在遵循 Apache-2.0 开源证书的前提下使用。\n\n# 👏 贡献者\n\n请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md)，感谢那些为 `gnet` 贡献过代码的开发者！\n\n<a href=\"https://github.com/panjf2000/gnet/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=panjf2000/gnet\" />\n</a>\n\n# ⚓ 相关文章\n\n- [A Million WebSockets and Go](https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/)\n- [Going Infinite, handling 1M websockets connections in Go](https://speakerdeck.com/eranyanay/going-infinite-handling-1m-websockets-connections-in-go)\n- [Go netpoller 原生网络模型之源码全面揭秘](https://strikefreedom.top/go-netpoll-io-multiplexing-reactor)\n- [gnet: 一个轻量级且高性能的 Golang 网络库](https://strikefreedom.top/go-event-loop-networking-library-gnet)\n- [最快的 Go 网络框架 gnet 来啦！](https://strikefreedom.top/releasing-gnet-v1-with-techempower)\n\n# ☕️ 打赏\n\n> 当您通过以下方式进行捐赠时，请务必留下姓名、GitHub 账号或其他社交媒体账号，以便我将其添加到捐赠者名单中，以表谢意。\n\n<table>\n  <tbody>\n    <tr>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://buymeacoffee.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/bmc_qr.png\" width=\"250\" alt=\"By me coffee\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://www.patreon.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/patreon_logo.png\" width=\"250\" alt=\"Patreon\" />\n        </a>\n      </td>\n      <td align=\"center\" valign=\"middle\">\n        <a target=\"_blank\" href=\"https://opencollective.com/panjf2000\">\n          <img src=\"https://res.strikefreedom.top/static_res/logos/open-collective-logo.png\" width=\"250\" alt=\"OpenCollective\" />\n        </a>\n      </td>\n    </tr>\n  </tbody>\n</table>\n\n# 🔑 JetBrains 开源证书支持\n\n`gnet` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发，基于 ***free JetBrains Open Source license(s)*** 正版免费授权，在此表达我的谢意。\n\n<a href=\"https://www.jetbrains.com/?from=gnet\" target=\"_blank\"><img src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg\" alt=\"JetBrains logo.\"></a>\n\n# 🔋 赞助商\n\n<p>\n  <h3>本项目由以下机构赞助：</h3>\n  <a href=\"https://www.digitalocean.com/\"><img src=\"https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg\" width=\"201px\" />\n  </a>\n</p>"
  },
  {
    "path": "acceptor_unix.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"runtime\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\nfunc (el *eventloop) accept0(fd int, _ netpoll.IOEvent, _ netpoll.IOFlags) error {\n\tfor {\n\t\tnfd, sa, err := socket.Accept(fd)\n\t\tswitch err {\n\t\tcase nil:\n\t\tcase unix.EAGAIN: // the Accept queue has been drained out, we can return now\n\t\t\treturn nil\n\t\tcase unix.EINTR, unix.ECONNRESET, unix.ECONNABORTED:\n\t\t\t// ECONNRESET or ECONNABORTED could indicate that a socket\n\t\t\t// in the Accept queue was closed before we Accept()ed it.\n\t\t\t// It's a silly error, let's retry it.\n\t\t\tcontinue\n\t\tdefault:\n\t\t\tel.getLogger().Errorf(\"Accept() failed due to error: %v\", err)\n\t\t\treturn errors.ErrAcceptSocket\n\t\t}\n\n\t\tremoteAddr := socket.SockaddrToTCPOrUnixAddr(sa)\n\t\tnetwork := el.listeners[fd].network\n\t\tif opts := el.engine.opts; opts.TCPKeepAlive > 0 && network == \"tcp\" &&\n\t\t\t(runtime.GOOS != \"linux\" && runtime.GOOS != \"freebsd\" && runtime.GOOS != \"dragonfly\") {\n\t\t\t// TCP keepalive options are not inherited from the listening socket\n\t\t\t// on platforms other than Linux, FreeBSD, or DragonFlyBSD.\n\t\t\t// We therefore need to set them on the accepted socket explicitly.\n\t\t\t//\n\t\t\t// Check out https://github.com/nginx/nginx/pull/337 for details.\n\t\t\tif err = setKeepAlive(\n\t\t\t\tnfd,\n\t\t\t\ttrue,\n\t\t\t\topts.TCPKeepAlive,\n\t\t\t\topts.TCPKeepInterval,\n\t\t\t\topts.TCPKeepCount); err != nil {\n\t\t\t\tel.getLogger().Errorf(\"failed to set TCP keepalive on fd=%d: %v\", fd, err)\n\t\t\t}\n\t\t}\n\n\t\tel := el.engine.eventLoops.next(remoteAddr)\n\t\tc := newStreamConn(network, nfd, el, sa, el.listeners[fd].addr, remoteAddr)\n\t\terr = el.poller.Trigger(queue.HighPriority, el.register, c)\n\t\tif err != nil {\n\t\t\tel.getLogger().Errorf(\"failed to enqueue the accepted socket fd=%d to poller: %v\", c.fd, err)\n\t\t\t_ = unix.Close(nfd)\n\t\t\tc.release()\n\t\t}\n\t}\n}\n\nfunc (el *eventloop) accept(fd int, ev netpoll.IOEvent, flags netpoll.IOFlags) error {\n\tnetwork := el.listeners[fd].network\n\tif network == \"udp\" {\n\t\treturn el.readUDP(fd, ev, flags)\n\t}\n\n\tnfd, sa, err := socket.Accept(fd)\n\tswitch err {\n\tcase nil:\n\tcase unix.EINTR, unix.EAGAIN, unix.ECONNRESET, unix.ECONNABORTED:\n\t\t// ECONNRESET or ECONNABORTED could indicate that a socket\n\t\t// in the Accept queue was closed before we Accept()ed it.\n\t\t// It's a silly error, let's retry it.\n\t\treturn nil\n\tdefault:\n\t\tel.getLogger().Errorf(\"Accept() failed due to error: %v\", err)\n\t\treturn errors.ErrAcceptSocket\n\t}\n\n\tremoteAddr := socket.SockaddrToTCPOrUnixAddr(sa)\n\tif opts := el.engine.opts; opts.TCPKeepAlive > 0 && el.listeners[fd].network == \"tcp\" &&\n\t\t(runtime.GOOS != \"linux\" && runtime.GOOS != \"freebsd\" && runtime.GOOS != \"dragonfly\") {\n\t\t// TCP keepalive options are not inherited from the listening socket\n\t\t// on platforms other than Linux, FreeBSD, or DragonFlyBSD.\n\t\t// We therefore need to set them on the accepted socket explicitly.\n\t\t//\n\t\t// Check out https://github.com/nginx/nginx/pull/337 for details.\n\t\tif err = setKeepAlive(\n\t\t\tnfd,\n\t\t\ttrue,\n\t\t\topts.TCPKeepAlive,\n\t\t\topts.TCPKeepInterval,\n\t\t\topts.TCPKeepCount); err != nil {\n\t\t\tel.getLogger().Errorf(\"failed to set TCP keepalive on fd=%d: %v\", fd, err)\n\t\t}\n\t}\n\n\tc := newStreamConn(network, nfd, el, sa, el.listeners[fd].addr, remoteAddr)\n\treturn el.register0(c)\n}\n"
  },
  {
    "path": "acceptor_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"runtime\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\nfunc (eng *engine) listenStream(ln net.Listener) (err error) {\n\tif eng.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\tdefer func() { eng.shutdown(err) }()\n\n\tfor {\n\t\t// Accept TCP socket.\n\t\ttc, e := ln.Accept()\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif !eng.beingShutdown.Load() {\n\t\t\t\teng.opts.Logger.Errorf(\"Accept() fails due to error: %v\", err)\n\t\t\t} else if errors.Is(err, net.ErrClosed) {\n\t\t\t\terr = errors.Join(err, errorx.ErrEngineShutdown)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tel := eng.eventLoops.next(tc.RemoteAddr())\n\t\tc := newStreamConn(el, tc, nil)\n\t\tel.ch <- &openConn{c: c}\n\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tvar buffer [0x10000]byte\n\t\t\tfor {\n\t\t\t\tn, err := tc.Read(buffer[:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tel.ch <- &netErr{c, err}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tel.ch <- packTCPConn(c, buffer[:n])\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc (eng *engine) ListenUDP(pc net.PacketConn) (err error) {\n\tif eng.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\tdefer func() { eng.shutdown(err) }()\n\n\tvar buffer [0x10000]byte\n\tfor {\n\t\t// Read data from UDP socket.\n\t\tn, addr, e := pc.ReadFrom(buffer[:])\n\t\tif e != nil {\n\t\t\terr = e\n\t\t\tif !eng.beingShutdown.Load() {\n\t\t\t\teng.opts.Logger.Errorf(\"failed to receive data from UDP fd due to error:%v\", err)\n\t\t\t} else if errors.Is(err, net.ErrClosed) {\n\t\t\t\terr = errors.Join(err, errorx.ErrEngineShutdown)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tel := eng.eventLoops.next(addr)\n\t\tc := newUDPConn(el, pc, nil, pc.LocalAddr(), addr, nil)\n\t\tel.ch <- packUDPConn(c, buffer[:n])\n\t}\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || windows\n\npackage gnet\n\nimport (\n\t\"bytes\"\n\tcrand \"crypto/rand\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/zap\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\tbbPool \"github.com/panjf2000/gnet/v2/pkg/pool/bytebuffer\"\n\tgoPool \"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\ntype connHandler struct {\n\tnetwork string\n\trspCh   chan []byte\n\tdata    []byte\n}\n\ntype clientEvents struct {\n\t*BuiltinEventEngine\n\ttester *testing.T\n\tsvr    *testClient\n}\n\nfunc (ev *clientEvents) OnBoot(e Engine) Action {\n\tfd, err := e.Dup()\n\tassert.ErrorIsf(ev.tester, err, errorx.ErrEmptyEngine, \"expected error: %v, but got: %v\",\n\t\terrorx.ErrUnsupportedOp, err)\n\tassert.EqualValuesf(ev.tester, fd, -1, \"expected -1, but got: %d\", fd)\n\n\tfd, err = e.DupListener(\"tcp\", \"abc\")\n\tassert.ErrorIsf(ev.tester, err, errorx.ErrEmptyEngine, \"expected error: %v, but got: %v\",\n\t\terrorx.ErrUnsupportedOp, err)\n\tassert.EqualValuesf(ev.tester, fd, -1, \"expected -1, but got: %d\", fd)\n\treturn None\n}\n\nvar pingMsg = []byte(\"PING\\r\\n\")\n\nfunc (ev *clientEvents) OnOpen(Conn) (out []byte, action Action) {\n\tout = pingMsg\n\treturn\n}\n\nfunc (ev *clientEvents) OnClose(Conn, error) Action {\n\tif ev.svr != nil {\n\t\tif atomic.AddInt32(&ev.svr.clientActive, -1) == 0 {\n\t\t\treturn Shutdown\n\t\t}\n\t}\n\treturn None\n}\n\nfunc (ev *clientEvents) OnTraffic(c Conn) (action Action) {\n\thandler := c.Context().(*connHandler)\n\tpacketLen := streamLen\n\tif handler.network == \"udp\" {\n\t\tpacketLen = datagramLen\n\t}\n\tbuf, err := c.Next(-1)\n\tassert.NoError(ev.tester, err)\n\thandler.data = append(handler.data, buf...)\n\tif len(handler.data) < packetLen {\n\t\treturn\n\t}\n\thandler.rspCh <- handler.data\n\thandler.data = nil\n\treturn\n}\n\nfunc (ev *clientEvents) OnTick() (delay time.Duration, action Action) {\n\tdelay = 200 * time.Millisecond\n\treturn\n}\n\nfunc (ev *clientEvents) OnShutdown(e Engine) {\n\tfd, err := e.Dup()\n\tassert.ErrorIsf(ev.tester, err, errorx.ErrEmptyEngine, \"expected error: %v, but got: %v\",\n\t\terrorx.ErrUnsupportedOp, err)\n\tassert.EqualValuesf(ev.tester, fd, -1, \"expected -1, but got: %d\", fd)\n\n\tfd, err = e.DupListener(\"tcp\", \"abc\")\n\tassert.ErrorIsf(ev.tester, err, errorx.ErrEmptyEngine, \"expected error: %v, but got: %v\",\n\t\terrorx.ErrUnsupportedOp, err)\n\tassert.EqualValuesf(ev.tester, fd, -1, \"expected -1, but got: %d\", fd)\n}\n\nfunc TestClient(t *testing.T) {\n\t// start an engine\n\t// connect 10 clients\n\t// each client will pipe random data for 1-3 seconds.\n\t// the writes to the engine will be random sizes. 0KB - 1MB.\n\t// the engine will echo back the data.\n\t// waits for graceful connection closing.\n\tt.Run(\"poll-LT\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{false, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{false, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{false, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{false, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-ET\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-ET-chunk\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 1 << 19, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 1 << 19, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-reuseport-LT\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-reuseport-ET\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9991\", &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"tcp\", \":9992\", &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9991\", &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"udp\", \":9992\", &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunClient(t, \"unix\", testUnixAddr(t), &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n}\n\ntype testClient struct {\n\t*BuiltinEventEngine\n\tclient        *Client\n\ttester        *testing.T\n\teng           Engine\n\tnetwork       string\n\taddr          string\n\tmulticore     bool\n\tasync         bool\n\tnclients      int\n\tstarted       int32\n\tconnected     int32\n\tclientActive  int32\n\tdisconnected  int32\n\tudpReadHeader int32\n}\n\nfunc (s *testClient) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\treturn\n}\n\nfunc (s *testClient) OnOpen(c Conn) (out []byte, action Action) {\n\tc.SetContext(&sync.Once{})\n\tatomic.AddInt32(&s.connected, 1)\n\tassert.NotNil(s.tester, c.LocalAddr(), \"nil local addr\")\n\tassert.NotNil(s.tester, c.RemoteAddr(), \"nil remote addr\")\n\treturn\n}\n\nfunc (s *testClient) OnClose(c Conn, err error) (action Action) {\n\tif err != nil {\n\t\tlogging.Debugf(\"error occurred on closed, %v\\n\", err)\n\t}\n\tif s.network != \"udp\" {\n\t\tassert.IsType(s.tester, c.Context(), new(sync.Once), \"invalid context\")\n\t}\n\n\tatomic.AddInt32(&s.disconnected, 1)\n\tif atomic.LoadInt32(&s.connected) == atomic.LoadInt32(&s.disconnected) &&\n\t\tatomic.LoadInt32(&s.disconnected) == int32(s.nclients) {\n\t\taction = Shutdown\n\t}\n\n\treturn\n}\n\nfunc (s *testClient) OnShutdown(Engine) {\n\tif s.network == \"udp\" {\n\t\tassert.EqualValues(s.tester, int32(s.nclients), atomic.LoadInt32(&s.udpReadHeader))\n\t}\n}\n\nfunc (s *testClient) OnTraffic(c Conn) (action Action) {\n\treadHeader := func() {\n\t\tping := make([]byte, len(pingMsg))\n\t\tn, err := io.ReadFull(c, ping)\n\t\tassert.NoError(s.tester, err)\n\t\tassert.EqualValues(s.tester, len(pingMsg), n)\n\t\tassert.Equal(s.tester, string(pingMsg), string(ping), \"bad header\")\n\t}\n\tv := c.Context()\n\tif v != nil {\n\t\tv.(*sync.Once).Do(readHeader)\n\t}\n\n\tif s.async {\n\t\tbuf := bbPool.Get()\n\t\t_, err := c.WriteTo(buf)\n\t\tassert.NoError(s.tester, err, \"WriteTo error\")\n\n\t\tif s.network == \"tcp\" || s.network == \"unix\" {\n\t\t\t// just for test\n\t\t\t_ = c.InboundBuffered()\n\t\t\t_ = c.OutboundBuffered()\n\t\t\t_, _ = c.Discard(1)\n\t\t}\n\t\tif v == nil && bytes.Equal(buf.Bytes(), pingMsg) {\n\t\t\tatomic.AddInt32(&s.udpReadHeader, 1)\n\t\t\tbuf.Reset()\n\t\t}\n\t\terr = goPool.DefaultWorkerPool.Submit(\n\t\t\tfunc() {\n\t\t\t\tif buf.Len() > 0 {\n\t\t\t\t\terr := c.AsyncWrite(buf.Bytes(), nil)\n\t\t\t\t\tassert.NoError(s.tester, err)\n\t\t\t\t}\n\t\t\t})\n\t\tassert.NoError(s.tester, err)\n\t\treturn\n\t}\n\n\tbuf, err := c.Next(-1)\n\tassert.NoError(s.tester, err, \"Reading data error\")\n\tif v == nil && bytes.Equal(buf, pingMsg) {\n\t\tatomic.AddInt32(&s.udpReadHeader, 1)\n\t\tbuf = nil\n\t}\n\tif len(buf) > 0 {\n\t\tn, err := c.Write(buf)\n\t\tassert.NoError(s.tester, err)\n\t\tassert.EqualValues(s.tester, len(buf), n)\n\t}\n\treturn\n}\n\nfunc (s *testClient) OnTick() (delay time.Duration, action Action) {\n\tdelay = 100 * time.Millisecond\n\tif atomic.CompareAndSwapInt32(&s.started, 0, 1) {\n\t\tfor i := 0; i < s.nclients; i++ {\n\t\t\tatomic.AddInt32(&s.clientActive, 1)\n\t\t\tvar netConn bool\n\t\t\tif i%2 == 0 {\n\t\t\t\tnetConn = true\n\t\t\t}\n\t\t\terr := goPool.DefaultWorkerPool.Submit(\n\t\t\t\tfunc() {\n\t\t\t\t\tstartGnetClient(s.tester, s.client, s.network, s.addr, s.multicore, s.async, netConn)\n\t\t\t\t})\n\t\t\tassert.NoError(s.tester, err)\n\n\t\t}\n\t}\n\tif s.network == \"udp\" && atomic.LoadInt32(&s.clientActive) == 0 {\n\t\taction = Shutdown\n\t\treturn\n\t}\n\treturn\n}\n\nfunc runClient(t *testing.T, network, addr string, conf *testConf) {\n\tts := &testClient{\n\t\ttester:    t,\n\t\tnetwork:   network,\n\t\taddr:      addr,\n\t\tmulticore: conf.multicore,\n\t\tasync:     conf.async,\n\t\tnclients:  conf.clients,\n\t}\n\tvar err error\n\tclientEV := &clientEvents{tester: t, svr: ts}\n\tts.client, err = NewClient(\n\t\tclientEV,\n\t\tWithMulticore(conf.multicore),\n\t\tWithEdgeTriggeredIO(conf.et),\n\t\tWithEdgeTriggeredIOChunk(conf.etChunk),\n\t\tWithTCPNoDelay(TCPNoDelay),\n\t\tWithLockOSThread(true),\n\t\tWithTicker(true),\n\t)\n\tassert.NoError(t, err)\n\n\terr = ts.client.Start()\n\tassert.NoError(t, err)\n\tdefer ts.client.Stop() //nolint:errcheck\n\n\terr = Run(ts,\n\t\tnetwork+\"://\"+addr,\n\t\tWithEdgeTriggeredIO(conf.et),\n\t\tWithEdgeTriggeredIOChunk(conf.etChunk),\n\t\tWithLockOSThread(conf.async),\n\t\tWithMulticore(conf.multicore),\n\t\tWithReusePort(conf.reuseport),\n\t\tWithTicker(true),\n\t\tWithTCPKeepAlive(time.Minute),\n\t\tWithTCPKeepInterval(time.Second*10),\n\t\tWithTCPKeepCount(10),\n\t\tWithLoadBalancing(conf.lb))\n\tassert.NoError(t, err)\n}\n\nfunc startGnetClient(t *testing.T, cli *Client, network, addr string, multicore, async, netDial bool) {\n\tvar (\n\t\tc   Conn\n\t\terr error\n\t)\n\thandler := &connHandler{\n\t\tnetwork: network,\n\t\trspCh:   make(chan []byte, 1),\n\t}\n\tif netDial {\n\t\tvar netConn net.Conn\n\t\tnetConn, err = net.Dial(network, addr)\n\t\tassert.NoError(t, err)\n\t\tc, err = cli.EnrollContext(netConn, handler)\n\t} else {\n\t\tc, err = cli.DialContext(network, addr, handler)\n\t}\n\tassert.NoError(t, err)\n\tdefer c.Close() //nolint:errcheck\n\terr = c.Wake(nil)\n\tassert.NoError(t, err)\n\trspCh := handler.rspCh\n\tduration := time.Duration((rand.Float64()*2+1)*float64(time.Second)) / 2\n\tlogging.Debugf(\"test duration: %v\", duration)\n\tstart := time.Now()\n\tfor time.Since(start) < duration {\n\t\treqData := make([]byte, streamLen)\n\t\tif network == \"udp\" {\n\t\t\treqData = reqData[:datagramLen]\n\t\t}\n\t\t_, err = crand.Read(reqData)\n\t\tassert.NoError(t, err)\n\t\terr = c.AsyncWrite(reqData, nil)\n\t\tassert.NoError(t, err)\n\t\trespData := <-rspCh\n\t\tassert.NoError(t, err)\n\t\tif !async {\n\t\t\t// assert.Equalf(t, reqData, respData, \"response mismatch with protocol:%s, multi-core:%t, content of bytes: %d vs %d\", network, multicore, string(reqData), string(respData))\n\t\t\tassert.Equalf(\n\t\t\t\tt,\n\t\t\t\treqData,\n\t\t\t\trespData,\n\t\t\t\t\"response mismatch with protocol:%s, multi-core:%t, length of bytes: %d vs %d\",\n\t\t\t\tnetwork,\n\t\t\t\tmulticore,\n\t\t\t\tlen(reqData),\n\t\t\t\tlen(respData),\n\t\t\t)\n\t\t}\n\t}\n}\n\ntype clientEventsForWake struct {\n\tBuiltinEventEngine\n\ttester *testing.T\n\tch     chan struct{}\n}\n\nfunc (ev *clientEventsForWake) OnBoot(_ Engine) Action {\n\tev.ch = make(chan struct{})\n\treturn None\n}\n\nfunc (ev *clientEventsForWake) OnTraffic(c Conn) (action Action) {\n\tn, err := c.Read(nil)\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, n)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tbuf := make([]byte, 10)\n\tn, err = c.Read(buf)\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, n)\n\tassert.ErrorIsf(ev.tester, err, io.ErrShortBuffer, \"expected error: %v, but got: %v\", io.ErrShortBuffer, err)\n\tbuf, err = c.Next(10)\n\tassert.Nilf(ev.tester, buf, \"expected: %v, but got: %v\", nil, buf)\n\tassert.ErrorIsf(ev.tester, err, io.ErrShortBuffer, \"expected error: %v, but got: %v\", io.ErrShortBuffer, err)\n\tbuf, err = c.Next(-1)\n\tassert.Emptyf(ev.tester, buf, \"expected an empty slice, but got: %v\", buf)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tbuf, err = c.Peek(10)\n\tassert.Nilf(ev.tester, buf, \"expected: %v, but got: %v\", nil, buf)\n\tassert.ErrorIsf(ev.tester, err, io.ErrShortBuffer, \"expected error: %v, but got: %v\", io.ErrShortBuffer, err)\n\tbuf, err = c.Peek(-1)\n\tassert.Emptyf(ev.tester, buf, \"expected an empty slice, but got: %v\", buf)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tn, err = c.Discard(10)\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, n)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tn, err = c.Discard(-1)\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, n)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tm, err := c.WriteTo(io.Discard)\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, m)\n\tassert.NoErrorf(ev.tester, err, \"expected: %v, but got: %v\", nil, err)\n\tn = c.InboundBuffered()\n\tassert.Zerof(ev.tester, n, \"expected: %v, but got: %v\", 0, m)\n\t<-ev.ch\n\treturn None\n}\n\ntype serverEventsForWake struct {\n\tBuiltinEventEngine\n\tnetwork, addr string\n\tclient        *Client\n\tclientEV      *clientEventsForWake\n\ttester        *testing.T\n\tclients       int32\n\tstarted       int32\n}\n\nfunc (ev *serverEventsForWake) OnOpen(_ Conn) ([]byte, Action) {\n\tatomic.AddInt32(&ev.clients, 1)\n\treturn nil, None\n}\n\nfunc (ev *serverEventsForWake) OnClose(_ Conn, _ error) Action {\n\tif atomic.AddInt32(&ev.clients, -1) == 0 {\n\t\treturn Shutdown\n\t}\n\treturn None\n}\n\nfunc (ev *serverEventsForWake) OnTick() (time.Duration, Action) {\n\tif atomic.CompareAndSwapInt32(&ev.started, 0, 1) {\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\ttestConnWakeImmediately(ev.tester, ev.client, ev.clientEV, ev.network, ev.addr)\n\t\t})\n\t\tassert.NoError(ev.tester, err)\n\t}\n\treturn 100 * time.Millisecond, None\n}\n\nfunc testConnWakeImmediately(t *testing.T, client *Client, clientEV *clientEventsForWake, network, addr string) {\n\tc, err := client.Dial(network, addr)\n\tassert.NoErrorf(t, err, \"failed to dial: %v\", err)\n\terr = c.Wake(nil)\n\tassert.NoError(t, err)\n\terr = c.Close()\n\tassert.NoError(t, err)\n\tclientEV.ch <- struct{}{}\n}\n\nfunc TestWakeConnImmediately(t *testing.T) {\n\tcurrentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tt.Cleanup(func() {\n\t\tlogging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore\n\t})\n\n\tclientEV := &clientEventsForWake{tester: t}\n\tlogPath := filepath.Join(t.TempDir(), \"gnet-test-wake-conn-immediately.log\")\n\tclient, err := NewClient(clientEV,\n\t\tWithSocketRecvBuffer(4*1024),\n\t\tWithSocketSendBuffer(4*1024),\n\t\tWithLogPath(logPath),\n\t\tWithLogLevel(logging.WarnLevel),\n\t\tWithReadBufferCap(512),\n\t\tWithWriteBufferCap(512))\n\tassert.NoError(t, err)\n\tlogging.Cleanup()\n\n\terr = client.Start()\n\tassert.NoError(t, err)\n\tdefer client.Stop() //nolint:errcheck\n\n\tserverEV := &serverEventsForWake{tester: t, network: \"tcp\", addr: \":18888\", client: client, clientEV: clientEV}\n\n\terr = Run(serverEV, serverEV.network+\"://\"+serverEV.addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestClientReadOnEOF(t *testing.T) {\n\tcurrentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tt.Cleanup(func() {\n\t\tlogging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore\n\t})\n\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:9999\")\n\tassert.NoError(t, err)\n\tdefer ln.Close() //nolint:errcheck\n\n\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\tfor {\n\t\t\tconn, err := ln.Accept()\n\t\t\tif err != nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tprocess(conn)\n\t\t\t})\n\t\t\tassert.NoError(t, err)\n\t\t}\n\t})\n\tassert.NoError(t, err)\n\n\tev := &clientReadOnEOF{\n\t\tresult: make(chan struct {\n\t\t\tdata []byte\n\t\t\terr  error\n\t\t}, 1),\n\t\tdata: []byte(\"test\"),\n\t}\n\tcli, err := NewClient(ev,\n\t\tWithSocketRecvBuffer(4*1024),\n\t\tWithSocketSendBuffer(4*1024),\n\t\tWithTCPKeepAlive(time.Minute),\n\t\tWithLogger(zap.NewExample().Sugar()),\n\t\tWithReadBufferCap(32*1024),\n\t\tWithWriteBufferCap(32*1024))\n\tassert.NoError(t, err)\n\tdefer cli.Stop() //nolint:errcheck\n\n\terr = cli.Start()\n\tassert.NoError(t, err)\n\n\t_, err = cli.Dial(\"tcp\", \"127.0.0.1:9999\")\n\tassert.NoError(t, err)\n\n\tselect {\n\tcase res := <-ev.result:\n\t\tassert.NoError(t, res.err)\n\t\tassert.EqualValuesf(t, ev.data, res.data, \"expected: %v, but got: %v\", ev.data, res.data)\n\tcase <-time.After(5 * time.Second):\n\t\tt.Errorf(\"timeout waiting for the result\")\n\t}\n}\n\nfunc process(conn net.Conn) {\n\tdefer conn.Close() //nolint:errcheck\n\tbuf := make([]byte, 8)\n\tn, err := conn.Read(buf)\n\tif err != nil {\n\t\treturn\n\t}\n\t_, _ = conn.Write(buf[:n])\n\t_ = conn.Close()\n}\n\ntype clientReadOnEOF struct {\n\tBuiltinEventEngine\n\tdata   []byte\n\tresult chan struct {\n\t\tdata []byte\n\t\terr  error\n\t}\n}\n\nfunc (clientReadOnEOF) OnBoot(Engine) (action Action) {\n\treturn None\n}\n\nfunc (cli clientReadOnEOF) OnOpen(Conn) (out []byte, action Action) {\n\treturn cli.data, None\n}\n\nfunc (clientReadOnEOF) OnClose(Conn, error) (action Action) {\n\treturn Close\n}\n\nfunc (cli clientReadOnEOF) OnTraffic(c Conn) (action Action) {\n\tdata, err := c.Next(-1)\n\tcli.result <- struct {\n\t\tdata []byte\n\t\terr  error\n\t}{data: data, err: err}\n\treturn None\n}\n"
  },
  {
    "path": "client_unix.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"strconv\"\n\t\"syscall\"\n\n\t\"golang.org/x/sync/errgroup\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/ring\"\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/math\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\n// Client of gnet.\ntype Client struct {\n\topts *Options\n\teng  *engine\n}\n\n// NewClient creates an instance of Client.\nfunc NewClient(eh EventHandler, opts ...Option) (cli *Client, err error) {\n\toptions := loadOptions(opts...)\n\tcli = new(Client)\n\tcli.opts = options\n\n\tlogger, logFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tif options.Logger == nil {\n\t\tif options.LogPath != \"\" {\n\t\t\tlogger, logFlusher, _ = logging.CreateLoggerAsLocalFile(options.LogPath, options.LogLevel)\n\t\t}\n\t\toptions.Logger = logger\n\t} else {\n\t\tlogger = options.Logger\n\t\tlogFlusher = nil\n\t}\n\tlogging.SetDefaultLoggerAndFlusher(logger, logFlusher)\n\n\trootCtx, shutdown := context.WithCancel(context.Background())\n\teg, ctx := errgroup.WithContext(rootCtx)\n\teng := engine{\n\t\tlisteners:    make(map[int]*listener),\n\t\topts:         options,\n\t\tturnOff:      shutdown,\n\t\teventHandler: eh,\n\t\teventLoops:   new(leastConnectionsLoadBalancer),\n\t\tconcurrency: struct {\n\t\t\t*errgroup.Group\n\t\t\tctx context.Context\n\t\t}{eg, ctx},\n\t}\n\n\tif options.EdgeTriggeredIOChunk > 0 {\n\t\toptions.EdgeTriggeredIO = true\n\t\toptions.EdgeTriggeredIOChunk = math.CeilToPowerOfTwo(options.EdgeTriggeredIOChunk)\n\t} else if options.EdgeTriggeredIO {\n\t\toptions.EdgeTriggeredIOChunk = 1 << 20 // 1MB\n\t}\n\n\trbc := options.ReadBufferCap\n\tswitch {\n\tcase rbc <= 0:\n\t\toptions.ReadBufferCap = MaxStreamBufferCap\n\tcase rbc <= ring.DefaultBufferSize:\n\t\toptions.ReadBufferCap = ring.DefaultBufferSize\n\tdefault:\n\t\toptions.ReadBufferCap = math.CeilToPowerOfTwo(rbc)\n\t}\n\twbc := options.WriteBufferCap\n\tswitch {\n\tcase wbc <= 0:\n\t\toptions.WriteBufferCap = MaxStreamBufferCap\n\tcase wbc <= ring.DefaultBufferSize:\n\t\toptions.WriteBufferCap = ring.DefaultBufferSize\n\tdefault:\n\t\toptions.WriteBufferCap = math.CeilToPowerOfTwo(wbc)\n\t}\n\tcli.eng = &eng\n\treturn\n}\n\n// Start starts the client event-loop, handing IO events.\nfunc (cli *Client) Start() error {\n\tnumEventLoop := determineEventLoops(cli.opts)\n\tlogging.Infof(\"Starting gnet client with %d event loops\", numEventLoop)\n\n\tcli.eng.eventHandler.OnBoot(Engine{cli.eng})\n\n\tvar el0 *eventloop\n\tfor i := 0; i < numEventLoop; i++ {\n\t\tp, err := netpoll.OpenPoller()\n\t\tif err != nil {\n\t\t\tcli.eng.closeEventLoops()\n\t\t\treturn err\n\t\t}\n\t\tel := eventloop{\n\t\t\tlisteners:    cli.eng.listeners,\n\t\t\tengine:       cli.eng,\n\t\t\tpoller:       p,\n\t\t\tbuffer:       make([]byte, cli.opts.ReadBufferCap),\n\t\t\teventHandler: cli.eng.eventHandler,\n\t\t}\n\t\tel.connections.init()\n\t\tcli.eng.eventLoops.register(&el)\n\t\tif cli.opts.Ticker && el.idx == 0 {\n\t\t\tel0 = &el\n\t\t}\n\t}\n\n\tcli.eng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\tcli.eng.concurrency.Go(el.run)\n\t\treturn true\n\t})\n\n\t// Start the ticker.\n\tif el0 != nil {\n\t\tctx := cli.eng.concurrency.ctx\n\t\tcli.eng.concurrency.Go(func() error {\n\t\t\tel0.ticker(ctx)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tlogging.Debugf(\"default logging level is %s\", logging.LogLevel())\n\n\treturn nil\n}\n\n// Stop stops the client event-loop.\nfunc (cli *Client) Stop() error {\n\tcli.eng.shutdown(nil)\n\n\tcli.eng.eventHandler.OnShutdown(Engine{cli.eng})\n\n\t// Notify all event-loops to exit.\n\tcli.eng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\tlogging.Error(el.poller.Trigger(queue.HighPriority,\n\t\t\tfunc(_ any) error { return errorx.ErrEngineShutdown }, nil))\n\t\treturn true\n\t})\n\n\t// Wait for all event-loops to exit.\n\terr := cli.eng.concurrency.Wait()\n\n\tcli.eng.closeEventLoops()\n\n\t// Put the engine into the shutdown state.\n\tcli.eng.inShutdown.Store(true)\n\n\t// Flush the logger.\n\tlogging.Cleanup()\n\n\treturn err\n}\n\n// Dial is like net.Dial().\nfunc (cli *Client) Dial(network, address string) (Conn, error) {\n\treturn cli.DialContext(network, address, nil)\n}\n\n// DialContext is like Dial but also accepts an empty interface ctx that can be obtained later via Conn.Context.\nfunc (cli *Client) DialContext(network, address string, ctx any) (Conn, error) {\n\tc, err := net.Dial(network, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cli.EnrollContext(c, ctx)\n}\n\n// Enroll converts a net.Conn to gnet.Conn and then adds it into the Client.\nfunc (cli *Client) Enroll(c net.Conn) (Conn, error) {\n\treturn cli.EnrollContext(c, nil)\n}\n\n// EnrollContext is like Enroll but also accepts an empty interface ctx that can be obtained later via Conn.Context.\nfunc (cli *Client) EnrollContext(c net.Conn, ctx any) (Conn, error) {\n\tdefer c.Close() //nolint:errcheck\n\n\tsc, ok := c.(syscall.Conn)\n\tif !ok {\n\t\treturn nil, errors.New(\"failed to convert net.Conn to syscall.Conn\")\n\t}\n\trc, err := sc.SyscallConn()\n\tif err != nil {\n\t\treturn nil, errors.New(\"failed to get syscall.RawConn from net.Conn\")\n\t}\n\n\tvar dupFD int\n\te := rc.Control(func(fd uintptr) {\n\t\tdupFD, err = socket.Dup(int(fd))\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif e != nil {\n\t\treturn nil, e\n\t}\n\n\tif cli.opts.SocketSendBuffer > 0 {\n\t\tif err = socket.SetSendBuffer(dupFD, cli.opts.SocketSendBuffer); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif cli.opts.SocketRecvBuffer > 0 {\n\t\tif err = socket.SetRecvBuffer(dupFD, cli.opts.SocketRecvBuffer); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tel := cli.eng.eventLoops.next(nil)\n\tvar (\n\t\tsockAddr unix.Sockaddr\n\t\tgc       *conn\n\t)\n\tswitch c.(type) {\n\tcase *net.UnixConn:\n\t\tsockAddr, _, _, err = socket.GetUnixSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tua := c.LocalAddr().(*net.UnixAddr)\n\t\tua.Name = c.RemoteAddr().String() + \".\" + strconv.Itoa(dupFD)\n\t\tgc = newStreamConn(\"unix\", dupFD, el, sockAddr, c.LocalAddr(), c.RemoteAddr())\n\tcase *net.TCPConn:\n\t\tif cli.opts.TCPNoDelay == TCPNoDelay {\n\t\t\tif err = socket.SetNoDelay(dupFD, 1); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif cli.opts.TCPKeepAlive > 0 {\n\t\t\tif err = setKeepAlive(\n\t\t\t\tdupFD,\n\t\t\t\ttrue,\n\t\t\t\tcli.opts.TCPKeepAlive,\n\t\t\t\tcli.opts.TCPKeepInterval,\n\t\t\t\tcli.opts.TCPKeepCount); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tsockAddr, _, _, _, err = socket.GetTCPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgc = newStreamConn(\"tcp\", dupFD, el, sockAddr, c.LocalAddr(), c.RemoteAddr())\n\tcase *net.UDPConn:\n\t\tsockAddr, _, _, _, err = socket.GetUDPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgc = newUDPConn(dupFD, el, c.LocalAddr(), sockAddr, true)\n\tdefault:\n\t\treturn nil, errorx.ErrUnsupportedProtocol\n\t}\n\tgc.ctx = ctx\n\n\tconnOpened := make(chan struct{})\n\tccb := &connWithCallback{c: gc, cb: func() {\n\t\tclose(connOpened)\n\t}}\n\terr = el.poller.Trigger(queue.HighPriority, el.register, ccb)\n\tif err != nil {\n\t\tgc.Close() //nolint:errcheck\n\t\treturn nil, err\n\t}\n\t<-connOpened\n\n\treturn gc, nil\n}\n"
  },
  {
    "path": "client_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\ntype Client struct {\n\topts *Options\n\teng  *engine\n}\n\nfunc NewClient(eh EventHandler, opts ...Option) (cli *Client, err error) {\n\toptions := loadOptions(opts...)\n\tcli = &Client{opts: options}\n\n\tlogger, logFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tif options.Logger == nil {\n\t\tif options.LogPath != \"\" {\n\t\t\tlogger, logFlusher, _ = logging.CreateLoggerAsLocalFile(options.LogPath, options.LogLevel)\n\t\t}\n\t\toptions.Logger = logger\n\t} else {\n\t\tlogger = options.Logger\n\t\tlogFlusher = nil\n\t}\n\tlogging.SetDefaultLoggerAndFlusher(logger, logFlusher)\n\n\trootCtx, shutdown := context.WithCancel(context.Background())\n\teg, ctx := errgroup.WithContext(rootCtx)\n\teng := engine{\n\t\tlisteners:    []*listener{},\n\t\topts:         options,\n\t\tturnOff:      shutdown,\n\t\teventHandler: eh,\n\t\teventLoops:   new(leastConnectionsLoadBalancer),\n\t\tconcurrency: struct {\n\t\t\t*errgroup.Group\n\t\t\tctx context.Context\n\t\t}{eg, ctx},\n\t}\n\tcli.eng = &eng\n\treturn\n}\n\nfunc (cli *Client) Start() error {\n\tnumEventLoop := determineEventLoops(cli.opts)\n\tlogging.Infof(\"Starting gnet client with %d event loops\", numEventLoop)\n\n\tcli.eng.eventHandler.OnBoot(Engine{cli.eng})\n\n\tvar el0 *eventloop\n\tfor i := 0; i < numEventLoop; i++ {\n\t\tel := eventloop{\n\t\t\tch:           make(chan any, 1024),\n\t\t\teng:          cli.eng,\n\t\t\tconnections:  make(map[*conn]struct{}),\n\t\t\teventHandler: cli.eng.eventHandler,\n\t\t}\n\t\tcli.eng.eventLoops.register(&el)\n\t\tcli.eng.concurrency.Go(el.run)\n\t\tif cli.opts.Ticker && el.idx == 0 {\n\t\t\tel0 = &el\n\t\t}\n\t}\n\n\tif el0 != nil {\n\t\tctx := cli.eng.concurrency.ctx\n\t\tcli.eng.concurrency.Go(func() error {\n\t\t\tel0.ticker(ctx)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tlogging.Debugf(\"default logging level is %s\", logging.LogLevel())\n\n\treturn nil\n}\n\nfunc (cli *Client) Stop() error {\n\tcli.eng.shutdown(nil)\n\n\tcli.eng.eventHandler.OnShutdown(Engine{cli.eng})\n\n\t// Notify all event-loops to exit.\n\tcli.eng.closeEventLoops()\n\n\t// Wait for all event-loops to exit.\n\terr := cli.eng.concurrency.Wait()\n\n\t// Put the engine into the shutdown state.\n\tcli.eng.inShutdown.Store(true)\n\n\t// Flush the logger.\n\tlogging.Cleanup()\n\n\treturn err\n}\n\nfunc (cli *Client) Dial(network, addr string) (Conn, error) {\n\treturn cli.DialContext(network, addr, nil)\n}\n\nfunc (cli *Client) DialContext(network, addr string, ctx any) (Conn, error) {\n\tvar (\n\t\tc   net.Conn\n\t\terr error\n\t)\n\tc, err = net.Dial(network, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn cli.EnrollContext(c, ctx)\n}\n\nfunc (cli *Client) Enroll(nc net.Conn) (gc Conn, err error) {\n\treturn cli.EnrollContext(nc, nil)\n}\n\nfunc (cli *Client) EnrollContext(nc net.Conn, ctx any) (gc Conn, err error) {\n\tel := cli.eng.eventLoops.next(nil)\n\tconnOpened := make(chan struct{})\n\tswitch v := nc.(type) {\n\tcase *net.TCPConn:\n\t\tif cli.opts.TCPNoDelay == TCPNoDelay {\n\t\t\tif err = v.SetNoDelay(true); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tc := newStreamConn(el, nc, ctx)\n\t\tif opts := cli.opts; opts.TCPKeepAlive > 0 {\n\t\t\tidle := opts.TCPKeepAlive\n\t\t\tintvl := opts.TCPKeepInterval\n\t\t\tif intvl == 0 {\n\t\t\t\tintvl = opts.TCPKeepAlive / 5\n\t\t\t}\n\t\t\tcnt := opts.TCPKeepCount\n\t\t\tif opts.TCPKeepCount == 0 {\n\t\t\t\tcnt = 5\n\t\t\t}\n\t\t\tif err = c.SetKeepAlive(true, idle, intvl, cnt); err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tel.ch <- &openConn{c: c, cb: func() { close(connOpened) }}\n\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tvar buffer [0x10000]byte\n\t\t\tfor {\n\t\t\t\tn, err := nc.Read(buffer[:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tel.ch <- &netErr{c, err}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tel.ch <- packTCPConn(c, buffer[:n])\n\t\t\t}\n\t\t})\n\t\tgc = c\n\tcase *net.UnixConn:\n\t\tc := newStreamConn(el, nc, ctx)\n\t\tel.ch <- &openConn{c: c, cb: func() { close(connOpened) }}\n\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tvar buffer [0x10000]byte\n\t\t\tfor {\n\t\t\t\tn, err := nc.Read(buffer[:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tel.ch <- &netErr{c, err}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tel.ch <- packTCPConn(c, buffer[:n])\n\t\t\t}\n\t\t})\n\t\tgc = c\n\tcase *net.UDPConn:\n\t\tc := newUDPConn(el, nil, nc, nc.LocalAddr(), nc.RemoteAddr(), ctx)\n\t\tel.ch <- &openConn{c: c, cb: func() { close(connOpened) }}\n\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tvar buffer [0x10000]byte\n\t\t\tfor {\n\t\t\t\tn, err := nc.Read(buffer[:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tel.ch <- &netErr{c, err}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tc := newUDPConn(el, nil, nc, nc.LocalAddr(), nc.RemoteAddr(), ctx)\n\t\t\t\tel.ch <- packUDPConn(c, buffer[:n])\n\t\t\t}\n\t\t})\n\t\tgc = c\n\tdefault:\n\t\treturn nil, errorx.ErrUnsupportedProtocol\n\t}\n\t<-connOpened\n\n\treturn\n}\n"
  },
  {
    "path": "conn_map.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && !gc_opt\n\npackage gnet\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/panjf2000/gnet/v2/internal/gfd\"\n)\n\ntype connMatrix struct {\n\tconnCount int32\n\tconnMap   map[int]*conn\n}\n\nfunc (cm *connMatrix) init() {\n\tcm.connMap = make(map[int]*conn)\n}\n\nfunc (cm *connMatrix) iterate(f func(*conn) bool) {\n\tfor _, c := range cm.connMap {\n\t\tif c != nil {\n\t\t\tif !f(c) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (cm *connMatrix) incCount(_ int, delta int32) {\n\tatomic.AddInt32(&cm.connCount, delta)\n}\n\nfunc (cm *connMatrix) loadCount() (n int32) {\n\treturn atomic.LoadInt32(&cm.connCount)\n}\n\nfunc (cm *connMatrix) addConn(c *conn, index int) {\n\tc.gfd = gfd.NewGFD(c.fd, index, 0, 0)\n\tcm.connMap[c.fd] = c\n\tcm.incCount(0, 1)\n}\n\nfunc (cm *connMatrix) delConn(c *conn) {\n\tdelete(cm.connMap, c.fd)\n\tcm.incCount(0, -1)\n}\n\nfunc (cm *connMatrix) getConn(fd int) *conn {\n\treturn cm.connMap[fd]\n}\n\n/*\nfunc (cm *connMatrix) getConnByGFD(fd gfd.GFD) *conn {\n\treturn cm.connMap[fd.Fd()]\n}\n*/\n"
  },
  {
    "path": "conn_matrix.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && gc_opt\n\npackage gnet\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/panjf2000/gnet/v2/internal/gfd\"\n)\n\ntype connMatrix struct {\n\tdisableCompact bool                          // disable compaction when it is true\n\tconnCounts     [gfd.ConnMatrixRowMax]int32   // number of active connections in event-loop\n\trow            int                           // next available row index\n\tcolumn         int                           // next available column index\n\ttable          [gfd.ConnMatrixRowMax][]*conn // connection matrix of *conn, multiple slices\n\tfd2gfd         map[int]gfd.GFD               // fd -> gfd.GFD\n}\n\nfunc (cm *connMatrix) init() {\n\tcm.fd2gfd = make(map[int]gfd.GFD)\n}\n\nfunc (cm *connMatrix) iterate(f func(*conn) bool) {\n\tcm.disableCompact = true\n\tdefer func() { cm.disableCompact = false }()\n\tfor _, conns := range cm.table {\n\t\tfor _, c := range conns {\n\t\t\tif c != nil {\n\t\t\t\tif !f(c) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (cm *connMatrix) incCount(row int, delta int32) {\n\tatomic.AddInt32(&cm.connCounts[row], delta)\n}\n\nfunc (cm *connMatrix) loadCount() (n int32) {\n\tfor i := 0; i < len(cm.connCounts); i++ {\n\t\tn += atomic.LoadInt32(&cm.connCounts[i])\n\t}\n\treturn\n}\n\nfunc (cm *connMatrix) addConn(c *conn, index int) {\n\tif cm.row >= gfd.ConnMatrixRowMax {\n\t\treturn\n\t}\n\n\tif cm.table[cm.row] == nil {\n\t\tcm.table[cm.row] = make([]*conn, gfd.ConnMatrixColumnMax)\n\t}\n\n\tc.gfd = gfd.NewGFD(c.fd, index, cm.row, cm.column)\n\tcm.fd2gfd[c.fd] = c.gfd\n\tcm.table[cm.row][cm.column] = c\n\tcm.incCount(cm.row, 1)\n\n\tif cm.column++; cm.column == gfd.ConnMatrixColumnMax {\n\t\tcm.row++\n\t\tcm.column = 0\n\t}\n}\n\nfunc (cm *connMatrix) delConn(c *conn) {\n\tcfd, cgfd := c.fd, c.gfd\n\n\tdelete(cm.fd2gfd, cfd)\n\tcm.incCount(cgfd.ConnMatrixRow(), -1)\n\tif cm.connCounts[cgfd.ConnMatrixRow()] == 0 {\n\t\tcm.table[cgfd.ConnMatrixRow()] = nil\n\t} else {\n\t\tcm.table[cgfd.ConnMatrixRow()][cgfd.ConnMatrixColumn()] = nil\n\t}\n\tif cm.row > cgfd.ConnMatrixRow() || cm.column > cgfd.ConnMatrixColumn() {\n\t\tcm.row, cm.column = cgfd.ConnMatrixRow(), cgfd.ConnMatrixColumn()\n\t}\n\n\t// Locate the last *conn in table and move it to the deleted location.\n\n\tif cm.disableCompact || cm.table[cgfd.ConnMatrixRow()] == nil { // the deleted *conn is the last one, do nothing here.\n\t\treturn\n\t}\n\n\t// Traverse backward to find the first non-empty point in the matrix until we reach the deleted position.\n\tfor row := gfd.ConnMatrixRowMax - 1; row >= cgfd.ConnMatrixRow(); row-- {\n\t\tif cm.connCounts[row] == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tcolumnMin := -1\n\t\tif row == cgfd.ConnMatrixRow() {\n\t\t\tcolumnMin = cgfd.ConnMatrixColumn()\n\t\t}\n\t\tfor column := gfd.ConnMatrixColumnMax - 1; column > columnMin; column-- {\n\t\t\tif cm.table[row][column] == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgFd := cm.table[row][column].gfd\n\t\t\tgFd.UpdateIndexes(cgfd.ConnMatrixRow(), cgfd.ConnMatrixColumn())\n\t\t\tcm.table[row][column].gfd = gFd\n\t\t\tcm.fd2gfd[gFd.Fd()] = gFd\n\t\t\tcm.table[cgfd.ConnMatrixRow()][cgfd.ConnMatrixColumn()] = cm.table[row][column]\n\t\t\tcm.incCount(row, -1)\n\t\t\tcm.incCount(cgfd.ConnMatrixRow(), 1)\n\n\t\t\tif cm.connCounts[row] == 0 {\n\t\t\t\tcm.table[row] = nil\n\t\t\t} else {\n\t\t\t\tcm.table[row][column] = nil\n\t\t\t}\n\n\t\t\tcm.row, cm.column = row, column\n\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (cm *connMatrix) getConn(fd int) *conn {\n\tgFD, ok := cm.fd2gfd[fd]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif cm.table[gFD.ConnMatrixRow()] == nil {\n\t\treturn nil\n\t}\n\treturn cm.table[gFD.ConnMatrixRow()][gFD.ConnMatrixColumn()]\n}\n\n/*\nfunc (cm *connMatrix) getConnByGFD(fd gfd.GFD) *conn {\n\tif cm.table[fd.ConnMatrixRow()] == nil {\n\t\treturn nil\n\t}\n\treturn cm.table[fd.ConnMatrixRow()][fd.ConnMatrixColumn()]\n}\n*/\n"
  },
  {
    "path": "conn_matrix_test.go",
    "content": "//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && gc_opt\n\npackage gnet\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/sys/unix\"\n\n\tgoPool \"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\nvar testVastConns = false\n\nfunc TestConnMatrix(t *testing.T) {\n\tt.Run(\"1k-connections\", func(t *testing.T) {\n\t\ttestConnMatrix(t, 1000)\n\t})\n\tt.Run(\"10k-connections\", func(t *testing.T) {\n\t\ttestConnMatrix(t, 10000)\n\t})\n\tt.Run(\"100k-connections\", func(t *testing.T) {\n\t\tif !testVastConns {\n\t\t\tt.Skip(\"skipped because testVastConns is set to false\")\n\t\t}\n\t\ttestConnMatrix(t, 100000)\n\t})\n\tt.Run(\"1m-connections\", func(t *testing.T) {\n\t\tif !testVastConns {\n\t\t\tt.Skip(\"skipped because testVastConns is set to false\")\n\t\t}\n\t\ttestConnMatrix(t, 1000000)\n\t})\n}\n\nconst (\n\tactionAdd = iota + 1\n\tactionDel\n)\n\ntype handleConn struct {\n\tc      *conn\n\taction int\n}\n\nfunc testConnMatrix(t *testing.T, n int) {\n\thandleConns := make(chan *handleConn, 1024)\n\tconnections := connMatrix{}\n\tconnections.init()\n\tel := eventloop{engine: &engine{opts: &Options{}}}\n\n\tdone := make(chan struct{})\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\tfor i := 0; i < n+n/2; i++ {\n\t\t\tv := <-handleConns\n\t\t\tswitch v.action {\n\t\t\tcase actionAdd:\n\t\t\t\tconnections.addConn(v.c, 0)\n\t\t\tcase actionDel:\n\t\t\t\tconnections.delConn(v.c)\n\t\t\t}\n\t\t}\n\t\tclose(done)\n\t})\n\trequire.NoError(t, err)\n\n\tfor i := 0; i < n; i++ {\n\t\tc := newStreamConn(\"tcp\", i, &el, &unix.SockaddrInet4{}, &net.TCPAddr{}, &net.TCPAddr{})\n\t\thandleConns <- &handleConn{c, actionAdd}\n\t\tif i%2 == 0 {\n\t\t\t_ = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\thandleConns <- &handleConn{c, actionDel}\n\t\t\t})\n\t\t}\n\t}\n\tm := n / 2\n\n\t<-done\n\tif count := connections.loadCount(); count != int32(n)/2 {\n\t\tt.Fatalf(\"unexpected conn count %d, expected %d\", count, int32(n)/2)\n\t}\n\n\tfor i := 0; i < len(connections.table); i++ {\n\t\tif connections.connCounts[i] == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor j := 0; j < len(connections.table[i]) && m > 0; j++ {\n\t\t\tm--\n\t\t\tc := connections.table[i][j]\n\t\t\tif c == nil {\n\t\t\t\tt.Fatalf(\"unexpected nil connection at row %d, column %d\", i, j)\n\t\t\t}\n\t\t\tif c.fd != c.gfd.Fd() {\n\t\t\t\tt.Fatalf(\"unexpected fd %d, expected fd %d\", c.gfd.Fd(), c.fd)\n\t\t\t}\n\t\t\tif i != c.gfd.ConnMatrixRow() || j != c.gfd.ConnMatrixColumn() {\n\t\t\t\tt.Fatalf(\"unexpected row %d, column %d, expected row %d, column %d\",\n\t\t\t\t\tc.gfd.ConnMatrixRow(), c.gfd.ConnMatrixColumn(), i, j)\n\t\t\t}\n\t\t\tgfd, ok := connections.fd2gfd[c.fd]\n\t\t\tif !ok {\n\t\t\t\tt.Fatalf(\"missing gfd for fd %d\", c.fd)\n\t\t\t}\n\t\t\tif gfd != c.gfd {\n\t\t\t\tt.Fatalf(\"expected gfd: %v, but got gfd: %v\", c.gfd, gfd)\n\t\t\t}\n\t\t}\n\t}\n\n\tt.Log(\"connMatrix remains compact after many additions and deletions, test done!\")\n}\n"
  },
  {
    "path": "connection_bsd.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"io\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n)\n\nfunc (c *conn) processIO(_ int, filter netpoll.IOEvent, flags netpoll.IOFlags) (err error) {\n\tel := c.loop\n\tswitch filter {\n\tcase unix.EVFILT_READ:\n\t\terr = el.read(c)\n\tcase unix.EVFILT_WRITE:\n\t\terr = el.write(c)\n\t}\n\t// EV_EOF indicates that the remote has closed the connection.\n\t// We check for EV_EOF after processing the read/write event\n\t// to ensure that nothing is left out on this event filter.\n\tif flags&unix.EV_EOF != 0 && c.opened && err == nil {\n\t\tswitch filter {\n\t\tcase unix.EVFILT_READ:\n\t\t\t// Received the event of EVFILT_READ|EV_EOF, but the previous eventloop.read\n\t\t\t// failed to drain the socket buffer, so we make sure we get it done this time.\n\t\t\tc.isEOF = true\n\t\t\terr = el.read(c)\n\t\tcase unix.EVFILT_WRITE:\n\t\t\t// On macOS, the kqueue in either LT or ET mode will notify with one event for the\n\t\t\t// EOF of the TCP remote: EVFILT_READ|EV_ADD|EV_CLEAR|EV_EOF. But for some reason,\n\t\t\t// two events will be issued in ET mode for the EOF of the Unix remote in this order:\n\t\t\t// 1) EVFILT_WRITE|EV_ADD|EV_CLEAR|EV_EOF, 2) EVFILT_READ|EV_ADD|EV_CLEAR|EV_EOF.\n\t\t\terr = el.write(c)\n\t\tdefault:\n\t\t\tc.outboundBuffer.Release() // don't bother to write to a connection that is already broken\n\t\t\terr = el.close(c, io.EOF)\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "connection_linux.go",
    "content": "/*\n * Copyright (c) 2021 The Gnet Authors. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage gnet\n\nimport (\n\t\"io\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n)\n\nfunc (c *conn) processIO(_ int, ev netpoll.IOEvent, _ netpoll.IOFlags) error {\n\tel := c.loop\n\t// First check for any unexpected non-IO events.\n\t// For these events we just close the connection directly.\n\tif ev&(netpoll.ErrEvents|unix.EPOLLRDHUP) != 0 && ev&netpoll.ReadWriteEvents == 0 {\n\t\tc.outboundBuffer.Release() // don't bother to write to a connection that is already broken\n\t\treturn el.close(c, io.EOF)\n\t}\n\t// Secondly, check for EPOLLOUT before EPOLLIN, the former has a higher priority\n\t// than the latter regardless of the aliveness of the current connection:\n\t//\n\t// 1. When the connection is alive and the system is overloaded, we want to\n\t// offload the incoming traffic by writing all pending data back to the remotes\n\t// before continuing to read and handle requests.\n\t// 2. When the connection is dead, we need to try writing any pending data back\n\t// to the remote first and then close the connection.\n\t//\n\t// We perform eventloop.write for EPOLLOUT because it can take good care of either case.\n\tif ev&(netpoll.WriteEvents|netpoll.ErrEvents) != 0 {\n\t\tif err := el.write(c); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// Check for EPOLLIN before EPOLLRDHUP in case that there are pending data in\n\t// the socket buffer.\n\tif ev&(netpoll.ReadEvents|netpoll.ErrEvents) != 0 {\n\t\tif err := el.read(c); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// Ultimately, check for EPOLLRDHUP, this event indicates that the remote has\n\t// either closed connection or shut down the writing half of the connection.\n\tif ev&unix.EPOLLRDHUP != 0 && c.opened {\n\t\tif ev&unix.EPOLLIN == 0 { // unreadable EPOLLRDHUP, close the connection directly\n\t\t\treturn el.close(c, io.EOF)\n\t\t}\n\t\t// Received the event of EPOLLIN|EPOLLRDHUP, but the previous eventloop.read\n\t\t// failed to drain the socket buffer, so we ensure to get it done this time.\n\t\tc.isEOF = true\n\t\treturn el.read(c)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "connection_unix.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/internal/gfd\"\n\t\"github.com/panjf2000/gnet/v2/pkg/bs\"\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/elastic\"\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\tgio \"github.com/panjf2000/gnet/v2/pkg/io\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\tbsPool \"github.com/panjf2000/gnet/v2/pkg/pool/byteslice\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\ntype conn struct {\n\tfd             int                    // file descriptor\n\tgfd            gfd.GFD                // gnet file descriptor\n\tctx            any                    // user-defined context\n\tremote         unix.Sockaddr          // remote socket address\n\tproto          string                 // protocol name: \"tcp\", \"udp\", or \"unix\".\n\tlocalAddr      net.Addr               // local addr\n\tremoteAddr     net.Addr               // remote addr\n\tloop           *eventloop             // connected event-loop\n\toutboundBuffer elastic.Buffer         // buffer for data that is eligible to be sent to the remote\n\tpollAttachment netpoll.PollAttachment // connection attachment for poller\n\tinboundBuffer  elastic.RingBuffer     // buffer for leftover data from the remote\n\tbuffer         []byte                 // buffer for the latest bytes\n\tcache          []byte                 // temporary cache for the inbound data\n\tisDatagram     bool                   // UDP protocol\n\topened         bool                   // connection opened event fired\n\tisEOF          bool                   // whether the connection has reached EOF\n}\n\nfunc newStreamConn(proto string, fd int, el *eventloop, sa unix.Sockaddr, localAddr, remoteAddr net.Addr) (c *conn) {\n\tc = &conn{\n\t\tfd:             fd,\n\t\tproto:          proto,\n\t\tremote:         sa,\n\t\tloop:           el,\n\t\tlocalAddr:      localAddr,\n\t\tremoteAddr:     remoteAddr,\n\t\tpollAttachment: netpoll.PollAttachment{FD: fd},\n\t}\n\tc.pollAttachment.Callback = c.processIO\n\tc.outboundBuffer.Reset(el.engine.opts.WriteBufferCap)\n\treturn\n}\n\nfunc newUDPConn(fd int, el *eventloop, localAddr net.Addr, sa unix.Sockaddr, connected bool) (c *conn) {\n\tc = &conn{\n\t\tfd:             fd,\n\t\tproto:          \"udp\",\n\t\tgfd:            gfd.NewGFD(fd, el.idx, 0, 0),\n\t\tremote:         sa,\n\t\tloop:           el,\n\t\tlocalAddr:      localAddr,\n\t\tremoteAddr:     socket.SockaddrToUDPAddr(sa),\n\t\tisDatagram:     true,\n\t\tpollAttachment: netpoll.PollAttachment{FD: fd, Callback: el.readUDP},\n\t}\n\tif connected {\n\t\tc.remote = nil\n\t}\n\treturn\n}\n\nfunc (c *conn) release() {\n\tc.opened = false\n\tc.isEOF = false\n\tc.ctx = nil\n\tc.buffer = nil\n\tif addr, ok := c.localAddr.(*net.TCPAddr); ok && len(c.loop.listeners) == 0 && len(addr.Zone) > 0 {\n\t\tbsPool.Put(bs.StringToBytes(addr.Zone))\n\t}\n\tif addr, ok := c.remoteAddr.(*net.TCPAddr); ok && len(addr.Zone) > 0 {\n\t\tbsPool.Put(bs.StringToBytes(addr.Zone))\n\t}\n\tif addr, ok := c.localAddr.(*net.UDPAddr); ok && len(c.loop.listeners) == 0 && len(addr.Zone) > 0 {\n\t\tbsPool.Put(bs.StringToBytes(addr.Zone))\n\t}\n\tif addr, ok := c.remoteAddr.(*net.UDPAddr); ok && len(addr.Zone) > 0 {\n\t\tbsPool.Put(bs.StringToBytes(addr.Zone))\n\t}\n\tc.localAddr = nil\n\tc.remoteAddr = nil\n\tif !c.isDatagram {\n\t\tc.remote = nil\n\t\tc.inboundBuffer.Done()\n\t\tc.outboundBuffer.Release()\n\t}\n}\n\nfunc (c *conn) open(buf []byte) error {\n\tif c.isDatagram && c.remote == nil {\n\t\treturn unix.Send(c.fd, buf, 0)\n\t}\n\n\tfor {\n\t\tn, err := unix.Write(c.fd, buf)\n\t\tif err != nil {\n\t\t\tif err == unix.EAGAIN {\n\t\t\t\t_, _ = c.outboundBuffer.Write(buf)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tbuf = buf[n:]\n\t\tif len(buf) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *conn) write(data []byte) (n int, err error) {\n\tisET := c.loop.engine.opts.EdgeTriggeredIO\n\tn = len(data)\n\t// If there is pending data in outbound buffer,\n\t// the current data ought to be appended to the\n\t// outbound buffer for maintaining the sequence\n\t// of network packets.\n\tif !c.outboundBuffer.IsEmpty() {\n\t\t_, _ = c.outboundBuffer.Write(data)\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = c.loop.close(c, os.NewSyscallError(\"write\", err))\n\t\t}\n\t}()\n\n\tvar sent int\nloop:\n\tif sent, err = unix.Write(c.fd, data); err != nil {\n\t\t// A temporary error occurs, append the data to outbound buffer,\n\t\t// writing it back to the remote in the next round for LT mode.\n\t\tif err == unix.EAGAIN {\n\t\t\t_, err = c.outboundBuffer.Write(data)\n\t\t\tif !isET {\n\t\t\t\terr = c.loop.poller.ModReadWrite(&c.pollAttachment, isET)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\treturn 0, err\n\t}\n\tdata = data[sent:]\n\tif isET && len(data) > 0 {\n\t\tgoto loop\n\t}\n\t// Failed to send all data back to the remote, buffer the leftover data for the next round.\n\tif len(data) > 0 {\n\t\t_, _ = c.outboundBuffer.Write(data)\n\t\terr = c.loop.poller.ModReadWrite(&c.pollAttachment, isET)\n\t}\n\n\treturn\n}\n\nfunc (c *conn) writev(bs [][]byte) (n int, err error) {\n\tisET := c.loop.engine.opts.EdgeTriggeredIO\n\n\tfor _, b := range bs {\n\t\tn += len(b)\n\t}\n\n\t// If there is pending data in outbound buffer,\n\t// the current data ought to be appended to the\n\t// outbound buffer for maintaining the sequence\n\t// of network packets.\n\tif !c.outboundBuffer.IsEmpty() {\n\t\t_, _ = c.outboundBuffer.Writev(bs)\n\t\treturn\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = c.loop.close(c, os.NewSyscallError(\"writev\", err))\n\t\t}\n\t}()\n\n\tremaining := n\n\tvar sent int\nloop:\n\tif sent, err = gio.Writev(c.fd, bs); err != nil {\n\t\t// A temporary error occurs, append the data to outbound buffer,\n\t\t// writing it back to the remote in the next round for LT mode.\n\t\tif err == unix.EAGAIN {\n\t\t\t_, err = c.outboundBuffer.Writev(bs)\n\t\t\tif !isET {\n\t\t\t\terr = c.loop.poller.ModReadWrite(&c.pollAttachment, isET)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\treturn 0, err\n\t}\n\tpos := len(bs)\n\tif remaining -= sent; remaining > 0 {\n\t\tfor i := range bs {\n\t\t\tbn := len(bs[i])\n\t\t\tif sent < bn {\n\t\t\t\tbs[i] = bs[i][sent:]\n\t\t\t\tpos = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tsent -= bn\n\t\t}\n\t}\n\tbs = bs[pos:]\n\tif isET && remaining > 0 {\n\t\tgoto loop\n\t}\n\n\t// Failed to send all data back to the remote, buffer the leftover data for the next round.\n\tif remaining > 0 {\n\t\t_, _ = c.outboundBuffer.Writev(bs)\n\t\terr = c.loop.poller.ModReadWrite(&c.pollAttachment, isET)\n\t}\n\n\treturn\n}\n\ntype asyncWriteHook struct {\n\tcallback AsyncCallback\n\tdata     []byte\n}\n\nfunc (c *conn) asyncWrite(a any) (err error) {\n\thook := a.(*asyncWriteHook)\n\tdefer func() {\n\t\tif hook.callback != nil {\n\t\t\t_ = hook.callback(c, err)\n\t\t}\n\t}()\n\n\tif !c.opened {\n\t\treturn net.ErrClosed\n\t}\n\n\t_, err = c.write(hook.data)\n\treturn\n}\n\ntype asyncWritevHook struct {\n\tcallback AsyncCallback\n\tdata     [][]byte\n}\n\nfunc (c *conn) asyncWritev(a any) (err error) {\n\thook := a.(*asyncWritevHook)\n\tdefer func() {\n\t\tif hook.callback != nil {\n\t\t\t_ = hook.callback(c, err)\n\t\t}\n\t}()\n\n\tif !c.opened {\n\t\treturn net.ErrClosed\n\t}\n\n\t_, err = c.writev(hook.data)\n\treturn\n}\n\nfunc (c *conn) sendTo(buf []byte, addr unix.Sockaddr) (n int, err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tn = 0\n\t\t}\n\t}()\n\n\tif addr != nil {\n\t\treturn len(buf), unix.Sendto(c.fd, buf, 0, addr)\n\t}\n\tif c.remote == nil { // connected UDP socket of client\n\t\treturn len(buf), unix.Send(c.fd, buf, 0)\n\t}\n\treturn len(buf), unix.Sendto(c.fd, buf, 0, c.remote) // unconnected UDP socket of server\n}\n\nfunc (c *conn) resetBuffer() {\n\tc.buffer = c.buffer[:0]\n\tc.inboundBuffer.Reset()\n\tc.inboundBuffer.Done()\n}\n\nfunc (c *conn) Read(p []byte) (n int, err error) {\n\tif c.inboundBuffer.IsEmpty() {\n\t\tn = copy(p, c.buffer)\n\t\tc.buffer = c.buffer[n:]\n\t\tif n == 0 && len(p) > 0 {\n\t\t\terr = io.ErrShortBuffer\n\t\t}\n\t\treturn\n\t}\n\tn, _ = c.inboundBuffer.Read(p)\n\tif n == len(p) {\n\t\treturn\n\t}\n\tm := copy(p[n:], c.buffer)\n\tn += m\n\tc.buffer = c.buffer[m:]\n\treturn\n}\n\nfunc (c *conn) Next(n int) (buf []byte, err error) {\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + len(c.buffer); n > totalLen {\n\t\treturn nil, io.ErrShortBuffer\n\t} else if n <= 0 {\n\t\tn = totalLen\n\t}\n\n\tif c.inboundBuffer.IsEmpty() {\n\t\tbuf = c.buffer[:n]\n\t\tc.buffer = c.buffer[n:]\n\t\treturn\n\t}\n\n\tbuf = bsPool.Get(n)\n\t_, err = c.Read(buf)\n\treturn\n}\n\nfunc (c *conn) Peek(n int) (buf []byte, err error) {\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + len(c.buffer); n > totalLen {\n\t\treturn nil, io.ErrShortBuffer\n\t} else if n <= 0 {\n\t\tn = totalLen\n\t}\n\n\tif c.inboundBuffer.IsEmpty() {\n\t\treturn c.buffer[:n], err\n\t}\n\n\thead, tail := c.inboundBuffer.Peek(n)\n\tif len(head) == n {\n\t\treturn head, err\n\t}\n\tbuf = bsPool.Get(n)[:0]\n\tbuf = append(buf, head...)\n\tbuf = append(buf, tail...)\n\tif inBufferLen >= n {\n\t\treturn\n\t}\n\n\tremaining := n - inBufferLen\n\tbuf = append(buf, c.buffer[:remaining]...)\n\tc.cache = buf\n\treturn\n}\n\nfunc (c *conn) Discard(n int) (int, error) {\n\tif len(c.cache) > 0 {\n\t\tbsPool.Put(c.cache)\n\t\tc.cache = nil\n\t}\n\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + len(c.buffer); n >= totalLen || n <= 0 {\n\t\tc.resetBuffer()\n\t\treturn totalLen, nil\n\t}\n\n\tif c.inboundBuffer.IsEmpty() {\n\t\tc.buffer = c.buffer[n:]\n\t\treturn n, nil\n\t}\n\n\tdiscarded, _ := c.inboundBuffer.Discard(n)\n\tif discarded < inBufferLen {\n\t\treturn discarded, nil\n\t}\n\n\tremaining := n - inBufferLen\n\tc.buffer = c.buffer[remaining:]\n\treturn n, nil\n}\n\nfunc (c *conn) Write(p []byte) (int, error) {\n\tif c.isDatagram {\n\t\treturn c.sendTo(p, nil)\n\t}\n\treturn c.write(p)\n}\n\nfunc (c *conn) SendTo(p []byte, addr net.Addr) (int, error) {\n\tif !c.isDatagram {\n\t\treturn 0, errorx.ErrUnsupportedOp\n\t}\n\n\tsa := socket.NetAddrToSockaddr(addr)\n\tif sa == nil {\n\t\treturn 0, errorx.ErrInvalidNetworkAddress\n\t}\n\n\treturn c.sendTo(p, sa)\n}\n\nfunc (c *conn) Writev(bs [][]byte) (int, error) {\n\tif c.isDatagram {\n\t\treturn 0, errorx.ErrUnsupportedOp\n\t}\n\treturn c.writev(bs)\n}\n\nfunc (c *conn) ReadFrom(r io.Reader) (int64, error) {\n\treturn c.outboundBuffer.ReadFrom(r)\n}\n\nfunc (c *conn) WriteTo(w io.Writer) (n int64, err error) {\n\tif !c.inboundBuffer.IsEmpty() {\n\t\tif n, err = c.inboundBuffer.WriteTo(w); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tvar m int\n\tm, err = w.Write(c.buffer)\n\tn += int64(m)\n\tc.buffer = c.buffer[m:]\n\treturn\n}\n\nfunc (c *conn) Flush() error {\n\treturn c.loop.write(c)\n}\n\nfunc (c *conn) InboundBuffered() int {\n\treturn c.inboundBuffer.Buffered() + len(c.buffer)\n}\n\nfunc (c *conn) OutboundBuffered() int {\n\treturn c.outboundBuffer.Buffered()\n}\n\nfunc (c *conn) Context() any         { return c.ctx }\nfunc (c *conn) SetContext(ctx any)   { c.ctx = ctx }\nfunc (c *conn) LocalAddr() net.Addr  { return c.localAddr }\nfunc (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }\n\n// Implementation of Socket interface\n\n// func (c *conn) Gfd() gfd.GFD             { return c.gfd }\n\nfunc (c *conn) Fd() int                        { return c.fd }\nfunc (c *conn) Dup() (fd int, err error)       { return socket.Dup(c.fd) }\nfunc (c *conn) SetReadBuffer(bytes int) error  { return socket.SetRecvBuffer(c.fd, bytes) }\nfunc (c *conn) SetWriteBuffer(bytes int) error { return socket.SetSendBuffer(c.fd, bytes) }\nfunc (c *conn) SetLinger(sec int) error        { return socket.SetLinger(c.fd, sec) }\nfunc (c *conn) SetNoDelay(noDelay bool) error {\n\treturn socket.SetNoDelay(c.fd, func(b bool) int {\n\t\tif b {\n\t\t\treturn 1\n\t\t}\n\t\treturn 0\n\t}(noDelay))\n}\n\nfunc (c *conn) SetKeepAlivePeriod(d time.Duration) error {\n\tif c.proto != \"tcp\" {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn socket.SetKeepAlivePeriod(c.fd, int(d.Seconds()))\n}\n\nfunc (c *conn) SetKeepAlive(enabled bool, idle, intvl time.Duration, cnt int) error {\n\tif c.proto != \"tcp\" {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn socket.SetKeepAlive(c.fd, enabled, int(idle.Seconds()), int(intvl.Seconds()), cnt)\n}\n\nfunc (c *conn) AsyncWrite(buf []byte, callback AsyncCallback) error {\n\tif c.isDatagram {\n\t\t_, err := c.sendTo(buf, nil)\n\t\t// TODO: it will not go asynchronously with UDP, so calling a callback is needless,\n\t\t//  we may remove this branch in the future, please don't rely on the callback\n\t\t// \tto do something important under UDP, if you're working with UDP, just call Conn.Write\n\t\t// \tto send back your data.\n\t\tif callback != nil {\n\t\t\t_ = callback(nil, nil)\n\t\t}\n\t\treturn err\n\t}\n\treturn c.loop.poller.Trigger(queue.HighPriority, c.asyncWrite, &asyncWriteHook{callback, buf})\n}\n\nfunc (c *conn) AsyncWritev(bs [][]byte, callback AsyncCallback) error {\n\tif c.isDatagram {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn c.loop.poller.Trigger(queue.HighPriority, c.asyncWritev, &asyncWritevHook{callback, bs})\n}\n\nfunc (c *conn) Wake(callback AsyncCallback) error {\n\treturn c.loop.poller.Trigger(queue.LowPriority, func(_ any) (err error) {\n\t\terr = c.loop.wake(c)\n\t\tif callback != nil {\n\t\t\t_ = callback(c, err)\n\t\t}\n\t\treturn\n\t}, nil)\n}\n\nfunc (c *conn) CloseWithCallback(callback AsyncCallback) error {\n\treturn c.loop.poller.Trigger(queue.LowPriority, func(_ any) (err error) {\n\t\terr = c.loop.close(c, nil)\n\t\tif callback != nil {\n\t\t\t_ = callback(c, err)\n\t\t}\n\t\treturn\n\t}, nil)\n}\n\nfunc (c *conn) Close() error {\n\treturn c.loop.poller.Trigger(queue.LowPriority, func(_ any) (err error) {\n\t\terr = c.loop.close(c, nil)\n\t\treturn\n\t}, nil)\n}\n\nfunc (c *conn) EventLoop() EventLoop {\n\treturn c.loop\n}\n\nfunc (*conn) SetDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (*conn) SetReadDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (*conn) SetWriteDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n"
  },
  {
    "path": "connection_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"golang.org/x/sys/windows\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/elastic\"\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\tbbPool \"github.com/panjf2000/gnet/v2/pkg/pool/bytebuffer\"\n\tbsPool \"github.com/panjf2000/gnet/v2/pkg/pool/byteslice\"\n\t\"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\ntype netErr struct {\n\tc   *conn\n\terr error\n}\n\ntype tcpConn struct {\n\tc *conn\n\tb *bbPool.ByteBuffer\n}\n\ntype udpConn struct {\n\tc *conn\n}\n\ntype openConn struct {\n\tc  *conn\n\tcb func()\n}\n\ntype conn struct {\n\tpc            net.PacketConn\n\tctx           any                // user-defined context\n\tloop          *eventloop         // owner event-loop\n\tbuffer        *bbPool.ByteBuffer // reuse memory of inbound data as a temporary buffer\n\tcache         []byte             // temporary cache for the inbound data\n\trawConn       net.Conn           // original connection\n\tlocalAddr     net.Addr           // local server addr\n\tremoteAddr    net.Addr           // remote addr\n\tinboundBuffer elastic.RingBuffer // buffer for data from the remote\n}\n\nfunc packTCPConn(c *conn, buf []byte) *tcpConn {\n\tb := bbPool.Get()\n\t_, _ = b.Write(buf)\n\treturn &tcpConn{c: c, b: b}\n}\n\nfunc unpackTCPConn(tc *tcpConn) *conn {\n\tif tc.c.buffer == nil { // the connection has been closed\n\t\treturn nil\n\t}\n\t_, _ = tc.c.buffer.Write(tc.b.B)\n\tbbPool.Put(tc.b)\n\ttc.b = nil\n\treturn tc.c\n}\n\nfunc packUDPConn(c *conn, buf []byte) *udpConn {\n\t_, _ = c.buffer.Write(buf)\n\treturn &udpConn{c}\n}\n\nfunc newStreamConn(el *eventloop, nc net.Conn, ctx any) (c *conn) {\n\treturn &conn{\n\t\tctx:        ctx,\n\t\tloop:       el,\n\t\tbuffer:     bbPool.Get(),\n\t\trawConn:    nc,\n\t\tlocalAddr:  nc.LocalAddr(),\n\t\tremoteAddr: nc.RemoteAddr(),\n\t}\n}\n\nfunc (c *conn) release() {\n\tc.ctx = nil\n\tc.localAddr = nil\n\tif c.rawConn != nil {\n\t\tc.rawConn = nil\n\t\tc.remoteAddr = nil\n\t}\n\tc.inboundBuffer.Done()\n\tbbPool.Put(c.buffer)\n\tc.buffer = nil\n}\n\nfunc newUDPConn(el *eventloop, pc net.PacketConn, rc net.Conn, localAddr, remoteAddr net.Addr, ctx any) *conn {\n\treturn &conn{\n\t\tctx:        ctx,\n\t\tpc:         pc,\n\t\trawConn:    rc,\n\t\tloop:       el,\n\t\tbuffer:     bbPool.Get(),\n\t\tlocalAddr:  localAddr,\n\t\tremoteAddr: remoteAddr,\n\t}\n}\n\nfunc (c *conn) resetBuffer() {\n\tc.buffer.Reset()\n\tc.inboundBuffer.Reset()\n\tc.inboundBuffer.Done()\n}\n\nfunc (c *conn) Read(p []byte) (n int, err error) {\n\tif c.inboundBuffer.IsEmpty() {\n\t\tn = copy(p, c.buffer.B)\n\t\tc.buffer.B = c.buffer.B[n:]\n\t\tif n == 0 && len(p) > 0 {\n\t\t\terr = io.ErrShortBuffer\n\t\t}\n\t\treturn\n\t}\n\tn, _ = c.inboundBuffer.Read(p)\n\tif n == len(p) {\n\t\treturn\n\t}\n\tm := copy(p[n:], c.buffer.B)\n\tn += m\n\tc.buffer.B = c.buffer.B[m:]\n\treturn\n}\n\nfunc (c *conn) Next(n int) (buf []byte, err error) {\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + c.buffer.Len(); n > totalLen {\n\t\treturn nil, io.ErrShortBuffer\n\t} else if n <= 0 {\n\t\tn = totalLen\n\t}\n\tif c.inboundBuffer.IsEmpty() {\n\t\tbuf = c.buffer.B[:n]\n\t\tc.buffer.B = c.buffer.B[n:]\n\t\treturn\n\t}\n\n\tbuf = bsPool.Get(n)\n\t_, err = c.Read(buf)\n\treturn\n}\n\nfunc (c *conn) Peek(n int) (buf []byte, err error) {\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + c.buffer.Len(); n > totalLen {\n\t\treturn nil, io.ErrShortBuffer\n\t} else if n <= 0 {\n\t\tn = totalLen\n\t}\n\tif c.inboundBuffer.IsEmpty() {\n\t\treturn c.buffer.B[:n], err\n\t}\n\thead, tail := c.inboundBuffer.Peek(n)\n\tif len(head) == n {\n\t\treturn head, err\n\t}\n\tbuf = bsPool.Get(n)[:0]\n\tbuf = append(buf, head...)\n\tbuf = append(buf, tail...)\n\tif inBufferLen >= n {\n\t\treturn\n\t}\n\n\tremaining := n - inBufferLen\n\tbuf = append(buf, c.buffer.B[:remaining]...)\n\tc.cache = buf\n\treturn\n}\n\nfunc (c *conn) Discard(n int) (int, error) {\n\tif len(c.cache) > 0 {\n\t\tbsPool.Put(c.cache)\n\t\tc.cache = nil\n\t}\n\n\tinBufferLen := c.inboundBuffer.Buffered()\n\tif totalLen := inBufferLen + c.buffer.Len(); n >= totalLen || n <= 0 {\n\t\tc.resetBuffer()\n\t\treturn totalLen, nil\n\t}\n\n\tif c.inboundBuffer.IsEmpty() {\n\t\tc.buffer.B = c.buffer.B[n:]\n\t\treturn n, nil\n\t}\n\n\tdiscarded, _ := c.inboundBuffer.Discard(n)\n\tif discarded < inBufferLen {\n\t\treturn discarded, nil\n\t}\n\n\tremaining := n - inBufferLen\n\tc.buffer.B = c.buffer.B[remaining:]\n\treturn n, nil\n}\n\nfunc (c *conn) Write(p []byte) (int, error) {\n\tif c.rawConn == nil && c.pc == nil {\n\t\treturn 0, net.ErrClosed\n\t}\n\tif c.rawConn != nil {\n\t\treturn c.rawConn.Write(p)\n\t}\n\treturn c.pc.WriteTo(p, c.remoteAddr)\n}\n\nfunc (c *conn) SendTo(p []byte, addr net.Addr) (int, error) {\n\tif c.pc == nil {\n\t\treturn 0, errorx.ErrUnsupportedOp\n\t}\n\n\tif addr == nil {\n\t\treturn 0, errorx.ErrInvalidNetworkAddress\n\t}\n\n\treturn c.pc.WriteTo(p, addr)\n}\n\nfunc (c *conn) Writev(bs [][]byte) (int, error) {\n\tif c.pc != nil { // not available for UDP\n\t\treturn 0, errorx.ErrUnsupportedOp\n\t}\n\n\tif c.rawConn != nil {\n\t\tbb := bbPool.Get()\n\t\tdefer bbPool.Put(bb)\n\t\tfor i := range bs {\n\t\t\t_, _ = bb.Write(bs[i])\n\t\t}\n\t\treturn c.rawConn.Write(bb.Bytes())\n\t}\n\treturn 0, net.ErrClosed\n}\n\nfunc (c *conn) ReadFrom(r io.Reader) (int64, error) {\n\tif c.rawConn != nil {\n\t\treturn io.Copy(c.rawConn, r)\n\t}\n\treturn 0, net.ErrClosed\n}\n\nfunc (c *conn) WriteTo(w io.Writer) (n int64, err error) {\n\tif !c.inboundBuffer.IsEmpty() {\n\t\tif n, err = c.inboundBuffer.WriteTo(w); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif c.buffer == nil {\n\t\treturn 0, nil\n\t}\n\tdefer c.buffer.Reset()\n\treturn c.buffer.WriteTo(w)\n}\n\nfunc (c *conn) Flush() error {\n\treturn nil\n}\n\nfunc (c *conn) InboundBuffered() int {\n\tif c.buffer == nil {\n\t\treturn 0\n\t}\n\treturn c.inboundBuffer.Buffered() + c.buffer.Len()\n}\n\nfunc (c *conn) OutboundBuffered() int {\n\treturn 0\n}\n\nfunc (c *conn) Context() any         { return c.ctx }\nfunc (c *conn) SetContext(ctx any)   { c.ctx = ctx }\nfunc (c *conn) LocalAddr() net.Addr  { return c.localAddr }\nfunc (c *conn) RemoteAddr() net.Addr { return c.remoteAddr }\n\nfunc (c *conn) Fd() (fd int) {\n\tif c.rawConn == nil {\n\t\treturn -1\n\t}\n\n\trc, err := c.rawConn.(syscall.Conn).SyscallConn()\n\tif err != nil {\n\t\treturn -1\n\t}\n\tif err := rc.Control(func(i uintptr) {\n\t\tfd = int(i)\n\t}); err != nil {\n\t\treturn -1\n\t}\n\treturn\n}\n\nfunc (c *conn) Dup() (fd int, err error) {\n\tif c.rawConn == nil && c.pc == nil {\n\t\treturn -1, net.ErrClosed\n\t}\n\n\tvar (\n\t\tsc syscall.Conn\n\t\tok bool\n\t)\n\tif c.rawConn != nil {\n\t\tsc, ok = c.rawConn.(syscall.Conn)\n\t} else {\n\t\tsc, ok = c.pc.(syscall.Conn)\n\t}\n\n\tif !ok {\n\t\treturn -1, errors.New(\"failed to convert net.Conn to syscall.Conn\")\n\t}\n\trc, err := sc.SyscallConn()\n\tif err != nil {\n\t\treturn -1, errors.New(\"failed to get syscall.RawConn from net.Conn\")\n\t}\n\n\tvar dupHandle windows.Handle\n\te := rc.Control(func(fd uintptr) {\n\t\tprocess := windows.CurrentProcess()\n\t\terr = windows.DuplicateHandle(\n\t\t\tprocess,\n\t\t\twindows.Handle(fd),\n\t\t\tprocess,\n\t\t\t&dupHandle,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\twindows.DUPLICATE_SAME_ACCESS,\n\t\t)\n\t})\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tif e != nil {\n\t\treturn -1, e\n\t}\n\n\treturn int(dupHandle), nil\n}\n\nfunc (c *conn) SetReadBuffer(bytes int) error {\n\tif c.rawConn == nil && c.pc == nil {\n\t\treturn net.ErrClosed\n\t}\n\n\tif c.rawConn != nil {\n\t\treturn c.rawConn.(interface{ SetReadBuffer(int) error }).SetReadBuffer(bytes)\n\t}\n\treturn c.pc.(interface{ SetReadBuffer(int) error }).SetReadBuffer(bytes)\n}\n\nfunc (c *conn) SetWriteBuffer(bytes int) error {\n\tif c.rawConn == nil && c.pc == nil {\n\t\treturn net.ErrClosed\n\t}\n\tif c.rawConn != nil {\n\t\treturn c.rawConn.(interface{ SetWriteBuffer(int) error }).SetWriteBuffer(bytes)\n\t}\n\treturn c.pc.(interface{ SetWriteBuffer(int) error }).SetWriteBuffer(bytes)\n}\n\nfunc (c *conn) SetLinger(sec int) error {\n\tif c.rawConn == nil {\n\t\treturn net.ErrClosed\n\t}\n\n\ttc, ok := c.rawConn.(*net.TCPConn)\n\tif !ok {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn tc.SetLinger(sec)\n}\n\nfunc (c *conn) SetNoDelay(noDelay bool) error {\n\tif c.rawConn == nil {\n\t\treturn net.ErrClosed\n\t}\n\n\ttc, ok := c.rawConn.(*net.TCPConn)\n\tif !ok {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn tc.SetNoDelay(noDelay)\n}\n\nfunc (c *conn) SetKeepAlivePeriod(d time.Duration) error {\n\treturn c.SetKeepAlive(d > 0, d, d/5, 5)\n}\n\nfunc (c *conn) SetKeepAlive(enabled bool, idle, intvl time.Duration, cnt int) error {\n\tif c.rawConn == nil && c.pc == nil {\n\t\treturn net.ErrClosed\n\t}\n\n\tif c.pc != nil {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\n\ttc, ok := c.rawConn.(*net.TCPConn)\n\tif !ok {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\n\tif enabled && (idle <= 0 || intvl <= 0 || cnt <= 0) {\n\t\treturn errors.New(\"invalid time duration\")\n\t}\n\n\tif err := tc.SetKeepAlive(enabled); err != nil {\n\t\treturn err\n\t}\n\n\tif !enabled {\n\t\treturn nil\n\t}\n\n\tif err := tc.SetKeepAlivePeriod(idle); err != nil {\n\t\treturn err\n\t}\n\n\tif err := windows.SetsockoptInt(\n\t\twindows.Handle(c.Fd()),\n\t\twindows.IPPROTO_TCP,\n\t\twindows.TCP_KEEPINTVL,\n\t\tint(intvl.Seconds())); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif err := windows.SetsockoptInt(\n\t\twindows.Handle(c.Fd()),\n\t\twindows.IPPROTO_TCP,\n\t\twindows.TCP_KEEPCNT,\n\t\tcnt); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\treturn nil\n}\n\n// Gfd return an uninitialized GFD which is not valid,\n// this method is only implemented for compatibility, don't use it on Windows.\n// func (c *conn) Gfd() gfd.GFD { return gfd.GFD{} }\n\nfunc (c *conn) AsyncWrite(buf []byte, cb AsyncCallback) error {\n\tfn := func() error {\n\t\t_, err := c.Write(buf)\n\t\tif cb != nil {\n\t\t\t_ = cb(c, err)\n\t\t}\n\t\treturn err\n\t}\n\n\tvar err error\n\tselect {\n\tcase c.loop.ch <- fn:\n\tdefault:\n\t\t// If the event-loop channel is full, asynchronize this operation to avoid blocking the eventloop.\n\t\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tc.loop.ch <- fn\n\t\t})\n\t}\n\n\treturn err\n}\n\nfunc (c *conn) AsyncWritev(bs [][]byte, cb AsyncCallback) error {\n\tif c.pc != nil {\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\n\tbuf := bbPool.Get()\n\tfor _, b := range bs {\n\t\t_, _ = buf.Write(b)\n\t}\n\treturn c.AsyncWrite(buf.Bytes(), func(c Conn, err error) error {\n\t\tdefer bbPool.Put(buf)\n\t\tif cb == nil {\n\t\t\treturn err\n\t\t}\n\t\treturn cb(c, err)\n\t})\n}\n\nfunc (c *conn) Wake(cb AsyncCallback) (err error) {\n\twakeFn := func() (err error) {\n\t\terr = c.loop.wake(c)\n\t\tif cb != nil {\n\t\t\t_ = cb(c, err)\n\t\t}\n\t\treturn\n\t}\n\n\tselect {\n\tcase c.loop.ch <- wakeFn:\n\tdefault:\n\t\t// If the event-loop channel is full, asynchronize this operation to avoid blocking the eventloop.\n\t\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tc.loop.ch <- wakeFn\n\t\t})\n\t}\n\n\treturn\n}\n\nfunc (c *conn) Close() (err error) {\n\tcloseFn := func() error {\n\t\treturn c.loop.close(c, nil)\n\t}\n\n\tselect {\n\tcase c.loop.ch <- closeFn:\n\tdefault:\n\t\t// If the event-loop channel is full, asynchronize this operation to avoid blocking the eventloop.\n\t\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tc.loop.ch <- closeFn\n\t\t})\n\t}\n\n\treturn\n}\n\nfunc (c *conn) CloseWithCallback(cb AsyncCallback) (err error) {\n\tcloseFn := func() (err error) {\n\t\terr = c.loop.close(c, nil)\n\t\tif cb != nil {\n\t\t\t_ = cb(c, err)\n\t\t}\n\t\treturn\n\t}\n\n\tselect {\n\tcase c.loop.ch <- closeFn:\n\tdefault:\n\t\t// If the event-loop channel is full, asynchronize this operation to avoid blocking the eventloop.\n\t\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\t\tc.loop.ch <- closeFn\n\t\t})\n\t}\n\n\treturn\n}\n\nfunc (c *conn) EventLoop() EventLoop {\n\treturn c.loop\n}\n\nfunc (*conn) SetDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (*conn) SetReadDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (*conn) SetWriteDeadline(_ time.Time) error {\n\treturn errorx.ErrUnsupportedOp\n}\n"
  },
  {
    "path": "context.go",
    "content": "/*\n * Copyright (c) 2025 The Gnet Authors. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"net\"\n)\n\n// contextKey is a key for Conn values in context.Context.\ntype contextKey struct{}\n\n// NewContext returns a new context.Context that carries the value\n// that will be attached to the Conn.\nfunc NewContext(ctx context.Context, v any) context.Context {\n\treturn context.WithValue(ctx, contextKey{}, v)\n}\n\n// FromContext retrieves context value of the Conn stored in ctx, if any.\nfunc FromContext(ctx context.Context) any {\n\treturn ctx.Value(contextKey{})\n}\n\n// connContextKey is a key for net.Conn values in context.Context.\ntype connContextKey struct{}\n\n// NewNetConnContext returns a new context.Context that carries the net.Conn value.\nfunc NewNetConnContext(ctx context.Context, c net.Conn) context.Context {\n\treturn context.WithValue(ctx, connContextKey{}, c)\n}\n\n// FromNetConnContext retrieves the net.Conn value from ctx, if any.\nfunc FromNetConnContext(ctx context.Context) (net.Conn, bool) {\n\tc, ok := ctx.Value(connContextKey{}).(net.Conn)\n\treturn c, ok\n}\n\n// netAddrContextKey is a key for net.Addr values in context.Context.\ntype netAddrContextKey struct{}\n\n// NewNetAddrContext returns a new context.Context that carries the net.Addr value.\nfunc NewNetAddrContext(ctx context.Context, a net.Addr) context.Context {\n\treturn context.WithValue(ctx, netAddrContextKey{}, a)\n}\n\n// FromNetAddrContext retrieves the net.Addr value from ctx, if any.\nfunc FromNetAddrContext(ctx context.Context) (net.Addr, bool) {\n\ta, ok := ctx.Value(netAddrContextKey{}).(net.Addr)\n\treturn a, ok\n}\n"
  },
  {
    "path": "engine_unix.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\ntype engine struct {\n\tlisteners    map[int]*listener // listeners for accepting incoming connections\n\topts         *Options          // options with engine\n\tingress      *eventloop        // main event-loop that monitors all listeners\n\teventLoops   loadBalancer      // event-loops for handling events\n\tinShutdown   atomic.Bool       // whether the engine is in shutdown\n\tturnOff      context.CancelFunc\n\teventHandler EventHandler // user eventHandler\n\tconcurrency  struct {\n\t\t*errgroup.Group\n\n\t\tctx context.Context\n\t}\n}\n\nfunc (eng *engine) isShutdown() bool {\n\treturn eng.inShutdown.Load()\n}\n\n// shutdown signals the engine to shut down.\nfunc (eng *engine) shutdown(err error) {\n\tif err != nil && !errors.Is(err, errorx.ErrEngineShutdown) {\n\t\teng.opts.Logger.Errorf(\"engine is being shutdown with error: %v\", err)\n\t}\n\t// Cancel the context to stop the engine.\n\teng.turnOff()\n}\n\nfunc (eng *engine) closeEventLoops() {\n\teng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\tfor _, ln := range el.listeners {\n\t\t\tln.close()\n\t\t}\n\t\t_ = el.poller.Close()\n\t\treturn true\n\t})\n\tif eng.ingress != nil {\n\t\tfor _, ln := range eng.listeners {\n\t\t\tln.close()\n\t\t}\n\t\terr := eng.ingress.poller.Close()\n\t\tif err != nil {\n\t\t\teng.opts.Logger.Errorf(\"failed to close poller when stopping engine: %v\", err)\n\t\t}\n\t}\n}\n\nfunc (eng *engine) runEventLoops(ctx context.Context, numEventLoop int) error {\n\tvar el0 *eventloop\n\tlns := eng.listeners\n\t// Create loops locally and bind the listeners.\n\tfor i := 0; i < numEventLoop; i++ {\n\t\tif i > 0 {\n\t\t\tlns = make(map[int]*listener, len(eng.listeners))\n\t\t\tfor _, l := range eng.listeners {\n\t\t\t\tln, err := initListener(l.network, l.address, eng.opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tlns[ln.fd] = ln\n\t\t\t}\n\t\t}\n\t\tp, err := netpoll.OpenPoller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tel := new(eventloop)\n\t\tel.listeners = lns\n\t\tel.engine = eng\n\t\tel.poller = p\n\t\tel.buffer = make([]byte, eng.opts.ReadBufferCap)\n\t\tel.connections.init()\n\t\tel.eventHandler = eng.eventHandler\n\t\tfor _, ln := range lns {\n\t\t\tif err = el.poller.AddRead(ln.packPollAttachment(el.accept), false); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\teng.eventLoops.register(el)\n\n\t\t// Start the ticker.\n\t\tif eng.opts.Ticker && el.idx == 0 {\n\t\t\tel0 = el\n\t\t}\n\t}\n\n\t// Start event-loops in the background.\n\teng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\teng.concurrency.Go(el.run)\n\t\treturn true\n\t})\n\n\tif el0 != nil {\n\t\teng.concurrency.Go(func() error {\n\t\t\tel0.ticker(ctx)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc (eng *engine) activateReactors(ctx context.Context, numEventLoop int) error {\n\tfor i := 0; i < numEventLoop; i++ {\n\t\tp, err := netpoll.OpenPoller()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tel := new(eventloop)\n\t\tel.listeners = eng.listeners\n\t\tel.engine = eng\n\t\tel.poller = p\n\t\tel.buffer = make([]byte, eng.opts.ReadBufferCap)\n\t\tel.connections.init()\n\t\tel.eventHandler = eng.eventHandler\n\t\teng.eventLoops.register(el)\n\t}\n\n\t// Start sub reactors in the background.\n\teng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\teng.concurrency.Go(el.orbit)\n\t\treturn true\n\t})\n\n\tp, err := netpoll.OpenPoller()\n\tif err != nil {\n\t\treturn err\n\t}\n\tel := new(eventloop)\n\tel.listeners = eng.listeners\n\tel.idx = -1\n\tel.engine = eng\n\tel.poller = p\n\tel.eventHandler = eng.eventHandler\n\tfor _, ln := range eng.listeners {\n\t\tif err = el.poller.AddRead(ln.packPollAttachment(el.accept0), true); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\teng.ingress = el\n\n\t// Start the main reactor in the background.\n\teng.concurrency.Go(el.rotate)\n\n\t// Start the ticker.\n\tif eng.opts.Ticker {\n\t\teng.concurrency.Go(func() error {\n\t\t\teng.ingress.ticker(ctx)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\treturn nil\n}\n\nfunc (eng *engine) start(ctx context.Context, numEventLoop int) error {\n\tif eng.opts.ReusePort {\n\t\treturn eng.runEventLoops(ctx, numEventLoop)\n\t}\n\n\treturn eng.activateReactors(ctx, numEventLoop)\n}\n\nfunc (eng *engine) stop(ctx context.Context, s Engine) {\n\t// Wait on a signal for shutdown\n\t<-ctx.Done()\n\n\teng.eventHandler.OnShutdown(s)\n\n\t// Notify all event-loops to exit.\n\teng.eventLoops.iterate(func(i int, el *eventloop) bool {\n\t\terr := el.poller.Trigger(queue.HighPriority, func(_ any) error { return errorx.ErrEngineShutdown }, nil)\n\t\tif err != nil {\n\t\t\teng.opts.Logger.Errorf(\"failed to enqueue shutdown signal of high-priority for event-loop(%d): %v\", i, err)\n\t\t}\n\t\treturn true\n\t})\n\tif eng.ingress != nil {\n\t\terr := eng.ingress.poller.Trigger(queue.HighPriority, func(_ any) error { return errorx.ErrEngineShutdown }, nil)\n\t\tif err != nil {\n\t\t\teng.opts.Logger.Errorf(\"failed to enqueue shutdown signal of high-priority for main event-loop: %v\", err)\n\t\t}\n\t}\n\n\tif err := eng.concurrency.Wait(); err != nil {\n\t\teng.opts.Logger.Errorf(\"engine shutdown error: %v\", err)\n\t}\n\n\t// Close all listeners and pollers of event-loops.\n\teng.closeEventLoops()\n\n\t// Put the engine into the shutdown state.\n\teng.inShutdown.Store(true)\n}\n\nfunc run(eventHandler EventHandler, listeners []*listener, options *Options, addrs []string) error {\n\tnumEventLoop := determineEventLoops(options)\n\tlogging.Infof(\"Launching gnet with %d event-loops, listening on: %s\",\n\t\tnumEventLoop, strings.Join(addrs, \" | \"))\n\n\tlns := make(map[int]*listener, len(listeners))\n\tfor _, ln := range listeners {\n\t\tlns[ln.fd] = ln\n\t}\n\trootCtx, shutdown := context.WithCancel(context.Background())\n\teg, ctx := errgroup.WithContext(rootCtx)\n\teng := engine{\n\t\tlisteners:    lns,\n\t\topts:         options,\n\t\tturnOff:      shutdown,\n\t\teventHandler: eventHandler,\n\t\tconcurrency: struct {\n\t\t\t*errgroup.Group\n\t\t\tctx context.Context\n\t\t}{eg, ctx},\n\t}\n\tswitch options.LB {\n\tcase RoundRobin:\n\t\teng.eventLoops = new(roundRobinLoadBalancer)\n\tcase LeastConnections:\n\t\teng.eventLoops = new(leastConnectionsLoadBalancer)\n\tcase SourceAddrHash:\n\t\teng.eventLoops = new(sourceAddrHashLoadBalancer)\n\t}\n\n\te := Engine{&eng}\n\tswitch eng.eventHandler.OnBoot(e) {\n\tcase None, Close:\n\tcase Shutdown:\n\t\treturn nil\n\t}\n\n\tif err := eng.start(ctx, numEventLoop); err != nil {\n\t\teng.closeEventLoops()\n\t\teng.opts.Logger.Errorf(\"gnet engine is stopping with error: %v\", err)\n\t\treturn err\n\t}\n\tdefer eng.stop(rootCtx, e)\n\n\tfor _, addr := range addrs {\n\t\tallEngines.Store(addr, &eng)\n\t}\n\n\treturn nil\n}\n\nfunc setKeepAlive(fd int, enabled bool, idle, intvl time.Duration, cnt int) error {\n\tif intvl == 0 {\n\t\tintvl = idle / 5\n\t}\n\tif cnt == 0 {\n\t\tcnt = 5\n\t}\n\treturn socket.SetKeepAlive(fd, enabled, int(idle.Seconds()), int(intvl.Seconds()), cnt)\n}\n\n/*\nfunc (eng *engine) sendCmd(cmd *asyncCmd, urgent bool) error {\n\tif !gfd.Validate(cmd.fd) {\n\t\treturn errors.ErrInvalidConn\n\t}\n\tel := eng.eventLoops.index(cmd.fd.EventLoopIndex())\n\tif el == nil {\n\t\treturn errors.ErrInvalidConn\n\t}\n\tif urgent {\n\t\treturn el.poller.Trigger(queue.LowPriority, el.execCmd, cmd)\n\t}\n\treturn el.poller.Trigger(el.execCmd, cmd)\n}\n*/\n"
  },
  {
    "path": "engine_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\ntype engine struct {\n\tlisteners     []*listener\n\topts          *Options     // options with engine\n\teventLoops    loadBalancer // event-loops for handling events\n\tinShutdown    atomic.Bool  // whether the engine is in shutdown\n\tbeingShutdown atomic.Bool  // whether the engine is being shutdown\n\tturnOff       context.CancelFunc\n\teventHandler  EventHandler // user eventHandler\n\tconcurrency   struct {\n\t\t*errgroup.Group\n\n\t\tctx context.Context\n\t}\n}\n\nfunc (eng *engine) isShutdown() bool {\n\treturn eng.inShutdown.Load()\n}\n\n// shutdown signals the engine to shut down.\nfunc (eng *engine) shutdown(err error) {\n\tif err != nil && !errors.Is(err, errorx.ErrEngineShutdown) {\n\t\teng.opts.Logger.Errorf(\"engine is being shutdown with error: %v\", err)\n\t}\n\teng.turnOff()\n\teng.beingShutdown.Store(true)\n}\n\nfunc (eng *engine) closeEventLoops() {\n\teng.eventLoops.iterate(func(i int, el *eventloop) bool {\n\t\tel.ch <- errorx.ErrEngineShutdown\n\t\treturn true\n\t})\n\tfor _, ln := range eng.listeners {\n\t\tln.close()\n\t}\n}\n\nfunc (eng *engine) start(ctx context.Context, numEventLoop int) error {\n\tvar el0 *eventloop\n\tfor i := 0; i < numEventLoop; i++ {\n\t\tel := eventloop{\n\t\t\tch:           make(chan any, 1024),\n\t\t\teng:          eng,\n\t\t\tconnections:  make(map[*conn]struct{}),\n\t\t\teventHandler: eng.eventHandler,\n\t\t}\n\t\teng.eventLoops.register(&el)\n\t\teng.concurrency.Go(el.run)\n\t\tif i == 0 && eng.opts.Ticker {\n\t\t\tel0 = &el\n\t\t}\n\t}\n\n\tif el0 != nil {\n\t\teng.concurrency.Go(func() error {\n\t\t\tel0.ticker(ctx)\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tfor _, ln := range eng.listeners {\n\t\tl := ln\n\t\tif l.pc != nil {\n\t\t\teng.concurrency.Go(func() error {\n\t\t\t\treturn eng.ListenUDP(l.pc)\n\t\t\t})\n\t\t} else {\n\t\t\teng.concurrency.Go(func() error {\n\t\t\t\treturn eng.listenStream(l.ln)\n\t\t\t})\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (eng *engine) stop(ctx context.Context, engine Engine) {\n\t<-ctx.Done()\n\n\teng.eventHandler.OnShutdown(engine)\n\n\teng.closeEventLoops()\n\n\tif err := eng.concurrency.Wait(); err != nil && !errors.Is(err, errorx.ErrEngineShutdown) {\n\t\teng.opts.Logger.Errorf(\"engine shutdown error: %v\", err)\n\t}\n\n\teng.inShutdown.Store(true)\n}\n\nfunc run(eventHandler EventHandler, listeners []*listener, options *Options, addrs []string) error {\n\tnumEventLoop := determineEventLoops(options)\n\tlogging.Infof(\"Launching gnet with %d event-loops, listening on: %s\",\n\t\tnumEventLoop, strings.Join(addrs, \" | \"))\n\n\trootCtx, shutdown := context.WithCancel(context.Background())\n\teg, ctx := errgroup.WithContext(rootCtx)\n\teng := engine{\n\t\topts:         options,\n\t\tlisteners:    listeners,\n\t\tturnOff:      shutdown,\n\t\teventHandler: eventHandler,\n\t\tconcurrency: struct {\n\t\t\t*errgroup.Group\n\t\t\tctx context.Context\n\t\t}{eg, ctx},\n\t}\n\n\tswitch options.LB {\n\tcase RoundRobin:\n\t\teng.eventLoops = new(roundRobinLoadBalancer)\n\t\t// If there are more than one listener, we can't use roundRobinLoadBalancer because\n\t\t// it's not concurrency-safe, replace it with leastConnectionsLoadBalancer.\n\t\tif len(listeners) > 1 {\n\t\t\teng.eventLoops = new(leastConnectionsLoadBalancer)\n\t\t}\n\tcase LeastConnections:\n\t\teng.eventLoops = new(leastConnectionsLoadBalancer)\n\tcase SourceAddrHash:\n\t\teng.eventLoops = new(sourceAddrHashLoadBalancer)\n\t}\n\n\tengine := Engine{eng: &eng}\n\tswitch eventHandler.OnBoot(engine) {\n\tcase None, Close:\n\tcase Shutdown:\n\t\treturn nil\n\t}\n\n\tif err := eng.start(ctx, numEventLoop); err != nil {\n\t\teng.opts.Logger.Errorf(\"gnet engine is stopping with error: %v\", err)\n\t\treturn err\n\t}\n\tdefer eng.stop(rootCtx, engine)\n\n\tfor _, addr := range addrs {\n\t\tallEngines.Store(addr, &eng)\n\t}\n\n\treturn nil\n}\n\n/*\nfunc (eng *engine) sendCmd(_ *asyncCmd, _ bool) error {\n\treturn errorx.ErrUnsupportedOp\n}\n*/\n"
  },
  {
    "path": "eventloop_unix.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\tgio \"github.com/panjf2000/gnet/v2/pkg/io\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\t\"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\ntype eventloop struct {\n\tlisteners    map[int]*listener // listeners\n\tidx          int               // loop index in the engine loops list\n\tengine       *engine           // engine in loop\n\tpoller       *netpoll.Poller   // epoll or kqueue\n\tbuffer       []byte            // read packet buffer whose capacity is set by user, default value is 64KB\n\tconnections  connMatrix        // loop connections storage\n\teventHandler EventHandler      // user eventHandler\n}\n\nfunc (el *eventloop) Register(ctx context.Context, addr net.Addr) (<-chan RegisteredResult, error) {\n\tif el.engine.isShutdown() {\n\t\treturn nil, errorx.ErrEngineInShutdown\n\t}\n\tif addr == nil {\n\t\treturn nil, errorx.ErrInvalidNetworkAddress\n\t}\n\treturn el.enroll(nil, addr, FromContext(ctx))\n}\n\nfunc (el *eventloop) Enroll(ctx context.Context, c net.Conn) (<-chan RegisteredResult, error) {\n\tif el.engine.isShutdown() {\n\t\treturn nil, errorx.ErrEngineInShutdown\n\t}\n\tif c == nil {\n\t\treturn nil, errorx.ErrInvalidNetConn\n\t}\n\treturn el.enroll(c, c.RemoteAddr(), FromContext(ctx))\n}\n\nfunc (el *eventloop) Execute(ctx context.Context, runnable Runnable) error {\n\tif el.engine.isShutdown() {\n\t\treturn errorx.ErrEngineInShutdown\n\t}\n\tif runnable == nil {\n\t\treturn errorx.ErrNilRunnable\n\t}\n\treturn el.poller.Trigger(queue.LowPriority, func(any) error {\n\t\treturn runnable.Run(ctx)\n\t}, nil)\n}\n\nfunc (el *eventloop) Schedule(context.Context, Runnable, time.Duration) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (el *eventloop) Close(c Conn) error {\n\treturn el.close(c.(*conn), nil)\n}\n\nfunc (el *eventloop) getLogger() logging.Logger {\n\treturn el.engine.opts.Logger\n}\n\nfunc (el *eventloop) countConn() int32 {\n\treturn el.connections.loadCount()\n}\n\nfunc (el *eventloop) closeConns() {\n\t// Close loops and all outstanding connections\n\tel.connections.iterate(func(c *conn) bool {\n\t\t_ = el.close(c, nil)\n\t\treturn true\n\t})\n}\n\ntype connWithCallback struct {\n\tc  *conn\n\tcb func()\n}\n\nfunc (el *eventloop) enroll(c net.Conn, addr net.Addr, ctx any) (resCh chan RegisteredResult, err error) {\n\tresCh = make(chan RegisteredResult, 1)\n\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\tdefer close(resCh)\n\n\t\tvar err error\n\t\tif c == nil {\n\t\t\tif c, err = net.Dial(addr.Network(), addr.String()); err != nil {\n\t\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tdefer c.Close() //nolint:errcheck\n\n\t\tsc, ok := c.(syscall.Conn)\n\t\tif !ok {\n\t\t\tresCh <- RegisteredResult{\n\t\t\t\tErr: fmt.Errorf(\"failed to assert syscall.Conn from net.Conn: %s\", addr.String()),\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\trc, err := sc.SyscallConn()\n\t\tif err != nil {\n\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\treturn\n\t\t}\n\n\t\tvar dupFD int\n\t\terr1 := rc.Control(func(fd uintptr) {\n\t\t\tdupFD, err = socket.Dup(int(fd))\n\t\t})\n\t\tif err != nil {\n\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\treturn\n\t\t}\n\t\tif err1 != nil {\n\t\t\tresCh <- RegisteredResult{Err: err1}\n\t\t\treturn\n\t\t}\n\n\t\tvar (\n\t\t\tsockAddr unix.Sockaddr\n\t\t\tgc       *conn\n\t\t)\n\t\tswitch c.(type) {\n\t\tcase *net.UnixConn:\n\t\t\tsockAddr, _, _, err = socket.GetUnixSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\t\tif err != nil {\n\t\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tua := c.LocalAddr().(*net.UnixAddr)\n\t\t\tua.Name = c.RemoteAddr().String() + \".\" + strconv.Itoa(dupFD)\n\t\t\tgc = newStreamConn(\"unix\", dupFD, el, sockAddr, c.LocalAddr(), c.RemoteAddr())\n\t\tcase *net.TCPConn:\n\t\t\tsockAddr, _, _, _, err = socket.GetTCPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\t\tif err != nil {\n\t\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgc = newStreamConn(\"tcp\", dupFD, el, sockAddr, c.LocalAddr(), c.RemoteAddr())\n\t\tcase *net.UDPConn:\n\t\t\tsockAddr, _, _, _, err = socket.GetUDPSockAddr(c.RemoteAddr().Network(), c.RemoteAddr().String())\n\t\t\tif err != nil {\n\t\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tgc = newUDPConn(dupFD, el, c.LocalAddr(), sockAddr, true)\n\t\tdefault:\n\t\t\tresCh <- RegisteredResult{Err: fmt.Errorf(\"unknown type of conn: %T\", c)}\n\t\t\treturn\n\t\t}\n\n\t\tgc.ctx = ctx\n\n\t\tconnOpened := make(chan struct{})\n\t\tccb := &connWithCallback{c: gc, cb: func() {\n\t\t\tclose(connOpened)\n\t\t}}\n\t\tif err := el.poller.Trigger(queue.LowPriority, el.register, ccb); err != nil {\n\t\t\tgc.Close() //nolint:errcheck\n\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\treturn\n\t\t}\n\t\t<-connOpened\n\n\t\tresCh <- RegisteredResult{Conn: gc}\n\t})\n\treturn\n}\n\nfunc (el *eventloop) register(a any) error {\n\tc, ok := a.(*conn)\n\tif !ok {\n\t\tccb := a.(*connWithCallback)\n\t\tc = ccb.c\n\t\tdefer ccb.cb()\n\t}\n\treturn el.register0(c)\n}\n\nfunc (el *eventloop) register0(c *conn) error {\n\taddEvents := el.poller.AddRead\n\tif el.engine.opts.EdgeTriggeredIO {\n\t\taddEvents = el.poller.AddReadWrite\n\t}\n\tif err := addEvents(&c.pollAttachment, el.engine.opts.EdgeTriggeredIO); err != nil {\n\t\t_ = unix.Close(c.fd)\n\t\tc.release()\n\t\treturn err\n\t}\n\tel.connections.addConn(c, el.idx)\n\tif c.isDatagram && c.remote != nil {\n\t\treturn nil\n\t}\n\treturn el.open(c)\n}\n\nfunc (el *eventloop) open(c *conn) error {\n\tc.opened = true\n\n\tout, action := el.eventHandler.OnOpen(c)\n\tif out != nil {\n\t\tif err := c.open(out); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif !c.outboundBuffer.IsEmpty() && !el.engine.opts.EdgeTriggeredIO {\n\t\tif err := el.poller.ModReadWrite(&c.pollAttachment, false); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) read0(a any) error {\n\treturn el.read(a.(*conn))\n}\n\nfunc (el *eventloop) read(c *conn) error {\n\tif !c.opened {\n\t\treturn nil\n\t}\n\n\tvar recv int\n\tisET := el.engine.opts.EdgeTriggeredIO\n\tchunk := el.engine.opts.EdgeTriggeredIOChunk\nloop:\n\tn, err := unix.Read(c.fd, el.buffer)\n\tif err != nil || n == 0 {\n\t\tif err == unix.EAGAIN {\n\t\t\treturn nil\n\t\t}\n\t\tif n == 0 {\n\t\t\terr = io.EOF\n\t\t}\n\t\treturn el.close(c, os.NewSyscallError(\"read\", err))\n\t}\n\trecv += n\n\n\tc.buffer = el.buffer[:n]\n\taction := el.eventHandler.OnTraffic(c)\n\tswitch action {\n\tcase None:\n\tcase Close:\n\t\treturn el.close(c, nil)\n\tcase Shutdown:\n\t\treturn errorx.ErrEngineShutdown\n\t}\n\t_, _ = c.inboundBuffer.Write(c.buffer)\n\tc.buffer = c.buffer[:0]\n\n\tif c.isEOF || (isET && recv < chunk) {\n\t\tgoto loop\n\t}\n\n\t// To prevent infinite reading in ET mode and starving other events,\n\t// we need to set up threshold for the maximum read bytes per connection\n\t// on each event-loop. If the threshold is reached and there are still\n\t// unread data in the socket buffer, we must issue another read event manually.\n\tif isET && n == len(el.buffer) {\n\t\treturn el.poller.Trigger(queue.LowPriority, el.read0, c)\n\t}\n\n\treturn nil\n}\n\nfunc (el *eventloop) write0(a any) error {\n\treturn el.write(a.(*conn))\n}\n\n// The default value of UIO_MAXIOV/IOV_MAX is 1024 on Linux and most BSD-like OSs.\nconst iovMax = 1024\n\nfunc (el *eventloop) write(c *conn) error {\n\tif c.outboundBuffer.IsEmpty() {\n\t\treturn nil\n\t}\n\n\tisET := el.engine.opts.EdgeTriggeredIO\n\tchunk := el.engine.opts.EdgeTriggeredIOChunk\n\tvar (\n\t\tn    int\n\t\tsent int\n\t\terr  error\n\t)\nloop:\n\tiov, _ := c.outboundBuffer.Peek(-1)\n\tif len(iov) > 1 {\n\t\tif len(iov) > iovMax {\n\t\t\tiov = iov[:iovMax]\n\t\t}\n\t\tn, err = gio.Writev(c.fd, iov)\n\t} else {\n\t\tn, err = unix.Write(c.fd, iov[0])\n\t}\n\t_, _ = c.outboundBuffer.Discard(n)\n\tswitch err {\n\tcase nil:\n\tcase unix.EAGAIN:\n\t\treturn nil\n\tdefault:\n\t\treturn el.close(c, os.NewSyscallError(\"write\", err))\n\t}\n\tsent += n\n\n\tif isET && !c.outboundBuffer.IsEmpty() && sent < chunk {\n\t\tgoto loop\n\t}\n\n\t// All data have been sent, it's no need to monitor the writable events for LT mode,\n\t// remove the writable event from poller to help the future event-loops if necessary.\n\tif !isET && c.outboundBuffer.IsEmpty() {\n\t\treturn el.poller.ModRead(&c.pollAttachment, false)\n\t}\n\n\t// To prevent infinite writing in ET mode and starving other events,\n\t// we need to set up threshold for the maximum write bytes per connection\n\t// on each event-loop. If the threshold is reached and there are still\n\t// pending data to write, we must issue another write event manually.\n\tif isET && !c.outboundBuffer.IsEmpty() {\n\t\treturn el.poller.Trigger(queue.HighPriority, el.write0, c)\n\t}\n\n\treturn nil\n}\n\nfunc (el *eventloop) close(c *conn, err error) error {\n\tif !c.opened || el.connections.getConn(c.fd) == nil {\n\t\treturn nil // ignore stale connections\n\t}\n\n\tel.connections.delConn(c)\n\taction := el.eventHandler.OnClose(c, err)\n\n\t// Send residual data in buffer back to the remote before actually closing the connection.\n\tfor !c.outboundBuffer.IsEmpty() {\n\t\tiov, _ := c.outboundBuffer.Peek(0)\n\t\tif len(iov) > iovMax {\n\t\t\tiov = iov[:iovMax]\n\t\t}\n\t\tn, err := gio.Writev(c.fd, iov)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\t_, _ = c.outboundBuffer.Discard(n)\n\t}\n\n\tc.release()\n\n\tvar errStr strings.Builder\n\terr0, err1 := el.poller.Delete(c.fd), unix.Close(c.fd)\n\tif err0 != nil {\n\t\terr0 = fmt.Errorf(\"failed to delete fd=%d from poller in event-loop(%d): %v\",\n\t\t\tc.fd, el.idx, os.NewSyscallError(\"delete\", err0))\n\t\terrStr.WriteString(err0.Error())\n\t\terrStr.WriteString(\" | \")\n\t}\n\tif err1 != nil {\n\t\terr1 = fmt.Errorf(\"failed to close fd=%d in event-loop(%d): %v\",\n\t\t\tc.fd, el.idx, os.NewSyscallError(\"close\", err1))\n\t\terrStr.WriteString(err1.Error())\n\t}\n\tif errStr.Len() > 0 {\n\t\treturn errors.New(strings.TrimSuffix(errStr.String(), \" | \"))\n\t}\n\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) wake(c *conn) error {\n\tif !c.opened || el.connections.getConn(c.fd) == nil {\n\t\treturn nil // ignore stale connections\n\t}\n\n\taction := el.eventHandler.OnTraffic(c)\n\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) ticker(ctx context.Context) {\n\tvar (\n\t\taction Action\n\t\tdelay  time.Duration\n\t\ttimer  *time.Timer\n\t)\n\tdefer func() {\n\t\tif timer != nil {\n\t\t\ttimer.Stop()\n\t\t}\n\t}()\n\tfor {\n\t\tdelay, action = el.eventHandler.OnTick()\n\t\tswitch action {\n\t\tcase None, Close:\n\t\tcase Shutdown:\n\t\t\t// It seems reasonable to mark this as low-priority, waiting for some tasks like asynchronous writes\n\t\t\t// to finish up before shutting down the service.\n\t\t\terr := el.poller.Trigger(queue.LowPriority, func(_ any) error { return errorx.ErrEngineShutdown }, nil)\n\t\t\tel.getLogger().Debugf(\"failed to enqueue shutdown signal of high-priority for event-loop(%d): %v\", el.idx, err)\n\t\t}\n\t\tif timer == nil {\n\t\t\ttimer = time.NewTimer(delay)\n\t\t} else {\n\t\t\ttimer.Reset(delay)\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tel.getLogger().Debugf(\"stopping ticker in event-loop(%d) from Engine, error:%v\", el.idx, ctx.Err())\n\t\t\treturn\n\t\tcase <-timer.C:\n\t\t}\n\t}\n}\n\nfunc (el *eventloop) readUDP(fd int, _ netpoll.IOEvent, _ netpoll.IOFlags) error {\n\tn, sa, err := unix.Recvfrom(fd, el.buffer, 0)\n\tif err != nil {\n\t\tif err == unix.EAGAIN {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to read UDP packet from fd=%d in event-loop(%d), %v\",\n\t\t\tfd, el.idx, os.NewSyscallError(\"recvfrom\", err))\n\t}\n\tvar c *conn\n\tif ln, ok := el.listeners[fd]; ok {\n\t\tc = newUDPConn(fd, el, ln.addr, sa, false)\n\t} else {\n\t\tc = el.connections.getConn(fd)\n\t}\n\tc.buffer = el.buffer[:n]\n\taction := el.eventHandler.OnTraffic(c)\n\tif c.remote != nil {\n\t\tc.release()\n\t}\n\tif action == Shutdown {\n\t\treturn errorx.ErrEngineShutdown\n\t}\n\treturn nil\n}\n\nfunc (el *eventloop) handleAction(c *conn, action Action) error {\n\tswitch action {\n\tcase None:\n\t\treturn nil\n\tcase Close:\n\t\treturn el.close(c, nil)\n\tcase Shutdown:\n\t\treturn errorx.ErrEngineShutdown\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n/*\nfunc (el *eventloop) execCmd(a any) (err error) {\n\tcmd := a.(*asyncCmd)\n\tc := el.connections.getConnByGFD(cmd.fd)\n\tif c == nil || c.gfd != cmd.fd {\n\t\treturn errorx.ErrInvalidConn\n\t}\n\n\tdefer func() {\n\t\tif cmd.cb != nil {\n\t\t\t_ = cmd.cb(c, err)\n\t\t}\n\t}()\n\n\tswitch cmd.typ {\n\tcase asyncCmdClose:\n\t\treturn el.close(c, nil)\n\tcase asyncCmdWake:\n\t\treturn el.wake(c)\n\tcase asyncCmdWrite:\n\t\t_, err = c.Write(cmd.param.([]byte))\n\tcase asyncCmdWritev:\n\t\t_, err = c.Writev(cmd.param.([][]byte))\n\tdefault:\n\t\treturn errorx.ErrUnsupportedOp\n\t}\n\treturn\n}\n*/\n"
  },
  {
    "path": "eventloop_unix_test.go",
    "content": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\tgoPool \"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\nfunc (lb *roundRobinLoadBalancer) register(el *eventloop) {\n\tlb.baseLoadBalancer.register(el)\n\tregisterInitConn(el)\n}\n\nfunc (lb *leastConnectionsLoadBalancer) register(el *eventloop) {\n\tlb.baseLoadBalancer.register(el)\n\tregisterInitConn(el)\n}\n\nfunc (lb *sourceAddrHashLoadBalancer) register(el *eventloop) {\n\tlb.baseLoadBalancer.register(el)\n\tregisterInitConn(el)\n}\n\nfunc registerInitConn(el *eventloop) {\n\tfor i := 0; i < int(atomic.LoadInt32(&nowEventLoopInitConn)); i++ {\n\t\tc := newStreamConn(\"tcp\", i, el, &unix.SockaddrInet4{}, &net.TCPAddr{}, &net.TCPAddr{})\n\t\tel.connections.addConn(c, el.idx)\n\t}\n}\n\n// nowEventLoopInitConn initializes the number of conn fake data, must be set to 0 after use.\nvar (\n\tnowEventLoopInitConn int32\n\ttestBigGC            = false\n)\n\nfunc BenchmarkGC4El100k(b *testing.B) {\n\toldGc := debug.SetGCPercent(-1)\n\n\tts1 := benchServeGC(b, \"tcp\", \":0\", true, 4, 100000)\n\tb.Run(\"Run-4-eventloop-100000\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\truntime.GC()\n\t\t}\n\t})\n\t_ = ts1.eng.Stop(context.Background())\n\n\tdebug.SetGCPercent(oldGc)\n}\n\nfunc BenchmarkGC4El200k(b *testing.B) {\n\toldGc := debug.SetGCPercent(-1)\n\n\tts1 := benchServeGC(b, \"tcp\", \":0\", true, 4, 200000)\n\tb.Run(\"Run-4-eventloop-200000\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\truntime.GC()\n\t\t}\n\t})\n\t_ = ts1.eng.Stop(context.Background())\n\n\tdebug.SetGCPercent(oldGc)\n}\n\nfunc BenchmarkGC4El500k(b *testing.B) {\n\toldGc := debug.SetGCPercent(-1)\n\n\tts1 := benchServeGC(b, \"tcp\", \":0\", true, 4, 500000)\n\tb.Run(\"Run-4-eventloop-500000\", func(b *testing.B) {\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\truntime.GC()\n\t\t}\n\t})\n\t_ = ts1.eng.Stop(context.Background())\n\n\tdebug.SetGCPercent(oldGc)\n}\n\nfunc benchServeGC(b *testing.B, network, addr string, async bool, elNum int, initConnCount int32) *benchmarkServerGC {\n\tts := &benchmarkServerGC{\n\t\ttester:        b,\n\t\tnetwork:       network,\n\t\taddr:          addr,\n\t\tasync:         async,\n\t\telNum:         elNum,\n\t\tinitOk:        make(chan struct{}),\n\t\tinitConnCount: initConnCount,\n\t}\n\n\tnowEventLoopInitConn = initConnCount\n\t_ = goPool.DefaultWorkerPool.Submit(func() {\n\t\terr := Run(ts,\n\t\t\tnetwork+\"://\"+addr,\n\t\t\tWithLockOSThread(async),\n\t\t\tWithNumEventLoop(elNum),\n\t\t\tWithTCPKeepAlive(time.Minute),\n\t\t\tWithTCPNoDelay(TCPDelay))\n\t\tassert.NoError(b, err)\n\t\tnowEventLoopInitConn = 0\n\t})\n\t<-ts.initOk\n\treturn ts\n}\n\ntype benchmarkServerGC struct {\n\t*BuiltinEventEngine\n\ttester        *testing.B\n\teng           Engine\n\tnetwork       string\n\taddr          string\n\tasync         bool\n\telNum         int\n\tinitConnCount int32\n\tinitOk        chan struct{}\n}\n\nfunc (s *benchmarkServerGC) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\t_ = goPool.DefaultWorkerPool.Submit(func() {\n\t\tfor s.eng.eng.eventLoops.len() != s.elNum || s.eng.CountConnections() != s.elNum*int(s.initConnCount) {\n\t\t\ttime.Sleep(time.Millisecond)\n\t\t}\n\t\tclose(s.initOk)\n\t})\n\treturn\n}\n\n// TestServeGC generate fake data asynchronously, if you need to test, manually open the comment.\nfunc TestServeGC(t *testing.T) {\n\tt.Run(\"gc-loop\", func(t *testing.T) {\n\t\tt.Run(\"1-loop-10000\", func(t *testing.T) {\n\t\t\tif testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 1, 10000)\n\t\t})\n\t\tt.Run(\"1-loop-100000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 1, 100000)\n\t\t})\n\t\tt.Run(\"1-loop-1000000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 1, 1000000)\n\t\t})\n\t\tt.Run(\"2-loop-10000\", func(t *testing.T) {\n\t\t\tif testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 2, 10000)\n\t\t})\n\t\tt.Run(\"2-loop-100000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 2, 100000)\n\t\t})\n\t\tt.Run(\"2-loop-1000000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 2, 1000000)\n\t\t})\n\t\tt.Run(\"4-loop-10000\", func(t *testing.T) {\n\t\t\tif testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 4, 10000)\n\t\t})\n\t\tt.Run(\"4-loop-100000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 4, 100000)\n\t\t})\n\t\tt.Run(\"4-loop-1000000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 4, 1000000)\n\t\t})\n\t\tt.Run(\"16-loop-10000\", func(t *testing.T) {\n\t\t\tif testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 16, 10000)\n\t\t})\n\t\tt.Run(\"16-loop-100000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 16, 100000)\n\t\t})\n\t\tt.Run(\"16-loop-1000000\", func(t *testing.T) {\n\t\t\tif !testBigGC {\n\t\t\t\tt.Skipf(\"Skip when testBigGC=%t\", testBigGC)\n\t\t\t}\n\t\t\ttestServeGC(t, \"tcp\", \":0\", true, true, 16, 1000000)\n\t\t})\n\t})\n}\n\nfunc testServeGC(t *testing.T, network, addr string, multicore, async bool, elNum int, initConnCount int32) {\n\tts := &testServerGC{\n\t\ttester:    t,\n\t\tnetwork:   network,\n\t\taddr:      addr,\n\t\tmulticore: multicore,\n\t\tasync:     async,\n\t\telNum:     elNum,\n\t}\n\n\tnowEventLoopInitConn = initConnCount\n\n\terr := Run(ts,\n\t\tnetwork+\"://\"+addr,\n\t\tWithLockOSThread(async),\n\t\tWithMulticore(multicore),\n\t\tWithNumEventLoop(elNum),\n\t\tWithTCPKeepAlive(time.Minute),\n\t\tWithTCPNoDelay(TCPDelay))\n\tassert.NoError(t, err)\n\tnowEventLoopInitConn = 0\n}\n\ntype testServerGC struct {\n\t*BuiltinEventEngine\n\ttester    *testing.T\n\teng       Engine\n\tnetwork   string\n\taddr      string\n\tmulticore bool\n\tasync     bool\n\telNum     int\n}\n\nfunc (s *testServerGC) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\tgcSecs := 5\n\tif testBigGC {\n\t\tgcSecs = 10\n\t}\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\ts.GC(gcSecs)\n\t})\n\tassert.NoError(s.tester, err)\n\n\treturn\n}\n\nfunc (s *testServerGC) GC(secs int) {\n\tdefer func() {\n\t\t_ = s.eng.Stop(context.Background())\n\t\truntime.GC()\n\t}()\n\tvar gcAllTime, gcAllCount time.Duration\n\tgcStart := time.Now()\n\tfor range time.Tick(time.Second) {\n\t\tgcAllCount++\n\t\tnow := time.Now()\n\t\truntime.GC()\n\t\tgcTime := time.Since(now)\n\t\tgcAllTime += gcTime\n\t\ts.tester.Log(s.tester.Name(), s.network, \"server gc:\", gcTime, \"average gc time:\", gcAllTime/gcAllCount)\n\t\tif time.Since(gcStart) >= time.Second*time.Duration(secs) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "eventloop_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\ntype eventloop struct {\n\tch           chan any           // channel for event-loop\n\tidx          int                // index of event-loop in event-loops\n\teng          *engine            // engine in loop\n\tconnCount    int32              // number of active connections in event-loop\n\tconnections  map[*conn]struct{} // TCP connection map: fd -> conn\n\teventHandler EventHandler       // user eventHandler\n}\n\nfunc (el *eventloop) Register(ctx context.Context, addr net.Addr) (<-chan RegisteredResult, error) {\n\tif el.eng.isShutdown() {\n\t\treturn nil, errorx.ErrEngineInShutdown\n\t}\n\tif addr == nil {\n\t\treturn nil, errorx.ErrInvalidNetworkAddress\n\t}\n\treturn el.enroll(nil, addr, FromContext(ctx))\n}\n\nfunc (el *eventloop) Enroll(ctx context.Context, c net.Conn) (<-chan RegisteredResult, error) {\n\tif el.eng.isShutdown() {\n\t\treturn nil, errorx.ErrEngineInShutdown\n\t}\n\tif c == nil {\n\t\treturn nil, errorx.ErrInvalidNetConn\n\t}\n\treturn el.enroll(c, c.RemoteAddr(), FromContext(ctx))\n}\n\nfunc (el *eventloop) Execute(ctx context.Context, runnable Runnable) error {\n\tif el.eng.isShutdown() {\n\t\treturn errorx.ErrEngineInShutdown\n\t}\n\tif runnable == nil {\n\t\treturn errorx.ErrNilRunnable\n\t}\n\treturn goroutine.DefaultWorkerPool.Submit(func() {\n\t\tel.ch <- func() error {\n\t\t\treturn runnable.Run(ctx)\n\t\t}\n\t})\n}\n\nfunc (el *eventloop) Schedule(context.Context, Runnable, time.Duration) error {\n\treturn errorx.ErrUnsupportedOp\n}\n\nfunc (el *eventloop) Close(c Conn) error {\n\treturn el.close(c.(*conn), nil)\n}\n\nfunc (el *eventloop) getLogger() logging.Logger {\n\treturn el.eng.opts.Logger\n}\n\nfunc (el *eventloop) enroll(c net.Conn, addr net.Addr, ctx any) (resCh chan RegisteredResult, err error) {\n\tresCh = make(chan RegisteredResult, 1)\n\terr = goroutine.DefaultWorkerPool.Submit(func() {\n\t\tdefer close(resCh)\n\n\t\tvar err error\n\t\tif c == nil {\n\t\t\tif c, err = net.Dial(addr.Network(), addr.String()); err != nil {\n\t\t\t\tresCh <- RegisteredResult{Err: err}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\tconnOpened := make(chan struct{})\n\t\tvar gc *conn\n\t\tswitch addr.Network() {\n\t\tcase \"tcp\", \"tcp4\", \"tcp6\", \"unix\":\n\t\t\tgc = newStreamConn(el, c, ctx)\n\t\t\tel.ch <- &openConn{c: gc, cb: func() { close(connOpened) }}\n\t\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\t\tvar buffer [0x10000]byte\n\t\t\t\tfor {\n\t\t\t\t\tn, err := c.Read(buffer[:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tel.ch <- &netErr{gc, err}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tel.ch <- packTCPConn(gc, buffer[:n])\n\t\t\t\t}\n\t\t\t})\n\t\tcase \"udp\", \"udp4\", \"udp6\":\n\t\t\tgc = newUDPConn(el, nil, c, c.LocalAddr(), c.RemoteAddr(), ctx)\n\t\t\tel.ch <- &openConn{c: gc, cb: func() { close(connOpened) }}\n\t\t\tgoroutine.DefaultWorkerPool.Submit(func() {\n\t\t\t\tvar buffer [0x10000]byte\n\t\t\t\tfor {\n\t\t\t\t\tn, err := c.Read(buffer[:])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tel.ch <- &netErr{gc, err}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tgc := newUDPConn(el, nil, c, c.LocalAddr(), c.RemoteAddr(), ctx)\n\t\t\t\t\tel.ch <- packUDPConn(gc, buffer[:n])\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\t<-connOpened\n\n\t\tresCh <- RegisteredResult{Conn: gc}\n\t})\n\treturn\n}\n\nfunc (el *eventloop) incConn(delta int32) {\n\tatomic.AddInt32(&el.connCount, delta)\n}\n\nfunc (el *eventloop) countConn() int32 {\n\treturn atomic.LoadInt32(&el.connCount)\n}\n\nfunc (el *eventloop) run() (err error) {\n\tdefer func() {\n\t\tel.eng.shutdown(err)\n\t\tfor c := range el.connections {\n\t\t\t_ = el.close(c, nil)\n\t\t}\n\t}()\n\n\tif el.eng.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\tfor i := range el.ch {\n\t\tswitch v := i.(type) {\n\t\tcase error:\n\t\t\terr = v\n\t\tcase *netErr:\n\t\t\terr = el.close(v.c, v.err)\n\t\tcase *openConn:\n\t\t\terr = el.open(v)\n\t\tcase *tcpConn:\n\t\t\terr = el.read(unpackTCPConn(v))\n\t\tcase *udpConn:\n\t\t\terr = el.readUDP(v.c)\n\t\tcase func() error:\n\t\t\terr = v()\n\t\t}\n\n\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\tel.getLogger().Debugf(\"event-loop(%d) is exiting in terms of the demand from user, %v\", el.idx, err)\n\t\t\tbreak\n\t\t} else if err != nil {\n\t\t\tel.getLogger().Debugf(\"event-loop(%d) got a nonlethal error: %v\", el.idx, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (el *eventloop) open(oc *openConn) error {\n\tif oc.cb != nil {\n\t\tdefer oc.cb()\n\t}\n\n\tc := oc.c\n\tel.connections[c] = struct{}{}\n\tel.incConn(1)\n\n\tout, action := el.eventHandler.OnOpen(c)\n\tif out != nil {\n\t\tif _, err := c.rawConn.Write(out); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) read(c *conn) error {\n\tif _, ok := el.connections[c]; !ok {\n\t\treturn nil // ignore stale wakes.\n\t}\n\taction := el.eventHandler.OnTraffic(c)\n\tswitch action {\n\tcase None:\n\tcase Close:\n\t\treturn el.close(c, nil)\n\tcase Shutdown:\n\t\treturn errorx.ErrEngineShutdown\n\t}\n\t_, _ = c.inboundBuffer.Write(c.buffer.B)\n\tc.buffer.Reset()\n\n\treturn nil\n}\n\nfunc (el *eventloop) readUDP(c *conn) error {\n\taction := el.eventHandler.OnTraffic(c)\n\tif action == Shutdown {\n\t\treturn errorx.ErrEngineShutdown\n\t}\n\tc.release()\n\treturn nil\n}\n\nfunc (el *eventloop) ticker(ctx context.Context) {\n\tif el == nil {\n\t\treturn\n\t}\n\tvar (\n\t\taction Action\n\t\tdelay  time.Duration\n\t\ttimer  *time.Timer\n\t)\n\tdefer func() {\n\t\tif timer != nil {\n\t\t\ttimer.Stop()\n\t\t}\n\t}()\n\tvar shutdown bool\n\tfor {\n\t\tdelay, action = el.eventHandler.OnTick()\n\t\tswitch action {\n\t\tcase None, Close:\n\t\tcase Shutdown:\n\t\t\tif !shutdown {\n\t\t\t\tshutdown = true\n\t\t\t\tel.ch <- errorx.ErrEngineShutdown\n\t\t\t\tel.getLogger().Debugf(\"stopping ticker in event-loop(%d) from Tick()\", el.idx)\n\t\t\t}\n\t\t}\n\t\tif timer == nil {\n\t\t\ttimer = time.NewTimer(delay)\n\t\t} else {\n\t\t\ttimer.Reset(delay)\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tel.getLogger().Debugf(\"stopping ticker in event-loop(%d) from Server, error:%v\", el.idx, ctx.Err())\n\t\t\treturn\n\t\tcase <-timer.C:\n\t\t}\n\t}\n}\n\nfunc (el *eventloop) wake(c *conn) error {\n\tif _, ok := el.connections[c]; !ok {\n\t\treturn nil // ignore stale wakes.\n\t}\n\taction := el.eventHandler.OnTraffic(c)\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) close(c *conn, err error) error {\n\tif _, ok := el.connections[c]; c.rawConn == nil || !ok {\n\t\treturn nil // ignore stale wakes.\n\t}\n\n\tdelete(el.connections, c)\n\tel.incConn(-1)\n\taction := el.eventHandler.OnClose(c, err)\n\terr = c.rawConn.Close()\n\tc.release()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to close connection=%s in event-loop(%d): %v\", c.remoteAddr, el.idx, err)\n\t}\n\n\treturn el.handleAction(c, action)\n}\n\nfunc (el *eventloop) handleAction(c *conn, action Action) error {\n\tswitch action {\n\tcase None:\n\t\treturn nil\n\tcase Close:\n\t\treturn el.close(c, nil)\n\tcase Shutdown:\n\t\treturn errorx.ErrEngineShutdown\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "gnet.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package gnet implements a high-performance, lightweight, non-blocking,\n// event-driven networking framework written in pure Go.\n//\n// Visit https://gnet.host/ for more details about gnet.\npackage gnet\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/url\"\n\t\"path\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/panjf2000/gnet/v2/internal/gfd\"\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/ring\"\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/math\"\n)\n\n// Action is an action that occurs after the completion of an event.\ntype Action int\n\nconst (\n\t// None indicates that no action should occur following an event.\n\tNone Action = iota\n\n\t// Close closes the connection.\n\tClose\n\n\t// Shutdown shutdowns the engine.\n\tShutdown\n)\n\n// Engine represents an engine context which provides some functions.\ntype Engine struct {\n\t// eng is the internal engine struct.\n\teng *engine\n}\n\n// Validate checks whether the engine is available.\nfunc (e Engine) Validate() error {\n\tif e.eng == nil || len(e.eng.listeners) == 0 {\n\t\treturn errorx.ErrEmptyEngine\n\t}\n\tif e.eng.isShutdown() {\n\t\treturn errorx.ErrEngineInShutdown\n\t}\n\treturn nil\n}\n\n// CountConnections counts the number of currently active connections and returns it.\nfunc (e Engine) CountConnections() (count int) {\n\tif e.Validate() != nil {\n\t\treturn -1\n\t}\n\n\te.eng.eventLoops.iterate(func(_ int, el *eventloop) bool {\n\t\tcount += int(el.countConn())\n\t\treturn true\n\t})\n\treturn\n}\n\n// Register registers the new connection to the event-loop that is chosen\n// based off of the algorithm set by WithLoadBalancing.\n// You should call either of the NewNetConnContext or NewNetAddrContext\n// and pass the returned context to this method. net.Conn will precede\n// net.Addr if both are present in the context.\n//\n// Note that you need to switch to another load-balancing algorithm over\n// the default RoundRobin when starting the engine, to avoid data race\n// issue if you plan on calling this method from somewhere later on.\nfunc (e Engine) Register(ctx context.Context) (<-chan RegisteredResult, error) {\n\tif err := e.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif e.eng.eventLoops.len() == 0 {\n\t\treturn nil, errorx.ErrEmptyEngine\n\t}\n\n\tc, ok := FromNetConnContext(ctx)\n\tif ok {\n\t\treturn e.eng.eventLoops.next(c.RemoteAddr()).Enroll(ctx, c)\n\t}\n\n\taddr, ok := FromNetAddrContext(ctx)\n\tif ok {\n\t\treturn e.eng.eventLoops.next(addr).Register(ctx, addr)\n\t}\n\n\treturn nil, errorx.ErrInvalidNetworkAddress\n}\n\n// Dup returns a copy of the underlying file descriptor of listener.\n// It is the caller's responsibility to close dupFD when finished.\n// Closing listener does not affect dupFD, and closing dupFD does not affect listener.\n//\n// Note that this method is only available when the engine has only one listener.\nfunc (e Engine) Dup() (fd int, err error) {\n\tif err := e.Validate(); err != nil {\n\t\treturn -1, err\n\t}\n\n\tif len(e.eng.listeners) > 1 {\n\t\treturn -1, errorx.ErrUnsupportedOp\n\t}\n\n\tfor _, ln := range e.eng.listeners {\n\t\tfd, err = ln.dup()\n\t}\n\n\treturn\n}\n\n// DupListener is like Dup, but it duplicates the listener with the given network and address.\n// This is useful when there are multiple listeners.\nfunc (e Engine) DupListener(network, addr string) (int, error) {\n\tif err := e.Validate(); err != nil {\n\t\treturn -1, err\n\t}\n\n\tfor _, ln := range e.eng.listeners {\n\t\tif ln.network == network && ln.address == addr {\n\t\t\treturn ln.dup()\n\t\t}\n\t}\n\n\treturn -1, errorx.ErrInvalidNetworkAddress\n}\n\n// Stop gracefully shuts down this Engine without interrupting any active event-loops,\n// it waits indefinitely for connections and event-loops to be closed and then shuts down.\nfunc (e Engine) Stop(ctx context.Context) error {\n\tif err := e.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\te.eng.shutdown(nil)\n\n\tticker := time.NewTicker(shutdownPollInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tif e.eng.isShutdown() {\n\t\t\treturn nil\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase <-ticker.C:\n\t\t}\n\t}\n}\n\n/*\ntype asyncCmdType uint8\n\nconst (\n\tasyncCmdClose = iota + 1\n\tasyncCmdWake\n\tasyncCmdWrite\n\tasyncCmdWritev\n)\n\ntype asyncCmd struct {\n\tfd  gfd.GFD\n\ttyp asyncCmdType\n\tcb  AsyncCallback\n\tparam any\n}\n\n// AsyncWrite writes data to the given connection asynchronously.\nfunc (e Engine) AsyncWrite(fd gfd.GFD, p []byte, cb AsyncCallback) error {\n\tif err := e.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\treturn e.eng.sendCmd(&asyncCmd{fd: fd, typ: asyncCmdWrite, cb: cb, param: p}, false)\n}\n\n// AsyncWritev is like AsyncWrite, but it accepts a slice of byte slices.\nfunc (e Engine) AsyncWritev(fd gfd.GFD, batch [][]byte, cb AsyncCallback) error {\n\tif err := e.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\treturn e.eng.sendCmd(&asyncCmd{fd: fd, typ: asyncCmdWritev, cb: cb, param: batch}, false)\n}\n\n// Close closes the given connection.\nfunc (e Engine) Close(fd gfd.GFD, cb AsyncCallback) error {\n\tif err := e.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\treturn e.eng.sendCmd(&asyncCmd{fd: fd, typ: asyncCmdClose, cb: cb}, false)\n}\n\n// Wake wakes up the given connection.\nfunc (e Engine) Wake(fd gfd.GFD, cb AsyncCallback) error {\n\tif err := e.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\treturn e.eng.sendCmd(&asyncCmd{fd: fd, typ: asyncCmdWake, cb: cb}, true)\n}\n*/\n\n// Reader is an interface that consists of a number of methods for reading that Conn must implement.\n//\n// Note that the methods in this interface are not concurrency-safe for concurrent use,\n// you must invoke them within any method in EventHandler.\ntype Reader interface {\n\tio.Reader\n\tio.WriterTo\n\n\t// Next returns the next n bytes and advances the inbound buffer.\n\t// buf must not be used in a new goroutine. Otherwise, use Read instead.\n\t//\n\t// If the number of the available bytes is less than requested,\n\t// a pair of (0, io.ErrShortBuffer) is returned.\n\tNext(n int) (buf []byte, err error)\n\n\t// Peek returns the next n bytes without advancing the inbound buffer,\n\t// the returned bytes remain valid until a Discard is called.\n\t// buf must neither be used in a new goroutine nor anywhere after the call\n\t// to Discard, make a copy of buf manually or use Read otherwise.\n\t//\n\t// If the number of the available bytes is less than requested,\n\t// a pair of (0, io.ErrShortBuffer) is returned.\n\tPeek(n int) (buf []byte, err error)\n\n\t// Discard advances the inbound buffer with next n bytes, returning the number of bytes discarded.\n\tDiscard(n int) (discarded int, err error)\n\n\t// InboundBuffered returns the number of bytes that can be read from the current buffer.\n\tInboundBuffered() int\n}\n\n// Writer is an interface that consists of a number of methods for writing that Conn must implement.\ntype Writer interface {\n\tio.Writer     // not concurrency-safe\n\tio.ReaderFrom // not concurrency-safe\n\n\t// SendTo transmits a message to the given address, it's not concurrency-safe.\n\t// It is available only for UDP sockets, an ErrUnsupportedOp will be returned\n\t// when it is called on a non-UDP socket.\n\t// This method should be used only when you need to send a message to a specific\n\t// address over the UDP socket, otherwise you should use Conn.Write() instead.\n\tSendTo(buf []byte, addr net.Addr) (n int, err error)\n\n\t// Writev writes multiple byte slices to remote synchronously, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tWritev(bs [][]byte) (n int, err error)\n\n\t// Flush writes any buffered data to the underlying connection, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tFlush() error\n\n\t// OutboundBuffered returns the number of bytes that can be read from the current buffer.\n\t// it's not concurrency-safe, you must invoke it within any method in EventHandler.\n\tOutboundBuffered() int\n\n\t// AsyncWrite writes bytes to remote asynchronously, it's concurrency-safe,\n\t// you don't have to invoke it within any method in EventHandler,\n\t// usually you would call it in an individual goroutine.\n\t//\n\t// Note that it will go synchronously with UDP, so it is needless to call\n\t// this asynchronous method, we may disable this method for UDP and just\n\t// return ErrUnsupportedOp in the future, therefore, please don't rely on\n\t// this method to do something important under UDP, if you're working with UDP,\n\t// just call Conn.Write to send back your data.\n\tAsyncWrite(buf []byte, callback AsyncCallback) (err error)\n\n\t// AsyncWritev writes multiple byte slices to remote asynchronously,\n\t// you don't have to invoke it within any method in EventHandler,\n\t// usually you would call it in an individual goroutine.\n\tAsyncWritev(bs [][]byte, callback AsyncCallback) (err error)\n}\n\n// AsyncCallback is a callback that will be invoked after the asynchronous function finishes.\n//\n// Note that the parameter gnet.Conn might have been already released when it's UDP protocol,\n// thus it shouldn't be accessed.\n// This callback will be executed in event-loop, thus it must not block, otherwise,\n// it blocks the event-loop.\ntype AsyncCallback func(c Conn, err error) error\n\n// Socket is a set of functions which manipulate the underlying file descriptor of a connection.\n//\n// Note that the methods in this interface are concurrency-safe for concurrent use,\n// you don't have to invoke them within any method in EventHandler.\ntype Socket interface {\n\t// Gfd returns the gfd of socket.\n\t// Gfd() gfd.GFD\n\n\t// Fd returns the underlying file descriptor.\n\tFd() int\n\n\t// Dup returns a copy of the underlying file descriptor.\n\t// It is the caller's responsibility to close fd when finished.\n\t// Closing c does not affect fd, and closing fd does not affect c.\n\t//\n\t// The returned file descriptor is different from the\n\t//  connection. Attempting to change the properties of the original\n\t// using this duplicate may or may not have the desired effect.\n\tDup() (int, error)\n\n\t// SetReadBuffer sets the size of the operating system's\n\t// receive buffer associated with the connection.\n\tSetReadBuffer(size int) error\n\n\t// SetWriteBuffer sets the size of the operating system's\n\t// transmit buffer associated with the connection.\n\tSetWriteBuffer(size int) error\n\n\t// SetLinger sets the behavior of Close on a connection which still\n\t// has data waiting to be sent or to be acknowledged.\n\t//\n\t// If secs < 0 (the default), the operating system finishes sending the\n\t// data in the background.\n\t//\n\t// If secs == 0, the operating system discards any unsent or\n\t// unacknowledged data.\n\t//\n\t// If secs > 0, the data is sent in the background as with sec < 0. On\n\t// some operating systems after sec seconds have elapsed any remaining\n\t// unsent data may be discarded.\n\tSetLinger(secs int) error\n\n\t// SetKeepAlivePeriod tells the operating system to send keep-alive\n\t// messages on the connection and sets period between TCP keep-alive probes.\n\tSetKeepAlivePeriod(d time.Duration) error\n\n\t// SetKeepAlive enables/disables the TCP keepalive with all socket options:\n\t// TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT. idle is the value for TCP_KEEPIDLE,\n\t// intvl is the value for TCP_KEEPINTVL, cnt is the value for TCP_KEEPCNT,\n\t// ignored when enabled is false.\n\t//\n\t// With TCP keep-alive enabled, idle is the time (in seconds) the connection\n\t// needs to remain idle before TCP starts sending keep-alive probes,\n\t// intvl is the time (in seconds) between individual keep-alive probes.\n\t// TCP will drop the connection after sending cnt probes without getting\n\t// any replies from the peer; then the socket is destroyed, and OnClose\n\t// is triggered.\n\t//\n\t// If one of idle, intvl, or cnt is less than 1, an error is returned.\n\tSetKeepAlive(enabled bool, idle, intvl time.Duration, cnt int) error\n\n\t// SetNoDelay controls whether the operating system should delay\n\t// packet transmission in hopes of sending fewer packets (Nagle's\n\t// algorithm).\n\t// The default is true (no delay), meaning that data is sent as soon as possible after a Write.\n\tSetNoDelay(noDelay bool) error\n}\n\n// Runnable defines the common protocol of an execution on an event-loop.\n// This interface should be implemented and passed to an event-loop in some way,\n// then the event-loop will invoke Run to perform the execution.\n// !!!Caution: Run must not contain any blocking operations like heavy disk or\n// network I/O, or else it will block the event-loop.\ntype Runnable interface {\n\t// Run is about to be executed by the event-loop.\n\tRun(ctx context.Context) error\n}\n\n// RunnableFunc is an adapter to allow the use of ordinary function as a Runnable.\ntype RunnableFunc func(ctx context.Context) error\n\n// Run executes the RunnableFunc itself.\nfunc (fn RunnableFunc) Run(ctx context.Context) error {\n\treturn fn(ctx)\n}\n\n// RegisteredResult is the result of a Register call.\ntype RegisteredResult struct {\n\tConn Conn\n\tErr  error\n}\n\n// EventLoop provides a set of methods for manipulating the event-loop.\ntype EventLoop interface {\n\t// Register connects to the given address and registers the connection to the current event-loop,\n\t// it's concurrency-safe.\n\tRegister(ctx context.Context, addr net.Addr) (<-chan RegisteredResult, error)\n\t// Enroll is like Register, but it accepts an established net.Conn instead of a net.Addr,\n\t// it's concurrency-safe.\n\tEnroll(ctx context.Context, c net.Conn) (<-chan RegisteredResult, error)\n\t// Execute will execute the given runnable on the event-loop at some time in the future,\n\t// it's concurrency-safe.\n\tExecute(ctx context.Context, runnable Runnable) error\n\t// Schedule is like Execute, but it allows you to specify when the runnable is executed.\n\t// In other words, the runnable will be executed when the delay duration is reached,\n\t// it's concurrency-safe.\n\t// TODO(panjf2000): not supported yet, implement this.\n\tSchedule(ctx context.Context, runnable Runnable, delay time.Duration) error\n\n\t// Close closes the given Conn that belongs to the current event-loop.\n\t// It must be called on the same event-loop that the connection belongs to.\n\t// This method is not concurrency-safe, you must invoke it on the event loop.\n\tClose(Conn) error\n}\n\n// Conn is an interface of underlying connection.\ntype Conn interface {\n\tReader // all methods in Reader are not concurrency-safe.\n\tWriter // some methods in Writer are concurrency-safe, some are not.\n\tSocket // all methods in Socket are concurrency-safe.\n\n\t// Context returns a user-defined context, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tContext() (ctx any)\n\n\t// EventLoop returns the event-loop that the connection belongs to.\n\t// The returned EventLoop is concurrency-safe.\n\tEventLoop() EventLoop\n\n\t// SetContext sets a user-defined context, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tSetContext(ctx any)\n\n\t// LocalAddr is the connection's local socket address, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tLocalAddr() net.Addr\n\n\t// RemoteAddr is the connection's remote address, it's not concurrency-safe,\n\t// you must invoke it within any method in EventHandler.\n\tRemoteAddr() net.Addr\n\n\t// Wake triggers an OnTraffic event for the current connection, it's concurrency-safe.\n\tWake(callback AsyncCallback) error\n\n\t// CloseWithCallback closes the current connection, it's concurrency-safe.\n\t// Usually you should provide a non-nil callback for this method,\n\t// otherwise your better choice is Close().\n\tCloseWithCallback(callback AsyncCallback) error\n\n\t// Close closes the current connection, implements net.Conn, it's concurrency-safe.\n\tClose() error\n\n\t// SetDeadline implements net.Conn.\n\tSetDeadline(time.Time) error\n\n\t// SetReadDeadline implements net.Conn.\n\tSetReadDeadline(time.Time) error\n\n\t// SetWriteDeadline implements net.Conn.\n\tSetWriteDeadline(time.Time) error\n}\n\ntype (\n\t// EventHandler represents the engine events' callbacks for the Run call.\n\t// Each event has an Action return value that is used manage the state\n\t// of the connection and engine.\n\tEventHandler interface {\n\t\t// OnBoot fires when the engine is ready for accepting connections.\n\t\t// The parameter engine has information and various utilities.\n\t\tOnBoot(eng Engine) (action Action)\n\n\t\t// OnShutdown fires when the engine is being shut down, it is called right after\n\t\t// all event-loops and connections are closed.\n\t\tOnShutdown(eng Engine)\n\n\t\t// OnOpen fires when a new connection has been opened.\n\t\t//\n\t\t// The Conn c has information about the connection such as its local and remote addresses.\n\t\t// The parameter out is the return value which is going to be sent back to the remote.\n\t\t// Sending large amounts of data back to the remote in OnOpen is usually not recommended.\n\t\tOnOpen(c Conn) (out []byte, action Action)\n\n\t\t// OnClose fires when a connection has been closed.\n\t\t// The parameter err is the last known connection error.\n\t\tOnClose(c Conn, err error) (action Action)\n\n\t\t// OnTraffic fires when a socket receives data from the remote.\n\t\t//\n\t\t// Also check out the comments on Reader and Writer interfaces.\n\t\tOnTraffic(c Conn) (action Action)\n\n\t\t// OnTick fires immediately after the engine starts and will fire again\n\t\t// following the duration specified by the delay return value.\n\t\tOnTick() (delay time.Duration, action Action)\n\t}\n\n\t// BuiltinEventEngine is a built-in implementation of EventHandler which feeds\n\t// each method with an empty implementation, you can embed it within your custom\n\t// struct when you don't intend to implement the entire EventHandler.\n\tBuiltinEventEngine struct{}\n)\n\n// OnBoot fires when the engine is ready for accepting connections.\n// The parameter engine has information and various utilities.\nfunc (*BuiltinEventEngine) OnBoot(_ Engine) (action Action) {\n\treturn\n}\n\n// OnShutdown fires when the engine is being shut down, it is called right after\n// all event-loops and connections are closed.\nfunc (*BuiltinEventEngine) OnShutdown(_ Engine) {\n}\n\n// OnOpen fires when a new connection has been opened.\n// The parameter out is the return value which is going to be sent back to the remote.\nfunc (*BuiltinEventEngine) OnOpen(_ Conn) (out []byte, action Action) {\n\treturn\n}\n\n// OnClose fires when a connection has been closed.\n// The parameter err is the last known connection error.\nfunc (*BuiltinEventEngine) OnClose(_ Conn, _ error) (action Action) {\n\treturn\n}\n\n// OnTraffic fires when a local socket receives data from the remote.\nfunc (*BuiltinEventEngine) OnTraffic(_ Conn) (action Action) {\n\treturn\n}\n\n// OnTick fires immediately after the engine starts and will fire again\n// following the duration specified by the delay return value.\nfunc (*BuiltinEventEngine) OnTick() (delay time.Duration, action Action) {\n\treturn\n}\n\n// MaxStreamBufferCap is the default buffer size for each stream-oriented connection(TCP/Unix).\nvar MaxStreamBufferCap = 64 * 1024 // 64KB\n\nfunc createListeners(addrs []string, opts ...Option) ([]*listener, *Options, error) {\n\toptions := loadOptions(opts...)\n\n\tlogger, logFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tif options.Logger == nil {\n\t\tif options.LogPath != \"\" {\n\t\t\tlogger, logFlusher, _ = logging.CreateLoggerAsLocalFile(options.LogPath, options.LogLevel)\n\t\t}\n\t\toptions.Logger = logger\n\t} else {\n\t\tlogger = options.Logger\n\t\tlogFlusher = nil\n\t}\n\tlogging.SetDefaultLoggerAndFlusher(logger, logFlusher)\n\n\tlogging.Debugf(\"default logging level is %s\", logging.LogLevel())\n\n\t// The maximum number of operating system threads that the Go program can use is initially set to 10000,\n\t// which should also be the maximum number of I/O event-loops locked to OS threads that users can start up.\n\tif options.LockOSThread && options.NumEventLoop > 10000 {\n\t\tlogging.Errorf(\"too many event-loops under LockOSThread mode, should be less than 10,000 \"+\n\t\t\t\"while you are trying to set up %d\\n\", options.NumEventLoop)\n\t\treturn nil, nil, errorx.ErrTooManyEventLoopThreads\n\t}\n\n\tif options.EdgeTriggeredIOChunk > 0 {\n\t\toptions.EdgeTriggeredIO = true\n\t\toptions.EdgeTriggeredIOChunk = math.CeilToPowerOfTwo(options.EdgeTriggeredIOChunk)\n\t} else if options.EdgeTriggeredIO {\n\t\toptions.EdgeTriggeredIOChunk = 1 << 20 // 1MB\n\t}\n\n\trbc := options.ReadBufferCap\n\tswitch {\n\tcase rbc <= 0:\n\t\toptions.ReadBufferCap = MaxStreamBufferCap\n\tcase rbc <= ring.DefaultBufferSize:\n\t\toptions.ReadBufferCap = ring.DefaultBufferSize\n\tdefault:\n\t\toptions.ReadBufferCap = math.CeilToPowerOfTwo(rbc)\n\t}\n\twbc := options.WriteBufferCap\n\tswitch {\n\tcase wbc <= 0:\n\t\toptions.WriteBufferCap = MaxStreamBufferCap\n\tcase wbc <= ring.DefaultBufferSize:\n\t\toptions.WriteBufferCap = ring.DefaultBufferSize\n\tdefault:\n\t\toptions.WriteBufferCap = math.CeilToPowerOfTwo(wbc)\n\t}\n\n\tvar hasUDP, hasUnix bool\n\tfor _, addr := range addrs {\n\t\tproto, _, err := parseProtoAddr(addr)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\thasUDP = hasUDP || strings.HasPrefix(proto, \"udp\")\n\t\thasUnix = hasUnix || proto == \"unix\"\n\t}\n\n\t// SO_REUSEPORT enables duplicate address and port bindings across various\n\t// Unix-like OSs, whereas there is platform-specific inconsistency:\n\t// Linux implemented SO_REUSEPORT with load balancing for incoming connections\n\t// while *BSD implemented it for only binding to the same address and port, which\n\t// makes it pointless to enable SO_REUSEPORT on *BSD and Darwin for gnet with\n\t// multiple event-loops because only the first or last event-loop will be constantly\n\t// woken up to accept incoming connections and handle I/O events while the rest of\n\t// event-loops remain idle.\n\t// Thus, we disable SO_REUSEPORT on *BSD and Darwin by default.\n\t//\n\t// Note that FreeBSD 12 introduced a new socket option named SO_REUSEPORT_LB\n\t// with the capability of load balancing, it's the equivalent of Linux's SO_REUSEPORT.\n\t// Also note that DragonFlyBSD 3.6.0 extended SO_REUSEPORT to distribute workload to\n\t// available sockets, which makes it the same as Linux's SO_REUSEPORT.\n\tgoos := runtime.GOOS\n\tif options.ReusePort &&\n\t\t(options.Multicore || options.NumEventLoop > 1) &&\n\t\t(goos != \"linux\" && goos != \"dragonfly\" && goos != \"freebsd\") {\n\t\toptions.ReusePort = false\n\t}\n\n\t// Despite the fact that SO_REUSEPORT can be set on a Unix domain socket\n\t// via setsockopt() without reporting an error, SO_REUSEPORT is actually\n\t// not supported for sockets of AF_UNIX. Thus, we avoid setting it on the\n\t// Unix domain sockets.\n\t// As of this commit https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net.git/commit/?id=5b0af621c3f6,\n\t// EOPNOTSUPP will be returned when trying to set SO_REUSEPORT on an AF_UNIX socket on Linux. We therefore\n\t// avoid setting it on Unix domain sockets on all UNIX-like platforms to keep this behavior consistent.\n\tif options.ReusePort && hasUnix {\n\t\toptions.ReusePort = false\n\t}\n\n\t// If there is UDP address in the list, we have no choice but to enable SO_REUSEPORT anyway,\n\t// also disable edge-triggered I/O for UDP by default.\n\tif hasUDP {\n\t\toptions.ReusePort = true\n\t\toptions.EdgeTriggeredIO = false\n\t}\n\n\tlisteners := make([]*listener, len(addrs))\n\tfor i, a := range addrs {\n\t\tproto, addr, err := parseProtoAddr(a)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tln, err := initListener(proto, addr, options)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tlisteners[i] = ln\n\t}\n\n\treturn listeners, options, nil\n}\n\n// Run starts handling events on the specified address.\n//\n// Address should use a scheme prefix and be formatted\n// like `tcp://192.168.0.10:9851` or `unix://socket`.\n// Valid network schemes:\n//\n//\ttcp   - bind to both IPv4 and IPv6\n//\ttcp4  - IPv4\n//\ttcp6  - IPv6\n//\tudp   - bind to both IPv4 and IPv6\n//\tudp4  - IPv4\n//\tudp6  - IPv6\n//\tunix  - Unix Domain Socket\n//\n// The \"tcp\" network scheme is assumed when one is not specified.\nfunc Run(eventHandler EventHandler, protoAddr string, opts ...Option) error {\n\tlisteners, options, err := createListeners([]string{protoAddr}, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tfor _, ln := range listeners {\n\t\t\tln.close()\n\t\t}\n\t\tlogging.Cleanup()\n\t}()\n\treturn run(eventHandler, listeners, options, []string{protoAddr})\n}\n\n// Rotate is like Run but accepts multiple network addresses.\nfunc Rotate(eventHandler EventHandler, addrs []string, opts ...Option) error {\n\tlisteners, options, err := createListeners(addrs, opts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tfor _, ln := range listeners {\n\t\t\tln.close()\n\t\t}\n\t\tlogging.Cleanup()\n\t}()\n\treturn run(eventHandler, listeners, options, addrs)\n}\n\nvar (\n\tallEngines sync.Map\n\n\t// shutdownPollInterval is how often we poll to check whether engine has been shut down during gnet.Stop().\n\tshutdownPollInterval = 500 * time.Millisecond\n)\n\n// Stop gracefully shuts down the engine without interrupting any active event-loops,\n// it waits indefinitely for connections and event-loops to be closed and then shuts down.\n//\n// Deprecated: The global Stop only shuts down the last registered Engine with the same\n// protocol and IP:Port as the previous Engine's, which can lead to leaks of Engine if\n// you invoke gnet.Run multiple times using the same protocol and IP:Port under the\n// condition that WithReuseAddr(true) and WithReusePort(true) are enabled.\n// Use Engine.Stop instead.\nfunc Stop(ctx context.Context, protoAddr string) error {\n\tvar eng *engine\n\tif s, ok := allEngines.Load(protoAddr); ok {\n\t\teng = s.(*engine)\n\t\teng.shutdown(nil)\n\t\tdefer allEngines.Delete(protoAddr)\n\t} else {\n\t\treturn errorx.ErrEngineInShutdown\n\t}\n\n\tif eng.isShutdown() {\n\t\treturn errorx.ErrEngineInShutdown\n\t}\n\n\tticker := time.NewTicker(shutdownPollInterval)\n\tdefer ticker.Stop()\n\tfor {\n\t\tif eng.isShutdown() {\n\t\t\treturn nil\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase <-ticker.C:\n\t\t}\n\t}\n}\n\nfunc parseProtoAddr(protoAddr string) (string, string, error) {\n\t// Percent-encode \"%\" in the address to avoid url.Parse error.\n\t// This is for cases like this: udp://[ff02::3%lo0]:9991\n\tprotoAddr = strings.ReplaceAll(protoAddr, \"%\", \"%25\")\n\n\tif runtime.GOOS == \"windows\" {\n\t\tif strings.HasPrefix(protoAddr, \"unix://\") {\n\t\t\tparts := strings.SplitN(protoAddr, \"://\", 2)\n\t\t\tif parts[1] == \"\" {\n\t\t\t\treturn \"\", \"\", errorx.ErrInvalidNetworkAddress\n\t\t\t}\n\t\t\treturn parts[0], parts[1], nil\n\t\t}\n\t}\n\n\tu, err := url.Parse(protoAddr)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tswitch u.Scheme {\n\tcase \"\":\n\t\treturn \"\", \"\", errorx.ErrInvalidNetworkAddress\n\tcase \"tcp\", \"tcp4\", \"tcp6\", \"udp\", \"udp4\", \"udp6\":\n\t\tif u.Host == \"\" || u.Path != \"\" {\n\t\t\treturn \"\", \"\", errorx.ErrInvalidNetworkAddress\n\t\t}\n\t\treturn u.Scheme, u.Host, nil\n\tcase \"unix\":\n\t\thostPath := path.Join(u.Host, u.Path)\n\t\tif hostPath == \"\" {\n\t\t\treturn \"\", \"\", errorx.ErrInvalidNetworkAddress\n\t\t}\n\t\treturn u.Scheme, hostPath, nil\n\tdefault:\n\t\treturn \"\", \"\", errorx.ErrUnsupportedProtocol\n\t}\n}\n\nfunc determineEventLoops(opts *Options) int {\n\tnumEventLoop := 1\n\tif opts.Multicore {\n\t\tnumEventLoop = runtime.NumCPU()\n\t}\n\tif opts.NumEventLoop > 0 {\n\t\tnumEventLoop = opts.NumEventLoop\n\t}\n\tif numEventLoop > gfd.EventLoopIndexMax {\n\t\tnumEventLoop = gfd.EventLoopIndexMax\n\t}\n\treturn numEventLoop\n}\n"
  },
  {
    "path": "gnet_test.go",
    "content": "package gnet\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\tcrand \"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"io\"\n\t\"math\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/zap\"\n\t\"golang.org/x/sync/errgroup\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\tbbPool \"github.com/panjf2000/gnet/v2/pkg/pool/bytebuffer\"\n\tgoPool \"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\nvar (\n\tdatagramLen = 1024\n\tstreamLen   = 1024 * 1024\n)\n\ntype testConf struct {\n\tet        bool\n\tetChunk   int\n\treuseport bool\n\tmulticore bool\n\tasync     bool\n\twritev    bool\n\tclients   int\n\tlb        LoadBalancing\n}\n\n// testUnixAddr uses os.MkdirTemp to get a name that is unique.\n// See https://github.com/golang/go/issues/62614.\nfunc testUnixAddr(t testing.TB) string {\n\t// Pass an empty pattern to get a directory name that is as short as possible.\n\t// If we end up with a name longer than the sun_path field in the sockaddr_un\n\t// struct, we won't be able to make the syscall to open the socket.\n\td, err := os.MkdirTemp(\"\", \"\")\n\trequire.NoError(t, err)\n\tt.Cleanup(func() { require.NoError(t, os.RemoveAll(d)) })\n\treturn filepath.Join(d, \"sock\")\n}\n\nfunc TestServer(t *testing.T) {\n\t// start an engine\n\t// connect 10 clients\n\t// each client will pipe random data for 1-3 seconds.\n\t// the writes to the engine will be random sizes. 0KB - 1MB.\n\t// the engine will echo back the data.\n\t// waits for graceful connection closing.\n\tt.Run(\"poll-LT\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, false, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{false, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{false, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, true, true, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-ET\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, false, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, true, true, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-ET-chunk\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 1 << 18, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 1 << 19, false, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 1 << 19, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 1 << 19, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 18, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 19, false, true, false, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 18, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 19, false, true, true, false, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 18, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 1 << 19, false, true, true, true, 10, SourceAddrHash})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-reuseport-LT\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{false, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{false, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-reuseport-ET\", func(t *testing.T) {\n\t\tt.Run(\"tcp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"tcp-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\"}, &testConf{true, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9992\"}, &testConf{true, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"udp-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9991\"}, &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"udp://:9992\"}, &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"unix-async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-multi-addrs-LT\", func(t *testing.T) {\n\t\tt.Run(\"sync\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"sync-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, false, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, false, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, false, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-multi-addrs-reuseport-LT\", func(t *testing.T) {\n\t\tt.Run(\"sync\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"sync-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, false, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, false, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{false, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-multi-addrs-ET\", func(t *testing.T) {\n\t\tt.Run(\"sync\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"sync-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, false, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, false, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, false, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n\n\tt.Run(\"poll-multi-addrs-reuseport-ET\", func(t *testing.T) {\n\t\tt.Run(\"sync\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, false, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, false, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"sync-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, false, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, false, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"udp://:9993\", \"udp://:9994\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, true, false, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"udp://:9997\", \"udp://:9998\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, true, false, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t\tt.Run(\"async-writev\", func(t *testing.T) {\n\t\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9991\", \"tcp://:9992\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, false, true, true, 10, RoundRobin})\n\t\t\t})\n\t\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\t\trunServer(t, []string{\"tcp://:9995\", \"tcp://:9996\", \"unix://\" + testUnixAddr(t), \"unix://\" + testUnixAddr(t)}, &testConf{true, 0, true, true, true, true, 10, LeastConnections})\n\t\t\t})\n\t\t})\n\t})\n}\n\ntype testServer struct {\n\t*BuiltinEventEngine\n\ttester       *testing.T\n\teng          Engine\n\taddrs        []string\n\tmulticore    bool\n\tasync        bool\n\twritev       bool\n\tnclients     int\n\tstarted      int32\n\tconnected    int32\n\tdisconnected int32\n\tclientActive int32\n}\n\nfunc (s *testServer) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\tif len(s.addrs) > 1 {\n\t\tfd, err := s.eng.Dup()\n\t\tassert.ErrorIsf(s.tester, err, errorx.ErrUnsupportedOp, \"dup error\")\n\t\tassert.EqualValuesf(s.tester, -1, fd, \"expected fd: -1, but got: %d\", fd)\n\n\t\taddr := s.addrs[rand.Intn(len(s.addrs))]\n\t\tnetwork, address, _ := parseProtoAddr(addr)\n\t\tfd, err = s.eng.DupListener(network, address)\n\t\tassert.NoErrorf(s.tester, err, \"DupListener error\")\n\t\tassert.Greaterf(s.tester, fd, 2, \"expected duplicated fd: > 2, but got: %d\", fd)\n\t\tassert.NoErrorf(s.tester, SysClose(fd), \"close fd error\")\n\n\t\t// Test invalid input\n\t\tfd, err = s.eng.DupListener(\"tcp\", \"abc\")\n\t\tassert.ErrorIsf(s.tester, err, errorx.ErrInvalidNetworkAddress, \"expected ErrInvalidNetworkAddress\")\n\t\tassert.EqualValuesf(s.tester, -1, fd, \"expected fd: -1, but got: %d\", fd)\n\t} else {\n\t\tfd, err := s.eng.Dup()\n\t\tassert.NoErrorf(s.tester, err, \"Dup error\")\n\t\tassert.Greaterf(s.tester, fd, 2, \"expected duplicated fd: > 2, but got: %d\", fd)\n\t\tassert.NoErrorf(s.tester, SysClose(fd), \"close fd error\")\n\t}\n\treturn\n}\n\nfunc (s *testServer) OnOpen(c Conn) (out []byte, action Action) {\n\tc.SetContext(c)\n\tatomic.AddInt32(&s.connected, 1)\n\tout = []byte(\"andypan\\r\\n\")\n\tassert.NotNil(s.tester, c.LocalAddr(), \"nil local addr\")\n\tassert.NotNil(s.tester, c.RemoteAddr(), \"nil remote addr\")\n\treturn\n}\n\nfunc (s *testServer) OnShutdown(_ Engine) {\n\tif len(s.addrs) > 1 {\n\t\tfd, err := s.eng.Dup()\n\t\tassert.ErrorIsf(s.tester, err, errorx.ErrUnsupportedOp, \"dup error\")\n\t\tassert.EqualValuesf(s.tester, -1, fd, \"expected fd: -1, but got: %d\", fd)\n\n\t\taddr := s.addrs[rand.Intn(len(s.addrs))]\n\t\tnetwork, address, _ := parseProtoAddr(addr)\n\t\tfd, err = s.eng.DupListener(network, address)\n\t\tassert.NoErrorf(s.tester, err, \"DupListener error\")\n\t\tassert.Greaterf(s.tester, fd, 2, \"expected duplicated fd: > 2, but got: %d\", fd)\n\t\tassert.NoErrorf(s.tester, SysClose(fd), \"close fd error\")\n\n\t\t// Test invalid input\n\t\tfd, err = s.eng.DupListener(\"tcp\", \"abc\")\n\t\tassert.ErrorIsf(s.tester, err, errorx.ErrInvalidNetworkAddress, \"expected ErrInvalidNetworkAddress\")\n\t\tassert.EqualValuesf(s.tester, -1, fd, \"expected fd: -1, but got: %d\", fd)\n\t} else {\n\t\tfd, err := s.eng.Dup()\n\t\tassert.NoErrorf(s.tester, err, \"Dup error\")\n\t\tassert.Greaterf(s.tester, fd, 2, \"expected duplicated fd: > 2, but got: %d\", fd)\n\t\tassert.NoErrorf(s.tester, SysClose(fd), \"close fd error\")\n\t}\n}\n\nfunc (s *testServer) OnClose(c Conn, err error) (action Action) {\n\tif err != nil {\n\t\tlogging.Debugf(\"error occurred on closed, %v\\n\", err)\n\t}\n\n\tassert.Equal(s.tester, c.Context(), c, \"invalid context\")\n\n\tatomic.AddInt32(&s.disconnected, 1)\n\treturn\n}\n\nfunc (s *testServer) OnTraffic(c Conn) (action Action) {\n\tif s.async {\n\t\tbuf := bbPool.Get()\n\t\tn, err := c.WriteTo(buf)\n\t\tassert.NoError(s.tester, err, \"WriteTo error\")\n\t\tassert.Greater(s.tester, n, int64(0), \"WriteTo error\")\n\t\tif c.LocalAddr().Network() == \"tcp\" || c.LocalAddr().Network() == \"unix\" {\n\t\t\terr = goPool.DefaultWorkerPool.Submit(\n\t\t\t\tfunc() {\n\t\t\t\t\tif s.writev {\n\t\t\t\t\t\tmid := buf.Len() / 2\n\t\t\t\t\t\tbs := make([][]byte, 2)\n\t\t\t\t\t\tbs[0] = buf.B[:mid]\n\t\t\t\t\t\tbs[1] = buf.B[mid:]\n\t\t\t\t\t\terr := c.AsyncWritev(bs, func(c Conn, err error) error {\n\t\t\t\t\t\t\tif c.RemoteAddr() != nil {\n\t\t\t\t\t\t\t\tlogging.Debugf(\"conn=%s done writev: %v\", c.RemoteAddr().String(), err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbbPool.Put(buf)\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t})\n\t\t\t\t\t\tassert.NoError(s.tester, err, \"AsyncWritev error\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\terr := c.AsyncWrite(buf.Bytes(), func(c Conn, err error) error {\n\t\t\t\t\t\t\tif c.RemoteAddr() != nil {\n\t\t\t\t\t\t\t\tlogging.Debugf(\"conn=%s done write: %v\", c.RemoteAddr().String(), err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbbPool.Put(buf)\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t})\n\t\t\t\t\t\tassert.NoError(s.tester, err, \"AsyncWritev error\")\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\tassert.NoError(s.tester, err)\n\t\t\treturn\n\t\t} else if c.LocalAddr().Network() == \"udp\" {\n\t\t\terr := goPool.DefaultWorkerPool.Submit(\n\t\t\t\tfunc() {\n\t\t\t\t\terr := c.AsyncWrite(buf.Bytes(), nil)\n\t\t\t\t\tassert.NoError(s.tester, err, \"AsyncWritev error\")\n\t\t\t\t})\n\t\t\tassert.NoError(s.tester, err)\n\t\t\treturn\n\t\t}\n\t\treturn\n\t}\n\n\tbuf, err := c.Next(-1)\n\tassert.NoError(s.tester, err, \"reading data error\")\n\tvar n int\n\tif s.writev {\n\t\tmid := len(buf) / 2\n\t\tn, err = c.Writev([][]byte{buf[:mid], buf[mid:]})\n\t} else {\n\t\tn, err = c.Write(buf)\n\t}\n\tassert.NoError(s.tester, err, \"writev error\")\n\tassert.EqualValues(s.tester, n, len(buf), \"short writev\")\n\n\t// Only for code coverage of testing.\n\tif !s.multicore {\n\t\tassert.NoError(s.tester, c.Flush(), \"flush error\")\n\t\t_ = c.Fd()\n\t\tfd, err := c.Dup()\n\t\tassert.NoError(s.tester, err, \"dup error\")\n\t\tassert.Greaterf(s.tester, fd, 2, \"expected fd: > 2, but got: %d\", fd)\n\t\tassert.NoError(s.tester, SysClose(fd), \"close error\")\n\t\t// TODO(panjf2000): somehow these two system calls will fail with Unix Domain Socket,\n\t\t//  returning \"invalid argument\" error on macOS in Github actions intermittently,\n\t\t//  try to figure it out.\n\t\tif c.LocalAddr().Network() == \"unix\" && runtime.GOOS == \"darwin\" {\n\t\t\t_ = c.SetReadBuffer(streamLen)\n\t\t\t_ = c.SetWriteBuffer(streamLen)\n\t\t} else {\n\t\t\tassert.NoError(s.tester, c.SetReadBuffer(streamLen), \"set read buffer error\")\n\t\t\tassert.NoError(s.tester, c.SetWriteBuffer(streamLen), \"set write buffer error\")\n\t\t}\n\t\tif c.LocalAddr().Network() == \"tcp\" {\n\t\t\tassert.NoError(s.tester, c.SetLinger(1), \"set linger error\")\n\t\t\tassert.NoError(s.tester, c.SetNoDelay(false), \"set no delay error\")\n\t\t\tassert.NoError(s.tester, c.SetKeepAlivePeriod(time.Minute), \"set keepalive period error\")\n\t\t\tassert.EqualError(s.tester, c.SetKeepAlive(true, 0, 0, 0),\n\t\t\t\t\"invalid time duration\", \"set keepalive error\")\n\t\t\tassert.NoError(s.tester, c.SetKeepAlive(false, 0, 0, 0), \"set keepalive error\")\n\t\t\tassert.NoError(s.tester, c.SetKeepAlive(true, time.Minute*10, time.Minute, 10), \"set keepalive error\")\n\t\t} else {\n\t\t\tassert.ErrorIs(s.tester, c.SetKeepAlivePeriod(time.Minute), errorx.ErrUnsupportedOp,\n\t\t\t\t\"non-TCP connection should not support keepalive\")\n\t\t\tassert.ErrorIs(s.tester, c.SetKeepAlive(true, 0, 0, 0), errorx.ErrUnsupportedOp,\n\t\t\t\t\"non-TCP connection should not support keepalive\")\n\t\t}\n\n\t\tassert.Zero(s.tester, c.InboundBuffered(), \"inbound buffer error\")\n\t\tassert.GreaterOrEqual(s.tester, c.OutboundBuffered(), 0, \"outbound buffer error\")\n\t\tn, err := c.Discard(1)\n\t\tassert.NoErrorf(s.tester, err, \"discard error\")\n\t\tassert.Zerof(s.tester, n, \"discard error\")\n\t\tassert.ErrorIs(s.tester, c.SetDeadline(time.Now().Add(time.Second)), errorx.ErrUnsupportedOp)\n\t\tassert.ErrorIs(s.tester, c.SetReadDeadline(time.Now().Add(time.Second)), errorx.ErrUnsupportedOp)\n\t\tassert.ErrorIs(s.tester, c.SetWriteDeadline(time.Now().Add(time.Second)), errorx.ErrUnsupportedOp)\n\n\t\tif c.LocalAddr().Network() == \"udp\" {\n\t\t\tn, err := c.Writev([][]byte{})\n\t\t\tassert.ErrorIs(s.tester, err, errorx.ErrUnsupportedOp, \"udp Writev error\")\n\t\t\tassert.Zero(s.tester, n, \"udp Writev error\")\n\t\t\terr = c.AsyncWritev([][]byte{}, nil)\n\t\t\tassert.ErrorIs(s.tester, err, errorx.ErrUnsupportedOp, \"udp Writev error\")\n\t\t\t_, err = c.SendTo(buf, nil)\n\t\t\tassert.ErrorIsf(s.tester, err, errorx.ErrInvalidNetworkAddress,\n\t\t\t\t\"got error: %v, expected error: %v\", err, errorx.ErrInvalidNetworkAddress)\n\t\t} else {\n\t\t\t_, err = c.SendTo(buf, c.RemoteAddr())\n\t\t\tassert.ErrorIsf(s.tester, err, errorx.ErrUnsupportedOp,\n\t\t\t\t\"got error: %v, expected error: %v\", err, errorx.ErrUnsupportedOp)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (s *testServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = 100 * time.Millisecond\n\tif atomic.CompareAndSwapInt32(&s.started, 0, 1) {\n\t\tfor _, protoAddr := range s.addrs {\n\t\t\tproto, addr, err := parseProtoAddr(protoAddr)\n\t\t\tassert.NoError(s.tester, err)\n\t\t\tfor i := 0; i < s.nclients; i++ {\n\t\t\t\tatomic.AddInt32(&s.clientActive, 1)\n\t\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\t\tstartClient(s.tester, proto, addr, s.multicore, s.async, 0, nil)\n\t\t\t\t\tatomic.AddInt32(&s.clientActive, -1)\n\t\t\t\t})\n\t\t\t\tassert.NoError(s.tester, err)\n\t\t\t}\n\t\t}\n\t}\n\tif atomic.LoadInt32(&s.clientActive) == 0 {\n\t\tvar streamAddrs int\n\t\tfor _, addr := range s.addrs {\n\t\t\tif !strings.HasPrefix(addr, \"udp\") {\n\t\t\t\tstreamAddrs++\n\t\t\t}\n\t\t}\n\t\tstreamConns := s.nclients * streamAddrs\n\t\tdisconnected := atomic.LoadInt32(&s.disconnected)\n\t\tif int(disconnected) == streamConns && disconnected == atomic.LoadInt32(&s.connected) {\n\t\t\taction = Shutdown\n\t\t\tassert.EqualValues(s.tester, 0, s.eng.CountConnections())\n\t\t}\n\t}\n\treturn\n}\n\nfunc runServer(t *testing.T, addrs []string, conf *testConf) {\n\tts := &testServer{\n\t\ttester:    t,\n\t\taddrs:     addrs,\n\t\tmulticore: conf.multicore,\n\t\tasync:     conf.async,\n\t\twritev:    conf.writev,\n\t\tnclients:  conf.clients,\n\t}\n\tvar err error\n\tif len(addrs) > 1 {\n\t\terr = Rotate(ts,\n\t\t\taddrs,\n\t\t\tWithEdgeTriggeredIO(conf.et),\n\t\t\tWithEdgeTriggeredIOChunk(conf.etChunk),\n\t\t\tWithLockOSThread(conf.async),\n\t\t\tWithMulticore(conf.multicore),\n\t\t\tWithReusePort(conf.reuseport),\n\t\t\tWithTicker(true),\n\t\t\tWithTCPKeepAlive(time.Minute),\n\t\t\tWithTCPKeepInterval(time.Second*10),\n\t\t\tWithTCPKeepCount(10),\n\t\t\tWithTCPNoDelay(TCPNoDelay),\n\t\t\tWithLoadBalancing(conf.lb))\n\t} else {\n\t\terr = Run(ts,\n\t\t\taddrs[0],\n\t\t\tWithEdgeTriggeredIO(conf.et),\n\t\t\tWithEdgeTriggeredIOChunk(conf.etChunk),\n\t\t\tWithLockOSThread(conf.async),\n\t\t\tWithMulticore(conf.multicore),\n\t\t\tWithReusePort(conf.reuseport),\n\t\t\tWithTicker(true),\n\t\t\tWithTCPKeepAlive(time.Minute),\n\t\t\tWithTCPKeepInterval(time.Second*10),\n\t\t\tWithTCPKeepCount(10),\n\t\t\tWithTCPNoDelay(TCPDelay),\n\t\t\tWithLoadBalancing(conf.lb))\n\t}\n\tassert.NoError(t, err)\n}\n\nfunc startClient(t *testing.T, network, addr string, multicore, async bool, packetSize int, stallCh chan struct{}) {\n\tc, err := net.Dial(network, addr)\n\tassert.NoError(t, err)\n\tdefer c.Close() //nolint:errcheck\n\trd := bufio.NewReader(c)\n\tif network != \"udp\" {\n\t\tmsg, err := rd.ReadBytes('\\n')\n\t\tassert.NoError(t, err)\n\t\tassert.Equal(t, string(msg), \"andypan\\r\\n\", \"bad header\")\n\t}\n\n\tif stallCh != nil {\n\t\t<-stallCh\n\t}\n\n\tduration := time.Duration((rand.Float64()*2+1)*float64(time.Second)) / 2\n\tlogging.Debugf(\"test duration: %v\", duration)\n\tstart := time.Now()\n\tif packetSize == 0 {\n\t\tpacketSize = streamLen\n\t\tif network == \"udp\" {\n\t\t\tpacketSize = datagramLen\n\t\t}\n\t}\n\tfor time.Since(start) < duration {\n\t\treqData := make([]byte, packetSize)\n\t\t_, err = crand.Read(reqData)\n\t\tassert.NoError(t, err)\n\t\t_, err = c.Write(reqData)\n\t\tassert.NoError(t, err)\n\t\trespData := make([]byte, len(reqData))\n\t\t_, err = io.ReadFull(rd, respData)\n\t\tassert.NoError(t, err)\n\t\tif !async {\n\t\t\t// assert.Equalf(t, reqData, respData, \"response mismatch with protocol:%s, multi-core:%t, content of bytes: %d vs %d\", network, multicore, string(reqData), string(respData))\n\t\t\tassert.Equalf(\n\t\t\t\tt,\n\t\t\t\treqData,\n\t\t\t\trespData,\n\t\t\t\t\"response mismatch with protocol:%s, multi-core:%t, length of bytes: %d vs %d\",\n\t\t\t\tnetwork,\n\t\t\t\tmulticore,\n\t\t\t\tlen(reqData),\n\t\t\t\tlen(respData),\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestDefaultGnetServer(*testing.T) {\n\tsvr := BuiltinEventEngine{}\n\tsvr.OnBoot(Engine{})\n\tsvr.OnOpen(nil)\n\tsvr.OnClose(nil, nil)\n\tsvr.OnTraffic(nil)\n\tsvr.OnTick()\n}\n\ntype testBadAddrServer struct {\n\t*BuiltinEventEngine\n}\n\nfunc (t *testBadAddrServer) OnBoot(_ Engine) (action Action) {\n\treturn Shutdown\n}\n\nfunc TestBadAddresses(t *testing.T) {\n\tevents := new(testBadAddrServer)\n\terr := Run(events, \"tulip://howdy\")\n\trequire.ErrorIs(t, err, errorx.ErrUnsupportedProtocol)\n\terr = Run(events, \"howdy\")\n\trequire.ErrorIs(t, err, errorx.ErrInvalidNetworkAddress)\n\terr = Run(events, \"tcp://\")\n\trequire.ErrorIs(t, err, errorx.ErrInvalidNetworkAddress)\n\terr = Run(events, \"tcp://localhost:8080/foo\")\n\trequire.ErrorIs(t, err, errorx.ErrInvalidNetworkAddress)\n\terr = Run(events, \"unix://\")\n\trequire.ErrorIs(t, err, errorx.ErrInvalidNetworkAddress)\n\terr = Run(events, \":foo\")\n\trequire.Error(t, err, \"missing protocol scheme\")\n\terr = Run(events, \"tcp://127.0.0.1\\n\")\n\trequire.Error(t, err, \"invalid control character in URL\")\n}\n\nfunc TestTick(t *testing.T) {\n\ttestTick(\"tcp\", \":9989\", t)\n}\n\ntype testTickServer struct {\n\t*BuiltinEventEngine\n\tcount int\n}\n\nfunc (t *testTickServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = time.Millisecond * 10\n\tif t.count == 25 {\n\t\taction = Shutdown\n\t\treturn\n\t}\n\tt.count++\n\treturn\n}\n\nfunc testTick(network, addr string, t *testing.T) {\n\tevents := &testTickServer{}\n\tstart := time.Now()\n\topts := Options{Ticker: true}\n\terr := Run(events, network+\"://\"+addr, WithOptions(opts))\n\trequire.NoError(t, err)\n\tdur := time.Since(start)\n\tif dur < 250&time.Millisecond || dur > time.Second {\n\t\tt.Logf(\"bad ticker timing: %d\", dur)\n\t}\n}\n\nfunc TestWakeConn(t *testing.T) {\n\ttestWakeConn(t, \"tcp\", \":9990\")\n}\n\ntype testWakeConnServer struct {\n\t*BuiltinEventEngine\n\ttester  *testing.T\n\tnetwork string\n\taddr    string\n\tconn    chan Conn\n\tc       Conn\n\twake    bool\n}\n\nfunc (t *testWakeConnServer) OnOpen(c Conn) (out []byte, action Action) {\n\tt.conn <- c\n\treturn\n}\n\nfunc (t *testWakeConnServer) OnClose(Conn, error) (action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testWakeConnServer) OnTraffic(c Conn) (action Action) {\n\t_, _ = c.Write([]byte(\"Waking up.\"))\n\taction = -1\n\treturn\n}\n\nfunc (t *testWakeConnServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.wake {\n\t\tt.wake = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tr := make([]byte, 10)\n\t\t\t_, err = conn.Read(r)\n\t\t\tassert.NoError(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tt.c = <-t.conn\n\t_ = t.c.Wake(func(c Conn, err error) error {\n\t\tlogging.Debugf(\"conn=%s done wake: %v\", c.RemoteAddr().String(), err)\n\t\treturn nil\n\t})\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testWakeConn(t *testing.T, network, addr string) {\n\tcurrentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tt.Cleanup(func() {\n\t\tlogging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore\n\t})\n\n\tsvr := &testWakeConnServer{tester: t, network: network, addr: addr, conn: make(chan Conn, 1)}\n\tlogger := zap.NewExample()\n\terr := Run(svr, network+\"://\"+addr,\n\t\tWithTicker(true),\n\t\tWithNumEventLoop(2*runtime.NumCPU()),\n\t\tWithLogger(logger.Sugar()),\n\t\tWithSocketRecvBuffer(4*1024),\n\t\tWithSocketSendBuffer(4*1024),\n\t\tWithReadBufferCap(2000),\n\t\tWithWriteBufferCap(2000))\n\tassert.NoError(t, err)\n\t_ = logger.Sync()\n}\n\nfunc TestShutdown(t *testing.T) {\n\ttestShutdown(t, \"tcp\", \":9991\")\n}\n\ntype testShutdownServer struct {\n\t*BuiltinEventEngine\n\ttester  *testing.T\n\teng     Engine\n\tnetwork string\n\taddr    string\n\tcount   int\n\tclients int32\n\tN       int\n}\n\nfunc (t *testShutdownServer) OnBoot(eng Engine) (action Action) {\n\tt.eng = eng\n\treturn\n}\n\nfunc (t *testShutdownServer) OnOpen(Conn) (out []byte, action Action) {\n\tassert.EqualValues(t.tester, atomic.AddInt32(&t.clients, 1), t.eng.CountConnections())\n\treturn\n}\n\nfunc (t *testShutdownServer) OnClose(Conn, error) (action Action) {\n\tatomic.AddInt32(&t.clients, -1)\n\treturn\n}\n\nfunc (t *testShutdownServer) OnTick() (delay time.Duration, action Action) {\n\tif t.count == 0 {\n\t\t// start clients\n\t\tfor i := 0; i < t.N; i++ {\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\t\tassert.NoError(t.tester, err)\n\t\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\t\t_, err = conn.Read([]byte{0})\n\t\t\t\tassert.Error(t.tester, err)\n\t\t\t})\n\t\t\tassert.NoError(t.tester, err)\n\t\t}\n\t} else if int(atomic.LoadInt32(&t.clients)) == t.N {\n\t\taction = Shutdown\n\t}\n\tt.count++\n\tdelay = time.Second / 20\n\treturn\n}\n\nfunc testShutdown(t *testing.T, network, addr string) {\n\tcurrentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tt.Cleanup(func() {\n\t\tlogging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore\n\t})\n\n\tevents := &testShutdownServer{tester: t, network: network, addr: addr, N: 100}\n\tlogPath := filepath.Join(t.TempDir(), \"gnet-test-shutdown.log\")\n\terr := Run(events, network+\"://\"+addr,\n\t\tWithLogPath(logPath),\n\t\tWithLogLevel(logging.WarnLevel),\n\t\tWithTicker(true),\n\t\tWithReadBufferCap(512),\n\t\tWithWriteBufferCap(512))\n\tassert.NoError(t, err)\n\tassert.Equal(t, 0, int(events.clients), \"did not close all clients\")\n}\n\nfunc TestCloseActionError(t *testing.T) {\n\ttestCloseActionError(t, \"tcp\", \":9992\")\n}\n\ntype testCloseActionErrorServer struct {\n\t*BuiltinEventEngine\n\ttester        *testing.T\n\tnetwork, addr string\n\taction        bool\n}\n\nfunc (t *testCloseActionErrorServer) OnClose(Conn, error) (action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testCloseActionErrorServer) OnTraffic(c Conn) (action Action) {\n\tn := c.InboundBuffered()\n\tbuf := make([]byte, n)\n\tm, err := c.Read(buf)\n\tassert.NoError(t.tester, err)\n\tassert.EqualValuesf(t.tester, n, m, \"read %d bytes, expected %d\", m, n)\n\tn, err = c.Write(buf)\n\tassert.NoError(t.tester, err)\n\tassert.EqualValuesf(t.tester, m, n, \"wrote %d bytes, expected %d\", n, m)\n\taction = Close\n\treturn\n}\n\nfunc (t *testCloseActionErrorServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.action {\n\t\tt.action = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tdata := []byte(\"Hello World!\")\n\t\t\t_, _ = conn.Write(data)\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.NoError(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testCloseActionError(t *testing.T, network, addr string) {\n\tevents := &testCloseActionErrorServer{tester: t, network: network, addr: addr}\n\terr := Run(events, network+\"://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestShutdownActionError(t *testing.T) {\n\ttestShutdownActionError(t, \"tcp\", \":9993\")\n}\n\ntype testShutdownActionErrorServer struct {\n\t*BuiltinEventEngine\n\ttester        *testing.T\n\tnetwork, addr string\n\taction        bool\n}\n\nfunc (t *testShutdownActionErrorServer) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Peek(-1)\n\t_, _ = c.Write(buf)\n\t_, _ = c.Discard(-1)\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testShutdownActionErrorServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.action {\n\t\tt.action = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tdata := []byte(\"Hello World!\")\n\t\t\t_, _ = conn.Write(data)\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.NoError(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testShutdownActionError(t *testing.T, network, addr string) {\n\tevents := &testShutdownActionErrorServer{tester: t, network: network, addr: addr}\n\terr := Run(events, network+\"://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestCloseActionOnOpen(t *testing.T) {\n\ttestCloseActionOnOpen(t, \"tcp\", \":9994\")\n}\n\ntype testCloseActionOnOpenServer struct {\n\t*BuiltinEventEngine\n\ttester        *testing.T\n\tnetwork, addr string\n\taction        bool\n}\n\nfunc (t *testCloseActionOnOpenServer) OnOpen(Conn) (out []byte, action Action) {\n\taction = Close\n\treturn\n}\n\nfunc (t *testCloseActionOnOpenServer) OnClose(Conn, error) (action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testCloseActionOnOpenServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.action {\n\t\tt.action = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testCloseActionOnOpen(t *testing.T, network, addr string) {\n\tevents := &testCloseActionOnOpenServer{tester: t, network: network, addr: addr}\n\terr := Run(events, network+\"://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestShutdownActionOnOpen(t *testing.T) {\n\ttestShutdownActionOnOpen(t, \"tcp\", \":9995\")\n}\n\ntype testShutdownActionOnOpenServer struct {\n\t*BuiltinEventEngine\n\ttester        *testing.T\n\tnetwork, addr string\n\taction        bool\n\teng           Engine\n}\n\nfunc (t *testShutdownActionOnOpenServer) OnOpen(Conn) (out []byte, action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testShutdownActionOnOpenServer) OnShutdown(e Engine) {\n\tt.eng = e\n\tfd, err := t.eng.Dup()\n\tassert.Greaterf(t.tester, fd, 2, \"expected fd: > 2, but got: %d\", fd)\n\tassert.NoErrorf(t.tester, err, \"dup error\")\n\tassert.NoErrorf(t.tester, SysClose(fd), \"close error\")\n\tlogging.Debugf(\"dup fd: %d with error: %v\\n\", fd, err)\n}\n\nfunc (t *testShutdownActionOnOpenServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.action {\n\t\tt.action = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testShutdownActionOnOpen(t *testing.T, network, addr string) {\n\tevents := &testShutdownActionOnOpenServer{tester: t, network: network, addr: addr}\n\terr := Run(events, network+\"://\"+addr, WithTicker(true))\n\trequire.NoError(t, err)\n\t_, err = events.eng.Dup()\n\trequire.ErrorIsf(t, err, errorx.ErrEngineInShutdown, \"expected error: %v, but got: %v\",\n\t\terrorx.ErrEngineInShutdown, err)\n}\n\nfunc TestUDPShutdown(t *testing.T) {\n\ttestUDPShutdown(t, \"udp4\", \":9000\")\n}\n\ntype testUDPShutdownServer struct {\n\t*BuiltinEventEngine\n\ttester  *testing.T\n\tnetwork string\n\taddr    string\n\ttick    bool\n}\n\nfunc (t *testUDPShutdownServer) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Peek(-1)\n\t_, _ = c.Write(buf)\n\t_, _ = c.Discard(-1)\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testUDPShutdownServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.tick {\n\t\tt.tick = true\n\t\tdelay = time.Millisecond * 100\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tdata := []byte(\"Hello World!\")\n\t\t\t_, err = conn.Write(data)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.NoError(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testUDPShutdown(t *testing.T, network, addr string) {\n\tsvr := &testUDPShutdownServer{tester: t, network: network, addr: addr}\n\terr := Run(svr, network+\"://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestCloseConnection(t *testing.T) {\n\ttestCloseConnection(t, \"tcp\", \":9996\")\n}\n\ntype testCloseConnectionServer struct {\n\t*BuiltinEventEngine\n\ttester        *testing.T\n\tnetwork, addr string\n\taction        bool\n}\n\nfunc (t *testCloseConnectionServer) OnClose(Conn, error) (action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testCloseConnectionServer) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Peek(-1)\n\t_, _ = c.Write(buf)\n\t_, _ = c.Discard(-1)\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\ttime.Sleep(time.Second)\n\t\terr := c.CloseWithCallback(func(_ Conn, err error) error {\n\t\t\tassert.ErrorIsf(t.tester, err, errorx.ErrEngineShutdown, \"should be engine shutdown error\")\n\t\t\treturn nil\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t})\n\tassert.NoError(t.tester, err)\n\treturn\n}\n\nfunc (t *testCloseConnectionServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = time.Millisecond * 100\n\tif !t.action {\n\t\tt.action = true\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tdata := []byte(\"Hello World!\")\n\t\t\t_, _ = conn.Write(data)\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\t// waiting the engine shutdown.\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.Error(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc testCloseConnection(t *testing.T, network, addr string) {\n\tevents := &testCloseConnectionServer{tester: t, network: network, addr: addr}\n\terr := Run(events, network+\"://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestServerOptionsCheck(t *testing.T) {\n\terr := Run(&BuiltinEventEngine{}, \"tcp://:3500\", WithNumEventLoop(10001), WithLockOSThread(true))\n\tassert.ErrorIs(t, err, errorx.ErrTooManyEventLoopThreads, \"error returned with LockOSThread option\")\n}\n\nfunc TestStopServer(t *testing.T) {\n\ttestStop(t, \"tcp\", \":9997\")\n}\n\ntype testStopServer struct {\n\t*BuiltinEventEngine\n\ttester                   *testing.T\n\tnetwork, addr, protoAddr string\n\teng                      Engine\n\taction                   bool\n}\n\nfunc (t *testStopServer) OnBoot(eng Engine) (action Action) {\n\tt.eng = eng\n\treturn\n}\n\nfunc (t *testStopServer) OnClose(Conn, error) (action Action) {\n\tlogging.Debugf(\"closing connection...\")\n\treturn\n}\n\nfunc (t *testStopServer) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Peek(-1)\n\t_, _ = c.Write(buf)\n\t_, _ = c.Discard(-1)\n\treturn\n}\n\nfunc (t *testStopServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = time.Millisecond * 100\n\tif !t.action {\n\t\tt.action = true\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close() //nolint:errcheck\n\t\t\tdata := []byte(\"Hello World!\")\n\t\t\t_, _ = conn.Write(data)\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.NoError(t.tester, err)\n\n\t\t\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\tlogging.Debugf(\"stop engine...\", t.eng.Stop(ctx))\n\t\t\t})\n\t\t\tassert.NoError(t.tester, err)\n\n\t\t\t// waiting the engine shutdown.\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.Error(t.tester, err)\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t\treturn\n\t}\n\treturn\n}\n\nfunc testStop(t *testing.T, network, addr string) {\n\tevents := &testStopServer{tester: t, network: network, addr: addr, protoAddr: network + \"://\" + addr}\n\terr := Run(events, events.protoAddr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestEngineStop(t *testing.T) {\n\ttestEngineStop(t, \"tcp\", \":9998\")\n}\n\ntype testStopEngine struct {\n\t*BuiltinEventEngine\n\ttester                   *testing.T\n\tnetwork, addr, protoAddr string\n\teng                      Engine\n\tstopIter                 int64\n\tname                     string\n\texchngCount              int64\n}\n\nfunc (t *testStopEngine) OnBoot(eng Engine) (action Action) {\n\tt.eng = eng\n\treturn\n}\n\nfunc (t *testStopEngine) OnClose(Conn, error) (action Action) {\n\tlogging.Debugf(\"closing connection...\")\n\treturn\n}\n\nfunc (t *testStopEngine) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Peek(-1)\n\t_, _ = c.Write(buf)\n\t_, _ = c.Discard(-1)\n\tatomic.AddInt64(&t.exchngCount, 1)\n\treturn\n}\n\nfunc (t *testStopEngine) OnTick() (delay time.Duration, action Action) {\n\tdelay = time.Millisecond * 100\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\tconn, err := net.Dial(t.network, t.addr)\n\t\tassert.NoError(t.tester, err)\n\t\tdefer conn.Close() //nolint:errcheck\n\t\tdata := []byte(\"Hello World! \" + t.name)\n\t\t_, _ = conn.Write(data)\n\t\t_, err = conn.Read(data)\n\t\tassert.NoError(t.tester, err)\n\n\t\titer := atomic.LoadInt64(&t.stopIter)\n\t\tif iter <= 0 {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\n\t\t\tdefer cancel()\n\t\t\tlogging.Debugf(\"stop engine...\", t.eng.Stop(ctx))\n\t\t\t// waiting the engine shutdown.\n\t\t\t_, err = conn.Read(data)\n\t\t\tassert.Error(t.tester, err)\n\t\t}\n\t\tatomic.AddInt64(&t.stopIter, -1)\n\t})\n\tassert.NoError(t.tester, err)\n\treturn\n}\n\nfunc testEngineStop(t *testing.T, network, addr string) {\n\tevents1 := &testStopEngine{tester: t, network: network, addr: addr, protoAddr: network + \"://\" + addr, name: \"1\", stopIter: 2}\n\tevents2 := &testStopEngine{tester: t, network: network, addr: addr, protoAddr: network + \"://\" + addr, name: \"2\", stopIter: 5}\n\n\tresult1 := make(chan error, 1)\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\terr := Run(events1, events1.protoAddr, WithTicker(true), WithReuseAddr(true), WithReusePort(true))\n\t\tresult1 <- err\n\t})\n\trequire.NoError(t, err)\n\t// ensure the first handler processes before starting the next since the delay per tick is 100ms\n\ttime.Sleep(150 * time.Millisecond)\n\tresult2 := make(chan error, 1)\n\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\terr := Run(events2, events2.protoAddr, WithTicker(true), WithReuseAddr(true), WithReusePort(true))\n\t\tresult2 <- err\n\t})\n\trequire.NoError(t, err)\n\n\terr = <-result1\n\trequire.NoError(t, err)\n\terr = <-result2\n\trequire.NoError(t, err)\n\t// make sure that each handler processed at least 1\n\trequire.Greater(t, events1.exchngCount, int64(0))\n\trequire.Greater(t, events2.exchngCount, int64(0))\n\trequire.Equal(t, int64(2+1+5+1), events1.exchngCount+events2.exchngCount)\n\t// stop an already stopped engine\n\trequire.Equal(t, errorx.ErrEngineInShutdown, events1.eng.Stop(context.Background()))\n}\n\n// Test should not panic when we wake up server_closed conn.\nfunc TestClosedWakeUp(t *testing.T) {\n\tevents := &testClosedWakeUpServer{\n\t\ttester:             t,\n\t\tBuiltinEventEngine: &BuiltinEventEngine{}, network: \"tcp\", addr: \":9999\", protoAddr: \"tcp://:9999\",\n\t\tclientClosed: make(chan struct{}),\n\t\tserverClosed: make(chan struct{}),\n\t\twakeup:       make(chan struct{}),\n\t}\n\n\terr := Run(events, events.protoAddr)\n\tassert.NoError(t, err)\n}\n\ntype testClosedWakeUpServer struct {\n\t*BuiltinEventEngine\n\ttester                   *testing.T\n\tnetwork, addr, protoAddr string\n\n\twakeup       chan struct{}\n\tserverClosed chan struct{}\n\tclientClosed chan struct{}\n}\n\nfunc (s *testClosedWakeUpServer) OnBoot(eng Engine) (action Action) {\n\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\tc, err := net.Dial(s.network, s.addr)\n\t\tassert.NoError(s.tester, err)\n\n\t\t_, err = c.Write([]byte(\"hello\"))\n\t\tassert.NoError(s.tester, err)\n\n\t\t<-s.wakeup\n\t\t_, err = c.Write([]byte(\"hello again\"))\n\t\tassert.NoError(s.tester, err)\n\n\t\tclose(s.clientClosed)\n\t\t<-s.serverClosed\n\n\t\tlogging.Debugf(\"stop engine...\", eng.Stop(context.TODO()))\n\t})\n\tassert.NoError(s.tester, err)\n\n\treturn None\n}\n\nfunc (s *testClosedWakeUpServer) OnTraffic(c Conn) Action {\n\tassert.NotNil(s.tester, c.RemoteAddr())\n\n\tselect {\n\tcase <-s.wakeup:\n\tdefault:\n\t\tclose(s.wakeup)\n\t}\n\n\terr := goPool.DefaultWorkerPool.Submit(func() { assert.NoError(s.tester, c.Wake(nil)) })\n\tassert.NoError(s.tester, err)\n\terr = goPool.DefaultWorkerPool.Submit(func() { assert.NoError(s.tester, c.Close()) })\n\tassert.NoError(s.tester, err)\n\n\t<-s.clientClosed\n\n\t_, _ = c.Write([]byte(\"answer\"))\n\treturn None\n}\n\nfunc (s *testClosedWakeUpServer) OnClose(Conn, error) (action Action) {\n\tselect {\n\tcase <-s.serverClosed:\n\tdefault:\n\t\tclose(s.serverClosed)\n\t}\n\treturn\n}\n\ntype testMultiInstLoggerRaceServer struct {\n\t*BuiltinEventEngine\n}\n\nfunc (t *testMultiInstLoggerRaceServer) OnBoot(_ Engine) (action Action) {\n\treturn Shutdown\n}\n\nfunc TestMultiInstLoggerRace(t *testing.T) {\n\tcurrentLogger, currentFlusher := logging.GetDefaultLogger(), logging.GetDefaultFlusher()\n\tt.Cleanup(func() {\n\t\tlogging.SetDefaultLoggerAndFlusher(currentLogger, currentFlusher) // restore\n\t})\n\n\tlogger1, _ := zap.NewDevelopment()\n\tevents1 := new(testMultiInstLoggerRaceServer)\n\tg := errgroup.Group{}\n\tg.Go(func() error {\n\t\terr := Run(events1, \"tulip://howdy\", WithLogger(logger1.Sugar()))\n\t\treturn err\n\t})\n\n\tlogger2, _ := zap.NewDevelopment()\n\tevents2 := new(testMultiInstLoggerRaceServer)\n\tg.Go(func() error {\n\t\terr := Run(events2, \"tulip://howdy\", WithLogger(logger2.Sugar()))\n\t\treturn err\n\t})\n\n\tassert.ErrorIs(t, g.Wait(), errorx.ErrUnsupportedProtocol)\n}\n\ntype testDisconnectedAsyncWriteServer struct {\n\tBuiltinEventEngine\n\ttester                *testing.T\n\taddr                  string\n\twritev, clientStarted bool\n\texit                  atomic.Bool\n}\n\nfunc (t *testDisconnectedAsyncWriteServer) OnTraffic(c Conn) Action {\n\t_, err := c.Next(0)\n\tassert.NoErrorf(t.tester, err, \"c.Next error: %v\", err)\n\n\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\tfor range time.Tick(100 * time.Millisecond) {\n\t\t\tif t.exit.Load() {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tvar err error\n\t\t\tif t.writev {\n\t\t\t\terr = c.AsyncWritev([][]byte{[]byte(\"hello\"), []byte(\"hello\")}, func(_ Conn, err error) error {\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.ErrorIsf(t.tester, err, net.ErrClosed, \"expected error: %v, but got: %v\", net.ErrClosed, err)\n\t\t\t\t\tt.exit.Store(true)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\terr = c.AsyncWrite([]byte(\"hello\"), func(_ Conn, err error) error {\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.ErrorIsf(t.tester, err, net.ErrClosed, \"expected error: %v, but got: %v\", net.ErrClosed, err)\n\t\t\t\t\tt.exit.Store(true)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t})\n\tassert.NoError(t.tester, err)\n\n\treturn None\n}\n\nfunc (t *testDisconnectedAsyncWriteServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = 500 * time.Millisecond\n\n\tif t.exit.Load() {\n\t\taction = Shutdown\n\t\treturn\n\t}\n\n\tif !t.clientStarted {\n\t\tt.clientStarted = true\n\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tc, err := net.Dial(\"tcp\", t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\t_, err = c.Write([]byte(\"hello\"))\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tassert.NoError(t.tester, c.Close())\n\t\t})\n\t\tassert.NoError(t.tester, err)\n\t}\n\treturn\n}\n\nfunc TestDisconnectedAsyncWrite(t *testing.T) {\n\tt.Run(\"async-write\", func(t *testing.T) {\n\t\tevents := &testDisconnectedAsyncWriteServer{tester: t, addr: \":10000\"}\n\t\terr := Run(events, \"tcp://:10000\", WithTicker(true))\n\t\tassert.NoError(t, err)\n\t})\n\tt.Run(\"async-writev\", func(t *testing.T) {\n\t\tevents := &testDisconnectedAsyncWriteServer{tester: t, addr: \":10001\", writev: true}\n\t\terr := Run(events, \"tcp://:10001\", WithTicker(true))\n\t\tassert.NoError(t, err)\n\t})\n}\n\nvar errIncompletePacket = errors.New(\"incomplete packet\")\n\ntype simServer struct {\n\tBuiltinEventEngine\n\ttester       *testing.T\n\teng          Engine\n\tnetwork      string\n\taddr         string\n\tmulticore    bool\n\tnclients     int\n\tpacketSize   int\n\tbatchWrite   int\n\tbatchRead    int\n\tstarted      int32\n\tconnected    int32\n\tdisconnected int32\n}\n\nfunc (s *simServer) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\treturn\n}\n\nfunc (s *simServer) OnOpen(c Conn) (out []byte, action Action) {\n\tc.SetContext(&testCodec{})\n\tatomic.AddInt32(&s.connected, 1)\n\tout = []byte(\"andypan\\r\\n\")\n\tassert.NotNil(s.tester, c.LocalAddr(), \"nil local addr\")\n\tassert.NotNil(s.tester, c.RemoteAddr(), \"nil remote addr\")\n\treturn\n}\n\nfunc (s *simServer) OnClose(_ Conn, err error) (action Action) {\n\tif err != nil {\n\t\tlogging.Debugf(\"error occurred on closed, %v\\n\", err)\n\t}\n\n\tatomic.AddInt32(&s.disconnected, 1)\n\tif atomic.LoadInt32(&s.connected) == atomic.LoadInt32(&s.disconnected) &&\n\t\tatomic.LoadInt32(&s.disconnected) == int32(s.nclients) {\n\t\taction = Shutdown\n\t}\n\n\treturn\n}\n\nfunc (s *simServer) OnTraffic(c Conn) (action Action) {\n\tcodec := c.Context().(*testCodec)\n\tvar packets [][]byte\n\tfor i := 0; i < s.batchRead; i++ {\n\t\tdata, err := codec.Decode(c)\n\t\tif errors.Is(err, errIncompletePacket) {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tlogging.Errorf(\"invalid packet: %v\", err)\n\t\t\treturn Close\n\t\t}\n\t\tpacket, _ := codec.Encode(data)\n\t\tpackets = append(packets, packet)\n\t}\n\tif n := len(packets); n > 1 {\n\t\t_, _ = c.Writev(packets)\n\t} else if n == 1 {\n\t\t_, _ = c.Write(packets[0])\n\t}\n\tif len(packets) == s.batchRead && c.InboundBuffered() > 0 {\n\t\terr := c.Wake(nil) // wake up the connection manually to avoid missing the leftover data\n\t\tassert.NoError(s.tester, err)\n\t}\n\treturn\n}\n\nfunc (s *simServer) OnTick() (delay time.Duration, action Action) {\n\tif atomic.CompareAndSwapInt32(&s.started, 0, 1) {\n\t\tfor i := 0; i < s.nclients; i++ {\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\trunSimClient(s.tester, s.network, s.addr, s.packetSize, s.batchWrite)\n\t\t\t})\n\t\t\tassert.NoError(s.tester, err)\n\t\t}\n\t}\n\tdelay = 100 * time.Millisecond\n\treturn\n}\n\n// All current protocols.\nconst (\n\tmagicNumber     = 1314\n\tmagicNumberSize = 2\n\tbodySize        = 4\n)\n\nvar magicNumberBytes []byte\n\nfunc init() {\n\tmagicNumberBytes = make([]byte, magicNumberSize)\n\tbinary.BigEndian.PutUint16(magicNumberBytes, uint16(magicNumber))\n}\n\n// Protocol format:\n//\n// * 0           2                       6\n// * +-----------+-----------------------+\n// * |   magic   |       body len        |\n// * +-----------+-----------+-----------+\n// * |                                   |\n// * +                                   +\n// * |           body bytes              |\n// * +                                   +\n// * |            ... ...                |\n// * +-----------------------------------+.\ntype testCodec struct{}\n\nfunc (codec testCodec) Encode(buf []byte) ([]byte, error) {\n\tbodyOffset := magicNumberSize + bodySize\n\tmsgLen := bodyOffset + len(buf)\n\n\tdata := make([]byte, msgLen)\n\tcopy(data, magicNumberBytes)\n\n\tbinary.BigEndian.PutUint32(data[magicNumberSize:bodyOffset], uint32(len(buf)))\n\tcopy(data[bodyOffset:msgLen], buf)\n\treturn data, nil\n}\n\nfunc (codec testCodec) Decode(c Conn) ([]byte, error) {\n\tbodyOffset := magicNumberSize + bodySize\n\tbuf, err := c.Peek(bodyOffset)\n\tif err != nil {\n\t\tif errors.Is(err, io.ErrShortBuffer) {\n\t\t\terr = errIncompletePacket\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tif !bytes.Equal(magicNumberBytes, buf[:magicNumberSize]) {\n\t\treturn nil, errors.New(\"invalid magic number\")\n\t}\n\n\tbodyLen := binary.BigEndian.Uint32(buf[magicNumberSize:bodyOffset])\n\tmsgLen := bodyOffset + int(bodyLen)\n\tbuf, err = c.Peek(msgLen)\n\tif err != nil {\n\t\tif errors.Is(err, io.ErrShortBuffer) {\n\t\t\terr = errIncompletePacket\n\t\t}\n\t\treturn nil, err\n\t}\n\tbody := make([]byte, bodyLen)\n\tcopy(body, buf[bodyOffset:msgLen])\n\t_, _ = c.Discard(msgLen)\n\n\treturn body, nil\n}\n\nfunc (codec testCodec) Unpack(buf []byte) ([]byte, error) {\n\tbodyOffset := magicNumberSize + bodySize\n\tif len(buf) < bodyOffset {\n\t\treturn nil, errIncompletePacket\n\t}\n\n\tif !bytes.Equal(magicNumberBytes, buf[:magicNumberSize]) {\n\t\treturn nil, errors.New(\"invalid magic number\")\n\t}\n\n\tbodyLen := binary.BigEndian.Uint32(buf[magicNumberSize:bodyOffset])\n\tmsgLen := bodyOffset + int(bodyLen)\n\tif len(buf) < msgLen {\n\t\treturn nil, errIncompletePacket\n\t}\n\n\treturn buf[bodyOffset:msgLen], nil\n}\n\nfunc TestSimServer(t *testing.T) {\n\tt.Run(\"packet-size=64,batch=200\", func(t *testing.T) {\n\t\trunSimServer(t, \":7200\", true, 10, 64, 200, -1)\n\t})\n\tt.Run(\"packet-size=128,batch=100\", func(t *testing.T) {\n\t\trunSimServer(t, \":7201\", false, 10, 128, 100, 10)\n\t})\n\tt.Run(\"packet-size=256,batch=50\", func(t *testing.T) {\n\t\trunSimServer(t, \":7202\", true, 10, 256, 50, -1)\n\t})\n\tt.Run(\"packet-size=512,batch=30\", func(t *testing.T) {\n\t\trunSimServer(t, \":7203\", false, 10, 512, 30, 3)\n\t})\n\tt.Run(\"packet-size=1024,batch=20\", func(t *testing.T) {\n\t\trunSimServer(t, \":7204\", true, 10, 1024, 20, -1)\n\t})\n\tt.Run(\"packet-size=64*1024,batch=10\", func(t *testing.T) {\n\t\trunSimServer(t, \":7205\", false, 10, 64*1024, 10, 1)\n\t})\n\tt.Run(\"packet-size=128*1024,batch=5\", func(t *testing.T) {\n\t\trunSimServer(t, \":7206\", true, 10, 128*1024, 5, -1)\n\t})\n\tt.Run(\"packet-size=512*1024,batch=3\", func(t *testing.T) {\n\t\trunSimServer(t, \":7207\", false, 10, 512*1024, 3, 1)\n\t})\n\tt.Run(\"packet-size=1024*1024,batch=2\", func(t *testing.T) {\n\t\trunSimServer(t, \":7208\", true, 10, 1024*1024, 2, -1)\n\t})\n}\n\nfunc runSimServer(t *testing.T, addr string, et bool, nclients, packetSize, batchWrite, batchRead int) {\n\tts := &simServer{\n\t\ttester:     t,\n\t\tnetwork:    \"tcp\",\n\t\taddr:       addr,\n\t\tmulticore:  true,\n\t\tnclients:   nclients,\n\t\tpacketSize: packetSize,\n\t\tbatchWrite: batchWrite,\n\t\tbatchRead:  batchRead,\n\t}\n\tif batchRead < 0 {\n\t\tts.batchRead = math.MaxInt32 // unlimited read batch\n\t}\n\terr := Run(ts,\n\t\tts.network+\"://\"+ts.addr,\n\t\tWithEdgeTriggeredIO(et),\n\t\tWithMulticore(ts.multicore),\n\t\tWithTicker(true),\n\t\tWithTCPKeepAlive(time.Minute),\n\t\tWithTCPKeepInterval(time.Second*10),\n\t\tWithTCPKeepCount(10),\n\t)\n\tassert.NoError(t, err)\n}\n\nfunc runSimClient(t *testing.T, network, addr string, packetSize, batch int) {\n\tc, err := net.Dial(network, addr)\n\tassert.NoError(t, err)\n\tdefer c.Close() //nolint:errcheck\n\trd := bufio.NewReader(c)\n\tmsg, err := rd.ReadBytes('\\n')\n\tassert.NoError(t, err)\n\tassert.Equal(t, string(msg), \"andypan\\r\\n\", \"bad header\")\n\tvar duration time.Duration\n\tpacketBytes := packetSize * batch\n\tswitch {\n\tcase packetBytes < 16*1024:\n\t\tduration = 2 * time.Second\n\tcase packetBytes < 32*1024:\n\t\tduration = 3 * time.Second\n\tcase packetBytes < 480*1024:\n\t\tduration = 4 * time.Second\n\tdefault:\n\t\tduration = 5 * time.Second\n\t}\n\tlogging.Debugf(\"test duration: %v\", duration)\n\tstart := time.Now()\n\tfor time.Since(start) < duration {\n\t\tbatchSendAndRecv(t, c, rd, packetSize, batch)\n\t}\n}\n\nfunc batchSendAndRecv(t *testing.T, c net.Conn, rd *bufio.Reader, packetSize, batch int) {\n\tcodec := testCodec{}\n\tvar (\n\t\trequests  [][]byte\n\t\tbuf       []byte\n\t\tpacketLen int\n\t)\n\tfor i := 0; i < batch; i++ {\n\t\treq := make([]byte, packetSize)\n\t\t_, err := crand.Read(req)\n\t\tassert.NoError(t, err)\n\t\trequests = append(requests, req)\n\t\tpacket, _ := codec.Encode(req)\n\t\tpacketLen = len(packet)\n\t\tbuf = append(buf, packet...)\n\t}\n\t_, err := c.Write(buf)\n\tassert.NoError(t, err)\n\trespPacket := make([]byte, batch*packetLen)\n\t_, err = io.ReadFull(rd, respPacket)\n\tassert.NoError(t, err)\n\tfor i, req := range requests {\n\t\trsp, err := codec.Unpack(respPacket[i*packetLen:])\n\t\tassert.NoError(t, err)\n\t\tassert.Equalf(t, req, rsp, \"request and response mismatch, packet size: %d, batch: %d, round: %d\",\n\t\t\tpacketSize, batch, i)\n\t}\n}\n\ntype testUDPSendtoServer struct {\n\tBuiltinEventEngine\n\n\taddr string\n\n\ttester           *testing.T\n\tstartClientsOnce sync.Once\n\tbroadcastMsg     []byte\n\n\tmu          sync.Mutex\n\tclientAddrs []net.Addr\n\tclientCount int\n}\n\nfunc (t *testUDPSendtoServer) OnBoot(_ Engine) (action Action) {\n\tt.broadcastMsg = []byte(\"Broadcasting message\")\n\tt.clientCount = 10\n\treturn\n}\n\nfunc (t *testUDPSendtoServer) OnTraffic(c Conn) Action {\n\tmsg, err := c.Next(-1)\n\tassert.NoErrorf(t.tester, err, \"c.Next error: %v\", err)\n\tassert.NotZero(t.tester, msg, \"c.Next should not return empty buffer\")\n\n\tt.mu.Lock()\n\tt.clientAddrs = append(t.clientAddrs, c.RemoteAddr())\n\tif len(t.clientAddrs) == t.clientCount {\n\t\tfor _, addr := range t.clientAddrs {\n\t\t\tn, err := c.SendTo(t.broadcastMsg, addr)\n\t\t\tassert.NoError(t.tester, err, \"c.SendTo error\")\n\t\t\tassert.EqualValuesf(t.tester, len(t.broadcastMsg), n,\n\t\t\t\t\"c.SendTo should send %d bytes, but sent %d bytes\", len(t.broadcastMsg), n)\n\t\t}\n\t}\n\tt.mu.Unlock()\n\n\treturn None\n}\n\nfunc (t *testUDPSendtoServer) OnTick() (delay time.Duration, action Action) {\n\tt.startClientsOnce.Do(func() {\n\t\tfor i := 0; i < t.clientCount; i++ {\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tc, err := net.Dial(\"udp\", t.addr)\n\t\t\t\tassert.NoError(t.tester, err)\n\t\t\t\tdefer c.Close() //nolint:errcheck\n\t\t\t\t_, err = c.Write([]byte(\"Hello World!\"))\n\t\t\t\tassert.NoError(t.tester, err)\n\t\t\t\tmsg := make([]byte, len(t.broadcastMsg))\n\t\t\t\t_, err = c.Read(msg)\n\t\t\t\tassert.NoError(t.tester, err)\n\t\t\t\tassert.EqualValuesf(t.tester, msg, t.broadcastMsg,\n\t\t\t\t\t\"broadcast message mismatch, expected: %s, got: %s\", t.broadcastMsg, msg)\n\t\t\t\tt.mu.Lock()\n\t\t\t\tt.clientCount--\n\t\t\t\tt.mu.Unlock()\n\t\t\t})\n\t\t\tassert.NoError(t.tester, err)\n\t\t}\n\t})\n\n\tt.mu.Lock()\n\tif t.clientCount == 0 {\n\t\taction = Shutdown\n\t}\n\tt.mu.Unlock()\n\n\tdelay = time.Millisecond * 100\n\n\treturn\n}\n\nfunc TestUDPSendtoServer(t *testing.T) {\n\t// The listening address without an explicit IP works on Linux and Windows\n\t// while sendto fails on macOS with EINVAL (invalid argument) that might\n\t// also occur on more BSD systems: FreeBSD, OpenBSD, NetBSD, and DragonflyBSD.\n\t// To pass this test on all platforms, specify an explicit IP address here.\n\t// addr := \":10000\"\n\taddr := \"127.0.0.1:10000\"\n\tevents := &testUDPSendtoServer{tester: t, addr: addr}\n\terr := Run(events, \"udp://\"+addr, WithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc startUDPEchoServer(t *testing.T, c *net.UDPConn) {\n\tdefer c.Close() //nolint:errcheck\n\n\tbuf := make([]byte, datagramLen)\n\tfor {\n\t\tnr, remote, err := c.ReadFromUDP(buf)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\t\tif nr > 0 {\n\t\t\tnw, err := c.WriteToUDP(buf[:nr], remote)\n\t\t\tassert.EqualValuesf(t, nr, nw, \"UDP echo server should send %d bytes, but sent %d bytes\", nr, nw)\n\t\t\tassert.NoErrorf(t, err, \"error occurred on UDP echo server %v write to %s, %v\",\n\t\t\t\tremote, c.LocalAddr().String(), err)\n\t\t}\n\t}\n}\n\nfunc startStreamEchoServer(t *testing.T, ln net.Listener) {\n\tdefer func() {\n\t\tln.Close() //nolint:errcheck\n\t\tif strings.HasPrefix(ln.Addr().Network(), \"unix\") {\n\t\t\tos.Remove(ln.Addr().String()) //nolint:errcheck\n\t\t}\n\t}()\n\n\tfor {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tdefer c.Close() //nolint:errcheck\n\n\t\t\tbuf := make([]byte, streamLen)\n\t\t\tfor {\n\t\t\t\tnr, err := c.Read(buf)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tb := buf[:nr]\n\t\t\t\tfor len(b) > 0 {\n\t\t\t\t\tnw, err := c.Write(b)\n\t\t\t\t\tassert.NoErrorf(t, err, \"error occurred on TCP echo server %s write to %s, %v\",\n\t\t\t\t\t\tln.Addr().String(), c.RemoteAddr().String(), err)\n\t\t\t\t\tb = b[nw:]\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\tassert.NoError(t, err, \"error occurred on TCP echo server %s submit task, %v\",\n\t\t\tln.Addr().String(), err)\n\t}\n}\n\ntype streamProxyServer struct {\n\tBuiltinEventEngine\n\n\tengine    Engine\n\teventLoop EventLoop\n\n\tstallCh chan struct{}\n\n\ttester       *testing.T\n\tListenerNet  string\n\tListenerAddr string\n\n\tconnected          int32\n\tdisconnected       int32\n\tbackendEstablished int32\n\n\tbackendServerPoolMu sync.Mutex\n\tbackendServerPool   []string\n\n\tstartClintOnce sync.Once\n\n\tbackendServers []string\n\tpacketSize     int\n}\n\nfunc (p *streamProxyServer) OnShutdown(eng Engine) {\n\tp.engine = eng\n\tp.tester.Logf(\"proxy server %s shutdown!\", p.ListenerAddr)\n}\n\nfunc (p *streamProxyServer) OnOpen(c Conn) (out []byte, action Action) {\n\tif c.LocalAddr().String() == p.ListenerAddr { // it's a server connection\n\t\tout = []byte(\"andypan\\r\\n\")\n\t\tp.backendServerPoolMu.Lock()\n\t\tbackendServer := p.backendServerPool[len(p.backendServerPool)-1]\n\t\tp.backendServerPool = p.backendServerPool[:len(p.backendServerPool)-1]\n\t\tp.backendServerPoolMu.Unlock()\n\n\t\t// Test the error handling of the methods of EventLoop.\n\t\t_, err := c.EventLoop().Register(context.Background(), nil)\n\t\tassert.ErrorIsf(p.tester, err, errorx.ErrInvalidNetworkAddress, \"Expected error: %v, but got: %v\",\n\t\t\terrorx.ErrInvalidNetworkAddress, err)\n\t\t_, err = c.EventLoop().Enroll(context.Background(), nil)\n\t\tassert.ErrorIsf(p.tester, err, errorx.ErrInvalidNetConn, \"Expected error: %v, but got: %v\",\n\t\t\terrorx.ErrInvalidNetConn, err)\n\t\terr = c.EventLoop().Execute(context.Background(), nil)\n\t\tassert.ErrorIsf(p.tester, err, errorx.ErrNilRunnable, \"Expected error: %v, but got: %v\",\n\t\t\terrorx.ErrNilRunnable, err)\n\t\terr = c.EventLoop().Schedule(context.Background(), nil, time.Millisecond)\n\t\tassert.ErrorIsf(p.tester, err, errorx.ErrUnsupportedOp, \"Expected error: %v, but got: %v\",\n\t\t\terrorx.ErrUnsupportedOp, err)\n\n\t\tnetwork, addr, err := parseProtoAddr(backendServer)\n\t\tassert.NoError(p.tester, err, \"parseProtoAddr error\")\n\t\tvar address net.Addr\n\t\tswitch {\n\t\tcase strings.HasPrefix(network, \"tcp\"):\n\t\t\taddress, err = net.ResolveTCPAddr(network, addr)\n\t\tcase strings.HasPrefix(network, \"udp\"):\n\t\t\taddress, err = net.ResolveUDPAddr(network, addr)\n\t\tcase strings.HasPrefix(network, \"unix\"):\n\t\t\taddress, err = net.ResolveUnixAddr(network, addr)\n\t\tdefault:\n\t\t\tassert.Failf(p.tester, \"unsupported protocol\", \"unsupported protocol: %s\", network)\n\t\t}\n\t\tassert.NoError(p.tester, err, \"ResolveNetAddr error\")\n\t\tctx := NewContext(context.Background(), c)\n\t\tresCh, err := c.EventLoop().Register(ctx, address)\n\t\tassert.NoError(p.tester, err, \"Register connection error\")\n\t\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\tres := <-resCh\n\t\t\tassert.NoError(p.tester, res.Err, \"Register connection error\")\n\t\t\tassert.NotNil(p.tester, res.Conn, \"Register connection nil\")\n\t\t})\n\t\tassert.NoError(p.tester, err, \"Submit task error\")\n\t\tatomic.AddInt32(&p.connected, 1)\n\t} else { // it's a client connection\n\t\t// Store the client connection in the context of the server connection.\n\t\tserverConn, ok := c.Context().(Conn)\n\t\tassert.True(p.tester, ok, \"context is not Conn\")\n\t\tassert.NotNil(p.tester, serverConn, \"context is not Conn\")\n\t\tserverConn.SetContext(c)\n\n\t\terr := c.EventLoop().Execute(NewContext(context.Background(), c.LocalAddr()),\n\t\t\tRunnableFunc(func(ctx context.Context) error {\n\t\t\t\tp.tester.Logf(\"backend connection %v established\", FromContext(ctx))\n\t\t\t\treturn nil\n\t\t\t}))\n\t\tassert.NoError(p.tester, err, \"Execute task error\")\n\n\t\tif int(atomic.AddInt32(&p.backendEstablished, 1)) == len(p.backendServers) {\n\t\t\t// Unlock the clients to start sending data.\n\t\t\tclose(p.stallCh)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *streamProxyServer) OnClose(c Conn, err error) (action Action) {\n\tconnType := \"client\"\n\tif c.LocalAddr().String() == p.ListenerAddr {\n\t\tconnType = \"server\"\n\t}\n\tlogging.Debugf(\"closing %s connection: %s\", connType, c.LocalAddr().String())\n\n\tif err != nil {\n\t\tlogging.Debugf(\"error occurred on server connnection closing, %v\", err)\n\t}\n\n\tif c.LocalAddr().String() == p.ListenerAddr { // it's a server connection\n\t\tpc, ok := c.Context().(Conn)\n\t\tassert.True(p.tester, ok, \"server context connection is nil\")\n\t\tassert.NotNil(p.tester, pc, \"server context connection is nil\")\n\t\terr := c.EventLoop().Close(pc) // close the corresponding client connection\n\t\tassert.NoError(p.tester, err, \"Close client connection error\")\n\n\t\tif disconnected := atomic.AddInt32(&p.disconnected, 1); int(disconnected) == len(p.backendServers) &&\n\t\t\tdisconnected == atomic.LoadInt32(&p.connected) {\n\t\t\tp.eventLoop = c.EventLoop()\n\t\t\taction = Shutdown\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (p *streamProxyServer) OnTraffic(c Conn) Action {\n\tconnType := \"client\"\n\tif c.LocalAddr().String() == p.ListenerAddr {\n\t\tconnType = \"server\"\n\t}\n\n\tpc, ok := c.Context().(Conn)\n\tif !ok {\n\t\t// The backend connection is not established yet, retry later.\n\t\tassert.NoError(p.tester, c.Wake(nil), \"Wake connection error\")\n\t\treturn None\n\t}\n\n\t_, err := c.WriteTo(pc)\n\tassert.NoErrorf(p.tester, err, \"%s: Write error from %s to %s\",\n\t\tconnType, c.LocalAddr().String(), pc.RemoteAddr().String())\n\treturn None\n}\n\nfunc (p *streamProxyServer) OnTick() (time.Duration, Action) {\n\tp.startClintOnce.Do(func() {\n\t\tfor i := 0; i < len(p.backendServers); i++ {\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tstartClient(p.tester, p.ListenerNet, p.ListenerAddr,\n\t\t\t\t\ttrue, false, p.packetSize, p.stallCh)\n\t\t\t})\n\t\t\tassert.NoErrorf(p.tester, err, \"Submit backend server %s error: %v\", p.ListenerAddr, err)\n\t\t}\n\t})\n\treturn time.Millisecond * 200, None\n}\n\nfunc TestStreamProxyServer(t *testing.T) {\n\tt.Run(\"tcp-proxy-server\", func(t *testing.T) {\n\t\taddr := \"tcp://127.0.0.1:10000\"\n\t\tbackendServers := []string{\n\t\t\t\"tcp://127.0.0.1:10001\",\n\t\t\t\"tcp://127.0.0.1:10002\",\n\t\t\t\"tcp://127.0.0.1:10003\",\n\t\t\t\"tcp://127.0.0.1:10004\",\n\t\t\t\"tcp://127.0.0.1:10005\",\n\t\t\t\"tcp://127.0.0.1:10006\",\n\t\t\t\"tcp://127.0.0.1:10007\",\n\t\t\t\"tcp://127.0.0.1:10008\",\n\t\t\t\"tcp://127.0.0.1:10009\",\n\t\t\t\"tcp://127.0.0.1:10010\",\n\t\t}\n\t\tt.Run(\"1-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, false)\n\t\t})\n\t\tt.Run(\"1-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, true)\n\t\t})\n\t\tt.Run(\"N-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, false)\n\t\t})\n\t\tt.Run(\"N-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, true)\n\t\t})\n\t})\n\n\tt.Run(\"unix-proxy-server\", func(t *testing.T) {\n\t\taddr := \"unix://unix-proxy-server.sock\"\n\t\tbackendServers := []string{\n\t\t\t\"unix://unix-proxy-server-1.sock\",\n\t\t\t\"unix://unix-proxy-server-2.sock\",\n\t\t\t\"unix://unix-proxy-server-3.sock\",\n\t\t\t\"unix://unix-proxy-server-4.sock\",\n\t\t\t\"unix://unix-proxy-server-5.sock\",\n\t\t\t\"unix://unix-proxy-server-6.sock\",\n\t\t\t\"unix://unix-proxy-server-7.sock\",\n\t\t\t\"unix://unix-proxy-server-8.sock\",\n\t\t\t\"unix://unix-proxy-server-9.sock\",\n\t\t\t\"unix://unix-proxy-server-10.sock\",\n\t\t}\n\t\tt.Run(\"1-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, false)\n\t\t})\n\t\tt.Run(\"1-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, true)\n\t\t})\n\t\tt.Run(\"N-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, false)\n\t\t})\n\t\tt.Run(\"N-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, true)\n\t\t})\n\t})\n\n\tt.Run(\"udp-proxy-server\", func(t *testing.T) {\n\t\taddr := \"tcp://127.0.0.1:11000\"\n\t\tbackendServers := []string{\n\t\t\t\"udp://127.0.0.1:11001\",\n\t\t\t\"udp://127.0.0.1:11002\",\n\t\t\t\"udp://127.0.0.1:11003\",\n\t\t\t\"udp://127.0.0.1:11004\",\n\t\t\t\"udp://127.0.0.1:11005\",\n\t\t\t\"udp://127.0.0.1:11006\",\n\t\t\t\"udp://127.0.0.1:11007\",\n\t\t\t\"udp://127.0.0.1:11008\",\n\t\t\t\"udp://127.0.0.1:11009\",\n\t\t\t\"udp://127.0.0.1:11010\",\n\t\t}\n\t\tt.Run(\"1-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, false)\n\t\t})\n\t\tt.Run(\"1-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, false, true)\n\t\t})\n\t\tt.Run(\"N-loop-LT\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, false)\n\t\t})\n\t\tt.Run(\"N-loop-ET\", func(t *testing.T) {\n\t\t\ttestStreamProxyServer(t, addr, backendServers, true, true)\n\t\t})\n\t})\n}\n\nfunc testStreamProxyServer(t *testing.T, addr string, backendServers []string, multicore, et bool) {\n\tnetwork, address, err := parseProtoAddr(addr)\n\trequire.NoError(t, err, \"parseProtoAddr error\")\n\n\tsrv := streamProxyServer{\n\t\ttester:            t,\n\t\tstallCh:           make(chan struct{}),\n\t\tListenerNet:       network,\n\t\tListenerAddr:      address,\n\t\tbackendServers:    backendServers,\n\t\tbackendServerPool: backendServers,\n\t}\n\n\tvar (\n\t\tbackends   errgroup.Group\n\t\tnetServers []io.Closer\n\t)\n\tfor _, backendServer := range backendServers {\n\t\tnetwork, addr, err := parseProtoAddr(backendServer)\n\t\trequire.NoError(t, err, \"parseProtoAddr error\")\n\t\tif strings.HasPrefix(network, \"udp\") {\n\t\t\tudpAddr, err := net.ResolveUDPAddr(network, addr)\n\t\t\trequire.NoError(t, err, \"ResolveUDPAddr error\")\n\t\t\tc, err := net.ListenUDP(\"udp\", udpAddr)\n\t\t\trequire.NoError(t, err, \"ListenUDP error\")\n\t\t\tbackends.Go(func() error {\n\t\t\t\tstartUDPEchoServer(t, c)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\trequire.NoErrorf(t, err, \"Start backend UDP server %s error: %v\", backendServer, err)\n\t\t\tsrv.packetSize = datagramLen\n\t\t\tnetServers = append(netServers, c)\n\t\t} else {\n\t\t\tln, err := net.Listen(network, addr)\n\t\t\trequire.NoErrorf(t, err, \"Create backend server %s error: %v\", network+\"://\"+addr, err)\n\t\t\tbackends.Go(func() error {\n\t\t\t\tstartStreamEchoServer(t, ln)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\trequire.NoErrorf(t, err, \"Start backend stream server %s error: %v\", backendServer, err)\n\t\t\tsrv.packetSize = streamLen\n\t\t\tnetServers = append(netServers, ln)\n\t\t}\n\t}\n\n\t// Give the backend servers some time to start.\n\ttime.Sleep(time.Second)\n\n\terr = Run(&srv, addr, WithEdgeTriggeredIO(et), WithMulticore(multicore), WithTicker(true))\n\trequire.NoErrorf(t, err, \"Run error: %v\", err)\n\n\t// Test the error handling of the methods of EventLoop after a shutdown.\n\t_, err = srv.engine.Register(context.Background())\n\tassert.ErrorIsf(t, err, errorx.ErrEngineInShutdown, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrEngineInShutdown, err)\n\t_, err = srv.eventLoop.Register(context.Background(), nil)\n\trequire.ErrorIsf(t, err, errorx.ErrEngineInShutdown, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrEngineInShutdown, err)\n\t_, err = srv.eventLoop.Enroll(context.Background(), nil)\n\trequire.ErrorIsf(t, err, errorx.ErrEngineInShutdown, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrEngineInShutdown, err)\n\terr = srv.eventLoop.Execute(context.Background(), nil)\n\trequire.ErrorIsf(t, err, errorx.ErrEngineInShutdown, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrEngineInShutdown, err)\n\terr = srv.eventLoop.Schedule(context.Background(), nil, time.Millisecond)\n\trequire.ErrorIsf(t, err, errorx.ErrUnsupportedOp, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrUnsupportedOp, err)\n\n\tfor _, server := range netServers {\n\t\trequire.NoError(t, server.Close(), \"Close backend server error\")\n\t}\n\n\tbackends.Wait() //nolint:errcheck\n}\n\ntype udpProxyServer struct {\n\tBuiltinEventEngine\n\n\tpacket []byte\n\n\tengine Engine\n\n\ttester *testing.T\n\n\tstallCh chan struct{}\n\n\tListenerNet  string\n\tListenerAddr string\n\n\tactiveClients      int32\n\tbackendEstablished int32\n\tbackendBytes       int32\n\n\tinitBackendPoolOnce sync.Once\n\tbackendServerPoolMu sync.Mutex\n\tbackendServerPool   []Conn\n\n\tstartClientOnce sync.Once\n\tbackendServers  []string\n\tpacketSize      int\n}\n\nfunc (p *udpProxyServer) OnBoot(eng Engine) (action Action) {\n\tp.engine = eng\n\t_, err := eng.Register(context.Background())\n\tassert.ErrorIsf(p.tester, err, errorx.ErrEmptyEngine, \"Expected error: %v, but got: %v\",\n\t\terrorx.ErrEmptyEngine, err)\n\treturn\n}\n\nfunc (p *udpProxyServer) OnShutdown(Engine) {\n\tassert.Zero(p.tester, atomic.LoadInt32(&p.backendBytes)%int32(len(p.packet)),\n\t\t\"backend received bytes should be a multiple of packet size\")\n}\n\nfunc (p *udpProxyServer) OnOpen(c Conn) (out []byte, action Action) {\n\tp.backendServerPoolMu.Lock()\n\tp.backendServerPool = append(p.backendServerPool, c)\n\tp.backendServerPoolMu.Unlock()\n\tif int(atomic.AddInt32(&p.backendEstablished, 1)) == len(p.backendServers) {\n\t\t// Unlock the clients to start sending data.\n\t\tclose(p.stallCh)\n\t}\n\treturn nil, None\n}\n\nfunc (p *udpProxyServer) OnTraffic(c Conn) Action {\n\tif c.LocalAddr().String() == p.ListenerAddr { // it's a server connection\n\t\t// Echo back to the client.\n\t\tbuf, err := c.Next(-1)\n\t\tassert.NoError(p.tester, err, \"Next error\")\n\t\tassert.Greaterf(p.tester, len(buf), 0, \"Next should not return empty buffer\")\n\t\tn, err := c.Write(buf)\n\t\tassert.NoError(p.tester, err, \"Write error\")\n\t\tassert.EqualValuesf(p.tester, n, len(buf), \"Write should send %d bytes, but sent %d bytes\", len(buf), n)\n\n\t\t// Send the packet to a random backend server.\n\t\tp.backendServerPoolMu.Lock()\n\t\tbackendServer := p.backendServerPool[rand.Intn(len(p.backendServerPool))]\n\t\tp.backendServerPoolMu.Unlock()\n\t\terr = backendServer.AsyncWrite(p.packet, nil)\n\t\tassert.NoError(p.tester, err, \"AsyncWrite error\")\n\t} else { // it's a backend connection\n\t\tbuf, err := c.Next(len(p.packet))\n\t\tassert.NoError(p.tester, err, \"Next error\")\n\t\tassert.EqualValuesf(p.tester, buf, p.packet, \"Packet mismatch, expected: %s, got: %s\", p.packet, buf)\n\t\tatomic.AddInt32(&p.backendBytes, int32(len(buf)))\n\t}\n\treturn None\n}\n\nfunc (p *udpProxyServer) OnTick() (delay time.Duration, action Action) {\n\tp.initBackendPoolOnce.Do(func() {\n\t\tfor i, backendServer := range p.backendServers {\n\t\t\tnetwork, addr, err := parseProtoAddr(backendServer)\n\t\t\tassert.NoError(p.tester, err, \"parseProtoAddr error\")\n\t\t\tvar address net.Addr\n\t\t\tswitch {\n\t\t\tcase strings.HasPrefix(network, \"tcp\"):\n\t\t\t\taddress, err = net.ResolveTCPAddr(network, addr)\n\t\t\tcase strings.HasPrefix(network, \"udp\"):\n\t\t\t\taddress, err = net.ResolveUDPAddr(network, addr)\n\t\t\tcase strings.HasPrefix(network, \"unix\"):\n\t\t\t\taddress, err = net.ResolveUnixAddr(network, addr)\n\t\t\tdefault:\n\t\t\t\tassert.Failf(p.tester, \"unsupported protocol\", \"unsupported protocol: %s\", network)\n\t\t\t}\n\t\t\tassert.NoError(p.tester, err, \"ResolveNetAddr error\")\n\n\t\t\t// Test the error handling with empty context.\n\t\t\t_, err = p.engine.Register(context.Background())\n\t\t\tassert.ErrorIs(p.tester, err, errorx.ErrInvalidNetworkAddress)\n\n\t\t\tvar resCh <-chan RegisteredResult\n\t\t\tif i%2 == 0 {\n\t\t\t\tresCh, err = p.engine.Register(NewNetAddrContext(context.Background(), address))\n\t\t\t} else {\n\t\t\t\tc, e := net.Dial(network, addr)\n\t\t\t\tassert.NoError(p.tester, e, \"Dial error\")\n\t\t\t\tresCh, err = p.engine.Register(NewNetConnContext(context.Background(), c))\n\t\t\t}\n\t\t\tassert.NoError(p.tester, err, \"Register connection error\")\n\t\t\terr = goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tres := <-resCh\n\t\t\t\tassert.NoError(p.tester, res.Err, \"Register connection error\")\n\t\t\t\tassert.NotNil(p.tester, res.Conn, \"Register connection nil\")\n\t\t\t})\n\t\t\tassert.NoError(p.tester, err, \"Submit connection error\")\n\t\t}\n\t})\n\tp.startClientOnce.Do(func() {\n\t\tfor i := 0; i < len(p.backendServers); i++ {\n\t\t\tatomic.AddInt32(&p.activeClients, 1)\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tstartClient(p.tester, p.ListenerNet, p.ListenerAddr,\n\t\t\t\t\ttrue, false, p.packetSize, p.stallCh)\n\t\t\t\tatomic.AddInt32(&p.activeClients, -1)\n\t\t\t})\n\t\t\tassert.NoErrorf(p.tester, err, \"Submit backend server %s error: %v\", p.ListenerAddr, err)\n\t\t}\n\t})\n\n\tif atomic.LoadInt32(&p.activeClients) == 0 {\n\t\treturn 0, Shutdown\n\t}\n\n\treturn time.Millisecond * 200, None\n}\n\nfunc TestUDPProxyServer(t *testing.T) {\n\tt.Run(\"backend-udp-proxy-server\", func(t *testing.T) {\n\t\taddr := \"udp://127.0.0.1:10000\"\n\t\tbackendServers := []string{\n\t\t\t\"udp://127.0.0.1:10001\",\n\t\t\t\"udp://127.0.0.1:10002\",\n\t\t\t\"udp://127.0.0.1:10003\",\n\t\t\t\"udp://127.0.0.1:10004\",\n\t\t\t\"udp://127.0.0.1:10005\",\n\t\t\t\"udp://127.0.0.1:10006\",\n\t\t\t\"udp://127.0.0.1:10007\",\n\t\t\t\"udp://127.0.0.1:10008\",\n\t\t\t\"udp://127.0.0.1:10009\",\n\t\t\t\"udp://127.0.0.1:10010\",\n\t\t}\n\t\tt.Run(\"1-loop-LT\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, false, false)\n\t\t})\n\t\tt.Run(\"1-loop-ET\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, false, true)\n\t\t})\n\t\tt.Run(\"N-loop-LT\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, true, false)\n\t\t})\n\t\tt.Run(\"N-loop-ET\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, true, true)\n\t\t})\n\t})\n\n\tt.Run(\"backend-tcp-proxy-server\", func(t *testing.T) {\n\t\taddr := \"udp://127.0.0.1:20000\"\n\t\tbackendServers := []string{\n\t\t\t\"tcp://127.0.0.1:20001\",\n\t\t\t\"tcp://127.0.0.1:20002\",\n\t\t\t\"tcp://127.0.0.1:20003\",\n\t\t\t\"tcp://127.0.0.1:20004\",\n\t\t\t\"tcp://127.0.0.1:20005\",\n\t\t\t\"tcp://127.0.0.1:20006\",\n\t\t\t\"tcp://127.0.0.1:20007\",\n\t\t\t\"tcp://127.0.0.1:20008\",\n\t\t\t\"tcp://127.0.0.1:20009\",\n\t\t\t\"tcp://127.0.0.1:20010\",\n\t\t}\n\t\tt.Run(\"1-loop-LT\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, false, false)\n\t\t})\n\t\tt.Run(\"1-loop-ET\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, false, true)\n\t\t})\n\t\tt.Run(\"N-loop-LT\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, true, false)\n\t\t})\n\t\tt.Run(\"N-loop-ET\", func(t *testing.T) {\n\t\t\ttestUDPProxyServer(t, addr, backendServers, true, true)\n\t\t})\n\t})\n}\n\nfunc testUDPProxyServer(t *testing.T, addr string, backendServers []string, multicore, et bool) {\n\tnetwork, address, err := parseProtoAddr(addr)\n\trequire.NoError(t, err, \"parseProtoAddr error\")\n\n\tsrv := udpProxyServer{\n\t\ttester:         t,\n\t\tstallCh:        make(chan struct{}),\n\t\tListenerNet:    network,\n\t\tListenerAddr:   address,\n\t\tbackendServers: backendServers,\n\t\tpacket:         []byte(\"andypan\"),\n\t\tpacketSize:     datagramLen,\n\t}\n\n\tvar (\n\t\tbackends   errgroup.Group\n\t\tnetServers []io.Closer\n\t)\n\tfor _, backendServer := range backendServers {\n\t\tnetwork, addr, err := parseProtoAddr(backendServer)\n\t\trequire.NoError(t, err, \"parseProtoAddr error\")\n\t\tif strings.HasPrefix(network, \"udp\") {\n\t\t\tudpAddr, err := net.ResolveUDPAddr(network, addr)\n\t\t\trequire.NoError(t, err, \"ResolveUDPAddr error\")\n\t\t\tc, err := net.ListenUDP(\"udp\", udpAddr)\n\t\t\trequire.NoError(t, err, \"ListenUDP error\")\n\t\t\tbackends.Go(func() error {\n\t\t\t\tstartUDPEchoServer(t, c)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\trequire.NoErrorf(t, err, \"Start backend UDP server %s error: %v\", backendServer, err)\n\t\t\tnetServers = append(netServers, c)\n\t\t} else {\n\t\t\tln, err := net.Listen(network, addr)\n\t\t\trequire.NoErrorf(t, err, \"Create backend server %s error: %v\", network+\"://\"+addr, err)\n\t\t\tbackends.Go(func() error {\n\t\t\t\tstartStreamEchoServer(t, ln)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\trequire.NoErrorf(t, err, \"Start backend stream server %s error: %v\", backendServer, err)\n\t\t\tnetServers = append(netServers, ln)\n\t\t}\n\t}\n\n\terr = Run(&srv, addr,\n\t\tWithLoadBalancing(LeastConnections),\n\t\tWithEdgeTriggeredIO(et),\n\t\tWithMulticore(multicore),\n\t\tWithTicker(true))\n\trequire.NoErrorf(t, err, \"Run error: %v\", err)\n\n\tfor _, server := range netServers {\n\t\trequire.NoError(t, server.Close(), \"Close backend server error\")\n\t}\n\n\tbackends.Wait() //nolint:errcheck\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/panjf2000/gnet/v2\n\nrequire (\n\tgithub.com/panjf2000/ants/v2 v2.11.3\n\tgithub.com/stretchr/testify v1.10.0\n\tgithub.com/valyala/bytebufferpool v1.0.0\n\tgo.uber.org/zap v1.27.0\n\tgolang.org/x/sync v0.11.0\n\tgolang.org/x/sys v0.30.0\n\tgopkg.in/natefinch/lumberjack.v2 v2.2.1\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\ngo 1.20\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=\ngithub.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngolang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=\ngolang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=\ngolang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/gfd/gfd.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n/*\nPackage gfd provides a structure GFD to store the fd, eventloop index, connStore indexes\nand some other information.\n\nGFD structure:\n|eventloop index|conn matrix row index|conn matrix column index|monotone sequence|  socket fd  |\n|   1 byte      |       1 byte        |        2 byte          |     4 byte      |    8 byte   |.\n*/\npackage gfd\n\nimport (\n\t\"encoding/binary\"\n\t\"math\"\n\t\"sync/atomic\"\n)\n\n// Constants for GFD.\nconst (\n\tConnMatrixColumnOffset = 2\n\tSequenceOffset         = 4\n\tFdOffset               = 8\n\tEventLoopIndexMax      = math.MaxUint8 + 1\n\tConnMatrixRowMax       = math.MaxUint8 + 1\n\tConnMatrixColumnMax    = math.MaxUint16 + 1\n)\n\ntype monotoneSeq uint32\n\nfunc (seq *monotoneSeq) Inc() uint32 {\n\treturn atomic.AddUint32((*uint32)(seq), 1)\n}\n\nvar monoSeq = new(monotoneSeq)\n\n// GFD is a structure to store the fd, eventloop index, connStore indexes.\ntype GFD [0x10]byte\n\n// Fd returns the underlying fd.\nfunc (gfd GFD) Fd() int {\n\treturn int(binary.BigEndian.Uint64(gfd[FdOffset:]))\n}\n\n// EventLoopIndex returns the eventloop index.\nfunc (gfd GFD) EventLoopIndex() int {\n\treturn int(gfd[0])\n}\n\n// ConnMatrixRow returns the connMatrix row index.\nfunc (gfd GFD) ConnMatrixRow() int {\n\treturn int(gfd[1])\n}\n\n// ConnMatrixColumn returns the connMatrix column index.\nfunc (gfd GFD) ConnMatrixColumn() int {\n\treturn int(binary.BigEndian.Uint16(gfd[ConnMatrixColumnOffset:SequenceOffset]))\n}\n\n// Sequence returns the monotonic sequence, only used to prevent fd duplication.\nfunc (gfd GFD) Sequence() uint32 {\n\treturn binary.BigEndian.Uint32(gfd[SequenceOffset:FdOffset])\n}\n\n// UpdateIndexes updates the connStore indexes.\nfunc (gfd *GFD) UpdateIndexes(row, column int) {\n\t(*gfd)[1] = byte(row)\n\tbinary.BigEndian.PutUint16((*gfd)[ConnMatrixColumnOffset:SequenceOffset], uint16(column))\n}\n\n// Validate checks if the GFD is valid.\nfunc (gfd GFD) Validate() bool {\n\treturn gfd.Fd() > 2 && gfd.Fd() <= math.MaxInt &&\n\t\tgfd.EventLoopIndex() >= 0 && gfd.EventLoopIndex() < EventLoopIndexMax &&\n\t\tgfd.ConnMatrixRow() >= 0 && gfd.ConnMatrixRow() < ConnMatrixRowMax &&\n\t\tgfd.ConnMatrixColumn() >= 0 && gfd.ConnMatrixColumn() < ConnMatrixColumnMax &&\n\t\tgfd.Sequence() > 0\n}\n\n// NewGFD creates a new GFD.\nfunc NewGFD(fd, elIndex, row, column int) (gfd GFD) {\n\tgfd[0] = byte(elIndex)\n\tgfd[1] = byte(row)\n\tbinary.BigEndian.PutUint16(gfd[ConnMatrixColumnOffset:SequenceOffset], uint16(column))\n\tbinary.BigEndian.PutUint32(gfd[SequenceOffset:FdOffset], monoSeq.Inc())\n\tbinary.BigEndian.PutUint64(gfd[FdOffset:], uint64(fd))\n\treturn\n}\n"
  },
  {
    "path": "listener_unix.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n\t\"github.com/panjf2000/gnet/v2/pkg/socket\"\n)\n\ntype listener struct {\n\topenOnce, closeOnce sync.Once\n\tfd                  int\n\taddr                net.Addr\n\taddress, network    string\n\tsockOptInts         []socket.Option[int]\n\tsockOptStrs         []socket.Option[string]\n\tpollAttachment      *netpoll.PollAttachment // listener attachment for poller\n}\n\nfunc (ln *listener) packPollAttachment(handler netpoll.PollEventHandler) *netpoll.PollAttachment {\n\tln.pollAttachment = &netpoll.PollAttachment{FD: ln.fd, Callback: handler}\n\treturn ln.pollAttachment\n}\n\nfunc (ln *listener) dup() (int, error) {\n\treturn socket.Dup(ln.fd)\n}\n\nfunc (ln *listener) open() (err error) {\n\tln.openOnce.Do(func() {\n\t\tswitch ln.network {\n\t\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\t\t\tln.fd, ln.addr, err = socket.TCPSocket(ln.network, ln.address, true, ln.sockOptInts, ln.sockOptStrs)\n\t\t\tln.network = \"tcp\"\n\t\tcase \"udp\", \"udp4\", \"udp6\":\n\t\t\tln.fd, ln.addr, err = socket.UDPSocket(ln.network, ln.address, false, ln.sockOptInts, ln.sockOptStrs)\n\t\t\tln.network = \"udp\"\n\t\tcase \"unix\":\n\t\t\t_ = os.RemoveAll(ln.address)\n\t\t\tln.fd, ln.addr, err = socket.UnixSocket(ln.network, ln.address, true, ln.sockOptInts, ln.sockOptStrs)\n\t\tdefault:\n\t\t\terr = errorx.ErrUnsupportedProtocol\n\t\t}\n\t})\n\treturn\n}\n\nfunc (ln *listener) close() {\n\tln.closeOnce.Do(func() {\n\t\tif ln.fd > 0 {\n\t\t\tlogging.Error(os.NewSyscallError(\"close\", unix.Close(ln.fd)))\n\t\t}\n\t\tln.fd = -1\n\t\tif ln.network == \"unix\" {\n\t\t\tlogging.Error(os.RemoveAll(ln.address))\n\t\t}\n\t})\n}\n\nfunc initListener(network, addr string, options *Options) (ln *listener, err error) {\n\tvar (\n\t\tsockOptInts []socket.Option[int]\n\t\tsockOptStrs []socket.Option[string]\n\t)\n\n\tif options.ReusePort && network != \"unix\" {\n\t\tsockOpt := socket.Option[int]{SetSockOpt: socket.SetReuseport, Opt: 1}\n\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t}\n\tif options.ReuseAddr {\n\t\tsockOpt := socket.Option[int]{SetSockOpt: socket.SetReuseAddr, Opt: 1}\n\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t}\n\tif options.TCPNoDelay == TCPNoDelay && strings.HasPrefix(network, \"tcp\") {\n\t\tsockOpt := socket.Option[int]{SetSockOpt: socket.SetNoDelay, Opt: 1}\n\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t}\n\tif options.SocketRecvBuffer > 0 {\n\t\tsockOpt := socket.Option[int]{SetSockOpt: socket.SetRecvBuffer, Opt: options.SocketRecvBuffer}\n\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t}\n\tif options.SocketSendBuffer > 0 {\n\t\tsockOpt := socket.Option[int]{SetSockOpt: socket.SetSendBuffer, Opt: options.SocketSendBuffer}\n\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t}\n\tif strings.HasPrefix(network, \"udp\") {\n\t\tudpAddr, err := net.ResolveUDPAddr(network, addr)\n\t\tif err == nil && udpAddr.IP.IsMulticast() {\n\t\t\tif sockoptFn := socket.SetMulticastMembership(network, udpAddr); sockoptFn != nil {\n\t\t\t\tsockOpt := socket.Option[int]{SetSockOpt: sockoptFn, Opt: options.MulticastInterfaceIndex}\n\t\t\t\tsockOptInts = append(sockOptInts, sockOpt)\n\t\t\t}\n\t\t}\n\t}\n\tif options.BindToDevice != \"\" {\n\t\tsockOpt := socket.Option[string]{SetSockOpt: socket.SetBindToDevice, Opt: options.BindToDevice}\n\t\tsockOptStrs = append(sockOptStrs, sockOpt)\n\t}\n\n\tln = &listener{network: network, address: addr, sockOptInts: sockOptInts, sockOptStrs: sockOptStrs}\n\terr = ln.open()\n\n\tif options.TCPKeepAlive > 0 && ln.network == \"tcp\" &&\n\t\t(runtime.GOOS == \"linux\" || runtime.GOOS == \"freebsd\" || runtime.GOOS == \"dragonfly\") {\n\t\t// TCP keepalive options will be inherited from the listening socket\n\t\t// only when running on Linux, FreeBSD, or DragonFlyBSD.\n\t\t//\n\t\t// Check out https://github.com/nginx/nginx/pull/337 for details.\n\t\terr = setKeepAlive(\n\t\t\tln.fd,\n\t\t\ttrue,\n\t\t\toptions.TCPKeepAlive,\n\t\t\toptions.TCPKeepInterval,\n\t\t\toptions.TCPKeepCount)\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "listener_windows.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/windows\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\ntype listener struct {\n\topenOnce, closeOnce sync.Once\n\tnetwork             string\n\taddress             string\n\tlc                  *net.ListenConfig\n\tln                  net.Listener\n\tpc                  net.PacketConn\n\taddr                net.Addr\n}\n\nfunc (l *listener) dup() (int, error) {\n\tif l.ln == nil && l.pc == nil {\n\t\treturn -1, errorx.ErrUnsupportedOp\n\t}\n\n\tvar (\n\t\tsc syscall.Conn\n\t\tok bool\n\t)\n\tif l.ln != nil {\n\t\tsc, ok = l.ln.(syscall.Conn)\n\t} else {\n\t\tsc, ok = l.pc.(syscall.Conn)\n\t}\n\n\tif !ok {\n\t\treturn -1, errors.New(\"failed to convert net.Conn to syscall.Conn\")\n\t}\n\trc, err := sc.SyscallConn()\n\tif err != nil {\n\t\treturn -1, errors.New(\"failed to get syscall.RawConn from net.Conn\")\n\t}\n\n\tvar dupHandle windows.Handle\n\te := rc.Control(func(fd uintptr) {\n\t\tprocess := windows.CurrentProcess()\n\t\terr = windows.DuplicateHandle(\n\t\t\tprocess,\n\t\t\twindows.Handle(fd),\n\t\t\tprocess,\n\t\t\t&dupHandle,\n\t\t\t0,\n\t\t\ttrue,\n\t\t\twindows.DUPLICATE_SAME_ACCESS,\n\t\t)\n\t})\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tif e != nil {\n\t\treturn -1, e\n\t}\n\n\treturn int(dupHandle), nil\n}\n\nfunc (l *listener) open() (err error) {\n\tl.openOnce.Do(func() {\n\t\tswitch l.network {\n\t\tcase \"udp\", \"udp4\", \"udp6\":\n\t\t\tif l.pc, err = l.lc.ListenPacket(context.Background(), l.network, l.address); err == nil {\n\t\t\t\tl.addr = l.pc.LocalAddr()\n\t\t\t}\n\t\tcase \"unix\":\n\t\t\t_ = os.Remove(l.address)\n\t\t\tfallthrough\n\t\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\t\t\tif l.ln, err = l.lc.Listen(context.Background(), l.network, l.address); err == nil {\n\t\t\t\tl.addr = l.ln.Addr()\n\t\t\t}\n\t\tdefault:\n\t\t\terr = errorx.ErrUnsupportedProtocol\n\t\t}\n\t})\n\treturn\n}\n\nfunc (l *listener) close() {\n\tl.closeOnce.Do(func() {\n\t\tif l.pc != nil {\n\t\t\tlogging.Error(os.NewSyscallError(\"close\", l.pc.Close()))\n\t\t\treturn\n\t\t}\n\t\tl.pc = nil\n\t\tlogging.Error(os.NewSyscallError(\"close\", l.ln.Close()))\n\t})\n}\n\nfunc initListener(network, addr string, options *Options) (*listener, error) {\n\tlc := net.ListenConfig{\n\t\tControl: func(network, address string, c syscall.RawConn) error {\n\t\t\treturn c.Control(func(fd uintptr) {\n\t\t\t\tif network != \"unix\" && (options.ReuseAddr || options.ReusePort) {\n\t\t\t\t\t_ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)\n\t\t\t\t}\n\t\t\t\tif options.TCPNoDelay == TCPNoDelay {\n\t\t\t\t\t_ = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_TCP, windows.TCP_NODELAY, 1)\n\t\t\t\t}\n\t\t\t\tif options.SocketRecvBuffer > 0 {\n\t\t\t\t\t_ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_RCVBUF, options.SocketRecvBuffer)\n\t\t\t\t}\n\t\t\t\tif options.SocketSendBuffer > 0 {\n\t\t\t\t\t_ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_SNDBUF, options.SocketSendBuffer)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tKeepAlive: options.TCPKeepAlive,\n\t}\n\n\tl := listener{network: network, address: addr, lc: &lc}\n\n\treturn &l, l.open()\n}\n"
  },
  {
    "path": "load_balancer.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"hash/crc32\"\n\t\"net\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/bs\"\n)\n\n// LoadBalancing represents the type of load-balancing algorithm.\ntype LoadBalancing int\n\nconst (\n\t// RoundRobin assigns the next accepted connection to the event-loop by polling event-loop list.\n\tRoundRobin LoadBalancing = iota\n\n\t// LeastConnections assigns the next accepted connection to the event-loop that is\n\t// serving the least number of active connections at the current time.\n\tLeastConnections\n\n\t// SourceAddrHash assigns the next accepted connection to the event-loop by hashing the remote address.\n\tSourceAddrHash\n)\n\ntype (\n\t// loadBalancer is an interface which manipulates the event-loop set.\n\tloadBalancer interface {\n\t\tregister(*eventloop)\n\t\tnext(net.Addr) *eventloop\n\t\tindex(int) *eventloop\n\t\titerate(func(int, *eventloop) bool)\n\t\tlen() int\n\t}\n\n\t// baseLoadBalancer with base lb.\n\tbaseLoadBalancer struct {\n\t\teventLoops []*eventloop\n\t\tsize       int\n\t}\n\n\t// roundRobinLoadBalancer with Round-Robin algorithm.\n\troundRobinLoadBalancer struct {\n\t\tbaseLoadBalancer\n\t\tnextIndex uint64\n\t}\n\n\t// leastConnectionsLoadBalancer with Least-Connections algorithm.\n\tleastConnectionsLoadBalancer struct {\n\t\tbaseLoadBalancer\n\t}\n\n\t// sourceAddrHashLoadBalancer with Hash algorithm.\n\tsourceAddrHashLoadBalancer struct {\n\t\tbaseLoadBalancer\n\t}\n)\n\n// ==================================== Implementation of base load-balancer ====================================\n\n// register adds a new eventloop into load-balancer.\nfunc (lb *baseLoadBalancer) register(el *eventloop) {\n\tel.idx = lb.size\n\tlb.eventLoops = append(lb.eventLoops, el)\n\tlb.size++\n}\n\n// index returns the eligible eventloop by index.\nfunc (lb *baseLoadBalancer) index(i int) *eventloop {\n\tif i >= lb.size {\n\t\treturn nil\n\t}\n\treturn lb.eventLoops[i]\n}\n\n// iterate iterates all the eventloops.\nfunc (lb *baseLoadBalancer) iterate(f func(int, *eventloop) bool) {\n\tfor i, el := range lb.eventLoops {\n\t\tif !f(i, el) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// len returns the length of event-loop list.\nfunc (lb *baseLoadBalancer) len() int {\n\treturn lb.size\n}\n\n// ==================================== Implementation of Round-Robin load-balancer ====================================\n\n// next returns the eligible event-loop based on Round-Robin algorithm.\nfunc (lb *roundRobinLoadBalancer) next(_ net.Addr) (el *eventloop) {\n\tel = lb.eventLoops[lb.nextIndex%uint64(lb.size)]\n\tlb.nextIndex++\n\treturn\n}\n\n// ================================= Implementation of Least-Connections load-balancer =================================\n\nfunc (lb *leastConnectionsLoadBalancer) next(_ net.Addr) (el *eventloop) {\n\tel = lb.eventLoops[0]\n\tminN := el.countConn()\n\tfor _, v := range lb.eventLoops[1:] {\n\t\tif n := v.countConn(); n < minN {\n\t\t\tminN = n\n\t\t\tel = v\n\t\t}\n\t}\n\treturn\n}\n\n// ======================================= Implementation of Hash load-balancer ========================================\n\n// hash converts a string to a unique hash code.\nfunc (*sourceAddrHashLoadBalancer) hash(s string) int {\n\tv := int(crc32.ChecksumIEEE(bs.StringToBytes(s)))\n\tif v >= 0 {\n\t\treturn v\n\t}\n\treturn -v\n}\n\n// next returns the eligible event-loop by taking the remainder of a hash code as the index of event-loop list.\nfunc (lb *sourceAddrHashLoadBalancer) next(netAddr net.Addr) *eventloop {\n\thashCode := lb.hash(netAddr.String())\n\treturn lb.eventLoops[hashCode%lb.size]\n}\n"
  },
  {
    "path": "options.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage gnet\n\nimport (\n\t\"time\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\n// Option is a function that will set up option.\ntype Option func(opts *Options)\n\nfunc loadOptions(options ...Option) *Options {\n\topts := new(Options)\n\tfor _, option := range options {\n\t\toption(opts)\n\t}\n\treturn opts\n}\n\n// TCPSocketOpt is the type of TCP socket options.\ntype TCPSocketOpt int\n\n// Available TCP socket options.\nconst (\n\tTCPNoDelay TCPSocketOpt = iota\n\tTCPDelay\n)\n\n// Options are configurations for the gnet application.\ntype Options struct {\n\t// LB represents the load-balancing algorithm used when assigning new connections\n\t// to event loops. This option is server-only, and it is not applicable to the client.\n\tLB LoadBalancing\n\n\t// ReuseAddr indicates whether to set the SO_REUSEADDR socket option.\n\t// This option is server-only.\n\tReuseAddr bool\n\n\t// ReusePort indicates whether to set the SO_REUSEPORT socket option.\n\t// This option is server-only.\n\tReusePort bool\n\n\t// MulticastInterfaceIndex is the index of the interface name where the multicast UDP addresses will be bound to.\n\t// This option is server-only.\n\tMulticastInterfaceIndex int\n\n\t// BindToDevice is the name of the interface to which the listening socket will be bound.\n\t// It is only available on Linux at the moment, an error will therefore be returned when\n\t// setting this option on non-linux platforms.\n\t// This option is server-only.\n\tBindToDevice string\n\n\t// Multicore indicates whether the engine will be effectively created with multi-cores, if so,\n\t// then you must take care with synchronizing memory between all event callbacks; otherwise,\n\t// it will run the engine with single thread. The number of threads in the engine will be\n\t// automatically assigned to the number of usable logical CPUs that can be leveraged by the\n\t// current process.\n\tMulticore bool\n\n\t// NumEventLoop is set up to start the given number of event-loop goroutines.\n\t// Note that a non-negative NumEventLoop will override Multicore.\n\tNumEventLoop int\n\n\t// ReadBufferCap is the maximum number of bytes that can be read from the remote when the readable event comes.\n\t// The default value is 64KB, it can either be reduced to avoid starving the subsequent connections or increased\n\t// to read more data from a socket.\n\t//\n\t// Note that ReadBufferCap will always be converted to the least power of two integer value greater than\n\t// or equal to its real amount.\n\tReadBufferCap int\n\n\t// WriteBufferCap is the maximum number of bytes that a static outbound buffer can hold,\n\t// if the data exceeds this value, the overflow bytes will be stored in the elastic linked list buffer.\n\t// The default value is 64KB.\n\t//\n\t// Note that WriteBufferCap will always be converted to the least power of two integer value greater than\n\t// or equal to its real amount.\n\tWriteBufferCap int\n\n\t// LockOSThread is used to determine whether each I/O event-loop should be associated to an OS thread,\n\t// it is useful when you need some kind of mechanisms like thread local storage, or invoke certain C\n\t// libraries (such as graphics lib: GLib) that require thread-level manipulation via cgo, or want all I/O\n\t// event-loops to actually run in parallel for a potential higher performance.\n\tLockOSThread bool\n\n\t// Ticker indicates whether the ticker has been set up.\n\tTicker bool\n\n\t// TCPKeepAlive enables the TCP keep-alive mechanism (SO_KEEPALIVE) and set its value\n\t// on TCP_KEEPIDLE.\n\t// When TCPKeepInterval is not set, 1/5 of TCPKeepAlive will be set on TCP_KEEPINTVL,\n\t// and 5 will be set on TCP_KEEPCNT if TCPKeepCount is not assigned to a positive value.\n\tTCPKeepAlive time.Duration\n\n\t// TCPKeepInterval is the value for TCP_KEEPINTVL, it's the interval between\n\t// TCP keep-alive probes.\n\tTCPKeepInterval time.Duration\n\n\t// TCPKeepCount is the number of keep-alive probes that will be sent before\n\t// the connection is considered dead and dropped.\n\tTCPKeepCount int\n\n\t// TCPNoDelay controls whether the operating system should delay\n\t// packet transmission in hopes of sending fewer packets (Nagle's algorithm).\n\t// When this option is assigned to TCPNoDelay, TCP_NODELAY socket option will\n\t// be turned on, on the contrary, if it is assigned to TCPDelay, the socket\n\t// option will be turned off.\n\t//\n\t// The default is TCPNoDelay, meaning that TCP_NODELAY is turned on and data\n\t// will not be buffered but sent as soon as possible after a write operation.\n\tTCPNoDelay TCPSocketOpt\n\n\t// SocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes.\n\tSocketRecvBuffer int\n\n\t// SocketSendBuffer sets the maximum socket send buffer of kernel in bytes.\n\tSocketSendBuffer int\n\n\t// LogPath specifies a local path where logs will be written, this is the easiest\n\t// way to set up logging, gnet instantiates a default uber-go/zap logger with this\n\t// given log path, you are also allowed to employ your own logger during the lifetime\n\t// by implementing the following logging.Logger interface.\n\t//\n\t// Note that this option can be overridden by a non-nil option Logger.\n\tLogPath string\n\n\t// LogLevel specifies the logging level, it should be used along with LogPath.\n\tLogLevel logging.Level\n\n\t// Logger is the customized logger for logging info, if it is not set,\n\t// then gnet will use the default logger powered by go.uber.org/zap.\n\tLogger logging.Logger\n\n\t// EdgeTriggeredIO enables the edge-triggered I/O for the underlying epoll/kqueue event-loop.\n\t// Don't enable it unless you are 100% sure what you are doing.\n\t// Note that this option is only available for stream-oriented protocol.\n\tEdgeTriggeredIO bool\n\n\t// EdgeTriggeredIOChunk specifies the number of bytes that `gnet` can\n\t// read/write up to in one event loop of ET. This option implies\n\t// EdgeTriggeredIO when it is set to a value greater than 0.\n\t// If EdgeTriggeredIO is set to true and EdgeTriggeredIOChunk is not set,\n\t// 1MB is used. The value of EdgeTriggeredIOChunk must be a power of 2,\n\t// otherwise, it will be rounded up to the nearest power of 2.\n\tEdgeTriggeredIOChunk int\n}\n\n// WithOptions sets up all options.\nfunc WithOptions(options Options) Option {\n\treturn func(opts *Options) {\n\t\t*opts = options\n\t}\n}\n\n// WithMulticore enables multi-cores mode for gnet engine.\nfunc WithMulticore(multicore bool) Option {\n\treturn func(opts *Options) {\n\t\topts.Multicore = multicore\n\t}\n}\n\n// WithLockOSThread enables LockOSThread mode for I/O event-loops.\nfunc WithLockOSThread(lockOSThread bool) Option {\n\treturn func(opts *Options) {\n\t\topts.LockOSThread = lockOSThread\n\t}\n}\n\n// WithReadBufferCap sets ReadBufferCap for reading bytes.\nfunc WithReadBufferCap(readBufferCap int) Option {\n\treturn func(opts *Options) {\n\t\topts.ReadBufferCap = readBufferCap\n\t}\n}\n\n// WithWriteBufferCap sets WriteBufferCap for pending bytes.\nfunc WithWriteBufferCap(writeBufferCap int) Option {\n\treturn func(opts *Options) {\n\t\topts.WriteBufferCap = writeBufferCap\n\t}\n}\n\n// WithLoadBalancing picks the load-balancing algorithm for gnet engine.\nfunc WithLoadBalancing(lb LoadBalancing) Option {\n\treturn func(opts *Options) {\n\t\topts.LB = lb\n\t}\n}\n\n// WithNumEventLoop sets the number of event loops for gnet engine.\nfunc WithNumEventLoop(numEventLoop int) Option {\n\treturn func(opts *Options) {\n\t\topts.NumEventLoop = numEventLoop\n\t}\n}\n\n// WithReusePort sets SO_REUSEPORT socket option.\nfunc WithReusePort(reusePort bool) Option {\n\treturn func(opts *Options) {\n\t\topts.ReusePort = reusePort\n\t}\n}\n\n// WithReuseAddr sets SO_REUSEADDR socket option.\nfunc WithReuseAddr(reuseAddr bool) Option {\n\treturn func(opts *Options) {\n\t\topts.ReuseAddr = reuseAddr\n\t}\n}\n\n// WithTCPKeepAlive enables the TCP keep-alive mechanism and sets its values.\nfunc WithTCPKeepAlive(tcpKeepAlive time.Duration) Option {\n\treturn func(opts *Options) {\n\t\topts.TCPKeepAlive = tcpKeepAlive\n\t}\n}\n\n// WithTCPKeepInterval sets the interval between TCP keep-alive probes.\nfunc WithTCPKeepInterval(tcpKeepInterval time.Duration) Option {\n\treturn func(opts *Options) {\n\t\topts.TCPKeepInterval = tcpKeepInterval\n\t}\n}\n\n// WithTCPKeepCount sets the number of keep-alive probes that will be sent before\n// the connection is considered dead and dropped.\nfunc WithTCPKeepCount(tcpKeepCount int) Option {\n\treturn func(opts *Options) {\n\t\topts.TCPKeepCount = tcpKeepCount\n\t}\n}\n\n// WithTCPNoDelay enable/disable the TCP_NODELAY socket option.\nfunc WithTCPNoDelay(tcpNoDelay TCPSocketOpt) Option {\n\treturn func(opts *Options) {\n\t\topts.TCPNoDelay = tcpNoDelay\n\t}\n}\n\n// WithSocketRecvBuffer sets the maximum socket receive buffer of kernel in bytes.\nfunc WithSocketRecvBuffer(recvBuf int) Option {\n\treturn func(opts *Options) {\n\t\topts.SocketRecvBuffer = recvBuf\n\t}\n}\n\n// WithSocketSendBuffer sets the maximum socket send buffer of kernel in bytes.\nfunc WithSocketSendBuffer(sendBuf int) Option {\n\treturn func(opts *Options) {\n\t\topts.SocketSendBuffer = sendBuf\n\t}\n}\n\n// WithTicker indicates whether a ticker is currently set.\nfunc WithTicker(ticker bool) Option {\n\treturn func(opts *Options) {\n\t\topts.Ticker = ticker\n\t}\n}\n\n// WithLogPath specifies a local path for logging file.\nfunc WithLogPath(fileName string) Option {\n\treturn func(opts *Options) {\n\t\topts.LogPath = fileName\n\t}\n}\n\n// WithLogLevel specifies the logging level for the local logging file.\nfunc WithLogLevel(lvl logging.Level) Option {\n\treturn func(opts *Options) {\n\t\topts.LogLevel = lvl\n\t}\n}\n\n// WithLogger specifies a customized logger.\nfunc WithLogger(logger logging.Logger) Option {\n\treturn func(opts *Options) {\n\t\topts.Logger = logger\n\t}\n}\n\n// WithMulticastInterfaceIndex sets the interface name where UDP multicast sockets will be bound to.\nfunc WithMulticastInterfaceIndex(idx int) Option {\n\treturn func(opts *Options) {\n\t\topts.MulticastInterfaceIndex = idx\n\t}\n}\n\n// WithBindToDevice sets the name of the interface to which the listening socket will be bound.\n//\n// It is only available on Linux at the moment, an error will therefore be returned when\n// setting this option on non-linux platforms.\nfunc WithBindToDevice(iface string) Option {\n\treturn func(opts *Options) {\n\t\topts.BindToDevice = iface\n\t}\n}\n\n// WithEdgeTriggeredIO enables the edge-triggered I/O for the underlying epoll/kqueue event-loop.\nfunc WithEdgeTriggeredIO(et bool) Option {\n\treturn func(opts *Options) {\n\t\topts.EdgeTriggeredIO = et\n\t}\n}\n\n// WithEdgeTriggeredIOChunk sets the number of bytes that `gnet` can\n// read/write up to in one event loop of ET.\nfunc WithEdgeTriggeredIOChunk(chunk int) Option {\n\treturn func(opts *Options) {\n\t\topts.EdgeTriggeredIOChunk = chunk\n\t}\n}\n"
  },
  {
    "path": "os_unix_test.go",
    "content": "//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage gnet\n\nimport (\n\t\"context\"\n\tcrand \"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\tgoPool \"github.com/panjf2000/gnet/v2/pkg/pool/goroutine\"\n)\n\nvar SysClose = unix.Close\n\n// NOTE: TestServeMulticast can fail with \"write: no buffer space available\" on Wi-Fi interface.\nfunc TestServeMulticast(t *testing.T) {\n\tt.Run(\"IPv4\", func(t *testing.T) {\n\t\t// 224.0.0.169 is an unassigned address from the Local Network Control Block\n\t\t// https://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml#multicast-addresses-1\n\t\tt.Run(\"udp-multicast\", func(t *testing.T) {\n\t\t\ttestMulticast(t, \"224.0.0.169:9991\", false, false, -1, 10)\n\t\t})\n\t\tt.Run(\"udp-multicast-reuseport\", func(t *testing.T) {\n\t\t\ttestMulticast(t, \"224.0.0.169:9991\", true, false, -1, 10)\n\t\t})\n\t\tt.Run(\"udp-multicast-reuseaddr\", func(t *testing.T) {\n\t\t\ttestMulticast(t, \"224.0.0.169:9991\", false, true, -1, 10)\n\t\t})\n\t})\n\tt.Run(\"IPv6\", func(t *testing.T) {\n\t\tiface, err := findLoopbackInterface()\n\t\tassert.NoError(t, err)\n\t\tif iface.Flags&net.FlagMulticast != net.FlagMulticast {\n\t\t\tt.Skip(\"multicast is not supported on loopback interface\")\n\t\t}\n\t\t// ff02::3 is an unassigned address from Link-Local Scope Multicast Addresses\n\t\t// https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml#link-local\n\t\tt.Run(\"udp-multicast\", func(t *testing.T) {\n\t\t\ttestMulticast(t, fmt.Sprintf(\"[ff02::3%%%s]:9991\", iface.Name), false, false, iface.Index, 10)\n\t\t})\n\t\tt.Run(\"udp-multicast-reuseport\", func(t *testing.T) {\n\t\t\ttestMulticast(t, fmt.Sprintf(\"[ff02::3%%%s]:9991\", iface.Name), true, false, iface.Index, 10)\n\t\t})\n\t\tt.Run(\"udp-multicast-reuseaddr\", func(t *testing.T) {\n\t\t\ttestMulticast(t, fmt.Sprintf(\"[ff02::3%%%s]:9991\", iface.Name), false, true, iface.Index, 10)\n\t\t})\n\t})\n}\n\nfunc findLoopbackInterface() (*net.Interface, error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, iface := range ifaces {\n\t\tif iface.Flags&net.FlagLoopback == net.FlagLoopback {\n\t\t\treturn &iface, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"no loopback interface\")\n}\n\nfunc testMulticast(t *testing.T, addr string, reuseport, reuseaddr bool, index, nclients int) {\n\tts := &testMcastServer{\n\t\tt:        t,\n\t\taddr:     addr,\n\t\tnclients: nclients,\n\t}\n\toptions := []Option{\n\t\tWithReuseAddr(reuseaddr),\n\t\tWithReusePort(reuseport),\n\t\tWithSocketRecvBuffer(2 * nclients * 1024), // enough space to receive messages from nclients to eliminate dropped packets\n\t\tWithTicker(true),\n\t}\n\tif index != -1 {\n\t\toptions = append(options, WithMulticastInterfaceIndex(index))\n\t}\n\terr := Run(ts, \"udp://\"+addr, options...)\n\tassert.NoError(t, err)\n}\n\ntype testMcastServer struct {\n\t*BuiltinEventEngine\n\tt        *testing.T\n\tmcast    sync.Map\n\taddr     string\n\tnclients int\n\tstarted  int32\n\tactive   int32\n}\n\nfunc (s *testMcastServer) startMcastClient() {\n\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancel()\n\tc, err := net.Dial(\"udp\", s.addr)\n\tassert.NoError(s.t, err)\n\tdefer c.Close() //nolint:errcheck\n\tch := make(chan []byte, 10000)\n\ts.mcast.Store(c.LocalAddr().String(), ch)\n\tduration := time.Duration((rand.Float64()*2+1)*float64(time.Second)) / 2\n\tlogging.Debugf(\"test duration: %v\", duration)\n\tstart := time.Now()\n\tfor time.Since(start) < duration {\n\t\treqData := make([]byte, 1024)\n\t\t_, err = crand.Read(reqData)\n\t\tassert.NoError(s.t, err)\n\t\t_, err = c.Write(reqData)\n\t\tassert.NoError(s.t, err)\n\t\t// Workaround for MacOS \"write: no buffer space available\" error messages\n\t\t// https://developer.apple.com/forums/thread/42334\n\t\ttime.Sleep(time.Millisecond * 100)\n\t\tselect {\n\t\tcase respData := <-ch:\n\t\t\tassert.Equalf(s.t, reqData, respData, \"response mismatch, length of bytes: %d vs %d\", len(reqData), len(respData))\n\t\tcase <-ctx.Done():\n\t\t\tassert.Fail(s.t, \"timeout receiving message\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (s *testMcastServer) OnTraffic(c Conn) (action Action) {\n\tbuf, _ := c.Next(-1)\n\tb := make([]byte, len(buf))\n\tcopy(b, buf)\n\tch, ok := s.mcast.Load(c.RemoteAddr().String())\n\tassert.True(s.t, ok)\n\tch.(chan []byte) <- b\n\treturn\n}\n\nfunc (s *testMcastServer) OnTick() (delay time.Duration, action Action) {\n\tif atomic.CompareAndSwapInt32(&s.started, 0, 1) {\n\t\tfor i := 0; i < s.nclients; i++ {\n\t\t\tatomic.AddInt32(&s.active, 1)\n\t\t\terr := goPool.DefaultWorkerPool.Submit(func() {\n\t\t\t\tdefer atomic.AddInt32(&s.active, -1)\n\t\t\t\ts.startMcastClient()\n\t\t\t})\n\t\t\tassert.NoError(s.t, err)\n\t\t}\n\t}\n\tif atomic.LoadInt32(&s.active) == 0 {\n\t\taction = Shutdown\n\t\treturn\n\t}\n\tdelay = time.Second / 5\n\treturn\n}\n\ntype testMulticastBindServer struct {\n\t*BuiltinEventEngine\n}\n\nfunc (t *testMulticastBindServer) OnTick() (delay time.Duration, action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc TestMulticastBindIPv4(t *testing.T) {\n\tts := &testMulticastBindServer{}\n\tiface, err := findLoopbackInterface()\n\tassert.NoError(t, err)\n\terr = Run(ts, \"udp://224.0.0.169:9991\",\n\t\tWithMulticastInterfaceIndex(iface.Index),\n\t\tWithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc TestMulticastBindIPv6(t *testing.T) {\n\tts := &testMulticastBindServer{}\n\tiface, err := findLoopbackInterface()\n\tassert.NoError(t, err)\n\terr = Run(ts, fmt.Sprintf(\"udp://[ff02::3%%%s]:9991\", iface.Name),\n\t\tWithMulticastInterfaceIndex(iface.Index),\n\t\tWithTicker(true))\n\tassert.NoError(t, err)\n}\n\nfunc detectLinuxEthernetInterfaceName() (string, error) {\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// Traditionally, network interfaces were named as eth0, eth1, etc., for Ethernet interfaces.\n\t// However, with the introduction of predictable network interface names. Meanwhile, modern\n\t// convention commonly uses patterns like eno[1-N], ens[1-N], enp<PCI slot>s<card index no>, etc.,\n\t// for Ethernet interfaces.\n\t// Check out https://www.thomas-krenn.com/en/wiki/Predictable_Network_Interface_Names and\n\t// https://en.wikipedia.org/wiki/Consistent_Network_Device_Naming for more details.\n\tregex := regexp.MustCompile(`e(no|ns|np|th)\\d+s*\\d*$`)\n\tfor _, iface := range ifaces {\n\t\tif iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagRunning == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif regex.MatchString(iface.Name) {\n\t\t\treturn iface.Name, nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"no Ethernet interface found\")\n}\n\nfunc getInterfaceIP(ifname string, ipv4 bool) (net.IP, error) {\n\tiface, err := net.InterfaceByName(ifname)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Get all unicast addresses for this interface\n\taddrs, err := iface.Addrs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Loop through the addresses and find the first IPv4 address\n\tfor _, addr := range addrs {\n\t\tvar ip net.IP\n\t\tswitch v := addr.(type) {\n\t\tcase *net.IPNet:\n\t\t\tip = v.IP\n\t\tcase *net.IPAddr:\n\t\t\tip = v.IP\n\t\t}\n\t\t// Check if the IP is IPv4.\n\t\tif ip != nil && (ip.To4() != nil) == ipv4 {\n\t\t\treturn ip, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"no valid IP address found\")\n}\n\ntype testBindToDeviceServer[T interface{ *net.TCPAddr | *net.UDPAddr }] struct {\n\tBuiltinEventEngine\n\ttester          *testing.T\n\tdata            []byte\n\tpackets         atomic.Int32\n\texpectedPackets int32\n\tnetwork         string\n\tloopBackAddr    T\n\teth0Addr        T\n\tbroadcastAddr   T\n}\n\nfunc netDial[T *net.TCPAddr | *net.UDPAddr](network string, a T) (net.Conn, error) {\n\taddr := any(a)\n\tswitch v := addr.(type) {\n\tcase *net.TCPAddr:\n\t\treturn net.DialTCP(network, nil, v)\n\tcase *net.UDPAddr:\n\t\treturn net.DialUDP(network, nil, v)\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported address type\")\n\t}\n}\n\nfunc (s *testBindToDeviceServer[T]) OnTraffic(c Conn) (action Action) {\n\tb, err := c.Next(-1)\n\tassert.NoError(s.tester, err)\n\tassert.EqualValues(s.tester, s.data, b)\n\t_, err = c.Write(b)\n\tassert.NoError(s.tester, err)\n\ts.packets.Add(1)\n\treturn\n}\n\nfunc (s *testBindToDeviceServer[T]) OnShutdown(_ Engine) {\n\tassert.EqualValues(s.tester, s.expectedPackets, s.packets.Load())\n}\n\nfunc (s *testBindToDeviceServer[T]) OnTick() (delay time.Duration, action Action) {\n\t// Send a packet to the loopback interface, it should never make its way to the server\n\t// because we've bound the server to eth0.\n\tc, err := netDial(s.network, s.loopBackAddr)\n\tif strings.HasPrefix(s.network, \"tcp\") {\n\t\tassert.ErrorContains(s.tester, err, \"connection refused\")\n\t} else {\n\t\tassert.NoError(s.tester, err)\n\t\tdefer c.Close() //nolint:errcheck\n\t\t_, err = c.Write(s.data)\n\t\tassert.NoError(s.tester, err)\n\t}\n\n\tif s.broadcastAddr != nil {\n\t\t// Send a packet to the broadcast address, it should reach the server.\n\t\tc6, err := netDial(s.network, s.broadcastAddr)\n\t\tassert.NoError(s.tester, err)\n\t\tdefer c6.Close() //nolint:errcheck\n\t\t_, err = c6.Write(s.data)\n\t\tassert.NoError(s.tester, err)\n\t}\n\n\t// Send a packet to the eth0 interface, it should reach the server.\n\tc4, err := netDial(s.network, s.eth0Addr)\n\tassert.NoError(s.tester, err)\n\tdefer c4.Close() //nolint:errcheck\n\t_, err = c4.Write(s.data)\n\tassert.NoError(s.tester, err)\n\tbuf := make([]byte, len(s.data))\n\t_, err = c4.Read(buf)\n\tassert.NoError(s.tester, err)\n\tassert.EqualValues(s.tester, s.data, buf, len(s.data), len(buf))\n\n\treturn time.Second, Shutdown\n}\n\nfunc TestBindToDevice(t *testing.T) {\n\tif runtime.GOOS != \"linux\" {\n\t\terr := Run(&testBindToDeviceServer[*net.UDPAddr]{}, \"tcp://:9999\", WithBindToDevice(\"eth0\"))\n\t\tassert.ErrorIs(t, err, errorx.ErrUnsupportedOp)\n\t\treturn\n\t}\n\n\tlp, err := findLoopbackInterface()\n\tassert.NoError(t, err)\n\tdev, err := detectLinuxEthernetInterfaceName()\n\tassert.NoErrorf(t, err, \"no testable Ethernet interface found\")\n\tt.Logf(\"detected Ethernet interface: %s\", dev)\n\tdata := []byte(\"hello\")\n\tt.Run(\"IPv4\", func(t *testing.T) {\n\t\tip, err := getInterfaceIP(dev, true)\n\t\tassert.NoError(t, err)\n\t\tt.Run(\"TCP\", func(t *testing.T) {\n\t\t\tts := &testBindToDeviceServer[*net.TCPAddr]{\n\t\t\t\ttester:          t,\n\t\t\t\tdata:            data,\n\t\t\t\texpectedPackets: 1,\n\t\t\t\tnetwork:         \"tcp\",\n\t\t\t\tloopBackAddr:    &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9999, Zone: \"\"},\n\t\t\t\teth0Addr:        &net.TCPAddr{IP: ip, Port: 9999, Zone: \"\"},\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\terr = Run(ts, \"tcp://0.0.0.0:9999\",\n\t\t\t\tWithTicker(true),\n\t\t\t\tWithBindToDevice(dev))\n\t\t\tassert.NoError(t, err)\n\t\t})\n\t\tt.Run(\"UDP\", func(t *testing.T) {\n\t\t\tts := &testBindToDeviceServer[*net.UDPAddr]{\n\t\t\t\ttester:          t,\n\t\t\t\tdata:            data,\n\t\t\t\texpectedPackets: 2,\n\t\t\t\tnetwork:         \"udp\",\n\t\t\t\tloopBackAddr:    &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 9999, Zone: \"\"},\n\t\t\t\teth0Addr:        &net.UDPAddr{IP: ip, Port: 9999, Zone: \"\"},\n\t\t\t\tbroadcastAddr:   &net.UDPAddr{IP: net.IPv4bcast, Port: 9999, Zone: \"\"},\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\terr = Run(ts, \"udp://0.0.0.0:9999\",\n\t\t\t\tWithTicker(true),\n\t\t\t\tWithBindToDevice(dev))\n\t\t\tassert.NoError(t, err)\n\t\t})\n\t})\n\tt.Run(\"IPv6\", func(t *testing.T) {\n\t\tt.Run(\"TCP\", func(t *testing.T) {\n\t\t\tip, err := getInterfaceIP(dev, false)\n\t\t\tassert.NoError(t, err)\n\t\t\tts := &testBindToDeviceServer[*net.TCPAddr]{\n\t\t\t\ttester:          t,\n\t\t\t\tdata:            data,\n\t\t\t\texpectedPackets: 1,\n\t\t\t\tnetwork:         \"tcp6\",\n\t\t\t\tloopBackAddr:    &net.TCPAddr{IP: net.IPv6loopback, Port: 9999, Zone: lp.Name},\n\t\t\t\teth0Addr:        &net.TCPAddr{IP: ip, Port: 9999, Zone: dev},\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\terr = Run(ts, \"tcp6://[::]:9999\",\n\t\t\t\tWithTicker(true),\n\t\t\t\tWithBindToDevice(dev))\n\t\t\tassert.NoError(t, err)\n\t\t})\n\t\tt.Run(\"UDP\", func(t *testing.T) {\n\t\t\tip, err := getInterfaceIP(dev, false)\n\t\t\tassert.NoError(t, err)\n\t\t\tts := &testBindToDeviceServer[*net.UDPAddr]{\n\t\t\t\ttester:          t,\n\t\t\t\tdata:            data,\n\t\t\t\texpectedPackets: 2,\n\t\t\t\tnetwork:         \"udp6\",\n\t\t\t\tloopBackAddr:    &net.UDPAddr{IP: net.IPv6loopback, Port: 9999, Zone: lp.Name},\n\t\t\t\teth0Addr:        &net.UDPAddr{IP: ip, Port: 9999, Zone: dev},\n\t\t\t\tbroadcastAddr:   &net.UDPAddr{IP: net.IPv6linklocalallnodes, Port: 9999, Zone: dev},\n\t\t\t}\n\t\t\tassert.NoError(t, err)\n\t\t\terr = Run(ts, \"udp6://[::]:9999\",\n\t\t\t\tWithTicker(true),\n\t\t\t\tWithBindToDevice(dev))\n\t\t\tassert.NoError(t, err)\n\t\t})\n\t})\n}\n\n/*\nfunc TestEngineAsyncWrite(t *testing.T) {\n\tt.Run(\"tcp\", func(t *testing.T) {\n\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\ttestEngineAsyncWrite(t, \"tcp\", \":18888\", false, false, 10, LeastConnections)\n\t\t})\n\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\ttestEngineAsyncWrite(t, \"tcp\", \":28888\", true, true, 10, RoundRobin)\n\t\t})\n\t})\n\tt.Run(\"unix\", func(t *testing.T) {\n\t\tt.Run(\"1-loop\", func(t *testing.T) {\n\t\t\ttestEngineAsyncWrite(t, \"unix\", \":18888\", false, false, 10, LeastConnections)\n\t\t})\n\t\tt.Run(\"N-loop\", func(t *testing.T) {\n\t\t\ttestEngineAsyncWrite(t, \"unix\", \":28888\", true, true, 10, RoundRobin)\n\t\t})\n\t})\n}\n\ntype testEngineAsyncWriteServer struct {\n\t*BuiltinEventEngine\n\ttester       *testing.T\n\teng          Engine\n\tnetwork      string\n\taddr         string\n\tmulticore    bool\n\twritev       bool\n\tnclients     int\n\tstarted      int32\n\tconnected    int32\n\tclientActive int32\n\tdisconnected int32\n\tworkerPool   *goPool.Pool\n}\n\nfunc (s *testEngineAsyncWriteServer) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\treturn\n}\n\nfunc (s *testEngineAsyncWriteServer) OnOpen(c Conn) (out []byte, action Action) {\n\tc.SetContext(c)\n\tatomic.AddInt32(&s.connected, 1)\n\tout = []byte(\"sweetness\\r\\n\")\n\tassert.NotNil(s.tester, c.LocalAddr(), \"nil local addr\")\n\tassert.NotNil(s.tester, c.RemoteAddr(), \"nil remote addr\")\n\treturn\n}\n\nfunc (s *testEngineAsyncWriteServer) OnClose(c Conn, err error) (action Action) {\n\tif err != nil {\n\t\tlogging.Debugf(\"error occurred on closed, %v\\n\", err)\n\t}\n\tif s.network != \"udp\" {\n\t\tassert.Equal(s.tester, c.Context(), c, \"invalid context\")\n\t}\n\n\tatomic.AddInt32(&s.disconnected, 1)\n\tif atomic.LoadInt32(&s.connected) == atomic.LoadInt32(&s.disconnected) &&\n\t\tatomic.LoadInt32(&s.disconnected) == int32(s.nclients) {\n\t\taction = Shutdown\n\t\ts.workerPool.Release()\n\t}\n\n\treturn\n}\n\nfunc (s *testEngineAsyncWriteServer) OnTraffic(c Conn) (action Action) {\n\tgFD := c.Gfd()\n\n\tbuf := bbPool.Get()\n\t_, _ = c.WriteTo(buf)\n\n\t// just for test\n\t_ = c.InboundBuffered()\n\t_ = c.OutboundBuffered()\n\t_, _ = c.Discard(1)\n\n\t_ = s.workerPool.Submit(\n\t\tfunc() {\n\t\t\tif s.writev {\n\t\t\t\tmid := buf.Len() / 2\n\t\t\t\tbs := make([][]byte, 2)\n\t\t\t\tbs[0] = buf.B[:mid]\n\t\t\t\tbs[1] = buf.B[mid:]\n\t\t\t\t_ = s.eng.AsyncWritev(gFD, bs, func(c Conn, err error) error {\n\t\t\t\t\tif c.RemoteAddr() != nil {\n\t\t\t\t\t\tlogging.Debugf(\"conn=%s done writev: %v\", c.RemoteAddr().String(), err)\n\t\t\t\t\t}\n\t\t\t\t\tbbPool.Put(buf)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\t_ = s.eng.AsyncWrite(gFD, buf.Bytes(), func(c Conn, err error) error {\n\t\t\t\t\tif c.RemoteAddr() != nil {\n\t\t\t\t\t\tlogging.Debugf(\"conn=%s done write: %v\", c.RemoteAddr().String(), err)\n\t\t\t\t\t}\n\t\t\t\t\tbbPool.Put(buf)\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\treturn\n}\n\nfunc (s *testEngineAsyncWriteServer) OnTick() (delay time.Duration, action Action) {\n\tdelay = time.Second / 5\n\tif atomic.CompareAndSwapInt32(&s.started, 0, 1) {\n\t\tfor i := 0; i < s.nclients; i++ {\n\t\t\tatomic.AddInt32(&s.clientActive, 1)\n\t\t\tgo func() {\n\t\t\t\tinitClient(s.tester, s.network, s.addr, s.multicore)\n\t\t\t\tatomic.AddInt32(&s.clientActive, -1)\n\t\t\t}()\n\t\t}\n\t}\n\tif s.network == \"udp\" && atomic.LoadInt32(&s.clientActive) == 0 {\n\t\taction = Shutdown\n\t\treturn\n\t}\n\treturn\n}\n\nfunc testEngineAsyncWrite(t *testing.T, network, addr string, multicore, writev bool, nclients int, lb LoadBalancing) {\n\tts := &testEngineAsyncWriteServer{\n\t\ttester:     t,\n\t\tnetwork:    network,\n\t\taddr:       addr,\n\t\tmulticore:  multicore,\n\t\twritev:     writev,\n\t\tnclients:   nclients,\n\t\tworkerPool: goPool.Default(),\n\t}\n\terr := Run(ts,\n\t\tnetwork+\"://\"+addr,\n\t\tWithMulticore(multicore),\n\t\tWithTicker(true),\n\t\tWithLoadBalancing(lb))\n\tassert.NoError(t, err)\n}\n\nfunc initClient(t *testing.T, network, addr string, multicore bool) {\n\trand.Seed(time.Now().UnixNano())\n\tc, err := net.Dial(network, addr)\n\tassert.NoError(t, err)\n\tdefer c.Close()\n\trd := bufio.NewReader(c)\n\tmsg, err := rd.ReadBytes('\\n')\n\tassert.NoError(t, err)\n\tassert.Equal(t, string(msg), \"sweetness\\r\\n\", \"bad header\")\n\tduration := time.Duration((rand.Float64()*2+1)*float64(time.Second)) / 2\n\tt.Logf(\"test duration: %dms\", duration/time.Millisecond)\n\tstart := time.Now()\n\tfor time.Since(start) < duration {\n\t\treqData := make([]byte, streamLen)\n\t\t_, err = rand.Read(reqData)\n\t\tassert.NoError(t, err)\n\t\t_, err = c.Write(reqData)\n\t\tassert.NoError(t, err)\n\t\trespData := make([]byte, len(reqData))\n\t\t_, err = io.ReadFull(rd, respData)\n\t\tassert.NoError(t, err)\n\t\tassert.Equalf(\n\t\t\tt,\n\t\t\tlen(reqData),\n\t\t\tlen(respData),\n\t\t\t\"response mismatch with protocol:%s, multi-core:%t, length of bytes: %d vs %d\",\n\t\t\tnetwork,\n\t\t\tmulticore,\n\t\t\tlen(reqData),\n\t\t\tlen(respData),\n\t\t)\n\t}\n}\n\nfunc TestEngineWakeConn(t *testing.T) {\n\ttestEngineWakeConn(t, \"tcp\", \":9990\")\n}\n\ntype testEngineWakeConnServer struct {\n\t*BuiltinEventEngine\n\ttester  *testing.T\n\teng     Engine\n\tnetwork string\n\taddr    string\n\tgFD     chan gfd.GFD\n\twake    bool\n}\n\nfunc (t *testEngineWakeConnServer) OnBoot(eng Engine) (action Action) {\n\tt.eng = eng\n\treturn\n}\n\nfunc (t *testEngineWakeConnServer) OnOpen(c Conn) (out []byte, action Action) {\n\tt.gFD <- c.Gfd()\n\treturn\n}\n\nfunc (t *testEngineWakeConnServer) OnClose(Conn, error) (action Action) {\n\taction = Shutdown\n\treturn\n}\n\nfunc (t *testEngineWakeConnServer) OnTraffic(c Conn) (action Action) {\n\t_, _ = c.Write([]byte(\"Waking up.\"))\n\taction = -1\n\treturn\n}\n\nfunc (t *testEngineWakeConnServer) OnTick() (delay time.Duration, action Action) {\n\tif !t.wake {\n\t\tt.wake = true\n\t\tdelay = time.Millisecond * 100\n\t\tgo func() {\n\t\t\tconn, err := net.Dial(t.network, t.addr)\n\t\t\tassert.NoError(t.tester, err)\n\t\t\tdefer conn.Close()\n\t\t\tr := make([]byte, 10)\n\t\t\t_, err = conn.Read(r)\n\t\t\tassert.NoError(t.tester, err)\n\t\t}()\n\t\treturn\n\t}\n\tgFD := <-t.gFD\n\t_ = t.eng.Wake(gFD, func(c Conn, err error) error {\n\t\tlogging.Debugf(\"conn=%s done wake: %v\", c.RemoteAddr().String(), err)\n\t\treturn nil\n\t})\n\tdelay = time.Millisecond * 100\n\treturn\n}\n\nfunc testEngineWakeConn(t *testing.T, network, addr string) {\n\tsvr := &testEngineWakeConnServer{tester: t, network: network, addr: addr, gFD: make(chan gfd.GFD, 1)}\n\tlogger := zap.NewExample()\n\terr := Run(svr, network+\"://\"+addr,\n\t\tWithTicker(true),\n\t\tWithNumEventLoop(2*runtime.NumCPU()),\n\t\tWithLogger(logger.Sugar()),\n\t\tWithSocketRecvBuffer(4*1024),\n\t\tWithSocketSendBuffer(4*1024),\n\t\tWithReadBufferCap(2000),\n\t\tWithWriteBufferCap(2000))\n\tassert.NoError(t, err)\n\t_ = logger.Sync()\n}\n\n// Test should not panic when we wake-up server_closed conn.\nfunc TestEngineClosedWakeUp(t *testing.T) {\n\tevents := &testEngineClosedWakeUpServer{\n\t\ttester:             t,\n\t\tBuiltinEventEngine: &BuiltinEventEngine{}, network: \"tcp\", addr: \":9999\", protoAddr: \"tcp://:9999\",\n\t\tclientClosed: make(chan struct{}),\n\t\tserverClosed: make(chan struct{}),\n\t\twakeup:       make(chan struct{}),\n\t}\n\n\terr := Run(events, events.protoAddr)\n\tassert.NoError(t, err)\n}\n\ntype testEngineClosedWakeUpServer struct {\n\t*BuiltinEventEngine\n\ttester                   *testing.T\n\tnetwork, addr, protoAddr string\n\n\teng Engine\n\n\twakeup       chan struct{}\n\tserverClosed chan struct{}\n\tclientClosed chan struct{}\n}\n\nfunc (s *testEngineClosedWakeUpServer) OnBoot(eng Engine) (action Action) {\n\ts.eng = eng\n\tgo func() {\n\t\tc, err := net.Dial(s.network, s.addr)\n\t\tassert.NoError(s.tester, err)\n\n\t\t_, err = c.Write([]byte(\"hello\"))\n\t\tassert.NoError(s.tester, err)\n\n\t\t<-s.wakeup\n\t\t_, err = c.Write([]byte(\"hello again\"))\n\t\tassert.NoError(s.tester, err)\n\n\t\tclose(s.clientClosed)\n\t\t<-s.serverClosed\n\n\t\tlogging.Debugf(\"stop engine...\", Stop(context.TODO(), s.protoAddr))\n\t}()\n\n\treturn None\n}\n\nfunc (s *testEngineClosedWakeUpServer) OnTraffic(c Conn) Action {\n\tassert.NotNil(s.tester, c.RemoteAddr())\n\n\tselect {\n\tcase <-s.wakeup:\n\tdefault:\n\t\tclose(s.wakeup)\n\t}\n\n\tfd := c.Gfd()\n\n\tgo func() { assert.NoError(s.tester, c.Wake(nil)) }()\n\tgo s.eng.Close(fd, nil)\n\n\t<-s.clientClosed\n\n\t_, _ = c.Write([]byte(\"answer\"))\n\treturn None\n}\n\nfunc (s *testEngineClosedWakeUpServer) OnClose(Conn, error) (action Action) {\n\tselect {\n\tcase <-s.serverClosed:\n\tdefault:\n\t\tclose(s.serverClosed)\n\t}\n\treturn\n}\n*/\n"
  },
  {
    "path": "os_windows_test.go",
    "content": "//go:build windows\n\npackage gnet\n\nimport (\n\t\"syscall\"\n)\n\nfunc SysClose(fd int) error {\n\treturn syscall.CloseHandle(syscall.Handle(fd))\n}\n"
  },
  {
    "path": "pkg/bs/bs.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package bs provides a few handy bytes/string functions.\npackage bs\n\nimport (\n\t\"unsafe\"\n)\n\n// BytesToString converts byte slice to a string without any memory allocation.\nfunc BytesToString(b []byte) string {\n\treturn unsafe.String(unsafe.SliceData(b), len(b))\n}\n\n// StringToBytes converts string to a byte slice without any memory allocation.\nfunc StringToBytes(s string) []byte {\n\treturn unsafe.Slice(unsafe.StringData(s), len(s))\n}\n"
  },
  {
    "path": "pkg/buffer/elastic/elastic_buffer_test.go",
    "content": "package elastic\n\nimport (\n\t\"bytes\"\n\tcrand \"crypto/rand\"\n\t\"math/rand\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMixedBuffer_Basic(t *testing.T) {\n\tconst maxStaticSize = 4 * 1024\n\tmb, _ := New(maxStaticSize)\n\tconst dataLen = 5 * 1024\n\tdata := make([]byte, dataLen)\n\t_, err := crand.Read(data)\n\trequire.NoError(t, err)\n\tn, err := mb.Write(data)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, n)\n\trequire.EqualValues(t, dataLen, mb.Buffered())\n\trequire.EqualValues(t, dataLen, mb.ringBuffer.Buffered())\n\n\trbn := mb.ringBuffer.Len()\n\tmb.Reset(-1)\n\tnewDataLen := rbn + 2*1024\n\tdata = make([]byte, newDataLen)\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\tn, err = mb.Write(data)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, newDataLen, n)\n\trequire.EqualValues(t, newDataLen, mb.Buffered())\n\trequire.EqualValues(t, rbn, mb.ringBuffer.Buffered())\n\n\tbs, err := mb.Peek(-1)\n\trequire.NoError(t, err)\n\tvar p []byte\n\tfor _, b := range bs {\n\t\tp = append(p, b...)\n\t}\n\trequire.EqualValues(t, data, p)\n\n\tbs, err = mb.Peek(rbn)\n\trequire.NoError(t, err)\n\tp = bs[0]\n\trequire.EqualValues(t, data[:rbn], p)\n\tn, err = mb.Discard(rbn)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, rbn, n)\n\trequire.NotNil(t, mb.ringBuffer)\n\tbs, err = mb.Peek(newDataLen - rbn)\n\trequire.NoError(t, err)\n\tp = bs[0]\n\trequire.EqualValues(t, data[rbn:], p)\n\tn, err = mb.Discard(newDataLen - rbn)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, newDataLen-rbn, n)\n\trequire.True(t, mb.IsEmpty())\n\n\truntime.GC() // release ring-buffer from pool.\n\tconst maxBlocks = 100\n\tvar (\n\t\theadCum int\n\t\tcum     int\n\t\tbuf     bytes.Buffer\n\t)\n\tbs = bs[:0]\n\tfor i := 0; i < maxBlocks; i++ {\n\t\tn := rand.Intn(512) + 128\n\t\tcum += n\n\t\tdata := make([]byte, n)\n\t\t_, err := crand.Read(data)\n\t\trequire.NoError(t, err)\n\t\tbuf.Write(data)\n\t\tif i < 3 {\n\t\t\theadCum += n\n\t\t\t_, _ = mb.Write(data)\n\t\t} else {\n\t\t\tbs = append(bs, data)\n\t\t}\n\t}\n\tn, err = mb.Writev(bs)\n\trequire.GreaterOrEqual(t, mb.ringBuffer.Len(), maxStaticSize)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum-headCum, n)\n\trequire.EqualValues(t, cum, mb.Buffered())\n\tbs, err = mb.Peek(-1)\n\trequire.NoError(t, err)\n\tp = p[:0]\n\tfor _, b := range bs {\n\t\tp = append(p, b...)\n\t}\n\trequire.EqualValues(t, buf.Bytes(), p)\n\tp = make([]byte, cum)\n\tn, err = mb.Read(p)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum, n)\n\trequire.EqualValues(t, buf.Bytes(), p)\n\n\trequire.NotNil(t, mb.ringBuffer)\n\trequire.True(t, mb.IsEmpty())\n}\n\nfunc TestMixedBuffer_ReadFrom(t *testing.T) {\n\tconst maxStaticSize = 2 * 1024\n\tmb, _ := New(maxStaticSize)\n\tconst dataLen = 2 * 1024\n\tdata := make([]byte, dataLen)\n\t_, err := crand.Read(data)\n\trequire.NoError(t, err)\n\tr := bytes.NewReader(data)\n\tn, err := mb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, n)\n\trequire.EqualValues(t, dataLen, mb.Buffered())\n\tnewData := make([]byte, dataLen)\n\t_, err = crand.Read(newData)\n\trequire.NoError(t, err)\n\tr.Reset(newData)\n\tn, err = mb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, n)\n\trequire.EqualValues(t, 2*dataLen, mb.Buffered())\n\trequire.False(t, mb.listBuffer.IsEmpty())\n\n\tbuf := make([]byte, dataLen)\n\tvar m int\n\tm, err = mb.Read(buf)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, m)\n\trequire.EqualValues(t, data, buf)\n\tbs, err := mb.Peek(dataLen)\n\trequire.NoError(t, err)\n\tvar p []byte\n\tfor _, b := range bs {\n\t\tp = append(p, b...)\n\t}\n\trequire.EqualValues(t, newData, p)\n\tm, err = mb.Discard(dataLen)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, m)\n\n\trequire.NotNil(t, mb.ringBuffer)\n\trequire.True(t, mb.IsEmpty())\n}\n\nfunc TestMixedBuffer_WriteTo(t *testing.T) {\n\tconst maxStaticSize = 4 * 1024\n\tmb, _ := New(maxStaticSize)\n\tconst maxBlocks = 50\n\tvar (\n\t\theadCum int\n\t\tcum     int\n\t\tbs      [][]byte\n\t\tbuf     bytes.Buffer\n\t)\n\n\tfor i := 0; i < maxBlocks; i++ {\n\t\tn := rand.Intn(512) + 128\n\t\tcum += n\n\t\tdata := make([]byte, n)\n\t\t_, err := crand.Read(data)\n\t\trequire.NoError(t, err)\n\t\tbuf.Write(data)\n\t\tif i < 3 {\n\t\t\theadCum += n\n\t\t\t_, _ = mb.Write(data)\n\t\t} else {\n\t\t\tbs = append(bs, data)\n\t\t}\n\t}\n\tn, err := mb.Writev(bs)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum-headCum, n)\n\trequire.EqualValues(t, cum, mb.Buffered())\n\n\tnewBuf := bytes.NewBuffer(nil)\n\tvar m int64\n\tm, err = mb.WriteTo(newBuf)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum, m)\n\trequire.EqualValues(t, buf.Bytes(), newBuf.Bytes())\n\n\trequire.NotNil(t, mb.ringBuffer)\n\trequire.True(t, mb.IsEmpty())\n}\n"
  },
  {
    "path": "pkg/buffer/elastic/elastic_ring_buffer.go",
    "content": "// Copyright (c) 2022 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package elastic implements an elastic ring-buffer.\npackage elastic\n\nimport (\n\t\"io\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/ring\"\n\trbPool \"github.com/panjf2000/gnet/v2/pkg/pool/ringbuffer\"\n)\n\n// RingBuffer is the elastic wrapper of ring.Buffer.\ntype RingBuffer struct {\n\trb *ring.Buffer\n}\n\nfunc (b *RingBuffer) instance() *ring.Buffer {\n\tif b.rb == nil {\n\t\tb.rb = rbPool.Get()\n\t}\n\n\treturn b.rb\n}\n\n// Done checks and returns the internal ring-buffer to pool.\nfunc (b *RingBuffer) Done() {\n\tif b.rb != nil {\n\t\trbPool.Put(b.rb)\n\t\tb.rb = nil\n\t}\n}\n\nfunc (b *RingBuffer) done() {\n\tif b.rb != nil && b.rb.IsEmpty() {\n\t\trbPool.Put(b.rb)\n\t\tb.rb = nil\n\t}\n}\n\n// Peek returns the next n bytes without advancing the read pointer,\n// it returns all bytes when n <= 0.\nfunc (b *RingBuffer) Peek(n int) (head []byte, tail []byte) {\n\tif b.rb == nil {\n\t\treturn nil, nil\n\t}\n\treturn b.rb.Peek(n)\n}\n\n// Discard skips the next n bytes by advancing the read pointer.\nfunc (b *RingBuffer) Discard(n int) (int, error) {\n\tif b.rb == nil {\n\t\treturn 0, ring.ErrIsEmpty\n\t}\n\n\tdefer b.done()\n\treturn b.rb.Discard(n)\n}\n\n// Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error\n// encountered.\n// Even if Read returns n < len(p), it may use all of p as scratch space during the call.\n// If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting\n// for more.\n// When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes,\n// it returns the number of bytes read. It may return the (non-nil) error from the same call or return the\n// error (and n == 0) from a subsequent call.\n// Callers should always process the n > 0 bytes returned before considering the error err.\n// Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF\n// behaviors.\nfunc (b *RingBuffer) Read(p []byte) (int, error) {\n\tif b.rb == nil {\n\t\treturn 0, ring.ErrIsEmpty\n\t}\n\n\tdefer b.done()\n\treturn b.rb.Read(p)\n}\n\n// ReadByte reads and returns the next byte from the input or ErrIsEmpty.\nfunc (b *RingBuffer) ReadByte() (byte, error) {\n\tif b.rb == nil {\n\t\treturn 0, ring.ErrIsEmpty\n\t}\n\n\tdefer b.done()\n\treturn b.rb.ReadByte()\n}\n\n// Write writes len(p) bytes from p to the underlying buf.\n// It returns the number of bytes written from p (n == len(p) > 0) and any error encountered that caused the write to\n// stop early.\n// If the length of p is greater than the writable capacity of this ring-buffer, it will allocate more memory to\n// this ring-buffer.\n// Write must not modify the slice data, even temporarily.\nfunc (b *RingBuffer) Write(p []byte) (int, error) {\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\treturn b.instance().Write(p)\n}\n\n// WriteByte writes one byte into buffer.\nfunc (b *RingBuffer) WriteByte(c byte) error {\n\treturn b.instance().WriteByte(c)\n}\n\n// Buffered returns the length of available bytes to read.\nfunc (b *RingBuffer) Buffered() int {\n\tif b.rb == nil {\n\t\treturn 0\n\t}\n\treturn b.rb.Buffered()\n}\n\n// Len returns the length of the underlying buffer.\nfunc (b *RingBuffer) Len() int {\n\tif b.rb == nil {\n\t\treturn 0\n\t}\n\treturn b.rb.Len()\n}\n\n// Cap returns the size of the underlying buffer.\nfunc (b *RingBuffer) Cap() int {\n\tif b.rb == nil {\n\t\treturn 0\n\t}\n\treturn b.rb.Cap()\n}\n\n// Available returns the length of available bytes to write.\nfunc (b *RingBuffer) Available() int {\n\tif b.rb == nil {\n\t\treturn 0\n\t}\n\treturn b.rb.Available()\n}\n\n// WriteString writes the contents of the string s to buffer, which accepts a slice of bytes.\nfunc (b *RingBuffer) WriteString(s string) (int, error) {\n\tif len(s) == 0 {\n\t\treturn 0, nil\n\t}\n\treturn b.instance().WriteString(s)\n}\n\n// Bytes returns all available read bytes. It does not move the read pointer and only copy the available data.\nfunc (b *RingBuffer) Bytes() []byte {\n\tif b.rb == nil {\n\t\treturn nil\n\t}\n\treturn b.rb.Bytes()\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (b *RingBuffer) ReadFrom(r io.Reader) (int64, error) {\n\treturn b.instance().ReadFrom(r)\n}\n\n// WriteTo implements io.WriterTo.\nfunc (b *RingBuffer) WriteTo(w io.Writer) (int64, error) {\n\tif b.rb == nil {\n\t\treturn 0, ring.ErrIsEmpty\n\t}\n\n\tdefer b.done()\n\treturn b.instance().WriteTo(w)\n}\n\n// IsFull tells if this ring-buffer is full.\nfunc (b *RingBuffer) IsFull() bool {\n\tif b.rb == nil {\n\t\treturn false\n\t}\n\treturn b.rb.IsFull()\n}\n\n// IsEmpty tells if this ring-buffer is empty.\nfunc (b *RingBuffer) IsEmpty() bool {\n\tif b.rb == nil {\n\t\treturn true\n\t}\n\treturn b.rb.IsEmpty()\n}\n\n// Reset the read pointer and write pointer to zero.\nfunc (b *RingBuffer) Reset() {\n\tif b.rb == nil {\n\t\treturn\n\t}\n\tb.rb.Reset()\n}\n"
  },
  {
    "path": "pkg/buffer/elastic/elastic_ring_list_buffer.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage elastic\n\nimport (\n\t\"io\"\n\t\"math\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/linkedlist\"\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\n// Buffer combines ring-buffer and list-buffer.\n// Ring-buffer is the top-priority buffer to store response data, gnet will only switch to\n// list-buffer if the data size of ring-buffer reaches the maximum(MaxStackingBytes), list-buffer is more\n// flexible and scalable, which helps the application reduce memory footprint.\ntype Buffer struct {\n\tmaxStaticBytes int\n\tringBuffer     RingBuffer\n\tlistBuffer     linkedlist.Buffer\n}\n\n// New instantiates an elastic.Buffer and returns it.\nfunc New(maxStaticBytes int) (*Buffer, error) {\n\tif maxStaticBytes <= 0 {\n\t\treturn nil, errorx.ErrNegativeSize\n\t}\n\treturn &Buffer{maxStaticBytes: maxStaticBytes}, nil\n}\n\n// Read reads data from the Buffer.\nfunc (mb *Buffer) Read(p []byte) (n int, err error) {\n\tn, err = mb.ringBuffer.Read(p)\n\tif n == len(p) {\n\t\treturn n, err\n\t}\n\tvar m int\n\tm, err = mb.listBuffer.Read(p[n:])\n\tn += m\n\treturn\n}\n\n// Peek returns n bytes as [][]byte, these bytes won't be discarded until Buffer.Discard() is called.\nfunc (mb *Buffer) Peek(n int) ([][]byte, error) {\n\tif n <= 0 || n == math.MaxInt32 {\n\t\tn = math.MaxInt32\n\t} else if n > mb.Buffered() {\n\t\treturn nil, io.ErrShortBuffer\n\t}\n\thead, tail := mb.ringBuffer.Peek(n)\n\tif mb.ringBuffer.Buffered() == n {\n\t\treturn [][]byte{head, tail}, nil\n\t}\n\treturn mb.listBuffer.PeekWithBytes(n, head, tail)\n}\n\n// Discard discards n bytes in this buffer.\nfunc (mb *Buffer) Discard(n int) (discarded int, err error) {\n\tdiscarded, err = mb.ringBuffer.Discard(n)\n\tif n <= discarded {\n\t\treturn\n\t}\n\n\tn -= discarded\n\tvar m int\n\tm, err = mb.listBuffer.Discard(n)\n\tdiscarded += m\n\treturn\n}\n\n// Write appends data to this buffer.\nfunc (mb *Buffer) Write(p []byte) (n int, err error) {\n\tif !mb.listBuffer.IsEmpty() || mb.ringBuffer.Buffered() >= mb.maxStaticBytes {\n\t\tmb.listBuffer.PushBack(p)\n\t\treturn len(p), nil\n\t}\n\tif mb.ringBuffer.Len() >= mb.maxStaticBytes {\n\t\twritable := mb.ringBuffer.Available()\n\t\tif n = len(p); n > writable {\n\t\t\t_, _ = mb.ringBuffer.Write(p[:writable])\n\t\t\tmb.listBuffer.PushBack(p[writable:])\n\t\t\treturn\n\t\t}\n\t}\n\treturn mb.ringBuffer.Write(p)\n}\n\n// Writev appends multiple byte slices to this buffer.\nfunc (mb *Buffer) Writev(bs [][]byte) (int, error) {\n\tif !mb.listBuffer.IsEmpty() || mb.ringBuffer.Buffered() >= mb.maxStaticBytes {\n\t\tvar n int\n\t\tfor _, b := range bs {\n\t\t\tmb.listBuffer.PushBack(b)\n\t\t\tn += len(b)\n\t\t}\n\t\treturn n, nil\n\t}\n\n\twritable := mb.ringBuffer.Available()\n\tif mb.ringBuffer.Len() < mb.maxStaticBytes {\n\t\twritable = mb.maxStaticBytes - mb.ringBuffer.Buffered()\n\t}\n\tvar pos, cum int\n\tfor i, b := range bs {\n\t\tpos = i\n\t\tcum += len(b)\n\t\tif len(b) > writable {\n\t\t\t_, _ = mb.ringBuffer.Write(b[:writable])\n\t\t\tmb.listBuffer.PushBack(b[writable:])\n\t\t\tbreak\n\t\t}\n\t\tn, _ := mb.ringBuffer.Write(b)\n\t\twritable -= n\n\t}\n\tfor pos++; pos < len(bs); pos++ {\n\t\tcum += len(bs[pos])\n\t\tmb.listBuffer.PushBack(bs[pos])\n\t}\n\treturn cum, nil\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (mb *Buffer) ReadFrom(r io.Reader) (int64, error) {\n\tif !mb.listBuffer.IsEmpty() || mb.ringBuffer.Buffered() >= mb.maxStaticBytes {\n\t\treturn mb.listBuffer.ReadFrom(r)\n\t}\n\treturn mb.ringBuffer.ReadFrom(r)\n}\n\n// WriteTo implements io.WriterTo.\nfunc (mb *Buffer) WriteTo(w io.Writer) (n int64, err error) {\n\tif n, err = mb.ringBuffer.WriteTo(w); err != nil {\n\t\treturn\n\t}\n\tvar m int64\n\tm, err = mb.listBuffer.WriteTo(w)\n\tn += m\n\treturn\n}\n\n// Buffered returns the number of bytes that can be read from the current buffer.\nfunc (mb *Buffer) Buffered() int {\n\treturn mb.ringBuffer.Buffered() + mb.listBuffer.Buffered()\n}\n\n// IsEmpty indicates whether this buffer is empty.\nfunc (mb *Buffer) IsEmpty() bool {\n\treturn mb.ringBuffer.IsEmpty() && mb.listBuffer.IsEmpty()\n}\n\n// Reset resets the buffer.\nfunc (mb *Buffer) Reset(maxStaticBytes int) {\n\tmb.ringBuffer.Reset()\n\tmb.listBuffer.Reset()\n\tif maxStaticBytes > 0 {\n\t\tmb.maxStaticBytes = maxStaticBytes\n\t}\n}\n\n// Release frees all resource of this buffer.\nfunc (mb *Buffer) Release() {\n\tmb.ringBuffer.Done()\n\tmb.listBuffer.Reset()\n}\n"
  },
  {
    "path": "pkg/buffer/linkedlist/linked_list_buffer.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package linkedlist implements a memory-reusable linked list of byte slices.\npackage linkedlist\n\nimport (\n\t\"io\"\n\t\"math\"\n\n\tbsPool \"github.com/panjf2000/gnet/v2/pkg/pool/byteslice\"\n)\n\ntype node struct {\n\tbuf  []byte\n\tnext *node\n}\n\nfunc (b *node) len() int {\n\treturn len(b.buf)\n}\n\n// Buffer is a linked list of node.\ntype Buffer struct {\n\thead  *node\n\ttail  *node\n\tsize  int\n\tbytes int\n}\n\n// Read reads data from the Buffer.\nfunc (llb *Buffer) Read(p []byte) (n int, err error) {\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\n\tfor b := llb.pop(); b != nil; b = llb.pop() {\n\t\tm := copy(p[n:], b.buf)\n\t\tn += m\n\t\tif m < b.len() {\n\t\t\tb.buf = b.buf[m:]\n\t\t\tllb.pushFront(b)\n\t\t} else {\n\t\t\tbsPool.Put(b.buf)\n\t\t}\n\t\tif n == len(p) {\n\t\t\treturn\n\t\t}\n\t}\n\tif n == 0 {\n\t\terr = io.EOF\n\t}\n\treturn\n}\n\n// AllocNode allocates a []byte with the given length that is expected to\n// be pushed into the Buffer.\nfunc (llb *Buffer) AllocNode(n int) []byte {\n\treturn bsPool.Get(n)\n}\n\n// FreeNode puts the given []byte back to the pool to free the memory.\nfunc (llb *Buffer) FreeNode(p []byte) {\n\tbsPool.Put(p)\n}\n\n// Append is like PushBack but appends b without copying it.\nfunc (llb *Buffer) Append(p []byte) {\n\tn := len(p)\n\tif n == 0 {\n\t\treturn\n\t}\n\tllb.pushBack(&node{buf: p})\n}\n\n// Pop removes and returns the buffer of the head or nil if the list is empty.\nfunc (llb *Buffer) Pop() []byte {\n\tn := llb.pop()\n\tif n == nil {\n\t\treturn nil\n\t}\n\treturn n.buf\n}\n\n// PushFront is a wrapper of pushFront, which accepts []byte as its argument.\nfunc (llb *Buffer) PushFront(p []byte) {\n\tn := len(p)\n\tif n == 0 {\n\t\treturn\n\t}\n\tb := bsPool.Get(n)\n\tcopy(b, p)\n\tllb.pushFront(&node{buf: b})\n}\n\n// PushBack is a wrapper of pushBack, which accepts []byte as its argument.\nfunc (llb *Buffer) PushBack(p []byte) {\n\tn := len(p)\n\tif n == 0 {\n\t\treturn\n\t}\n\tb := bsPool.Get(n)\n\tcopy(b, p)\n\tllb.pushBack(&node{buf: b})\n}\n\n// Peek assembles the up to maxBytes of [][]byte based on the list of node,\n// it won't remove these nodes from l until Discard() is called.\nfunc (llb *Buffer) Peek(maxBytes int) ([][]byte, error) {\n\tif maxBytes <= 0 || maxBytes == math.MaxInt32 {\n\t\tmaxBytes = math.MaxInt32\n\t} else if maxBytes > llb.Buffered() {\n\t\treturn nil, io.ErrShortBuffer\n\t}\n\tvar bs [][]byte\n\tvar cum int\n\tfor iter := llb.head; iter != nil; iter = iter.next {\n\t\toffset := iter.len()\n\t\tif cum+offset > maxBytes {\n\t\t\toffset = maxBytes - cum\n\t\t}\n\t\tbs = append(bs, iter.buf[:offset])\n\t\tif cum += offset; cum == maxBytes {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn bs, nil\n}\n\n// PeekWithBytes is like Peek but accepts [][]byte and puts them onto head.\nfunc (llb *Buffer) PeekWithBytes(maxBytes int, bs ...[]byte) ([][]byte, error) {\n\tif maxBytes <= 0 || maxBytes == math.MaxInt32 {\n\t\tmaxBytes = math.MaxInt32\n\t} else if maxBytes > llb.Buffered() {\n\t\treturn nil, io.ErrShortBuffer\n\t}\n\tvar bss [][]byte\n\tvar cum int\n\tfor _, b := range bs {\n\t\tif n := len(b); n > 0 {\n\t\t\toffset := n\n\t\t\tif cum+offset > maxBytes {\n\t\t\t\toffset = maxBytes - cum\n\t\t\t}\n\t\t\tbss = append(bss, b[:offset])\n\t\t\tif cum += offset; cum == maxBytes {\n\t\t\t\treturn bss, nil\n\t\t\t}\n\t\t}\n\t}\n\tfor iter := llb.head; iter != nil; iter = iter.next {\n\t\toffset := iter.len()\n\t\tif cum+offset > maxBytes {\n\t\t\toffset = maxBytes - cum\n\t\t}\n\t\tbss = append(bss, iter.buf[:offset])\n\t\tif cum += offset; cum == maxBytes {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn bss, nil\n}\n\n// Discard removes some nodes based on n bytes.\nfunc (llb *Buffer) Discard(n int) (discarded int, err error) {\n\tif n <= 0 {\n\t\treturn\n\t}\n\tfor n != 0 {\n\t\tb := llb.pop()\n\t\tif b == nil {\n\t\t\tbreak\n\t\t}\n\t\tif n < b.len() {\n\t\t\tb.buf = b.buf[n:]\n\t\t\tdiscarded += n\n\t\t\tllb.pushFront(b)\n\t\t\tbreak\n\t\t}\n\t\tn -= b.len()\n\t\tdiscarded += b.len()\n\t\tbsPool.Put(b.buf)\n\t}\n\treturn\n}\n\nconst minRead = 512\n\n// ReadFrom implements io.ReaderFrom.\nfunc (llb *Buffer) ReadFrom(r io.Reader) (n int64, err error) {\n\tvar m int\n\tfor {\n\t\tb := bsPool.Get(minRead)\n\t\tm, err = r.Read(b)\n\t\tif m < 0 {\n\t\t\tpanic(\"Buffer.ReadFrom: reader returned negative count from Read\")\n\t\t}\n\t\tn += int64(m)\n\t\tb = b[:m]\n\t\tif err == io.EOF {\n\t\t\tbsPool.Put(b)\n\t\t\treturn n, nil\n\t\t}\n\t\tif err != nil {\n\t\t\tbsPool.Put(b)\n\t\t\treturn\n\t\t}\n\t\tllb.pushBack(&node{buf: b})\n\t}\n}\n\n// WriteTo implements io.WriterTo.\nfunc (llb *Buffer) WriteTo(w io.Writer) (n int64, err error) {\n\tvar m int\n\tfor b := llb.pop(); b != nil; b = llb.pop() {\n\t\tm, err = w.Write(b.buf)\n\t\tif m > b.len() {\n\t\t\tpanic(\"Buffer.WriteTo: invalid Write count\")\n\t\t}\n\t\tn += int64(m)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif m < b.len() {\n\t\t\tb.buf = b.buf[m:]\n\t\t\tllb.pushFront(b)\n\t\t\treturn n, io.ErrShortWrite\n\t\t}\n\t\tbsPool.Put(b.buf)\n\t}\n\treturn\n}\n\n// Len returns the length of the list.\nfunc (llb *Buffer) Len() int {\n\treturn llb.size\n}\n\n// Buffered returns the number of bytes that can be read from the current buffer.\nfunc (llb *Buffer) Buffered() int {\n\treturn llb.bytes\n}\n\n// IsEmpty reports whether l is empty.\nfunc (llb *Buffer) IsEmpty() bool {\n\treturn llb.head == nil\n}\n\n// Reset removes all elements from this list.\nfunc (llb *Buffer) Reset() {\n\tfor b := llb.pop(); b != nil; b = llb.pop() {\n\t\tbsPool.Put(b.buf)\n\t}\n\tllb.head = nil\n\tllb.tail = nil\n\tllb.size = 0\n\tllb.bytes = 0\n}\n\n// pop returns and removes the head of l. If l is empty, it returns nil.\nfunc (llb *Buffer) pop() *node {\n\tif llb.head == nil {\n\t\treturn nil\n\t}\n\tb := llb.head\n\tllb.head = b.next\n\tif llb.head == nil {\n\t\tllb.tail = nil\n\t}\n\tb.next = nil\n\tllb.size--\n\tllb.bytes -= b.len()\n\treturn b\n}\n\n// pushFront adds the new node to the head of l.\nfunc (llb *Buffer) pushFront(b *node) {\n\tif b == nil {\n\t\treturn\n\t}\n\tif llb.head == nil {\n\t\tb.next = nil\n\t\tllb.tail = b\n\t} else {\n\t\tb.next = llb.head\n\t}\n\tllb.head = b\n\tllb.size++\n\tllb.bytes += b.len()\n}\n\n// pushBack adds a new node to the tail of l.\nfunc (llb *Buffer) pushBack(b *node) {\n\tif b == nil {\n\t\treturn\n\t}\n\tif llb.tail == nil {\n\t\tllb.head = b\n\t} else {\n\t\tllb.tail.next = b\n\t}\n\tb.next = nil\n\tllb.tail = b\n\tllb.size++\n\tllb.bytes += b.len()\n}\n"
  },
  {
    "path": "pkg/buffer/linkedlist/llbuffer_test.go",
    "content": "package linkedlist\n\nimport (\n\t\"bytes\"\n\tcrand \"crypto/rand\"\n\t\"math/rand\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLinkedListBuffer_Basic(t *testing.T) {\n\tconst maxBlocks = 100\n\tvar (\n\t\tllb Buffer\n\t\tcum int\n\t\tbuf bytes.Buffer\n\t)\n\tfor i := 0; i < maxBlocks; i++ {\n\t\tn := rand.Intn(1024) + 128\n\t\tcum += n\n\t\tdata := make([]byte, n)\n\t\t_, err := crand.Read(data)\n\t\trequire.NoError(t, err)\n\t\tllb.PushBack(data)\n\t\tbuf.Write(data)\n\t}\n\trequire.EqualValues(t, maxBlocks, llb.Len())\n\trequire.EqualValues(t, cum, llb.Buffered())\n\n\tbs, err := llb.Peek(cum / 4)\n\trequire.NoError(t, err)\n\tvar p []byte\n\tfor _, b := range bs {\n\t\tp = append(p, b...)\n\t}\n\tpn := len(p)\n\trequire.EqualValues(t, pn, cum/4)\n\trequire.EqualValues(t, buf.Bytes()[:pn], p)\n\ttmpA := make([]byte, cum/16)\n\ttmpB := make([]byte, cum/16)\n\t_, err = crand.Read(tmpA)\n\trequire.NoError(t, err)\n\t_, err = crand.Read(tmpB)\n\trequire.NoError(t, err)\n\tbs, err = llb.PeekWithBytes(cum/4, tmpA, tmpB)\n\trequire.NoError(t, err)\n\tp = p[:0]\n\tfor _, b := range bs {\n\t\tp = append(p, b...)\n\t}\n\tpn = len(p)\n\trequire.EqualValues(t, pn, cum/4)\n\tvar tmpBuf bytes.Buffer\n\ttmpBuf.Write(tmpA)\n\ttmpBuf.Write(tmpB)\n\ttmpBuf.Write(buf.Bytes()[:pn-len(tmpA)-len(tmpB)])\n\trequire.EqualValues(t, tmpBuf.Bytes(), p)\n\n\tpn, _ = llb.Discard(pn)\n\tbuf.Next(pn)\n\tp = make([]byte, cum-pn)\n\tn, err := llb.Read(p)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum-pn, n)\n\trequire.EqualValues(t, buf.Bytes(), p)\n\trequire.True(t, llb.IsEmpty())\n}\n\nfunc TestLinkedListBuffer_ReadFrom(t *testing.T) {\n\tvar llb Buffer\n\tconst dataLen = 4 * 1024\n\tdata := make([]byte, dataLen)\n\t_, err := crand.Read(data)\n\trequire.NoError(t, err)\n\tr := bytes.NewReader(data)\n\tn, err := llb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, n)\n\trequire.EqualValues(t, dataLen, llb.Buffered())\n\n\tllb.Reset()\n\tconst headLen = 256\n\thead := make([]byte, headLen)\n\t_, err = crand.Read(head)\n\trequire.NoError(t, err)\n\tllb.PushBack(head)\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\tr.Reset(data)\n\tn, err = llb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, dataLen, n)\n\trequire.EqualValues(t, headLen+dataLen, llb.Buffered())\n\tbuf := make([]byte, headLen+dataLen)\n\tvar m int\n\tm, err = llb.Read(buf)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, headLen+dataLen, m)\n\trequire.EqualValues(t, append(head, data...), buf)\n\trequire.True(t, llb.IsEmpty())\n}\n\nfunc TestLinkedListBuffer_WriteTo(t *testing.T) {\n\tconst maxBlocks = 20\n\tvar (\n\t\tllb Buffer\n\t\tcum int\n\t\tbuf bytes.Buffer\n\t)\n\tfor i := 0; i < maxBlocks; i++ {\n\t\tn := rand.Intn(1024) + 128\n\t\tcum += n\n\t\tdata := make([]byte, n)\n\t\t_, err := crand.Read(data)\n\t\trequire.NoError(t, err)\n\t\tllb.PushBack(data)\n\t\tbuf.Write(data)\n\t}\n\trequire.EqualValues(t, maxBlocks, llb.Len())\n\trequire.EqualValues(t, cum, llb.Buffered())\n\n\tnewBuf := bytes.NewBuffer(nil)\n\tn, err := llb.WriteTo(newBuf)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum, n)\n\trequire.EqualValues(t, buf.Bytes(), newBuf.Bytes())\n\n\tllb.Reset()\n\tbuf.Reset()\n\tnewBuf.Reset()\n\tcum = 0\n\tfor i := 0; i < maxBlocks; i++ {\n\t\tn := rand.Intn(1024) + 128\n\t\tcum += n\n\t\tdata := make([]byte, n)\n\t\t_, err := crand.Read(data)\n\t\trequire.NoError(t, err)\n\t\tllb.PushBack(data)\n\t\tbuf.Write(data)\n\t}\n\trequire.EqualValues(t, maxBlocks, llb.Len())\n\trequire.EqualValues(t, cum, llb.Buffered())\n\n\tvar discarded int\n\tdiscarded, err = llb.Discard(cum / 2)\n\trequire.NoError(t, err)\n\tbuf.Next(discarded)\n\tn, err = llb.WriteTo(newBuf)\n\trequire.NoError(t, err)\n\trequire.EqualValues(t, cum-discarded, n)\n\trequire.EqualValues(t, buf.Bytes(), newBuf.Bytes())\n\tllb.Reset()\n\tbuf.Reset()\n\tnewBuf.Reset()\n}\n"
  },
  {
    "path": "pkg/buffer/ring/ring_buffer.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n// Copyright (c) 2019 Chao yuepan, Allen Xu\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n\n// Package ring implements a memory-efficient circular buffer.\npackage ring\n\nimport (\n\t\"errors\"\n\t\"io\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/bs\"\n\t\"github.com/panjf2000/gnet/v2/pkg/math\"\n\tbsPool \"github.com/panjf2000/gnet/v2/pkg/pool/byteslice\"\n)\n\nconst (\n\t// MinRead is the minimum slice size passed to a Read call by\n\t// Buffer.ReadFrom. As long as the Buffer has at least MinRead bytes beyond\n\t// what is required to hold the contents of r, ReadFrom will not grow the\n\t// underlying buffer.\n\tMinRead = 512\n\t// DefaultBufferSize is the first-time allocation on a ring-buffer.\n\tDefaultBufferSize   = 1024     // 1KB\n\tbufferGrowThreshold = 4 * 1024 // 4KB\n)\n\n// ErrIsEmpty will be returned when trying to read an empty ring-buffer.\nvar ErrIsEmpty = errors.New(\"ring-buffer is empty\")\n\n// Buffer is a circular buffer that implement io.ReaderWriter interface.\ntype Buffer struct {\n\tbuf     []byte\n\tsize    int\n\tr       int // next position to read\n\tw       int // next position to write\n\tisEmpty bool\n}\n\n// New returns a new Buffer whose buffer has the given size.\nfunc New(size int) *Buffer {\n\tif size == 0 {\n\t\treturn &Buffer{isEmpty: true}\n\t}\n\tsize = math.CeilToPowerOfTwo(size)\n\treturn &Buffer{\n\t\tbuf:     make([]byte, size),\n\t\tsize:    size,\n\t\tisEmpty: true,\n\t}\n}\n\n// Peek returns the next n bytes without advancing the read pointer,\n// it returns all bytes when n <= 0.\nfunc (rb *Buffer) Peek(n int) (head []byte, tail []byte) {\n\tif rb.isEmpty {\n\t\treturn\n\t}\n\n\tif n <= 0 {\n\t\treturn rb.peekAll()\n\t}\n\n\tif rb.w > rb.r {\n\t\tm := rb.w - rb.r // length of ring-buffer\n\t\tif m > n {\n\t\t\tm = n\n\t\t}\n\t\thead = rb.buf[rb.r : rb.r+m]\n\t\treturn\n\t}\n\n\tm := rb.size - rb.r + rb.w // length of ring-buffer\n\tif m > n {\n\t\tm = n\n\t}\n\n\tif rb.r+m <= rb.size {\n\t\thead = rb.buf[rb.r : rb.r+m]\n\t} else {\n\t\tc1 := rb.size - rb.r\n\t\thead = rb.buf[rb.r:]\n\t\tc2 := m - c1\n\t\ttail = rb.buf[:c2]\n\t}\n\n\treturn\n}\n\n// peekAll returns all bytes without advancing the read pointer.\nfunc (rb *Buffer) peekAll() (head []byte, tail []byte) {\n\tif rb.isEmpty {\n\t\treturn\n\t}\n\n\tif rb.w > rb.r {\n\t\thead = rb.buf[rb.r:rb.w]\n\t\treturn\n\t}\n\n\thead = rb.buf[rb.r:]\n\tif rb.w != 0 {\n\t\ttail = rb.buf[:rb.w]\n\t}\n\n\treturn\n}\n\n// Discard skips the next n bytes by advancing the read pointer.\nfunc (rb *Buffer) Discard(n int) (discarded int, err error) {\n\tif n <= 0 {\n\t\treturn 0, nil\n\t}\n\n\tdiscarded = rb.Buffered()\n\tif n < discarded {\n\t\trb.r = (rb.r + n) % rb.size\n\t\treturn n, nil\n\t}\n\trb.Reset()\n\treturn\n}\n\n// Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error\n// encountered.\n// Even if Read returns n < len(p), it may use all of p as scratch space during the call.\n// If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting\n// for more.\n// When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes,\n// it returns the number of bytes read. It may return the (non-nil) error from the same call or return the\n// error (and n == 0) from a subsequent call.\n// Callers should always process the n > 0 bytes returned before considering the error err.\n// Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF\n// behaviors.\nfunc (rb *Buffer) Read(p []byte) (n int, err error) {\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\n\tif rb.isEmpty {\n\t\treturn 0, ErrIsEmpty\n\t}\n\n\tif rb.w > rb.r {\n\t\tn = rb.w - rb.r\n\t\tif n > len(p) {\n\t\t\tn = len(p)\n\t\t}\n\t\tcopy(p, rb.buf[rb.r:rb.r+n])\n\t\trb.r += n\n\t\tif rb.r == rb.w {\n\t\t\trb.Reset()\n\t\t}\n\t\treturn\n\t}\n\n\tn = rb.size - rb.r + rb.w\n\tif n > len(p) {\n\t\tn = len(p)\n\t}\n\n\tif rb.r+n <= rb.size {\n\t\tcopy(p, rb.buf[rb.r:rb.r+n])\n\t} else {\n\t\tc1 := rb.size - rb.r\n\t\tcopy(p, rb.buf[rb.r:])\n\t\tc2 := n - c1\n\t\tcopy(p[c1:], rb.buf[:c2])\n\t}\n\trb.r = (rb.r + n) % rb.size\n\tif rb.r == rb.w {\n\t\trb.Reset()\n\t}\n\n\treturn\n}\n\n// ReadByte reads and returns the next byte from the input or ErrIsEmpty.\nfunc (rb *Buffer) ReadByte() (b byte, err error) {\n\tif rb.isEmpty {\n\t\treturn 0, ErrIsEmpty\n\t}\n\tb = rb.buf[rb.r]\n\trb.r++\n\tif rb.r == rb.size {\n\t\trb.r = 0\n\t}\n\tif rb.r == rb.w {\n\t\trb.Reset()\n\t}\n\n\treturn\n}\n\n// Write writes len(p) bytes from p to the underlying buf.\n// It returns the number of bytes written from p (n == len(p) > 0) and any error encountered that caused the write to\n// stop early.\n// If the length of p is greater than the writable capacity of this ring-buffer, it will allocate more memory to\n// this ring-buffer.\n// Write must not modify the slice data, even temporarily.\nfunc (rb *Buffer) Write(p []byte) (n int, err error) {\n\tn = len(p)\n\tif n == 0 {\n\t\treturn\n\t}\n\n\tfree := rb.Available()\n\tif n > free {\n\t\trb.grow(rb.size + n - free)\n\t}\n\n\tif rb.w >= rb.r {\n\t\tc1 := rb.size - rb.w\n\t\tif c1 >= n {\n\t\t\tcopy(rb.buf[rb.w:], p)\n\t\t\trb.w += n\n\t\t} else {\n\t\t\tcopy(rb.buf[rb.w:], p[:c1])\n\t\t\tc2 := n - c1\n\t\t\tcopy(rb.buf, p[c1:])\n\t\t\trb.w = c2\n\t\t}\n\t} else {\n\t\tcopy(rb.buf[rb.w:], p)\n\t\trb.w += n\n\t}\n\n\tif rb.w == rb.size {\n\t\trb.w = 0\n\t}\n\n\trb.isEmpty = false\n\n\treturn\n}\n\n// WriteByte writes one byte into buffer.\nfunc (rb *Buffer) WriteByte(c byte) error {\n\tif rb.Available() < 1 {\n\t\trb.grow(1)\n\t}\n\trb.buf[rb.w] = c\n\trb.w++\n\n\tif rb.w == rb.size {\n\t\trb.w = 0\n\t}\n\trb.isEmpty = false\n\n\treturn nil\n}\n\n// Buffered returns the length of available bytes to read.\nfunc (rb *Buffer) Buffered() int {\n\tif rb.r == rb.w {\n\t\tif rb.isEmpty {\n\t\t\treturn 0\n\t\t}\n\t\treturn rb.size\n\t}\n\n\tif rb.w > rb.r {\n\t\treturn rb.w - rb.r\n\t}\n\n\treturn rb.size - rb.r + rb.w\n}\n\n// Len returns the length of the underlying buffer.\nfunc (rb *Buffer) Len() int {\n\treturn len(rb.buf)\n}\n\n// Cap returns the size of the underlying buffer.\nfunc (rb *Buffer) Cap() int {\n\treturn rb.size\n}\n\n// Available returns the length of available bytes to write.\nfunc (rb *Buffer) Available() int {\n\tif rb.r == rb.w {\n\t\tif rb.isEmpty {\n\t\t\treturn rb.size\n\t\t}\n\t\treturn 0\n\t}\n\n\tif rb.w < rb.r {\n\t\treturn rb.r - rb.w\n\t}\n\n\treturn rb.size - rb.w + rb.r\n}\n\n// WriteString writes the contents of the string s to buffer, which accepts a slice of bytes.\nfunc (rb *Buffer) WriteString(s string) (int, error) {\n\treturn rb.Write(bs.StringToBytes(s))\n}\n\n// Bytes returns all available read bytes. It does not move the read pointer and only copy the available data.\nfunc (rb *Buffer) Bytes() []byte {\n\tif rb.isEmpty {\n\t\treturn nil\n\t} else if rb.w == rb.r {\n\t\tvar bb []byte\n\t\tbb = append(bb, rb.buf[rb.r:]...)\n\t\tbb = append(bb, rb.buf[:rb.w]...)\n\t\treturn bb\n\t}\n\n\tvar bb []byte\n\tif rb.w > rb.r {\n\t\tbb = append(bb, rb.buf[rb.r:rb.w]...)\n\t\treturn bb\n\t}\n\n\tbb = append(bb, rb.buf[rb.r:]...)\n\n\tif rb.w != 0 {\n\t\tbb = append(bb, rb.buf[:rb.w]...)\n\t}\n\n\treturn bb\n}\n\n// ReadFrom implements io.ReaderFrom.\nfunc (rb *Buffer) ReadFrom(r io.Reader) (n int64, err error) {\n\tvar m int\n\tfor {\n\t\tif rb.Available() < MinRead {\n\t\t\trb.grow(rb.Buffered() + MinRead)\n\t\t}\n\n\t\tif rb.w >= rb.r {\n\t\t\tm, err = r.Read(rb.buf[rb.w:])\n\t\t\tif m < 0 {\n\t\t\t\tpanic(\"RingBuffer.ReadFrom: reader returned negative count from Read\")\n\t\t\t}\n\t\t\trb.isEmpty = false\n\t\t\trb.w = (rb.w + m) % rb.size\n\t\t\tn += int64(m)\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tm, err = r.Read(rb.buf[:rb.r])\n\t\t\tif m < 0 {\n\t\t\t\tpanic(\"RingBuffer.ReadFrom: reader returned negative count from Read\")\n\t\t\t}\n\t\t\trb.w = (rb.w + m) % rb.size\n\t\t\tn += int64(m)\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tm, err = r.Read(rb.buf[rb.w:rb.r])\n\t\t\tif m < 0 {\n\t\t\t\tpanic(\"RingBuffer.ReadFrom: reader returned negative count from Read\")\n\t\t\t}\n\t\t\trb.isEmpty = false\n\t\t\trb.w = (rb.w + m) % rb.size\n\t\t\tn += int64(m)\n\t\t\tif err == io.EOF {\n\t\t\t\treturn n, nil\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// WriteTo implements io.WriterTo.\nfunc (rb *Buffer) WriteTo(w io.Writer) (int64, error) {\n\tif rb.isEmpty {\n\t\treturn 0, ErrIsEmpty\n\t}\n\n\tif rb.w > rb.r {\n\t\tn := rb.w - rb.r\n\t\tm, err := w.Write(rb.buf[rb.r : rb.r+n])\n\t\tif m > n {\n\t\t\tpanic(\"RingBuffer.WriteTo: invalid Write count\")\n\t\t}\n\t\trb.r += m\n\t\tif rb.r == rb.w {\n\t\t\trb.Reset()\n\t\t}\n\t\tif err != nil {\n\t\t\treturn int64(m), err\n\t\t}\n\t\tif !rb.isEmpty {\n\t\t\treturn int64(m), io.ErrShortWrite\n\t\t}\n\t\treturn int64(m), nil\n\t}\n\n\tn := rb.size - rb.r + rb.w\n\tif rb.r+n <= rb.size {\n\t\tm, err := w.Write(rb.buf[rb.r : rb.r+n])\n\t\tif m > n {\n\t\t\tpanic(\"RingBuffer.WriteTo: invalid Write count\")\n\t\t}\n\t\trb.r = (rb.r + m) % rb.size\n\t\tif rb.r == rb.w {\n\t\t\trb.Reset()\n\t\t}\n\t\tif err != nil {\n\t\t\treturn int64(m), err\n\t\t}\n\t\tif !rb.isEmpty {\n\t\t\treturn int64(m), io.ErrShortWrite\n\t\t}\n\t\treturn int64(m), nil\n\t}\n\n\tvar cum int64\n\tc1 := rb.size - rb.r\n\tm, err := w.Write(rb.buf[rb.r:])\n\tif m > c1 {\n\t\tpanic(\"RingBuffer.WriteTo: invalid Write count\")\n\t}\n\trb.r = (rb.r + m) % rb.size\n\tif err != nil {\n\t\treturn int64(m), err\n\t}\n\tif m < c1 {\n\t\treturn int64(m), io.ErrShortWrite\n\t}\n\tcum += int64(m)\n\tc2 := n - c1\n\tm, err = w.Write(rb.buf[:c2])\n\tif m > c2 {\n\t\tpanic(\"RingBuffer.WriteTo: invalid Write count\")\n\t}\n\trb.r = m\n\tcum += int64(m)\n\tif rb.r == rb.w {\n\t\trb.Reset()\n\t}\n\tif err != nil {\n\t\treturn cum, err\n\t}\n\tif !rb.isEmpty {\n\t\treturn cum, io.ErrShortWrite\n\t}\n\treturn cum, nil\n}\n\n// IsFull tells if this ring-buffer is full.\nfunc (rb *Buffer) IsFull() bool {\n\treturn rb.r == rb.w && !rb.isEmpty\n}\n\n// IsEmpty tells if this ring-buffer is empty.\nfunc (rb *Buffer) IsEmpty() bool {\n\treturn rb.isEmpty\n}\n\n// Reset the read pointer and write pointer to zero.\nfunc (rb *Buffer) Reset() {\n\trb.isEmpty = true\n\trb.r, rb.w = 0, 0\n}\n\nfunc (rb *Buffer) grow(newCap int) {\n\tif n := rb.size; n == 0 {\n\t\tif newCap <= DefaultBufferSize {\n\t\t\tnewCap = DefaultBufferSize\n\t\t} else {\n\t\t\tnewCap = math.CeilToPowerOfTwo(newCap)\n\t\t}\n\t} else {\n\t\tdoubleCap := n + n\n\t\tif newCap <= doubleCap {\n\t\t\tif n < bufferGrowThreshold {\n\t\t\t\tnewCap = doubleCap\n\t\t\t} else {\n\t\t\t\t// Check 0 < n to detect overflow and prevent an infinite loop.\n\t\t\t\tfor 0 < n && n < newCap {\n\t\t\t\t\tn += n / 4\n\t\t\t\t}\n\t\t\t\t// The n calculation doesn't overflow, set n to newCap.\n\t\t\t\tif n > 0 {\n\t\t\t\t\tnewCap = n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tnewBuf := bsPool.Get(newCap)\n\toldLen := rb.Buffered()\n\t_, _ = rb.Read(newBuf)\n\tbsPool.Put(rb.buf)\n\trb.buf = newBuf\n\trb.r = 0\n\trb.w = oldLen\n\trb.size = newCap\n\tif rb.w > 0 {\n\t\trb.isEmpty = false\n\t}\n}\n"
  },
  {
    "path": "pkg/buffer/ring/ring_buffer_test.go",
    "content": "package ring\n\nimport (\n\t\"bytes\"\n\tcrand \"crypto/rand\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRingBuffer_Write(t *testing.T) {\n\trb := New(64)\n\n\t_, err := rb.ReadByte()\n\tassert.ErrorIs(t, err, ErrIsEmpty, \"expect nil err, but got nil\")\n\t// check empty or full\n\tassert.True(t, rb.IsEmpty(), \"expect IsEmpty is true but got false\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 64, rb.Available(), \"expect free 64 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\n\t// write 4 * 4 = 16 bytes\n\tdata := []byte(strings.Repeat(\"abcd\", 4))\n\tn, _ := rb.Write(data)\n\tassert.EqualValuesf(t, 16, n, \"expect write 16 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 16, rb.Buffered(), \"expect len 16 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 48, rb.Available(), \"expect free 48 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, data, rb.Bytes(), \"expect 4 abcd but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// write 48 bytes, should full\n\tdata = []byte(strings.Repeat(\"abcd\", 12))\n\tn, _ = rb.Write(data)\n\tassert.EqualValuesf(t, 48, n, \"expect write 48 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 64, rb.Buffered(), \"expect len 64 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.Available(), \"expect free 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.w, \"expect r.w=0 but got %d. r.r=%d\", rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte(strings.Repeat(\"abcd\", 16)), rb.Bytes(), \"expect 16 abcd but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.True(t, rb.IsFull(), \"expect IsFull is true but got false\")\n\tassert.True(t, rb.IsFull(), \"expect IsFull is true but got false\")\n\n\t// write more 4 bytes, should scale from 64 to 128 bytes.\n\tn, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 1)))\n\tassert.EqualValuesf(t, 4, n, \"expect write 4 bytes but got %d\", n)\n\tsize := rb.Cap()\n\tassert.EqualValuesf(t, 68, rb.Buffered(), \"expect len 68 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, size-68, rb.Available(), \"expect free 60 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// reset this ringbuffer and set a long slice\n\trb.Reset()\n\tn, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 20)))\n\tassert.EqualValuesf(t, 80, n, \"expect write 80 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 80, rb.Buffered(), \"expect len 80 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, size-80, rb.Available(), \"expect free 48 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.Greaterf(t, rb.w, 0, \"expect r.w>=0 but got %d. r.r=%d\", rb.w, rb.r)\n\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\tassert.EqualValuesf(t, []byte(strings.Repeat(\"abcd\", 20)), rb.Bytes(), \"expect 16 abcd but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\n\trb.Reset()\n\tsize = rb.Cap()\n\t// write 4 * 2 = 8 bytes\n\tn, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 2)))\n\tassert.EqualValuesf(t, 8, n, \"expect write 16 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 8, rb.Buffered(), \"expect len 16 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, size-8, rb.Available(), \"expect free 48 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tbuf := make([]byte, 5)\n\t_, _ = rb.Read(buf)\n\tassert.EqualValuesf(t, 3, rb.Buffered(), \"expect len 3 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\t_, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 15)))\n\n\tassert.EqualValuesf(t, []byte(\"bcd\"+strings.Repeat(\"abcd\", 15)), rb.Bytes(), \"expect 63 ... but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\n\trb.Reset()\n\tn, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 32)))\n\tassert.EqualValuesf(t, 128, n, \"expect write 128 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 0, rb.Available(), \"expect free 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tbuf = make([]byte, 16)\n\t_, _ = rb.Read(buf)\n\tn, _ = rb.Write([]byte(strings.Repeat(\"1234\", 4)))\n\tassert.EqualValuesf(t, 16, n, \"expect write 16 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 0, rb.Available(), \"expect free 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte(strings.Repeat(\"abcd\", 32)+strings.Repeat(\"1234\", 4)), append(buf, rb.Bytes()...), \"expect 16 abcd and 4 1234 but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n}\n\nfunc TestZeroRingBuffer(t *testing.T) {\n\trb := New(0)\n\thead, tail := rb.Peek(2)\n\tassert.Empty(t, head, \"head should be empty\")\n\tassert.Empty(t, tail, \"tail should be empty\")\n\thead, tail = rb.Peek(-1)\n\tassert.Empty(t, head, \"head should be empty\")\n\tassert.Empty(t, tail, \"tail should be empty\")\n\tassert.EqualValues(t, 0, rb.Buffered(), \"expect length is 0\")\n\tassert.EqualValues(t, 0, rb.Available(), \"expect free is 0\")\n\tbuf := []byte(strings.Repeat(\"1234\", 12))\n\t_, _ = rb.Write(buf)\n\tassert.EqualValuesf(t, DefaultBufferSize, rb.Len(), \"expect rb.Len()=%d, but got rb.Len()=%d\", DefaultBufferSize, rb.Len())\n\tassert.EqualValuesf(t, DefaultBufferSize, rb.Cap(), \"expect rb.Cap()=%d, but got rb.Cap()=%d\", DefaultBufferSize, rb.Cap())\n\tassert.Truef(t, rb.r == 0 && rb.w == 48 && rb.size == DefaultBufferSize, \"expect rb.r=0, rb.w=48, rb.size=64, rb.mask=63, but got rb.r=%d, rb.w=%d, rb.size=%d\", rb.r, rb.w, rb.size)\n\tassert.EqualValues(t, buf, rb.Bytes(), \"expect it is equal\")\n\t_, _ = rb.Discard(48)\n\tassert.Truef(t, rb.IsEmpty() && rb.r == 0 && rb.w == 0, \"expect rb is empty and rb.r=rb.w=0, but got rb.r=%d and rb.w=%d\", rb.r, rb.w)\n}\n\nfunc TestRingBufferGrow(t *testing.T) {\n\trb := New(0)\n\thead, tail := rb.Peek(2)\n\tassert.Empty(t, head, \"head should be empty\")\n\tassert.Empty(t, tail, \"tail should be empty\")\n\tdata := make([]byte, DefaultBufferSize+1)\n\tn, err := crand.Read(data)\n\tassert.NoError(t, err, \"failed to generate random data\")\n\tassert.EqualValuesf(t, DefaultBufferSize+1, n, \"expect random data length is %d but got %d\", DefaultBufferSize+1, n)\n\tn, err = rb.Write(data)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, DefaultBufferSize+1, n)\n\tassert.EqualValues(t, 2*DefaultBufferSize, rb.Cap())\n\tassert.EqualValues(t, 2*DefaultBufferSize, rb.Len())\n\tassert.EqualValues(t, DefaultBufferSize+1, rb.Buffered())\n\tassert.EqualValues(t, DefaultBufferSize-1, rb.Available())\n\tassert.EqualValues(t, data, rb.Bytes())\n\n\trb = New(DefaultBufferSize)\n\tnewData := make([]byte, 3*512)\n\tn, err = crand.Read(newData)\n\tassert.NoError(t, err, \"failed to generate random data\")\n\tassert.EqualValuesf(t, 3*512, n, \"expect random data length is %d but got %d\", 3*512, n)\n\tn, err = rb.Write(newData)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, 3*512, n)\n\tassert.EqualValues(t, 2*DefaultBufferSize, rb.Cap())\n\tassert.EqualValues(t, 2*DefaultBufferSize, rb.Len())\n\tassert.EqualValues(t, 3*512, rb.Buffered())\n\tassert.EqualValues(t, 512, rb.Available())\n\tassert.EqualValues(t, newData, rb.Bytes())\n\n\trb.Reset()\n\tdata = make([]byte, bufferGrowThreshold)\n\tn, err = crand.Read(data)\n\tassert.NoError(t, err, \"failed to generate random data\")\n\tassert.EqualValuesf(t, bufferGrowThreshold, n, \"expect random data length is %d but got %d\", bufferGrowThreshold, n)\n\tn, err = rb.Write(data)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, bufferGrowThreshold, n)\n\tassert.EqualValues(t, bufferGrowThreshold, rb.Cap())\n\tassert.EqualValues(t, bufferGrowThreshold, rb.Len())\n\tassert.EqualValues(t, bufferGrowThreshold, rb.Buffered())\n\tassert.EqualValues(t, 0, rb.Available())\n\tassert.EqualValues(t, data, rb.Bytes())\n\tnewData = make([]byte, bufferGrowThreshold/2+1)\n\tn, err = crand.Read(newData)\n\tassert.NoError(t, err, \"failed to generate random data\")\n\tassert.EqualValuesf(t, bufferGrowThreshold/2+1, n, \"expect random data length is %d but got %d\", bufferGrowThreshold, n)\n\tn, err = rb.Write(newData)\n\tassert.NoError(t, err)\n\tassert.EqualValues(t, bufferGrowThreshold/2+1, n)\n\tassert.EqualValues(t, 1.25*(1.25*bufferGrowThreshold), rb.Cap())\n\tassert.EqualValues(t, 1.25*(1.25*bufferGrowThreshold), rb.Len())\n\tassert.EqualValues(t, 1.5*bufferGrowThreshold+1, rb.Buffered())\n\tassert.EqualValues(t, 1.25*(1.25*bufferGrowThreshold)-rb.Buffered(), rb.Available())\n\tassert.EqualValues(t, append(data, newData...), rb.Bytes())\n}\n\nfunc TestRingBuffer_Read(t *testing.T) {\n\trb := New(64)\n\n\t// check empty or full\n\tassert.True(t, rb.IsEmpty(), \"expect IsEmpty is true but got false\")\n\tassert.False(t, rb.IsFull(), \"expect isfull is false but got true\")\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 64, rb.Available(), \"expect free 64 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\n\t// read empty\n\tbuf := make([]byte, 1024)\n\tn, err := rb.Read(buf)\n\tassert.ErrorIs(t, err, ErrIsEmpty, \"expect ErrIsEmpty but got nil\")\n\tassert.EqualValuesf(t, 0, n, \"expect read 0 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 64, rb.Available(), \"expect free 64 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.r, \"expect r.r=0 but got %d. r.w=%d\", rb.r, rb.w)\n\n\t// write 16 bytes to read\n\t_, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 4)))\n\t// read all data from buffer, it will be shrunk from 64 to 32.\n\tn, err = rb.Read(buf)\n\tassert.NoErrorf(t, err, \"read failed: %v\", err)\n\tassert.EqualValuesf(t, 16, n, \"expect read 16 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 64, rb.Available(), \"expect free 64 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.r, \"expect r.r=0 but got %d. r.w=%d\", rb.r, rb.w)\n\n\t// write long slice to read, it will scale from 32 to 128 bytes.\n\t_, _ = rb.Write([]byte(strings.Repeat(\"abcd\", 20)))\n\t// read all data from buffer, it will be shrunk from 128 to 64.\n\tn, err = rb.Read(buf)\n\tassert.NoErrorf(t, err, \"read failed: %v\", err)\n\tassert.EqualValuesf(t, 80, n, \"expect read 80 bytes but got %d\", n)\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 128, rb.Available(), \"expect free 128 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.r, \"expect r.r=0 but got %d. r.w=%d\", rb.r, rb.w)\n\n\trb.Reset()\n\t_, _ = rb.Write([]byte(strings.Repeat(\"1234\", 32)))\n\tassert.True(t, rb.IsFull(), \"ring buffer should be full\")\n\tassert.EqualValuesf(t, 0, rb.Available(), \"expect free 0 bytes but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.w, \"expect r.2=0 but got %d. r.r=%d\", rb.w, rb.r)\n\thead, tail := rb.Peek(64)\n\tassert.Truef(t, len(head) == 64 && tail == nil, \"expect len(head)=64 and tail=nil, yet len(head)=%d and tail != nil\", len(head))\n\tassert.EqualValuesf(t, 0, rb.r, \"expect r.r=0 but got %d\", rb.r)\n\tassert.EqualValues(t, []byte(strings.Repeat(\"1234\", 16)), head)\n\t_, _ = rb.Discard(64)\n\tassert.EqualValuesf(t, 64, rb.r, \"expect r.r=64 but got %d\", rb.r)\n\t_, _ = rb.Write([]byte(strings.Repeat(\"1234\", 4)))\n\tassert.EqualValuesf(t, 16, rb.w, \"expect r.w=16 but got %d\", rb.w)\n\thead, tail = rb.Peek(128)\n\tassert.Truef(t, len(head) == 64 && len(tail) == 16, \"expect len(head)=64 and len(tail)=16, yet len(head)=%d and len(tail)=%d\", len(head), len(tail))\n\tassert.EqualValues(t, []byte(strings.Repeat(\"1234\", 16)), head)\n\tassert.EqualValues(t, []byte(strings.Repeat(\"1234\", 4)), tail)\n\n\thead, tail = rb.Peek(-1)\n\tassert.Truef(t, len(head) == 64 && len(tail) == 16, \"expect len(head)=64 and len(tail)=16, yet len(head)=%d and len(tail)=%d\", len(head), len(tail))\n\tassert.EqualValues(t, []byte(strings.Repeat(\"1234\", 16)), head)\n\tassert.EqualValues(t, []byte(strings.Repeat(\"1234\", 4)), tail)\n\n\t_, _ = rb.Discard(64)\n\t_, _ = rb.Discard(16)\n\tassert.True(t, rb.IsEmpty(), \"should be empty\")\n}\n\nfunc TestRingBuffer_ByteInterface(t *testing.T) {\n\trb := New(2)\n\n\t// write one\n\t_ = rb.WriteByte('a')\n\tassert.EqualValuesf(t, 1, rb.Buffered(), \"expect len 1 byte but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 1, rb.Available(), \"expect free 1 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte{'a'}, rb.Bytes(), \"expect a but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\t// check empty or full\n\tassert.Falsef(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// write two, isFull\n\t_ = rb.WriteByte('b')\n\tassert.EqualValuesf(t, 2, rb.Buffered(), \"expect len 2 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 0, rb.Available(), \"expect free 0 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte{'a', 'b'}, rb.Bytes(), \"expect a but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.True(t, rb.IsFull(), \"expect IsFull is true but got false\")\n\n\t// write, it will scale from 2 to 4 bytes.\n\t_ = rb.WriteByte('c')\n\tassert.EqualValuesf(t, 3, rb.Buffered(), \"expect len 3 bytes but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 1, rb.Available(), \"expect free 1 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte{'a', 'b', 'c'}, rb.Bytes(), \"expect a but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// read one\n\tb, err := rb.ReadByte()\n\tassert.NoErrorf(t, err, \"ReadByte failed: %v\", err)\n\tassert.EqualValuesf(t, 'a', b, \"expect a but got %c. r.w=%d, r.r=%d\", b, rb.w, rb.r)\n\tassert.EqualValuesf(t, 2, rb.Buffered(), \"expect len 2 byte but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 2, rb.Available(), \"expect free 2 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\tassert.EqualValuesf(t, []byte{'b', 'c'}, rb.Bytes(), \"expect a but got %s. r.w=%d, r.r=%d\", rb.Bytes(), rb.w, rb.r)\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// read two\n\tb, _ = rb.ReadByte()\n\tassert.EqualValuesf(t, 'b', b, \"expect b but got %c. r.w=%d, r.r=%d\", b, rb.w, rb.r)\n\tassert.EqualValuesf(t, 1, rb.Buffered(), \"expect len 1 byte but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 3, rb.Available(), \"expect free 3 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\t// check empty or full\n\tassert.False(t, rb.IsEmpty(), \"expect IsEmpty is false but got true\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n\n\t// read three\n\t_, _ = rb.ReadByte()\n\tassert.EqualValuesf(t, 0, rb.Buffered(), \"expect len 0 byte but got %d. r.w=%d, r.r=%d\", rb.Buffered(), rb.w, rb.r)\n\tassert.EqualValuesf(t, 4, rb.Available(), \"expect free 4 byte but got %d. r.w=%d, r.r=%d\", rb.Available(), rb.w, rb.r)\n\t// check empty or full\n\tassert.True(t, rb.IsEmpty(), \"expect IsEmpty is true but got false\")\n\tassert.False(t, rb.IsFull(), \"expect IsFull is false but got true\")\n}\n\nfunc TestRingBuffer_ReadFrom(t *testing.T) {\n\trb := New(0)\n\tconst dataLen = 4 * 1024\n\tdata := make([]byte, dataLen)\n\t_, err := crand.Read(data)\n\trequire.NoError(t, err)\n\tr := bytes.NewReader(data)\n\tn, err := rb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.False(t, rb.IsEmpty())\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should read %d bytes, but got %d\", dataLen, n)\n\trequire.EqualValuesf(t, dataLen, rb.Buffered(), \"ringbuffer should have %d bytes, but got %d\", dataLen, rb.Buffered())\n\tbuf, _ := rb.Peek(-1)\n\trequire.EqualValues(t, data, buf)\n\tbuf = make([]byte, dataLen)\n\tvar m int\n\tm, err = rb.Read(buf)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, m, \"ringbuffer should read %d bytes, but got %d\", dataLen, m)\n\trequire.EqualValues(t, data, buf)\n\trequire.Truef(t, rb.IsEmpty(), \"ringbuffer should be empty, but it isn't\")\n\trequire.Zerof(t, rb.Buffered(), \"ringbuffer should be empty, but still have %d bytes\", rb.Buffered())\n\n\trb = New(0)\n\tconst prefixLen = 2 * 1024\n\tprefix := make([]byte, prefixLen)\n\t_, err = crand.Read(prefix)\n\trequire.NoError(t, err)\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\tr.Reset(data)\n\tm, err = rb.Write(prefix)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, prefixLen, m, \"ringbuffer should read %d bytes, but got %d\", prefixLen, m)\n\tn, err = rb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should read %d bytes, but got %d\", dataLen, n)\n\trequire.EqualValuesf(t, prefixLen+dataLen, rb.Buffered(), \"ringbuffer should have %d bytes, but got %d\",\n\t\tprefixLen+dataLen, rb.Buffered())\n\thead, tail := rb.Peek(prefixLen)\n\trequire.Nil(t, tail)\n\trequire.EqualValues(t, prefix, head)\n\t_, _ = rb.Discard(prefixLen)\n\trequire.EqualValuesf(t, dataLen, rb.Buffered(), \"ringbuffer should have %d bytes, but got %d\", dataLen, rb.Buffered())\n\thead, tail = rb.Peek(-1)\n\trequire.Nil(t, tail)\n\trequire.EqualValues(t, data, head)\n\t_, _ = rb.Discard(dataLen)\n\trequire.Truef(t, rb.IsEmpty(), \"ringbuffer should be empty, but it isn't\")\n\trequire.Zerof(t, rb.Buffered(), \"ringbuffer should be empty, but still have %d bytes\", rb.Buffered())\n\n\tconst initLen = 5 * 1024\n\trb = New(initLen)\n\t_, err = crand.Read(prefix)\n\trequire.NoError(t, err)\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\tr.Reset(data)\n\tm, err = rb.Write(prefix)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, prefixLen, m, \"ringbuffer should read %d bytes, but got %d\", prefixLen, m)\n\tconst partLen = 1024\n\thead, tail = rb.Peek(partLen)\n\trequire.Nil(t, tail)\n\trequire.EqualValues(t, prefix[:partLen], head)\n\t_, _ = rb.Discard(partLen)\n\tn, err = rb.ReadFrom(r)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should read %d bytes, but got %d\", dataLen, n)\n\tbuf, tail = rb.Peek(-1)\n\tbuf = append(buf, tail...)\n\trequire.EqualValues(t, append(prefix[partLen:], data...), buf)\n\t_, _ = rb.Discard(prefixLen + dataLen - partLen)\n\trequire.Truef(t, rb.IsEmpty(), \"ringbuffer should be empty, but it isn't\")\n\trequire.Zerof(t, rb.Buffered(), \"ringbuffer should be empty, but still have %d bytes\", rb.Buffered())\n}\n\nfunc TestRingBuffer_WriteTo(t *testing.T) {\n\trb := New(5 * 1024)\n\tconst dataLen = 4 * 1024\n\tdata := make([]byte, dataLen)\n\t_, err := crand.Read(data)\n\trequire.NoError(t, err)\n\tn, err := rb.Write(data)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should write %d bytes, but got %d\", dataLen, n)\n\tbuf := bytes.NewBuffer(nil)\n\tvar m int64\n\tm, err = rb.WriteTo(buf)\n\trequire.NoError(t, err)\n\trequire.True(t, rb.IsEmpty())\n\trequire.EqualValuesf(t, dataLen, m, \"ringbuffer should write %d bytes, but got %d\", dataLen, m)\n\trequire.EqualValues(t, data, buf.Bytes())\n\n\tbuf.Reset()\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\trb = New(dataLen)\n\tn, err = rb.Write(data)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should write %d bytes, but got %d\", dataLen, n)\n\trequire.Truef(t, rb.IsFull(), \"ringbuffer should be full, but it isn't\")\n\tm, err = rb.WriteTo(buf)\n\trequire.NoError(t, err)\n\trequire.True(t, rb.IsEmpty())\n\trequire.EqualValuesf(t, dataLen, m, \"ringbuffer should write %d bytes, but got %d\", dataLen, m)\n\trequire.EqualValues(t, data, buf.Bytes())\n\n\tbuf.Reset()\n\trb.Reset()\n\t_, err = crand.Read(data)\n\trequire.NoError(t, err)\n\tn, err = rb.Write(data)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen, n, \"ringbuffer should write %d bytes, but got %d\", dataLen, n)\n\trequire.Truef(t, rb.IsFull(), \"ringbuffer should be full, but it isn't\")\n\tconst partLen = 1024\n\thead, tail := rb.Peek(partLen)\n\trequire.Nil(t, tail)\n\trequire.EqualValues(t, data[:partLen], head)\n\t_, _ = rb.Discard(partLen)\n\tpartData := make([]byte, partLen/2)\n\t_, err = crand.Read(partData)\n\trequire.NoError(t, err)\n\tn, err = rb.Write(partData)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, partLen/2, n, \"ringbuffer should write %d bytes, but got %d\", dataLen, n)\n\trequire.EqualValues(t, partLen/2, rb.Available())\n\tm, err = rb.WriteTo(buf)\n\trequire.NoError(t, err)\n\trequire.EqualValuesf(t, dataLen-partLen/2, m, \"ringbuffer should write %d bytes, but got %d\", dataLen-partLen/2, m)\n\trequire.EqualValues(t, append(data[partLen:], partData...), buf.Bytes())\n\trequire.True(t, rb.IsEmpty())\n}\n"
  },
  {
    "path": "pkg/errors/errors.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package errors defines common errors for gnet.\npackage errors\n\nimport \"errors\"\n\nvar (\n\t// ErrEmptyEngine occurs when trying to do something with an empty engine.\n\tErrEmptyEngine = errors.New(\"gnet: the internal engine is empty\")\n\t// ErrEngineShutdown occurs when server is closing.\n\tErrEngineShutdown = errors.New(\"gnet: server is going to be shutdown\")\n\t// ErrEngineInShutdown occurs when attempting to shut the server down more than once.\n\tErrEngineInShutdown = errors.New(\"gnet: server is already in shutdown\")\n\t// ErrAcceptSocket occurs when acceptor does not accept the new connection properly.\n\tErrAcceptSocket = errors.New(\"gnet: accept a new connection error\")\n\t// ErrTooManyEventLoopThreads occurs when attempting to set up more than 10,000 event-loop goroutines under LockOSThread mode.\n\tErrTooManyEventLoopThreads = errors.New(\"gnet: too many event-loops under LockOSThread mode\")\n\t// ErrUnsupportedProtocol occurs when trying to use protocol that is not supported.\n\tErrUnsupportedProtocol = errors.New(\"gnet: only unix, tcp/tcp4/tcp6, udp/udp4/udp6 are supported\")\n\t// ErrUnsupportedTCPProtocol occurs when trying to use an unsupported TCP protocol.\n\tErrUnsupportedTCPProtocol = errors.New(\"gnet: only tcp/tcp4/tcp6 are supported\")\n\t// ErrUnsupportedUDPProtocol occurs when trying to use an unsupported UDP protocol.\n\tErrUnsupportedUDPProtocol = errors.New(\"gnet: only udp/udp4/udp6 are supported\")\n\t// ErrUnsupportedUDSProtocol occurs when trying to use an unsupported Unix protocol.\n\tErrUnsupportedUDSProtocol = errors.New(\"gnet: only unix is supported\")\n\t// ErrUnsupportedOp occurs when calling some methods that are either not supported or have not been implemented yet.\n\tErrUnsupportedOp = errors.New(\"gnet: unsupported operation\")\n\t// ErrNegativeSize occurs when trying to pass a negative size to a buffer.\n\tErrNegativeSize = errors.New(\"gnet: negative size is not allowed\")\n\t// ErrNoIPv4AddressOnInterface occurs when an IPv4 multicast address is set on an interface but IPv4 is not configured.\n\tErrNoIPv4AddressOnInterface = errors.New(\"gnet: no IPv4 address on interface\")\n\t// ErrInvalidNetworkAddress occurs when the network address is invalid.\n\tErrInvalidNetworkAddress = errors.New(\"gnet: invalid network address\")\n\t// ErrInvalidNetConn occurs when trying to do something with an empty net.Conn.\n\tErrInvalidNetConn = errors.New(\"gnet: the net.Conn is empty\")\n\t// ErrNilRunnable occurs when trying to execute a nil runnable.\n\tErrNilRunnable = errors.New(\"gnet: nil runnable is not allowed\")\n)\n"
  },
  {
    "path": "pkg/io/io.go",
    "content": "/*\n * Copyright (c) 2025 The Gnet Authors. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Package io provides some handy network I/O functions.\npackage io\n"
  },
  {
    "path": "pkg/io/io_bsd.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || netbsd || openbsd\n\npackage io\n\nimport (\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// Writev invokes the writev system call directly.\n//\n// Note that SYS_WRITEV is about to be deprecated on Darwin\n// and the Go team suggested to use libSystem wrappers instead of direct system-calls,\n// hence, this way to implement the writev might not be backward-compatible in the future.\nfunc Writev(fd int, bs [][]byte) (int, error) {\n\tif len(bs) == 0 {\n\t\treturn 0, nil\n\t}\n\tiov := bytes2iovec(bs)\n\tn, _, err := unix.RawSyscall(unix.SYS_WRITEV, uintptr(fd), uintptr(unsafe.Pointer(&iov[0])), uintptr(len(iov))) //nolint:staticcheck\n\tif err != 0 {\n\t\treturn int(n), err\n\t}\n\treturn int(n), nil\n}\n\n// Readv invokes the readv system call directly.\n//\n// Note that SYS_READV is about to be deprecated on Darwin\n// and the Go team suggested to use libSystem wrappers instead of direct system-calls,\n// hence, this way to implement the readv might not be backward-compatible in the future.\nfunc Readv(fd int, bs [][]byte) (int, error) {\n\tif len(bs) == 0 {\n\t\treturn 0, nil\n\t}\n\tiov := bytes2iovec(bs)\n\t// syscall\n\tn, _, err := unix.RawSyscall(unix.SYS_READV, uintptr(fd), uintptr(unsafe.Pointer(&iov[0])), uintptr(len(iov))) //nolint:staticcheck\n\tif err != 0 {\n\t\treturn int(n), err\n\t}\n\treturn int(n), nil\n}\n\nvar _zero uintptr\n\nfunc bytes2iovec(bs [][]byte) []unix.Iovec {\n\tiovecs := make([]unix.Iovec, len(bs))\n\tfor i, b := range bs {\n\t\tiovecs[i].SetLen(len(b))\n\t\tif len(b) > 0 {\n\t\t\tiovecs[i].Base = &b[0]\n\t\t} else {\n\t\t\tiovecs[i].Base = (*byte)(unsafe.Pointer(&_zero))\n\t\t}\n\t}\n\treturn iovecs\n}\n"
  },
  {
    "path": "pkg/io/io_linux.go",
    "content": "/*\n * Copyright (c) 2021 The Gnet Authors. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage io\n\nimport \"golang.org/x/sys/unix\"\n\n// Writev calls writev() on Linux.\nfunc Writev(fd int, iov [][]byte) (int, error) {\n\tif len(iov) == 0 {\n\t\treturn 0, nil\n\t}\n\treturn unix.Writev(fd, iov)\n}\n\n// Readv calls readv() on Linux.\nfunc Readv(fd int, iov [][]byte) (int, error) {\n\tif len(iov) == 0 {\n\t\treturn 0, nil\n\t}\n\treturn unix.Readv(fd, iov)\n}\n"
  },
  {
    "path": "pkg/logging/logger.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package logging provides logging functionality for gnet applications,\n// it sets up a default logger (powered by go.uber.org/zap)\n// that is about to be used by your gnet application.\n// You're allowed to replace the default logger with your customized logger by\n// implementing Logger and assign it to the functional option via gnet.WithLogger,\n// and then passing it to gnet.Run or gnet.Rotate.\n//\n// The environment variable `GNET_LOGGING_LEVEL` determines which zap logger level will be applied for logging.\n// The environment variable `GNET_LOGGING_FILE` is set to a local file path when you want to print logs into local file.\n// Alternatives of logging level (the variable of logging level ought to be integer):\n/*\nconst (\n\t// DebugLevel logs are typically voluminous, and are usually disabled in\n\t// production.\n\tDebugLevel Level = iota - 1\n\t// InfoLevel is the default logging priority.\n\tInfoLevel\n\t// WarnLevel logs are more important than Info, but don't need individual\n\t// human review.\n\tWarnLevel\n\t// ErrorLevel logs are high-priority. If an application is running smoothly,\n\t// it shouldn't generate any error-level logs.\n\tErrorLevel\n\t// DPanicLevel logs are particularly important errors. In development the\n\t// logger panics after writing the message.\n\tDPanicLevel\n\t// PanicLevel logs a message, then panics.\n\tPanicLevel\n\t// FatalLevel logs a message, then calls os.Exit(1).\n\tFatalLevel\n)\n*/\npackage logging\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/buffer\"\n\t\"go.uber.org/zap/zapcore\"\n\t\"gopkg.in/natefinch/lumberjack.v2\"\n)\n\n// Flusher is the callback function which flushes any buffered log entries to the underlying writer.\n// It is usually called before the gnet process exits.\ntype Flusher = func() error\n\nvar (\n\tmu                  sync.RWMutex\n\tdefaultLogger       Logger\n\tdefaultLoggingLevel Level\n\tdefaultFlusher      Flusher\n)\n\n// Level is the alias of zapcore.Level.\ntype Level = zapcore.Level\n\nconst (\n\t// DebugLevel logs are typically voluminous, and are usually disabled in\n\t// production.\n\tDebugLevel = zapcore.DebugLevel\n\t// InfoLevel is the default logging priority.\n\tInfoLevel = zapcore.InfoLevel\n\t// WarnLevel logs are more important than Info, but don't need individual\n\t// human review.\n\tWarnLevel = zapcore.WarnLevel\n\t// ErrorLevel logs are high-priority. If an application is running smoothly,\n\t// it shouldn't generate any error-level logs.\n\tErrorLevel = zapcore.ErrorLevel\n\t// DPanicLevel logs are particularly important errors. In development the\n\t// logger panics after writing the message.\n\tDPanicLevel = zapcore.DPanicLevel\n\t// PanicLevel logs a message, then panics.\n\tPanicLevel = zapcore.PanicLevel\n\t// FatalLevel logs a message, then calls os.Exit(1).\n\tFatalLevel = zapcore.FatalLevel\n)\n\nfunc init() {\n\tlvl := os.Getenv(\"GNET_LOGGING_LEVEL\")\n\tif len(lvl) > 0 {\n\t\tloggingLevel, err := strconv.ParseInt(lvl, 10, 8)\n\t\tif err != nil {\n\t\t\tpanic(\"invalid GNET_LOGGING_LEVEL, \" + err.Error())\n\t\t}\n\t\tdefaultLoggingLevel = Level(loggingLevel)\n\t}\n\n\t// Initializes the inside default logger of gnet.\n\tfileName := os.Getenv(\"GNET_LOGGING_FILE\")\n\tif len(fileName) > 0 {\n\t\tvar err error\n\t\tdefaultLogger, defaultFlusher, err = CreateLoggerAsLocalFile(fileName, defaultLoggingLevel)\n\t\tif err != nil {\n\t\t\tpanic(\"invalid GNET_LOGGING_FILE, \" + err.Error())\n\t\t}\n\t} else {\n\t\tcore := zapcore.NewCore(getDevEncoder(), zapcore.Lock(os.Stdout), defaultLoggingLevel)\n\t\tzapLogger := zap.New(core,\n\t\t\tzap.Development(),\n\t\t\tzap.AddCaller(),\n\t\t\tzap.AddStacktrace(ErrorLevel),\n\t\t\tzap.ErrorOutput(zapcore.Lock(os.Stderr)))\n\t\tdefaultLogger = zapLogger.Sugar()\n\t}\n}\n\ntype prefixEncoder struct {\n\tzapcore.Encoder\n\n\tprefix  string\n\tbufPool buffer.Pool\n}\n\nfunc (e *prefixEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {\n\tbuf := e.bufPool.Get()\n\n\tbuf.AppendString(e.prefix)\n\tbuf.AppendString(\" \")\n\n\tlogEntry, err := e.Encoder.EncodeEntry(entry, fields)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t_, err = buf.Write(logEntry.Bytes())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf, nil\n}\n\nfunc getDevEncoder() zapcore.Encoder {\n\tencoderConfig := zap.NewDevelopmentEncoderConfig()\n\tencoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder\n\tencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder\n\treturn &prefixEncoder{\n\t\tEncoder: zapcore.NewConsoleEncoder(encoderConfig),\n\t\tprefix:  \"[gnet]\",\n\t\tbufPool: buffer.NewPool(),\n\t}\n}\n\nfunc getProdEncoder() zapcore.Encoder {\n\tencoderConfig := zap.NewProductionEncoderConfig()\n\tencoderConfig.EncodeTime = zapcore.RFC3339NanoTimeEncoder\n\tencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder\n\treturn &prefixEncoder{\n\t\tEncoder: zapcore.NewConsoleEncoder(encoderConfig),\n\t\tprefix:  \"[gnet]\",\n\t\tbufPool: buffer.NewPool(),\n\t}\n}\n\n// GetDefaultLogger returns the default logger.\nfunc GetDefaultLogger() Logger {\n\tmu.RLock()\n\tdefer mu.RUnlock()\n\treturn defaultLogger\n}\n\n// GetDefaultFlusher returns the default flusher.\nfunc GetDefaultFlusher() Flusher {\n\tmu.RLock()\n\tdefer mu.RUnlock()\n\treturn defaultFlusher\n}\n\n// SetDefaultLoggerAndFlusher sets the default logger and its flusher.\nfunc SetDefaultLoggerAndFlusher(logger Logger, flusher Flusher) {\n\tmu.Lock()\n\tdefaultLogger, defaultFlusher = logger, flusher\n\tmu.Unlock()\n}\n\n// LogLevel tells what the default logging level is.\nfunc LogLevel() string {\n\treturn strings.ToUpper(defaultLoggingLevel.String())\n}\n\n// CreateLoggerAsLocalFile setups the logger by local file path.\nfunc CreateLoggerAsLocalFile(localFilePath string, logLevel Level) (logger Logger, flush func() error, err error) {\n\tif len(localFilePath) == 0 {\n\t\treturn nil, nil, errors.New(\"invalid local logger path\")\n\t}\n\n\t// lumberjack.Logger is already safe for concurrent use, so we don't need to lock it.\n\tlumberJackLogger := &lumberjack.Logger{\n\t\tFilename:   localFilePath,\n\t\tMaxSize:    100, // megabytes\n\t\tMaxBackups: 2,\n\t\tMaxAge:     15, // days\n\t}\n\n\tencoder := getProdEncoder()\n\tws := zapcore.AddSync(lumberJackLogger)\n\tzapcore.Lock(ws)\n\n\tlevelEnabler := zap.LevelEnablerFunc(func(level Level) bool {\n\t\treturn level >= logLevel\n\t})\n\tcore := zapcore.NewCore(encoder, ws, levelEnabler)\n\tzapLogger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(ErrorLevel))\n\tlogger = zapLogger.Sugar()\n\tflush = zapLogger.Sync\n\treturn\n}\n\n// Cleanup does something windup for logger, like closing, flushing, etc.\nfunc Cleanup() {\n\tmu.RLock()\n\tif defaultFlusher != nil {\n\t\t_ = defaultFlusher()\n\t}\n\tmu.RUnlock()\n}\n\n// Error prints err if it's not nil.\nfunc Error(err error) {\n\tif err != nil {\n\t\tmu.RLock()\n\t\tdefaultLogger.Errorf(\"error occurs during runtime, %v\", err)\n\t\tmu.RUnlock()\n\t}\n}\n\n// Debugf logs messages at DEBUG level.\nfunc Debugf(format string, args ...any) {\n\tmu.RLock()\n\tdefaultLogger.Debugf(format, args...)\n\tmu.RUnlock()\n}\n\n// Infof logs messages at INFO level.\nfunc Infof(format string, args ...any) {\n\tmu.RLock()\n\tdefaultLogger.Infof(format, args...)\n\tmu.RUnlock()\n}\n\n// Warnf logs messages at WARN level.\nfunc Warnf(format string, args ...any) {\n\tmu.RLock()\n\tdefaultLogger.Warnf(format, args...)\n\tmu.RUnlock()\n}\n\n// Errorf logs messages at ERROR level.\nfunc Errorf(format string, args ...any) {\n\tmu.RLock()\n\tdefaultLogger.Errorf(format, args...)\n\tmu.RUnlock()\n}\n\n// Fatalf logs messages at FATAL level.\nfunc Fatalf(format string, args ...any) {\n\tmu.RLock()\n\tdefaultLogger.Fatalf(format, args...)\n\tmu.RUnlock()\n}\n\n// Logger is used for logging formatted messages.\ntype Logger interface {\n\t// Debugf logs messages at DEBUG level.\n\tDebugf(format string, args ...any)\n\t// Infof logs messages at INFO level.\n\tInfof(format string, args ...any)\n\t// Warnf logs messages at WARN level.\n\tWarnf(format string, args ...any)\n\t// Errorf logs messages at ERROR level.\n\tErrorf(format string, args ...any)\n\t// Fatalf logs messages at FATAL level.\n\tFatalf(format string, args ...any)\n}\n"
  },
  {
    "path": "pkg/math/math.go",
    "content": "// Copyright (c) 2022 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package math provides a few fast math functions.\npackage math\n\nimport \"math/bits\"\n\nconst (\n\tbitSize       = 32 << (^uint(0) >> 63)\n\tmaxintHeadBit = 1 << (bitSize - 2)\n)\n\n// IsPowerOfTwo reports whether the given n is a power of two.\nfunc IsPowerOfTwo(n int) bool {\n\treturn n > 0 && n&(n-1) == 0\n}\n\n// CeilToPowerOfTwo returns n if it is a power-of-two, otherwise the next-highest power-of-two.\nfunc CeilToPowerOfTwo(n int) int {\n\tif n&maxintHeadBit != 0 && n > maxintHeadBit {\n\t\tpanic(\"argument is too large\")\n\t}\n\n\tif n <= 2 {\n\t\treturn 2\n\t}\n\treturn 1 << bits.Len(uint(n-1))\n}\n\n// FloorToPowerOfTwo returns n if it is a power-of-two, otherwise the next-highest power-of-two.\nfunc FloorToPowerOfTwo(n int) int {\n\tif n <= 2 {\n\t\treturn n\n\t}\n\n\tn |= n >> 1\n\tn |= n >> 2\n\tn |= n >> 4\n\tn |= n >> 8\n\tn |= n >> 16\n\n\treturn n - (n >> 1)\n}\n\n// ClosestPowerOfTwo returns n if it is a power-of-two, otherwise the closest power-of-two.\nfunc ClosestPowerOfTwo(n int) int {\n\tnext := CeilToPowerOfTwo(n)\n\tif prev := next / 2; (n - prev) < (next - n) {\n\t\tnext = prev\n\t}\n\treturn next\n}\n"
  },
  {
    "path": "pkg/math/math_test.go",
    "content": "package math\n\nimport \"testing\"\n\nfunc TestCeilToPowerOfTwo(t *testing.T) {\n\ttype args struct {\n\t\tn int\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant int\n\t}{\n\t\t// Boundary value tests: 0, 1, 2\n\t\t{name: \"zero\", args: args{n: 0}, want: 2},\n\t\t{name: \"one\", args: args{n: 1}, want: 2},\n\t\t{name: \"two\", args: args{n: 2}, want: 2},\n\n\t\t// Small value tests: 3-15\n\t\t{name: \"three\", args: args{n: 3}, want: 1 << 2},\n\t\t{name: \"four\", args: args{n: 4}, want: 1 << 2},\n\t\t{name: \"five\", args: args{n: 5}, want: 1 << 3},\n\t\t{name: \"six\", args: args{n: 6}, want: 1 << 3},\n\t\t{name: \"seven\", args: args{n: 7}, want: 1 << 3},\n\t\t{name: \"eight\", args: args{n: 8}, want: 1 << 3},\n\t\t{name: \"nine\", args: args{n: 9}, want: 1 << 4},\n\t\t{name: \"ten\", args: args{n: 10}, want: 1 << 4},\n\t\t{name: \"fifteen\", args: args{n: 15}, want: 1 << 4},\n\n\t\t// Tests for powers of two\n\t\t{name: \"power_of_two_16\", args: args{n: 1 << 4}, want: 1 << 4},\n\t\t{name: \"power_of_two_32\", args: args{n: 1 << 5}, want: 1 << 5},\n\t\t{name: \"power_of_two_64\", args: args{n: 1 << 6}, want: 1 << 6},\n\t\t{name: \"power_of_two_128\", args: args{n: 1 << 7}, want: 1 << 7},\n\t\t{name: \"power_of_two_256\", args: args{n: 1 << 8}, want: 1 << 8},\n\t\t{name: \"power_of_two_512\", args: args{n: 1 << 9}, want: 1 << 9},\n\t\t{name: \"power_of_two_1024\", args: args{n: 1 << 10}, want: 1 << 10},\n\n\t\t// Values near powers of two\n\t\t{name: \"near_power_17\", args: args{n: (1 << 4) + 1}, want: 1 << 5},\n\t\t{name: \"near_power_31\", args: args{n: (1 << 5) - 1}, want: 1 << 5},\n\t\t{name: \"near_power_33\", args: args{n: (1 << 5) + 1}, want: 1 << 6},\n\t\t{name: \"near_power_63\", args: args{n: (1 << 6) - 1}, want: 1 << 6},\n\t\t{name: \"near_power_65\", args: args{n: (1 << 6) + 1}, want: 1 << 7},\n\t\t{name: \"near_power_127\", args: args{n: (1 << 7) - 1}, want: 1 << 7},\n\t\t{name: \"near_power_129\", args: args{n: (1 << 7) + 1}, want: 1 << 8},\n\t\t{name: \"near_power_255\", args: args{n: (1 << 8) - 1}, want: 1 << 8},\n\t\t{name: \"near_power_257\", args: args{n: (1 << 8) + 1}, want: 1 << 9},\n\t\t{name: \"near_power_511\", args: args{n: (1 << 9) - 1}, want: 1 << 9},\n\t\t{name: \"near_power_513\", args: args{n: (1 << 9) + 1}, want: 1 << 10},\n\t\t{name: \"near_power_1023\", args: args{n: (1 << 10) - 1}, want: 1 << 10},\n\n\t\t// Medium value tests\n\t\t{name: \"medium_100\", args: args{n: 100}, want: 1 << 7},\n\t\t{name: \"medium_200\", args: args{n: 200}, want: 1 << 8},\n\t\t{name: \"medium_500\", args: args{n: 500}, want: 1 << 9},\n\t\t{name: \"medium_1000\", args: args{n: 1000}, want: 1 << 10},\n\t\t{name: \"medium_2000\", args: args{n: 2000}, want: 1 << 11},\n\t\t{name: \"medium_5000\", args: args{n: 5000}, want: 1 << 13},\n\t\t{name: \"medium_10000\", args: args{n: 10000}, want: 1 << 14},\n\n\t\t// Large value tests: around 2^10\n\t\t{name: \"large_1024_minus_1\", args: args{n: 1<<10 - 1}, want: 1 << 10},\n\t\t{name: \"large_1024\", args: args{n: 1 << 10}, want: 1 << 10},\n\t\t{name: \"large_1024_plus_1\", args: args{n: 1<<10 + 1}, want: 1 << 11},\n\t\t{name: \"large_2047\", args: args{n: (1 << 11) - 1}, want: 1 << 11},\n\t\t{name: \"large_2048\", args: args{n: 1 << 11}, want: 1 << 11},\n\t\t{name: \"large_2049\", args: args{n: (1 << 11) + 1}, want: 1 << 12},\n\n\t\t// Very large value tests: around 2^20\n\t\t{name: \"very_large_1M_minus_1\", args: args{n: 1<<20 - 1}, want: 1 << 20},\n\t\t{name: \"very_large_1M\", args: args{n: 1 << 20}, want: 1 << 20},\n\t\t{name: \"very_large_1M_plus_1\", args: args{n: 1<<20 + 1}, want: 1 << 21},\n\n\t\t// Huge value tests: around 2^30 (32-bit system)\n\t\t{name: \"huge_1G_minus_1\", args: args{n: 1<<30 - 1}, want: 1 << 30},\n\t\t{name: \"huge_1G\", args: args{n: 1 << 30}, want: 1 << 30},\n\t\t{name: \"huge_1G_plus_1\", args: args{n: 1<<30 + 1}, want: 1 << 31},\n\n\t\t// 64-bit system tests: around 2^32\n\t\t{name: \"extreme_2_32_minus_1\", args: args{n: 1<<32 - 1}, want: 1 << 32},\n\t\t{name: \"extreme_2_32\", args: args{n: 1 << 32}, want: 1 << 32},\n\t\t{name: \"extreme_2_32_plus_1\", args: args{n: 1<<32 + 1}, want: 1 << 33},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := CeilToPowerOfTwo(tt.args.n); got != tt.want {\n\t\t\t\tt.Errorf(\"CeilToPowerOfTwo() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_bsd_32bit.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && (386 || arm || mips || mipsle)\n\npackage netpoll\n\ntype keventIdent = uint32\n"
  },
  {
    "path": "pkg/netpoll/defs_bsd_64bit.go",
    "content": "// Copyright (c) 2023 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || riscv64)\n\npackage netpoll\n\ntype keventIdent = uint64\n"
  },
  {
    "path": "pkg/netpoll/defs_linux.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build !poll_opt\n\npackage netpoll\n\nimport \"golang.org/x/sys/unix\"\n\ntype epollevent = unix.EpollEvent\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_386.go",
    "content": "// created by cgo -cdefs and then converted to Go\n// cgo -cdefs defs2_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents uint32\n\tdata   [8]byte // to match amd64\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_amd64.go",
    "content": "// created by cgo -cdefs and then converted to Go\n// cgo -cdefs defs_linux.go defs1_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents uint32\n\tdata   [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_arm.go",
    "content": "// Copyright 2014 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents uint32\n\t_pad   uint32\n\tdata   [8]byte // to match amd64\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_arm64.go",
    "content": "// Created by cgo -cdefs and converted (by hand) to Go\n// ../cmd/cgo/cgo -cdefs defs_linux.go defs1_linux.go defs2_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents uint32\n\t_pad   uint32\n\tdata   [8]byte // to match amd64\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_mips64x.go",
    "content": "// Copyright 2015 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build (mips64 || mips64le) && linux && poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_mipsx.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build (mips || mipsle) && linux && poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      uint64\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_ppc64.go",
    "content": "// created by cgo -cdefs and then converted to Go\n// cgo -cdefs defs_linux.go defs3_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_ppc64le.go",
    "content": "// created by cgo -cdefs and then converted to Go\n// cgo -cdefs defs_linux.go defs3_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_riscv64.go",
    "content": "// Generated using cgo, then manually converted into appropriate naming and code\n// for the Go runtime.\n// go tool cgo -godefs defs_linux.go defs1_linux.go defs2_linux.go\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_linux_s390x.go",
    "content": "// Copyright 2016 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build poll_opt\n\npackage netpoll\n\ntype epollevent struct {\n\tevents    uint32\n\tpad_cgo_0 [4]byte\n\tdata      [8]byte // unaligned uintptr\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_poller.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage netpoll\n\n// PollEventHandler is the callback for I/O events notified by the poller.\ntype PollEventHandler func(int, IOEvent, IOFlags) error\n\n// PollAttachment is the user data which is about to be stored in \"void *ptr\" of epoll_data or \"void *udata\" of kevent.\ntype PollAttachment struct {\n\tFD       int\n\tCallback PollEventHandler\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_poller_bsd.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || openbsd\n\npackage netpoll\n\n// IOFlags represents the flags of IO events.\ntype IOFlags = uint16\n\n// IOEvent is the integer type of I/O events on BSD's.\ntype IOEvent = int16\n"
  },
  {
    "path": "pkg/netpoll/defs_poller_epoll.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build linux\n\npackage netpoll\n\nimport \"golang.org/x/sys/unix\"\n\n// IOFlags represents the flags of IO events.\ntype IOFlags = uint16\n\n// IOEvent is the integer type of I/O events on Linux.\ntype IOEvent = uint32\n\nconst (\n\t// InitPollEventsCap represents the initial capacity of poller event-list.\n\tInitPollEventsCap = 128\n\t// MaxPollEventsCap is the maximum limitation of events that the poller can process.\n\tMaxPollEventsCap = 1024\n\t// MinPollEventsCap is the minimum limitation of events that the poller can process.\n\tMinPollEventsCap = 32\n\t// MaxAsyncTasksAtOneTime is the maximum amount of asynchronous tasks that the event-loop will process at one time.\n\tMaxAsyncTasksAtOneTime = 256\n\t// ReadEvents represents readable events that are polled by epoll.\n\tReadEvents = unix.EPOLLIN | unix.EPOLLPRI\n\t// WriteEvents represents writeable events that are polled by epoll.\n\tWriteEvents = unix.EPOLLOUT\n\t// ReadWriteEvents represents both readable and writeable events.\n\tReadWriteEvents = ReadEvents | WriteEvents\n\t// ErrEvents represents exceptional events that occurred.\n\tErrEvents = unix.EPOLLERR | unix.EPOLLHUP\n)\n\n// IsReadEvent checks if the event is a read event.\nfunc IsReadEvent(event IOEvent) bool {\n\treturn event&ReadEvents != 0\n}\n\n// IsWriteEvent checks if the event is a write event.\nfunc IsWriteEvent(event IOEvent) bool {\n\treturn event&WriteEvents != 0\n}\n\n// IsErrorEvent checks if the event is an error event.\nfunc IsErrorEvent(event IOEvent, _ IOFlags) bool {\n\treturn event&ErrEvents != 0\n}\n\ntype eventList struct {\n\tsize   int\n\tevents []epollevent\n}\n\nfunc newEventList(size int) *eventList {\n\treturn &eventList{size, make([]epollevent, size)}\n}\n\nfunc (el *eventList) expand() {\n\tif newSize := el.size << 1; newSize <= MaxPollEventsCap {\n\t\tel.size = newSize\n\t\tel.events = make([]epollevent, newSize)\n\t}\n}\n\nfunc (el *eventList) shrink() {\n\tif newSize := el.size >> 1; newSize >= MinPollEventsCap {\n\t\tel.size = newSize\n\t\tel.events = make([]epollevent, newSize)\n\t}\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_poller_kqueue.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || netbsd || openbsd\n\npackage netpoll\n\nimport \"golang.org/x/sys/unix\"\n\nconst (\n\t// InitPollEventsCap represents the initial capacity of poller event-list.\n\tInitPollEventsCap = 64\n\t// MaxPollEventsCap is the maximum limitation of events that the poller can process.\n\tMaxPollEventsCap = 512\n\t// MinPollEventsCap is the minimum limitation of events that the poller can process.\n\tMinPollEventsCap = 16\n\t// MaxAsyncTasksAtOneTime is the maximum amount of asynchronous tasks that the event-loop will process at one time.\n\tMaxAsyncTasksAtOneTime = 128\n\t// ReadEvents represents readable events that are polled by kqueue.\n\tReadEvents = unix.EVFILT_READ\n\t// WriteEvents represents writeable events that are polled by kqueue.\n\tWriteEvents = unix.EVFILT_WRITE\n\t// ReadWriteEvents represents both readable and writeable events.\n\tReadWriteEvents = ReadEvents | WriteEvents\n\t// ErrEvents represents exceptional events that occurred.\n\tErrEvents = unix.EV_EOF | unix.EV_ERROR\n)\n\n// IsReadEvent checks if the event is a read event.\nfunc IsReadEvent(event IOEvent) bool {\n\treturn event == ReadEvents\n}\n\n// IsWriteEvent checks if the event is a write event.\nfunc IsWriteEvent(event IOEvent) bool {\n\treturn event == WriteEvents\n}\n\n// IsErrorEvent checks if the event is an error event.\nfunc IsErrorEvent(_ IOEvent, flags IOFlags) bool {\n\treturn flags&ErrEvents != 0\n}\n\ntype eventList struct {\n\tsize   int\n\tevents []unix.Kevent_t\n}\n\nfunc newEventList(size int) *eventList {\n\treturn &eventList{size, make([]unix.Kevent_t, size)}\n}\n\nfunc (el *eventList) expand() {\n\tif newSize := el.size << 1; newSize <= MaxPollEventsCap {\n\t\tel.size = newSize\n\t\tel.events = make([]unix.Kevent_t, newSize)\n\t}\n}\n\nfunc (el *eventList) shrink() {\n\tif newSize := el.size >> 1; newSize >= MinPollEventsCap {\n\t\tel.size = newSize\n\t\tel.events = make([]unix.Kevent_t, newSize)\n\t}\n}\n"
  },
  {
    "path": "pkg/netpoll/defs_poller_netbsd.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage netpoll\n\n// IOEvent is the integer type of I/O events on BSD's.\ntype IOEvent = uint32\n\n// IOFlags represents the flags of IO events.\ntype IOFlags = uint32\n"
  },
  {
    "path": "pkg/netpoll/example_test.go",
    "content": "// Copyright (c) 2025 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage netpoll_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n)\n\nfunc Example() {\n\tln, err := net.Listen(\"tcp\", \"127.0.0.1:9090\")\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Error listening: %v\", err))\n\t}\n\n\tdefer ln.Close() //nolint:errcheck\n\n\tctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)\n\tdefer cancel()\n\n\tgo func() {\n\t\tc, err := ln.Accept()\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"Error accepting connection: %v\", err))\n\t\t}\n\n\t\tdefer c.Close() //nolint:errcheck\n\n\t\tbuf := make([]byte, 64)\n\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tcancel()\n\t\t\t\tfmt.Printf(\"Signal received: %v\\n\", ctx.Err())\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\t_, err := c.Read(buf)\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"Error reading data from client: %v\", err))\n\t\t\t}\n\t\t\tfmt.Printf(\"Received data from client: %s\\n\", buf)\n\n\t\t\t_, err = c.Write([]byte(\"Hello, client!\"))\n\t\t\tif err != nil {\n\t\t\t\tpanic(fmt.Sprintf(\"Error writing data to client: %v\", err))\n\t\t\t}\n\t\t\tfmt.Println(\"Sent data to client\")\n\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t}\n\t}()\n\n\t// Wait for the server to start running.\n\ttime.Sleep(500 * time.Millisecond)\n\n\tpoller, err := netpoll.OpenPoller()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Error opening poller: %v\", err))\n\t}\n\n\tdefer poller.Close() //nolint:errcheck\n\n\taddr, err := net.ResolveTCPAddr(\"tcp\", ln.Addr().String())\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Error resolving TCP address: %v\", err))\n\t}\n\tc, err := net.DialTCP(\"tcp\", nil, addr)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Error dialing TCP address: %v\", err))\n\t}\n\n\tf, err := c.File()\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"Error getting file from connection: %v\", err))\n\t}\n\n\tcloseClient := func() {\n\t\tc.Close() //nolint:errcheck\n\t\tf.Close() //nolint:errcheck\n\t}\n\tdefer closeClient()\n\n\tsendData := true\n\n\tpa := netpoll.PollAttachment{\n\t\tFD: int(f.Fd()),\n\t\tCallback: func(fd int, event netpoll.IOEvent, flags netpoll.IOFlags) error { //nolint:revive\n\t\t\tif netpoll.IsErrorEvent(event, flags) {\n\t\t\t\tcloseClient()\n\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t}\n\n\t\t\tif netpoll.IsReadEvent(event) {\n\t\t\t\tsendData = true\n\t\t\t\tbuf := make([]byte, 64)\n\t\t\t\t_, err := c.Read(buf)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcloseClient()\n\t\t\t\t\tfmt.Println(\"Error reading data from server:\", err)\n\t\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t\t}\n\t\t\t\tfmt.Printf(\"Received data from server: %s\\n\", buf)\n\t\t\t\t// Process the data...\n\t\t\t}\n\n\t\t\tif netpoll.IsWriteEvent(event) && sendData {\n\t\t\t\tsendData = false\n\t\t\t\t// Write data to the connection...\n\t\t\t\t_, err := c.Write([]byte(\"Hello, server!\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcloseClient()\n\t\t\t\t\tfmt.Println(\"Error writing data to server:\", err)\n\t\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t\t}\n\t\t\t\tfmt.Println(\"Sent data to server\")\n\t\t\t}\n\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tif err := poller.AddReadWrite(&pa, false); err != nil {\n\t\tpanic(fmt.Sprintf(\"Error adding file descriptor to poller: %v\", err))\n\t}\n\n\terr = poller.Polling(func(fd int, event netpoll.IOEvent, flags netpoll.IOFlags) error {\n\t\treturn pa.Callback(fd, event, flags)\n\t})\n\n\tfmt.Printf(\"Poller exited with error: %v\", err)\n}\n"
  },
  {
    "path": "pkg/netpoll/netpoll.go",
    "content": "// Copyright (c) 2025 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\n/*\nPackage netpoll provides a portable event-driven interface for network I/O.\n\nThe underlying facility of event notification is OS-specific:\n  - epoll on Linux - https://man7.org/linux/man-pages/man7/epoll.7.html\n  - kqueue on *BSD/Darwin - https://man.freebsd.org/cgi/man.cgi?kqueue\n\nWith the help of the netpoll package, you can easily build your own high-performance\nevent-driven network applications based on epoll/kqueue.\n\nThe Poller represents the event notification facility whose backend is epoll or kqueue.\nThe OpenPoller function creates a new Poller instance:\n\n\tpoller, err := netpoll.OpenPoller()\n\tif err != nil {\n\t\t// handle error\n\t}\n\n\tdefer poller.Close()\n\n\taddr, err := net.ResolveTCPAddr(\"tcp\", \"127.0.0.1:9090\")\n\tif err != nil {\n\t\t// handle error\n\t}\n\tc, err := net.DialTCP(\"tcp\", nil, addr)\n\tif err != nil {\n\t\t// handle error\n\t}\n\n\tf, err := c.File()\n\tif err != nil {\n\t\t// handle error\n\t}\n\n\tcloseClient := func() {\n\t\tc.Close()\n\t\tf.Close()\n\t}\n\tdefer closeClient()\n\nThe PollAttachment consists of a file descriptor and its callback function.\nPollAttachment is used to register a file descriptor to Poller.\nThe callback function is called when an event occurs on the file descriptor:\n\n\tpa := netpoll.PollAttachment{\n\t\tFD: int(f.Fd()),\n\t\tCallback: func(fd int, event netpoll.IOEvent, flags netpoll.IOFlags) error {\n\t\t\tif netpoll.IsErrorEvent(event, flags) {\n\t\t\t\tcloseClient()\n\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t}\n\n\t\t\tif netpoll.IsReadEvent(event) {\n\t\t\t\tbuf := make([]byte, 64)\n\t\t\t\t// Read data from the connection.\n\t\t\t\t_, err := c.Read(buf)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcloseClient()\n\t\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t\t}\n\t\t\t\t// Process the data...\n\t\t\t}\n\n\t\t\tif netpoll.IsWriteEvent(event) {\n\t\t\t\t// Write data to the connection.\n\t\t\t\t_, err := c.Write([]byte(\"hello\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\tcloseClient()\n\t\t\t\t\treturn errors.ErrEngineShutdown\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nil\n\t\t}}\n\n\tif err := poller.AddReadWrite(&pa, false); err != nil {\n\t\t// handle error\n\t}\n\nThe Poller.Polling function starts the event loop monitoring file descriptors and\nwaiting for I/O events to occur:\n\n\tpoller.Polling(func(fd int, event netpoll.IOEvent, flags netpoll.IOFlags) error {\n\t\treturn pa.Callback(fd, event, flags)\n\t})\n\nOr\n\n\tpoller.Polling()\n\nif you've enabled the build tag `poll_opt`.\n*/\npackage netpoll\n"
  },
  {
    "path": "pkg/netpoll/poller_epoll_default.go",
    "content": "// Copyright (c) 2019 Andy Pan\n// Copyright (c) 2017 Joshua J Baker\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build linux && !poll_opt\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n)\n\n// Poller represents a poller which is in charge of monitoring file-descriptors.\ntype Poller struct {\n\tfd                          int    // epoll fd\n\tefd                         int    // eventfd\n\tefdBuf                      []byte // efd buffer to read an 8-byte integer\n\twakeupCall                  int32\n\tasyncTaskQueue              queue.AsyncTaskQueue // queue with low priority\n\turgentAsyncTaskQueue        queue.AsyncTaskQueue // queue with high priority\n\thighPriorityEventsThreshold int32                // threshold of high-priority events\n}\n\n// OpenPoller instantiates a poller.\nfunc OpenPoller() (poller *Poller, err error) {\n\tpoller = new(Poller)\n\tif poller.fd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC); err != nil {\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"epoll_create1\", err)\n\t\treturn\n\t}\n\tif poller.efd, err = unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"eventfd\", err)\n\t\treturn\n\t}\n\tpoller.efdBuf = make([]byte, 8)\n\tif err = poller.AddRead(&PollAttachment{FD: poller.efd}, true); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\treturn\n\t}\n\tpoller.asyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.urgentAsyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.highPriorityEventsThreshold = MaxPollEventsCap\n\treturn\n}\n\n// Close closes the poller.\nfunc (p *Poller) Close() error {\n\t_ = unix.Close(p.efd)\n\treturn os.NewSyscallError(\"close\", unix.Close(p.fd))\n}\n\n// Make the endianness of bytes compatible with more linux OSs under different processor-architectures,\n// according to http://man7.org/linux/man-pages/man2/eventfd.2.html.\nvar (\n\tu uint64 = 1\n\tb        = (*(*[8]byte)(unsafe.Pointer(&u)))[:]\n)\n\n// Trigger enqueues task and wakes up the poller to process pending tasks.\n// By default, any incoming task will enqueued into urgentAsyncTaskQueue\n// before the threshold of high-priority events is reached. When it happens,\n// any asks other than high-priority tasks will be shunted to asyncTaskQueue.\n//\n// Note that asyncTaskQueue is a queue of low-priority whose size may grow large and tasks in it may backlog.\nfunc (p *Poller) Trigger(priority queue.EventPriority, fn queue.Func, param any) (err error) {\n\ttask := queue.GetTask()\n\ttask.Exec, task.Param = fn, param\n\tif priority > queue.HighPriority && p.urgentAsyncTaskQueue.Length() >= p.highPriorityEventsThreshold {\n\t\tp.asyncTaskQueue.Enqueue(task)\n\t} else {\n\t\t// There might be some low-priority tasks overflowing into urgentAsyncTaskQueue in a flash,\n\t\t// but that's tolerable because it ought to be a rare case.\n\t\tp.urgentAsyncTaskQueue.Enqueue(task)\n\t}\n\tif atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\tfor {\n\t\t\t_, err = unix.Write(p.efd, b)\n\t\t\tif err == unix.EAGAIN {\n\t\t\t\t_, _ = unix.Read(p.efd, p.efdBuf)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn os.NewSyscallError(\"write\", err)\n}\n\n// Polling blocks the current goroutine, monitoring the registered file descriptors and waiting for network I/O.\n// When I/O occurs on any of the file descriptors, the provided callback function is invoked.\nfunc (p *Poller) Polling(callback PollEventHandler) error {\n\tel := newEventList(InitPollEventsCap)\n\tvar doChores bool\n\n\tmsec := -1\n\tfor {\n\t\tn, err := unix.EpollWait(p.fd, el.events, msec)\n\t\tif n == 0 || (n < 0 && err == unix.EINTR) {\n\t\t\tmsec = -1\n\t\t\truntime.Gosched()\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\tlogging.Errorf(\"error occurs in epoll: %v\", os.NewSyscallError(\"epoll_wait\", err))\n\t\t\treturn err\n\t\t}\n\t\tmsec = 0\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tev := &el.events[i]\n\t\t\tif fd := int(ev.Fd); fd == p.efd { // poller is awakened to run tasks in queues.\n\t\t\t\tdoChores = true\n\t\t\t} else {\n\t\t\t\terr = callback(fd, ev.Events, 0)\n\t\t\t\tif errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif doChores {\n\t\t\tdoChores = false\n\t\t\ttask := p.urgentAsyncTaskQueue.Dequeue()\n\t\t\tfor ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() {\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tfor i := 0; i < MaxAsyncTasksAtOneTime; i++ {\n\t\t\t\tif task = p.asyncTaskQueue.Dequeue(); task == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tatomic.StoreInt32(&p.wakeupCall, 0)\n\t\t\tif (!p.asyncTaskQueue.IsEmpty() || !p.urgentAsyncTaskQueue.IsEmpty()) && atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\t\t\tfor {\n\t\t\t\t\t_, err = unix.Write(p.efd, b)\n\t\t\t\t\tif err == unix.EAGAIN {\n\t\t\t\t\t\t_, _ = unix.Read(p.efd, p.efdBuf)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogging.Errorf(\"failed to notify next round of event-loop for leftover tasks, %v\", os.NewSyscallError(\"write\", err))\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif n == el.size {\n\t\t\tel.expand()\n\t\t} else if n < el.size>>1 {\n\t\t\tel.shrink()\n\t\t}\n\t}\n}\n\n// AddReadWrite registers the given file descriptor with readable and writable events to the poller.\nfunc (p *Poller) AddReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev uint32 = ReadWriteEvents\n\tif edgeTriggered {\n\t\tev |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\treturn os.NewSyscallError(\"epoll_ctl add\",\n\t\tunix.EpollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &unix.EpollEvent{Fd: int32(pa.FD), Events: ev}))\n}\n\n// AddRead registers the given file descriptor with readable event to the poller.\nfunc (p *Poller) AddRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev uint32 = ReadEvents\n\tif edgeTriggered {\n\t\tev |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\treturn os.NewSyscallError(\"epoll_ctl add\",\n\t\tunix.EpollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &unix.EpollEvent{Fd: int32(pa.FD), Events: ev}))\n}\n\n// AddWrite registers the given file descriptor with writable event to the poller.\nfunc (p *Poller) AddWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev uint32 = WriteEvents\n\tif edgeTriggered {\n\t\tev |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\treturn os.NewSyscallError(\"epoll_ctl add\",\n\t\tunix.EpollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &unix.EpollEvent{Fd: int32(pa.FD), Events: ev}))\n}\n\n// ModRead modifies the given file descriptor with readable event in the poller.\nfunc (p *Poller) ModRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev uint32 = ReadEvents\n\tif edgeTriggered {\n\t\tev |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\treturn os.NewSyscallError(\"epoll_ctl mod\",\n\t\tunix.EpollCtl(p.fd, unix.EPOLL_CTL_MOD, pa.FD, &unix.EpollEvent{Fd: int32(pa.FD), Events: ev}))\n}\n\n// ModReadWrite modifies the given file descriptor with readable and writable events in the poller.\nfunc (p *Poller) ModReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev uint32 = ReadWriteEvents\n\tif edgeTriggered {\n\t\tev |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\treturn os.NewSyscallError(\"epoll_ctl mod\",\n\t\tunix.EpollCtl(p.fd, unix.EPOLL_CTL_MOD, pa.FD, &unix.EpollEvent{Fd: int32(pa.FD), Events: ev}))\n}\n\n// Delete removes the given file descriptor from the poller.\nfunc (p *Poller) Delete(fd int) error {\n\treturn os.NewSyscallError(\"epoll_ctl del\", unix.EpollCtl(p.fd, unix.EPOLL_CTL_DEL, fd, nil))\n}\n"
  },
  {
    "path": "pkg/netpoll/poller_epoll_ultimate.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build linux && poll_opt\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n)\n\n// Poller represents a poller which is in charge of monitoring file-descriptors.\ntype Poller struct {\n\tfd                          int             // epoll fd\n\tepa                         *PollAttachment // PollAttachment for waking events\n\tefdBuf                      []byte          // efd buffer to read an 8-byte integer\n\twakeupCall                  int32\n\tasyncTaskQueue              queue.AsyncTaskQueue // queue with low priority\n\turgentAsyncTaskQueue        queue.AsyncTaskQueue // queue with high priority\n\thighPriorityEventsThreshold int32                // threshold of high-priority events\n}\n\n// OpenPoller instantiates a poller.\nfunc OpenPoller() (poller *Poller, err error) {\n\tpoller = new(Poller)\n\tif poller.fd, err = unix.EpollCreate1(unix.EPOLL_CLOEXEC); err != nil {\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"epoll_create1\", err)\n\t\treturn\n\t}\n\tvar efd int\n\tif efd, err = unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"eventfd\", err)\n\t\treturn\n\t}\n\tpoller.efdBuf = make([]byte, 8)\n\tpoller.epa = &PollAttachment{FD: efd}\n\tif err = poller.AddRead(poller.epa, true); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\treturn\n\t}\n\tpoller.asyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.urgentAsyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.highPriorityEventsThreshold = MaxPollEventsCap\n\treturn\n}\n\n// Close closes the poller.\nfunc (p *Poller) Close() error {\n\t_ = unix.Close(p.epa.FD)\n\treturn os.NewSyscallError(\"close\", unix.Close(p.fd))\n}\n\n// Make the endianness of bytes compatible with more linux OSs under different processor-architectures,\n// according to http://man7.org/linux/man-pages/man2/eventfd.2.html.\nvar (\n\tu uint64 = 1\n\tb        = (*(*[8]byte)(unsafe.Pointer(&u)))[:]\n)\n\n// Trigger enqueues task and wakes up the poller to process pending tasks.\n// By default, any incoming task will enqueued into urgentAsyncTaskQueue\n// before the threshold of high-priority events is reached. When it happens,\n// any asks other than high-priority tasks will be shunted to asyncTaskQueue.\n//\n// Note that asyncTaskQueue is a queue of low-priority whose size may grow large and tasks in it may backlog.\nfunc (p *Poller) Trigger(priority queue.EventPriority, fn queue.Func, param any) (err error) {\n\ttask := queue.GetTask()\n\ttask.Exec, task.Param = fn, param\n\tif priority > queue.HighPriority && p.urgentAsyncTaskQueue.Length() >= p.highPriorityEventsThreshold {\n\t\tp.asyncTaskQueue.Enqueue(task)\n\t} else {\n\t\t// There might be some low-priority tasks overflowing into urgentAsyncTaskQueue in a flash,\n\t\t// but that's tolerable because it ought to be a rare case.\n\t\tp.urgentAsyncTaskQueue.Enqueue(task)\n\t}\n\tif atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\tfor {\n\t\t\t_, err = unix.Write(p.epa.FD, b)\n\t\t\tif err == unix.EAGAIN {\n\t\t\t\t_, _ = unix.Read(p.epa.FD, p.efdBuf)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\treturn os.NewSyscallError(\"write\", err)\n}\n\n// Polling blocks the current goroutine, monitoring the registered file descriptors and waiting for network I/O.\n// When I/O occurs on any of the file descriptors, the provided callback function is invoked.\nfunc (p *Poller) Polling() error {\n\tel := newEventList(InitPollEventsCap)\n\tvar doChores bool\n\n\tmsec := -1\n\tfor {\n\t\tn, err := epollWait(p.fd, el.events, msec)\n\t\tif n == 0 || (n < 0 && err == unix.EINTR) {\n\t\t\tmsec = -1\n\t\t\truntime.Gosched()\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\tlogging.Errorf(\"error occurs in epoll: %v\", os.NewSyscallError(\"epoll_wait\", err))\n\t\t\treturn err\n\t\t}\n\t\tmsec = 0\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tev := &el.events[i]\n\t\t\tpollAttachment := restorePollAttachment(unsafe.Pointer(&ev.data))\n\t\t\tif pollAttachment.FD == p.epa.FD { // poller is awakened to run tasks in queues.\n\t\t\t\tdoChores = true\n\t\t\t} else {\n\t\t\t\terr = pollAttachment.Callback(pollAttachment.FD, ev.events, 0)\n\t\t\t\tif errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif doChores {\n\t\t\tdoChores = false\n\t\t\ttask := p.urgentAsyncTaskQueue.Dequeue()\n\t\t\tfor ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() {\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tfor i := 0; i < MaxAsyncTasksAtOneTime; i++ {\n\t\t\t\tif task = p.asyncTaskQueue.Dequeue(); task == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tatomic.StoreInt32(&p.wakeupCall, 0)\n\t\t\tif (!p.asyncTaskQueue.IsEmpty() || !p.urgentAsyncTaskQueue.IsEmpty()) && atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\t\t\tfor {\n\t\t\t\t\t_, err = unix.Write(p.epa.FD, b)\n\t\t\t\t\tif err == unix.EAGAIN {\n\t\t\t\t\t\t_, _ = unix.Read(p.epa.FD, p.efdBuf)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tlogging.Errorf(\"failed to notify next round of event-loop for leftover tasks, %v\", os.NewSyscallError(\"write\", err))\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif n == el.size {\n\t\t\tel.expand()\n\t\t} else if n < el.size>>1 {\n\t\t\tel.shrink()\n\t\t}\n\t}\n}\n\n// AddReadWrite registers the given file descriptor with readable and writable events to the poller.\nfunc (p *Poller) AddReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev epollevent\n\tev.events = ReadWriteEvents\n\tif edgeTriggered {\n\t\tev.events |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&ev.data), pa)\n\treturn os.NewSyscallError(\"epoll_ctl add\", epollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &ev))\n}\n\n// AddRead registers the given file descriptor with readable event to the poller.\nfunc (p *Poller) AddRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev epollevent\n\tev.events = ReadEvents\n\tif edgeTriggered {\n\t\tev.events |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&ev.data), pa)\n\treturn os.NewSyscallError(\"epoll_ctl add\", epollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &ev))\n}\n\n// AddWrite registers the given file descriptor with writable event to the poller.\nfunc (p *Poller) AddWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev epollevent\n\tev.events = WriteEvents\n\tif edgeTriggered {\n\t\tev.events |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&ev.data), pa)\n\treturn os.NewSyscallError(\"epoll_ctl add\", epollCtl(p.fd, unix.EPOLL_CTL_ADD, pa.FD, &ev))\n}\n\n// ModRead modifies the given file descriptor with readable event in the poller.\nfunc (p *Poller) ModRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev epollevent\n\tev.events = ReadEvents\n\tif edgeTriggered {\n\t\tev.events |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&ev.data), pa)\n\treturn os.NewSyscallError(\"epoll_ctl mod\", epollCtl(p.fd, unix.EPOLL_CTL_MOD, pa.FD, &ev))\n}\n\n// ModReadWrite modifies the given file descriptor with readable and writable events in the poller.\nfunc (p *Poller) ModReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar ev epollevent\n\tev.events = ReadWriteEvents\n\tif edgeTriggered {\n\t\tev.events |= unix.EPOLLET | unix.EPOLLRDHUP\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&ev.data), pa)\n\treturn os.NewSyscallError(\"epoll_ctl mod\", epollCtl(p.fd, unix.EPOLL_CTL_MOD, pa.FD, &ev))\n}\n\n// Delete removes the given file descriptor from the poller.\nfunc (p *Poller) Delete(fd int) error {\n\treturn os.NewSyscallError(\"epoll_ctl del\", epollCtl(p.fd, unix.EPOLL_CTL_DEL, fd, nil))\n}\n"
  },
  {
    "path": "pkg/netpoll/poller_kqueue_default.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && !poll_opt\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n)\n\n// Poller represents a poller which is in charge of monitoring file-descriptors.\ntype Poller struct {\n\tfd                          int\n\tpipe                        []int\n\twakeupCall                  int32\n\tasyncTaskQueue              queue.AsyncTaskQueue // queue with low priority\n\turgentAsyncTaskQueue        queue.AsyncTaskQueue // queue with high priority\n\thighPriorityEventsThreshold int32                // threshold of high-priority events\n}\n\n// OpenPoller instantiates a poller.\nfunc OpenPoller() (poller *Poller, err error) {\n\tpoller = new(Poller)\n\tif poller.fd, err = unix.Kqueue(); err != nil {\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"kqueue\", err)\n\t\treturn\n\t}\n\tif err = poller.addWakeupEvent(); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"kevent | pipe2\", err)\n\t\treturn\n\t}\n\tpoller.asyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.urgentAsyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.highPriorityEventsThreshold = MaxPollEventsCap\n\treturn\n}\n\n// Close closes the poller.\nfunc (p *Poller) Close() error {\n\tif len(p.pipe) == 2 {\n\t\t_ = unix.Close(p.pipe[0])\n\t\t_ = unix.Close(p.pipe[1])\n\t}\n\treturn os.NewSyscallError(\"close\", unix.Close(p.fd))\n}\n\n// Trigger enqueues task and wakes up the poller to process pending tasks.\n// By default, any incoming task will enqueued into urgentAsyncTaskQueue\n// before the threshold of high-priority events is reached. When it happens,\n// any asks other than high-priority tasks will be shunted to asyncTaskQueue.\n//\n// Note that asyncTaskQueue is a queue of low-priority whose size may grow large and tasks in it may backlog.\nfunc (p *Poller) Trigger(priority queue.EventPriority, fn queue.Func, param any) (err error) {\n\ttask := queue.GetTask()\n\ttask.Exec, task.Param = fn, param\n\tif priority > queue.HighPriority && p.urgentAsyncTaskQueue.Length() >= p.highPriorityEventsThreshold {\n\t\tp.asyncTaskQueue.Enqueue(task)\n\t} else {\n\t\t// There might be some low-priority tasks overflowing into urgentAsyncTaskQueue in a flash,\n\t\t// but that's tolerable because it ought to be a rare case.\n\t\tp.urgentAsyncTaskQueue.Enqueue(task)\n\t}\n\tif atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\terr = p.wakePoller()\n\t}\n\treturn os.NewSyscallError(\"kevent | write\", err)\n}\n\n// Polling blocks the current goroutine, monitoring the registered file descriptors and waiting for network I/O.\n// When I/O occurs on any of the file descriptors, the provided callback function is invoked.\nfunc (p *Poller) Polling(callback PollEventHandler) error {\n\tel := newEventList(InitPollEventsCap)\n\n\tvar (\n\t\tts       unix.Timespec\n\t\ttsp      *unix.Timespec\n\t\tdoChores bool\n\t)\n\tfor {\n\t\tn, err := unix.Kevent(p.fd, nil, el.events, tsp)\n\t\tif n == 0 || (n < 0 && err == unix.EINTR) {\n\t\t\ttsp = nil\n\t\t\truntime.Gosched()\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\tlogging.Errorf(\"error occurs in kqueue: %v\", os.NewSyscallError(\"kevent wait\", err))\n\t\t\treturn err\n\t\t}\n\t\ttsp = &ts\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tev := &el.events[i]\n\t\t\tif fd := int(ev.Ident); fd == 0 { // poller is awakened to run tasks in queues\n\t\t\t\tdoChores = true\n\t\t\t\tp.drainWakeupEvent()\n\t\t\t} else {\n\t\t\t\terr = callback(fd, ev.Filter, ev.Flags)\n\t\t\t\tif errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif doChores {\n\t\t\tdoChores = false\n\t\t\ttask := p.urgentAsyncTaskQueue.Dequeue()\n\t\t\tfor ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() {\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tfor i := 0; i < MaxAsyncTasksAtOneTime; i++ {\n\t\t\t\tif task = p.asyncTaskQueue.Dequeue(); task == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tatomic.StoreInt32(&p.wakeupCall, 0)\n\t\t\tif (!p.asyncTaskQueue.IsEmpty() || !p.urgentAsyncTaskQueue.IsEmpty()) && atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\t\t\tif err = p.wakePoller(); err != nil {\n\t\t\t\t\tdoChores = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif n == el.size {\n\t\t\tel.expand()\n\t\t} else if n < el.size>>1 {\n\t\t\tel.shrink()\n\t\t}\n\t}\n}\n\n// AddReadWrite registers the given file descriptor with readable and writable events to the poller.\nfunc (p *Poller) AddReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar flags IOFlags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tflags |= unix.EV_CLEAR\n\t}\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{\n\t\t{Ident: keventIdent(pa.FD), Flags: flags, Filter: unix.EVFILT_READ},\n\t\t{Ident: keventIdent(pa.FD), Flags: flags, Filter: unix.EVFILT_WRITE},\n\t}, nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// AddRead registers the given file descriptor with readable event to the poller.\nfunc (p *Poller) AddRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar flags IOFlags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tflags |= unix.EV_CLEAR\n\t}\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{\n\t\t{Ident: keventIdent(pa.FD), Flags: flags, Filter: unix.EVFILT_READ},\n\t}, nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// AddWrite registers the given file descriptor with writable event to the poller.\nfunc (p *Poller) AddWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar flags IOFlags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tflags |= unix.EV_CLEAR\n\t}\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{\n\t\t{Ident: keventIdent(pa.FD), Flags: flags, Filter: unix.EVFILT_WRITE},\n\t}, nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// ModRead modifies the given file descriptor with readable event in the poller.\nfunc (p *Poller) ModRead(pa *PollAttachment, _ bool) error {\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{\n\t\t{Ident: keventIdent(pa.FD), Flags: unix.EV_DELETE, Filter: unix.EVFILT_WRITE},\n\t}, nil, nil)\n\treturn os.NewSyscallError(\"kevent delete\", err)\n}\n\n// ModReadWrite modifies the given file descriptor with readable and writable events in the poller.\nfunc (p *Poller) ModReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar flags IOFlags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tflags |= unix.EV_CLEAR\n\t}\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{\n\t\t{Ident: keventIdent(pa.FD), Flags: flags, Filter: unix.EVFILT_WRITE},\n\t}, nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// Delete removes the given file descriptor from the poller.\nfunc (*Poller) Delete(_ int) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/netpoll/poller_kqueue_ultimate.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && poll_opt\n\npackage netpoll\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync/atomic\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n)\n\n// Poller represents a poller which is in charge of monitoring file-descriptors.\ntype Poller struct {\n\tfd                          int\n\tpipe                        []int\n\twakeupCall                  int32\n\tasyncTaskQueue              queue.AsyncTaskQueue // queue with low priority\n\turgentAsyncTaskQueue        queue.AsyncTaskQueue // queue with high priority\n\thighPriorityEventsThreshold int32                // threshold of high-priority events\n}\n\n// OpenPoller instantiates a poller.\nfunc OpenPoller() (poller *Poller, err error) {\n\tpoller = new(Poller)\n\tif poller.fd, err = unix.Kqueue(); err != nil {\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"kqueue\", err)\n\t\treturn\n\t}\n\tif err = poller.addWakeupEvent(); err != nil {\n\t\t_ = poller.Close()\n\t\tpoller = nil\n\t\terr = os.NewSyscallError(\"kevent | pipe2\", err)\n\t\treturn\n\t}\n\tpoller.asyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.urgentAsyncTaskQueue = queue.NewLockFreeQueue()\n\tpoller.highPriorityEventsThreshold = MaxPollEventsCap\n\treturn\n}\n\n// Close closes the poller.\nfunc (p *Poller) Close() error {\n\tif len(p.pipe) == 2 {\n\t\t_ = unix.Close(p.pipe[0])\n\t\t_ = unix.Close(p.pipe[1])\n\t}\n\treturn os.NewSyscallError(\"close\", unix.Close(p.fd))\n}\n\n// Trigger enqueues task and wakes up the poller to process pending tasks.\n// By default, any incoming task will enqueued into urgentAsyncTaskQueue\n// before the threshold of high-priority events is reached. When it happens,\n// any asks other than high-priority tasks will be shunted to asyncTaskQueue.\n//\n// Note that asyncTaskQueue is a queue of low-priority whose size may grow large and tasks in it may backlog.\nfunc (p *Poller) Trigger(priority queue.EventPriority, fn queue.Func, param any) (err error) {\n\ttask := queue.GetTask()\n\ttask.Exec, task.Param = fn, param\n\tif priority > queue.HighPriority && p.urgentAsyncTaskQueue.Length() >= p.highPriorityEventsThreshold {\n\t\tp.asyncTaskQueue.Enqueue(task)\n\t} else {\n\t\t// There might be some low-priority tasks overflowing into urgentAsyncTaskQueue in a flash,\n\t\t// but that's tolerable because it ought to be a rare case.\n\t\tp.urgentAsyncTaskQueue.Enqueue(task)\n\t}\n\tif atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\terr = p.wakePoller()\n\t}\n\treturn os.NewSyscallError(\"kevent | write\", err)\n}\n\n// Polling blocks the current goroutine, monitoring the registered file descriptors and waiting for network I/O.\n// When I/O occurs on any of the file descriptors, the provided callback function is invoked.\nfunc (p *Poller) Polling() error {\n\tel := newEventList(InitPollEventsCap)\n\n\tvar (\n\t\tts       unix.Timespec\n\t\ttsp      *unix.Timespec\n\t\tdoChores bool\n\t)\n\tfor {\n\t\tn, err := unix.Kevent(p.fd, nil, el.events, tsp)\n\t\tif n == 0 || (n < 0 && err == unix.EINTR) {\n\t\t\ttsp = nil\n\t\t\truntime.Gosched()\n\t\t\tcontinue\n\t\t} else if err != nil {\n\t\t\tlogging.Errorf(\"error occurs in kqueue: %v\", os.NewSyscallError(\"kevent wait\", err))\n\t\t\treturn err\n\t\t}\n\t\ttsp = &ts\n\n\t\tfor i := 0; i < n; i++ {\n\t\t\tev := &el.events[i]\n\t\t\tif ev.Ident == 0 { // poller is awakened to run tasks in queues\n\t\t\t\tdoChores = true\n\t\t\t\tp.drainWakeupEvent()\n\t\t\t} else {\n\t\t\t\tpollAttachment := restorePollAttachment(unsafe.Pointer(&ev.Udata))\n\t\t\t\terr = pollAttachment.Callback(int(ev.Ident), ev.Filter, ev.Flags)\n\t\t\t\tif errors.Is(err, errorx.ErrAcceptSocket) || errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif doChores {\n\t\t\tdoChores = false\n\t\t\ttask := p.urgentAsyncTaskQueue.Dequeue()\n\t\t\tfor ; task != nil; task = p.urgentAsyncTaskQueue.Dequeue() {\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tfor i := 0; i < MaxAsyncTasksAtOneTime; i++ {\n\t\t\t\tif task = p.asyncTaskQueue.Dequeue(); task == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\terr = task.Exec(task.Param)\n\t\t\t\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tqueue.PutTask(task)\n\t\t\t}\n\t\t\tatomic.StoreInt32(&p.wakeupCall, 0)\n\t\t\tif (!p.asyncTaskQueue.IsEmpty() || !p.urgentAsyncTaskQueue.IsEmpty()) && atomic.CompareAndSwapInt32(&p.wakeupCall, 0, 1) {\n\t\t\t\tif err = p.wakePoller(); err != nil {\n\t\t\t\t\tdoChores = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif n == el.size {\n\t\t\tel.expand()\n\t\t} else if n < el.size>>1 {\n\t\t\tel.shrink()\n\t\t}\n\t}\n}\n\n// AddReadWrite registers the given file descriptor with readable and writable events to the poller.\nfunc (p *Poller) AddReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar evs [2]unix.Kevent_t\n\tevs[0].Ident = keventIdent(pa.FD)\n\tevs[0].Filter = unix.EVFILT_READ\n\tevs[0].Flags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tevs[0].Flags |= unix.EV_CLEAR\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&evs[0].Udata), pa)\n\tevs[1] = evs[0]\n\tevs[1].Filter = unix.EVFILT_WRITE\n\t_, err := unix.Kevent(p.fd, evs[:], nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// AddRead registers the given file descriptor with readable event to the poller.\nfunc (p *Poller) AddRead(pa *PollAttachment, edgeTriggered bool) error {\n\tvar evs [1]unix.Kevent_t\n\tevs[0].Ident = keventIdent(pa.FD)\n\tevs[0].Filter = unix.EVFILT_READ\n\tevs[0].Flags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tevs[0].Flags |= unix.EV_CLEAR\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&evs[0].Udata), pa)\n\t_, err := unix.Kevent(p.fd, evs[:], nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// AddWrite registers the given file descriptor with writable event to the poller.\nfunc (p *Poller) AddWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar evs [1]unix.Kevent_t\n\tevs[0].Ident = keventIdent(pa.FD)\n\tevs[0].Filter = unix.EVFILT_WRITE\n\tevs[0].Flags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tevs[0].Flags |= unix.EV_CLEAR\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&evs[0].Udata), pa)\n\t_, err := unix.Kevent(p.fd, evs[:], nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// ModRead modifies the given file descriptor with readable event in the poller.\nfunc (p *Poller) ModRead(pa *PollAttachment, _ bool) error {\n\tvar evs [1]unix.Kevent_t\n\tevs[0].Ident = keventIdent(pa.FD)\n\tevs[0].Filter = unix.EVFILT_WRITE\n\tevs[0].Flags = unix.EV_DELETE\n\t_, err := unix.Kevent(p.fd, evs[:], nil, nil)\n\treturn os.NewSyscallError(\"kevent delete\", err)\n}\n\n// ModReadWrite modifies the given file descriptor with readable and writable events in the poller.\nfunc (p *Poller) ModReadWrite(pa *PollAttachment, edgeTriggered bool) error {\n\tvar evs [1]unix.Kevent_t\n\tevs[0].Ident = keventIdent(pa.FD)\n\tevs[0].Filter = unix.EVFILT_WRITE\n\tevs[0].Flags = unix.EV_ADD\n\tif edgeTriggered {\n\t\tevs[0].Flags |= unix.EV_CLEAR\n\t}\n\tconvertPollAttachment(unsafe.Pointer(&evs[0].Udata), pa)\n\t_, err := unix.Kevent(p.fd, evs[:], nil, nil)\n\treturn os.NewSyscallError(\"kevent add\", err)\n}\n\n// Delete removes the given file descriptor from the poller.\nfunc (p *Poller) Delete(_ int) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/netpoll/poller_kqueue_wakeup.go",
    "content": "//  Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\n//go:build darwin || dragonfly || freebsd\n\npackage netpoll\n\nimport (\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\nfunc (p *Poller) addWakeupEvent() error {\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{{\n\t\tIdent:  0,\n\t\tFilter: unix.EVFILT_USER,\n\t\tFlags:  unix.EV_ADD | unix.EV_CLEAR,\n\t}}, nil, nil)\n\treturn err\n}\n\nfunc (p *Poller) wakePoller() error {\nretry:\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{{\n\t\tIdent:  0,\n\t\tFilter: unix.EVFILT_USER,\n\t\tFflags: unix.NOTE_TRIGGER,\n\t}}, nil, nil)\n\tif err == nil {\n\t\treturn nil\n\t}\n\tif err == unix.EINTR {\n\t\t// All changes contained in the changelist should have been applied\n\t\t// before returning EINTR. But let's be skeptical and retry it anyway,\n\t\t// to make a 100% commitment.\n\t\tgoto retry\n\t}\n\tlogging.Warnf(\"failed to wake up the poller: %v\", err)\n\treturn err\n}\n\nfunc (p *Poller) drainWakeupEvent() {}\n"
  },
  {
    "path": "pkg/netpoll/poller_kqueue_wakeup1.go",
    "content": "//  Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n//  Licensed under the Apache License, Version 2.0 (the \"License\");\n//  you may not use this file except in compliance with the License.\n//  You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//  Unless required by applicable law or agreed to in writing, software\n//  distributed under the License is distributed on an \"AS IS\" BASIS,\n//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//  See the License for the specific language governing permissions and\n//  limitations under the License.\n\n//go:build netbsd || openbsd\n\npackage netpoll\n\nimport (\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\n// TODO(panjf2000): NetBSD didn't implement EVFILT_USER for user-established events\n// until NetBSD 10.0, check out https://www.netbsd.org/releases/formal-10/NetBSD-10.0.html\n// Therefore we use the pipe to wake up the kevent on NetBSD at this point. Get back here\n// and switch to EVFILT_USER when we bump up the minimal requirement of NetBSD to 10.0.\n// Alternatively, maybe we can use EVFILT_USER on the NetBSD by checking the kernel version\n// via uname(3) and fall back to the pipe if the kernel version is older than 10.0.\n\nfunc (p *Poller) addWakeupEvent() error {\n\tp.pipe = make([]int, 2)\n\tif err := unix.Pipe2(p.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC); err != nil {\n\t\tlogging.Fatalf(\"failed to create pipe for wakeup event: %v\", err)\n\t}\n\t_, err := unix.Kevent(p.fd, []unix.Kevent_t{{\n\t\tIdent:  uint64(p.pipe[0]),\n\t\tFilter: unix.EVFILT_READ,\n\t\tFlags:  unix.EV_ADD,\n\t}}, nil, nil)\n\treturn err\n}\n\nfunc (p *Poller) wakePoller() error {\nretry:\n\t_, err := unix.Write(p.pipe[1], []byte(\"x\"))\n\tif err == nil || err == unix.EAGAIN {\n\t\treturn nil\n\t}\n\tif err == unix.EINTR {\n\t\tgoto retry\n\t}\n\tlogging.Warnf(\"failed to write to the wakeup pipe: %v\", err)\n\treturn err\n}\n\nfunc (p *Poller) drainWakeupEvent() {\n\tvar buf [8]byte\n\t_, _ = unix.Read(p.pipe[0], buf[:])\n}\n"
  },
  {
    "path": "pkg/netpoll/poller_unix_ultimate.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && poll_opt\n\npackage netpoll\n\nimport \"unsafe\"\n\nfunc convertPollAttachment(ptr unsafe.Pointer, attachment *PollAttachment) {\n\t*(**PollAttachment)(ptr) = attachment\n}\n\nfunc restorePollAttachment(ptr unsafe.Pointer) *PollAttachment {\n\treturn *(**PollAttachment)(ptr)\n}\n"
  },
  {
    "path": "pkg/netpoll/syscall_epoll_generic_linux.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build !arm64 && !riscv64 && poll_opt\n\npackage netpoll\n\nimport (\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc epollWait(epfd int, events []epollevent, msec int) (int, error) {\n\tvar ep unsafe.Pointer\n\tif len(events) > 0 {\n\t\tep = unsafe.Pointer(&events[0])\n\t} else {\n\t\tep = unsafe.Pointer(&zero)\n\t}\n\tvar (\n\t\tnp    uintptr\n\t\terrno unix.Errno\n\t)\n\tif msec == 0 { // non-block system call, use RawSyscall6 to avoid getting preempted by runtime\n\t\tnp, _, errno = unix.RawSyscall6(unix.SYS_EPOLL_WAIT, uintptr(epfd), uintptr(ep), uintptr(len(events)), 0, 0, 0)\n\t} else {\n\t\tnp, _, errno = unix.Syscall6(unix.SYS_EPOLL_WAIT, uintptr(epfd), uintptr(ep), uintptr(len(events)), uintptr(msec), 0, 0)\n\t}\n\tif errno != 0 {\n\t\treturn int(np), errnoErr(errno)\n\t}\n\treturn int(np), nil\n}\n"
  },
  {
    "path": "pkg/netpoll/syscall_epoll_linux.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build poll_opt\n\npackage netpoll\n\nimport (\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc epollCtl(epfd int, op int, fd int, event *epollevent) error {\n\t_, _, errno := unix.RawSyscall6(unix.SYS_EPOLL_CTL, uintptr(epfd), uintptr(op), uintptr(fd), uintptr(unsafe.Pointer(event)), 0, 0)\n\tif errno != 0 {\n\t\treturn errnoErr(errno)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/netpoll/syscall_epoll_riscv64_arm64_linux.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build ((linux && arm64) || (linux && riscv64)) && poll_opt\n\npackage netpoll\n\nimport (\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc epollWait(epfd int, events []epollevent, msec int) (int, error) {\n\tvar ep unsafe.Pointer\n\tif len(events) > 0 {\n\t\tep = unsafe.Pointer(&events[0])\n\t} else {\n\t\tep = unsafe.Pointer(&zero)\n\t}\n\tvar (\n\t\tnp    uintptr\n\t\terrno unix.Errno\n\t)\n\tif msec == 0 { // non-block system call, use RawSyscall6 to avoid getting preempted by runtime\n\t\tnp, _, errno = unix.RawSyscall6(unix.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(ep), uintptr(len(events)), 0, 0, 0)\n\t} else {\n\t\tnp, _, errno = unix.Syscall6(unix.SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(ep), uintptr(len(events)), uintptr(msec), 0, 0)\n\t}\n\tif errno != 0 {\n\t\treturn int(np), errnoErr(errno)\n\t}\n\treturn int(np), nil\n}\n"
  },
  {
    "path": "pkg/netpoll/syscall_errors_linux.go",
    "content": "// Copyright 2009 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//go:build poll_opt\n\npackage netpoll\n\nimport \"golang.org/x/sys/unix\"\n\n// Do the interface allocations only once for common\n// Errno values.\nvar (\n\terrEAGAIN error = unix.EAGAIN\n\terrEINVAL error = unix.EINVAL\n\terrENOENT error = unix.ENOENT\n)\n\n// errnoErr returns common boxed Errno values, to prevent\n// allocations at runtime.\nfunc errnoErr(e unix.Errno) error {\n\tswitch e {\n\tcase unix.EAGAIN:\n\t\treturn errEAGAIN\n\tcase unix.EINVAL:\n\t\treturn errEINVAL\n\tcase unix.ENOENT:\n\t\treturn errENOENT\n\t}\n\treturn e\n}\n\nvar zero uintptr\n"
  },
  {
    "path": "pkg/pool/bytebuffer/bytebuffer.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package bytebuffer is a pool of bytebufferpool.ByteBuffer.\npackage bytebuffer\n\nimport \"github.com/valyala/bytebufferpool\"\n\n// ByteBuffer is the alias of bytebufferpool.ByteBuffer.\ntype ByteBuffer = bytebufferpool.ByteBuffer\n\nvar (\n\t// Get returns an empty byte buffer from the pool, exported from gnet/bytebuffer.\n\tGet = bytebufferpool.Get\n\t// Put returns byte buffer to the pool, exported from gnet/bytebuffer.\n\tPut = func(b *ByteBuffer) {\n\t\tif b != nil {\n\t\t\tbytebufferpool.Put(b)\n\t\t}\n\t}\n)\n"
  },
  {
    "path": "pkg/pool/byteslice/byteslice.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package byteslice implements a pool of byte slices consisting of sync.Pool's\n// that collect byte slices with different length sizes from 0 to 32 in powers of 2.\npackage byteslice\n\nimport (\n\t\"math\"\n\t\"math/bits\"\n\t\"sync\"\n\t\"unsafe\"\n)\n\nvar builtinPool Pool\n\n// Pool consists of 32 sync.Pool, representing byte slices of length from 0 to 32 in powers of 2.\ntype Pool struct {\n\tpools [32]sync.Pool\n}\n\n// Get returns a byte slice with given length from the built-in pool.\nfunc Get(size int) []byte {\n\treturn builtinPool.Get(size)\n}\n\n// Put returns the byte slice to the built-in pool.\nfunc Put(buf []byte) {\n\tbuiltinPool.Put(buf)\n}\n\n// Get retrieves a byte slice of the length requested by the caller from pool or allocates a new one.\nfunc (p *Pool) Get(size int) []byte {\n\tif size <= 0 {\n\t\treturn nil\n\t}\n\tif size > math.MaxInt32 {\n\t\treturn make([]byte, size)\n\t}\n\tidx := index(uint32(size))\n\tptr, _ := p.pools[idx].Get().(*byte)\n\tif ptr == nil {\n\t\treturn make([]byte, size, 1<<idx)\n\t}\n\treturn unsafe.Slice(ptr, 1<<idx)[:size]\n}\n\n// Put returns the byte slice to the pool.\nfunc (p *Pool) Put(buf []byte) {\n\tsize := cap(buf)\n\tif size == 0 || size > math.MaxInt32 {\n\t\treturn\n\t}\n\tidx := index(uint32(size))\n\tif size != 1<<idx { // this byte slice is not from Pool.Get(), put it into the previous interval of idx\n\t\tidx--\n\t}\n\t// Store the pointer to the underlying array instead of the pointer to the slice itself,\n\t// which circumvents the escape of buf from the stack to the heap.\n\tp.pools[idx].Put(unsafe.SliceData(buf))\n}\n\nfunc index(n uint32) uint32 {\n\treturn uint32(bits.Len32(n - 1))\n}\n"
  },
  {
    "path": "pkg/pool/byteslice/byteslice_test.go",
    "content": "package byteslice\n\nimport (\n\t\"runtime/debug\"\n\t\"testing\"\n)\n\nfunc TestByteSlice(t *testing.T) {\n\tbuf := Get(8)\n\tcopy(buf, \"ff\")\n\tif string(buf[:2]) != \"ff\" {\n\t\tt.Fatal(\"expect copy result is ff, but not\")\n\t}\n\n\t// Disable GC to test re-acquire the same data\n\tgc := debug.SetGCPercent(-1)\n\n\tPut(buf)\n\n\tnewBuf := Get(7)\n\tif &newBuf[0] != &buf[0] {\n\t\tt.Fatal(\"expect newBuf and buf to be the same array\")\n\t}\n\tif string(newBuf[:2]) != \"ff\" {\n\t\tt.Fatal(\"expect the newBuf is the buf, but not\")\n\t}\n\n\t// Re-enable GC\n\tdebug.SetGCPercent(gc)\n}\n\nfunc BenchmarkByteSlice(b *testing.B) {\n\tb.Run(\"Run.N\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tbs := Get(1024)\n\t\t\tPut(bs)\n\t\t}\n\t})\n\tb.Run(\"Run.Parallel\", func(b *testing.B) {\n\t\tb.ReportAllocs()\n\t\tb.RunParallel(func(pb *testing.PB) {\n\t\t\tfor pb.Next() {\n\t\t\t\tbs := Get(1024)\n\t\t\t\tPut(bs)\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "pkg/pool/goroutine/goroutine.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package goroutine is a wrapper of github.com/panjf2000/ants with some practical configurations.\npackage goroutine\n\nimport (\n\t\"time\"\n\n\t\"github.com/panjf2000/ants/v2\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/logging\"\n)\n\nconst (\n\t// DefaultAntsPoolSize sets up the capacity of worker pool, 256 * 1024.\n\tDefaultAntsPoolSize = 1 << 18\n\n\t// ExpiryDuration is the interval time to clean up those expired workers.\n\tExpiryDuration = 10 * time.Second\n\n\t// Nonblocking decides what to do when submitting a new task to a full worker pool: waiting for a available worker\n\t// or returning nil directly.\n\tNonblocking = true\n)\n\nfunc init() {\n\t// It releases the default pool from ants.\n\tants.Release()\n}\n\n// DefaultWorkerPool is the global worker pool.\nvar DefaultWorkerPool = Default()\n\n// Pool is the alias of ants.Pool.\ntype Pool = ants.Pool\n\ntype antsLogger struct {\n\tlogging.Logger\n}\n\n// Printf implements the ants.Logger interface.\nfunc (l antsLogger) Printf(format string, args ...any) {\n\tl.Infof(format, args...)\n}\n\n// Default instantiates a non-blocking goroutine pool with the capacity of DefaultAntsPoolSize.\nfunc Default() *Pool {\n\toptions := ants.Options{\n\t\tExpiryDuration: ExpiryDuration,\n\t\tNonblocking:    Nonblocking,\n\t\tLogger:         &antsLogger{logging.GetDefaultLogger()},\n\t\tPanicHandler: func(a any) {\n\t\t\tlogging.Errorf(\"goroutine pool panic: %v\", a)\n\t\t},\n\t}\n\tdefaultAntsPool, _ := ants.NewPool(DefaultAntsPoolSize, ants.WithOptions(options))\n\treturn defaultAntsPool\n}\n"
  },
  {
    "path": "pkg/pool/ringbuffer/ringbuffer.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n// Copyright (c) 2016 Aliaksandr Valialkin, VertaMedia\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n// Use of this source code is governed by a MIT license that can be found\n// at https://github.com/valyala/bytebufferpool/blob/master/LICENSE\n\n// Package ringbuffer implements a GC-friendly pool of ring buffers.\npackage ringbuffer\n\nimport (\n\t\"math/bits\"\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/buffer/ring\"\n)\n\nconst (\n\tminBitSize = 6 // 2**6=64 is a CPU cache line size\n\tsteps      = 20\n\n\tminSize = 1 << minBitSize\n\n\tcalibrateCallsThreshold = 42000\n\tmaxPercentile           = 0.95\n)\n\n// RingBuffer is the alias of ring.Buffer.\ntype RingBuffer = ring.Buffer\n\n// Pool represents ring-buffer pool.\n//\n// Distinct pools may be used for distinct types of byte buffers.\n// Properly determined byte buffer types with their own pools may help to reduce\n// memory waste.\ntype Pool struct {\n\tcalls       [steps]uint64\n\tcalibrating uint64\n\n\tdefaultSize uint64\n\tmaxSize     uint64\n\n\tpool sync.Pool\n}\n\nvar builtinPool Pool\n\n// Get returns an empty byte buffer from the pool.\n//\n// Got byte buffer may be returned to the pool via Put call.\n// This reduces the number of memory allocations required for byte buffer\n// management.\nfunc Get() *RingBuffer { return builtinPool.Get() }\n\n// Get returns new byte buffer with zero length.\n//\n// The byte buffer may be returned to the pool via Put after the use\n// in order to minimize GC overhead.\nfunc (p *Pool) Get() *RingBuffer {\n\tv := p.pool.Get()\n\tif v != nil {\n\t\treturn v.(*RingBuffer)\n\t}\n\treturn ring.New(int(atomic.LoadUint64(&p.defaultSize)))\n}\n\n// Put returns byte buffer to the pool.\n//\n// RingBuffer mustn't be touched after returning it to the pool,\n// otherwise, data races will occur.\nfunc Put(b *RingBuffer) { builtinPool.Put(b) }\n\n// Put releases byte buffer obtained via Get to the pool.\n//\n// The buffer mustn't be accessed after returning to the pool.\nfunc (p *Pool) Put(b *RingBuffer) {\n\tidx := index(b.Len())\n\n\tif atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {\n\t\tp.calibrate()\n\t}\n\n\tmaxSize := int(atomic.LoadUint64(&p.maxSize))\n\tif maxSize == 0 || b.Cap() <= maxSize {\n\t\tb.Reset()\n\t\tp.pool.Put(b)\n\t}\n}\n\nfunc (p *Pool) calibrate() {\n\tif !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {\n\t\treturn\n\t}\n\n\ta := make(callSizes, 0, steps)\n\tvar callsSum uint64\n\tfor i := uint64(0); i < steps; i++ {\n\t\tcalls := atomic.SwapUint64(&p.calls[i], 0)\n\t\tcallsSum += calls\n\t\ta = append(a, callSize{\n\t\t\tcalls: calls,\n\t\t\tsize:  minSize << i,\n\t\t})\n\t}\n\tsort.Sort(a)\n\n\tdefaultSize := a[0].size\n\tmaxSize := defaultSize\n\n\tmaxSum := uint64(float64(callsSum) * maxPercentile)\n\tcallsSum = 0\n\tfor i := 0; i < steps; i++ {\n\t\tif callsSum > maxSum {\n\t\t\tbreak\n\t\t}\n\t\tcallsSum += a[i].calls\n\t\tsize := a[i].size\n\t\tif size > maxSize {\n\t\t\tmaxSize = size\n\t\t}\n\t}\n\n\tatomic.StoreUint64(&p.defaultSize, defaultSize)\n\tatomic.StoreUint64(&p.maxSize, maxSize)\n\n\tatomic.StoreUint64(&p.calibrating, 0)\n}\n\ntype callSize struct {\n\tcalls uint64\n\tsize  uint64\n}\n\ntype callSizes []callSize\n\nfunc (ci callSizes) Len() int {\n\treturn len(ci)\n}\n\nfunc (ci callSizes) Less(i, j int) bool {\n\treturn ci[i].calls > ci[j].calls\n}\n\nfunc (ci callSizes) Swap(i, j int) {\n\tci[i], ci[j] = ci[j], ci[i]\n}\n\nfunc index(n int) int {\n\tn--\n\tn >>= minBitSize\n\tidx := 0\n\tif n > 0 {\n\t\tidx = bits.Len(uint(n))\n\t}\n\tif idx >= steps {\n\t\tidx = steps - 1\n\t}\n\treturn idx\n}\n"
  },
  {
    "path": "pkg/queue/lock_free_queue.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package queue delivers an implementation of lock-free concurrent queue based on\n// the algorithm presented by Maged M. Michael and Michael L. Scot. in 1996: https://dl.acm.org/doi/10.1145/248052.248106\n//\n// Pseudocode of non-blocking concurrent queue algorithm:\n/*\n\tstructure pointer_t {ptr: pointer to node_t, count: unsigned integer}\n\tstructure node_t {value: data type, next: pointer_t}\n\tstructure queue_t {Head: pointer_t, Tail: pointer_t}\n\n\tinitialize(Q: pointer to queue_t)\n\tnode = new_node()\t\t// Allocate a free node\n\tnode->next.ptr = NULL\t// Make it the only node in the linked list\n\tQ->Head.ptr = Q->Tail.ptr = node\t// Both Head and Tail point to it\n\n\tenqueue(Q: pointer to queue_t, value: data type)\n\tE1:   node = new_node()\t// Allocate a new node from the free list\n\tE2:   node->value = value\t// Copy enqueued value into node\n\tE3:   node->next.ptr = NULL\t// Set next pointer of node to NULL\n\tE4:   loop\t\t\t// Keep trying until Enqueue is done\n\tE5:      tail = Q->Tail\t// Read Tail.ptr and Tail.count together\n\tE6:      next = tail.ptr->next\t// Read next ptr and count fields together\n\tE7:      if tail == Q->Tail\t// Are tail and next consistent?\n\t\t\t\t// Was Tail pointing to the last node?\n\tE8:         if next.ptr == NULL\n\t\t\t\t\t// Try to link node at the end of the linked list\n\tE9:            if CAS(&tail.ptr->next, next, <node, next.count+1>)\n\tE10:               break\t// Enqueue is done.  Exit loop\n\tE11:            endif\n\tE12:         else\t\t// Tail was not pointing to the last node\n\t\t\t\t\t// Try to swing Tail to the next node\n\tE13:            CAS(&Q->Tail, tail, <next.ptr, tail.count+1>)\n\tE14:         endif\n\tE15:      endif\n\tE16:   endloop\n\t\t\t// Enqueue is done.  Try to swing Tail to the inserted node\n\tE17:   CAS(&Q->Tail, tail, <node, tail.count+1>)\n\n\tdequeue(Q: pointer to queue_t, pvalue: pointer to data type): boolean\n\tD1:   loop\t\t\t     // Keep trying until Dequeue is done\n\tD2:      head = Q->Head\t     // Read Head\n\tD3:      tail = Q->Tail\t     // Read Tail\n\tD4:      next = head.ptr->next    // Read Head.ptr->next\n\tD5:      if head == Q->Head\t     // Are head, tail, and next consistent?\n\tD6:         if head.ptr == tail.ptr // Is queue empty or Tail falling behind?\n\tD7:            if next.ptr == NULL  // Is queue empty?\n\tD8:               return FALSE      // Queue is empty, couldn't dequeue\n\tD9:            endif\n\t\t\t\t\t// Tail is falling behind.  Try to advance it\n\tD10:            CAS(&Q->Tail, tail, <next.ptr, tail.count+1>)\n\tD11:         else\t\t     // No need to deal with Tail\n\t\t\t\t\t// Read value before CAS\n\t\t\t\t\t// Otherwise, another dequeue might free the next node\n\tD12:            *pvalue = next.ptr->value\n\t\t\t\t\t// Try to swing Head to the next node\n\tD13:            if CAS(&Q->Head, head, <next.ptr, head.count+1>)\n\tD14:               break             // Dequeue is done.  Exit loop\n\tD15:            endif\n\tD16:         endif\n\tD17:      endif\n\tD18:   endloop\n\tD19:   free(head.ptr)\t\t     // It is safe now to free the old node\n\tD20:   return TRUE                   // Queue was not empty, dequeue succeeded\n*/\npackage queue\n\nimport (\n\t\"sync/atomic\"\n\t\"unsafe\"\n)\n\n// lockFreeQueue is a simple, fast, and practical non-blocking and concurrent queue with no lock.\ntype lockFreeQueue struct {\n\thead   unsafe.Pointer\n\ttail   unsafe.Pointer\n\tlength int32\n}\n\ntype node struct {\n\tvalue *Task\n\tnext  unsafe.Pointer\n}\n\n// NewLockFreeQueue instantiates and returns a lockFreeQueue.\nfunc NewLockFreeQueue() AsyncTaskQueue {\n\tn := unsafe.Pointer(&node{})\n\treturn &lockFreeQueue{head: n, tail: n}\n}\n\n// Enqueue puts the given value v at the tail of the queue.\nfunc (q *lockFreeQueue) Enqueue(task *Task) {\n\tn := &node{value: task}\nretry:\n\ttail := load(&q.tail)\n\tnext := load(&tail.next)\n\t// Are tail and next consistent?\n\tif tail == load(&q.tail) {\n\t\tif next == nil {\n\t\t\t// Try to link node at the end of the linked list.\n\t\t\tif cas(&tail.next, next, n) { // enqueue is done.\n\t\t\t\t// Try to swing tail to the inserted node.\n\t\t\t\tcas(&q.tail, tail, n)\n\t\t\t\tatomic.AddInt32(&q.length, 1)\n\t\t\t\treturn\n\t\t\t}\n\t\t} else { // tail was not pointing to the last node\n\t\t\t// Try to swing tail to the next node.\n\t\t\tcas(&q.tail, tail, next)\n\t\t}\n\t}\n\tgoto retry\n}\n\n// Dequeue removes and returns the value at the head of the queue.\n// It returns nil if the queue is empty.\nfunc (q *lockFreeQueue) Dequeue() *Task {\nretry:\n\thead := load(&q.head)\n\ttail := load(&q.tail)\n\tnext := load(&head.next)\n\t// Are head, tail, and next consistent?\n\tif head == load(&q.head) {\n\t\t// Is queue empty or tail falling behind?\n\t\tif head == tail {\n\t\t\t// Is queue empty?\n\t\t\tif next == nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tcas(&q.tail, tail, next) // tail is falling behind, try to advance it.\n\t\t} else {\n\t\t\t// Read value before CAS, otherwise another dequeue might free the next node.\n\t\t\ttask := next.value\n\t\t\tif cas(&q.head, head, next) { // dequeue is done, return value.\n\t\t\t\tatomic.AddInt32(&q.length, -1)\n\t\t\t\treturn task\n\t\t\t}\n\t\t}\n\t}\n\tgoto retry\n}\n\n// IsEmpty indicates whether this queue is empty or not.\nfunc (q *lockFreeQueue) IsEmpty() bool {\n\treturn atomic.LoadInt32(&q.length) == 0\n}\n\n// Length returns the number of elements in the queue.\nfunc (q *lockFreeQueue) Length() int32 {\n\treturn atomic.LoadInt32(&q.length)\n}\n\nfunc load(p *unsafe.Pointer) (n *node) {\n\treturn (*node)(atomic.LoadPointer(p))\n}\n\nfunc cas(p *unsafe.Pointer, old, new *node) bool { //nolint:revive\n\treturn atomic.CompareAndSwapPointer(p, unsafe.Pointer(old), unsafe.Pointer(new))\n}\n"
  },
  {
    "path": "pkg/queue/queue.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package queue implements a lock-free queue for asynchronous tasks.\npackage queue\n\nimport \"sync\"\n\n// Func is the callback function executed by poller.\ntype Func func(any) error\n\n// Task is a wrapper that contains function and its argument.\ntype Task struct {\n\tExec  Func\n\tParam any\n}\n\nvar taskPool = sync.Pool{New: func() any { return new(Task) }}\n\n// GetTask gets a cached Task from pool.\nfunc GetTask() *Task {\n\treturn taskPool.Get().(*Task)\n}\n\n// PutTask puts the trashy Task back in pool.\nfunc PutTask(task *Task) {\n\ttask.Exec, task.Param = nil, nil\n\ttaskPool.Put(task)\n}\n\n// AsyncTaskQueue is a queue storing asynchronous tasks.\ntype AsyncTaskQueue interface {\n\tEnqueue(*Task)\n\tDequeue() *Task\n\tIsEmpty() bool\n\tLength() int32\n}\n\n// EventPriority is the priority of an event.\ntype EventPriority int\n\nconst (\n\t// HighPriority is for the tasks expected to be executed\n\t// as soon as possible.\n\tHighPriority EventPriority = iota\n\t// LowPriority is for the tasks that won't matter much\n\t// even if they are deferred a little bit.\n\tLowPriority\n)\n"
  },
  {
    "path": "pkg/queue/queue_test.go",
    "content": "package queue_test\n\nimport (\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/queue\"\n)\n\nfunc TestLockFreeQueue(t *testing.T) {\n\tconst taskNum = 10000\n\tq := queue.NewLockFreeQueue()\n\tvar wg sync.WaitGroup\n\twg.Add(4)\n\tgo func() {\n\t\tfor i := 0; i < taskNum; i++ {\n\t\t\ttask := &queue.Task{}\n\t\t\tq.Enqueue(task)\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tfor i := 0; i < taskNum; i++ {\n\t\t\ttask := &queue.Task{}\n\t\t\tq.Enqueue(task)\n\t\t}\n\t\twg.Done()\n\t}()\n\n\tvar counter int32\n\tgo func() {\n\t\tfor {\n\t\t\ttask := q.Dequeue()\n\t\t\tif task != nil {\n\t\t\t\tatomic.AddInt32(&counter, 1)\n\t\t\t}\n\t\t\tif task == nil && atomic.LoadInt32(&counter) == 2*taskNum {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\tfor {\n\t\t\ttask := q.Dequeue()\n\t\t\tif task != nil {\n\t\t\t\tatomic.AddInt32(&counter, 1)\n\t\t\t}\n\t\t\tif task == nil && atomic.LoadInt32(&counter) == 2*taskNum {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\tt.Logf(\"sent and received all %d tasks\", 2*taskNum)\n}\n"
  },
  {
    "path": "pkg/socket/fd_unix.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"sync/atomic\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// Dup duplicates the given fd and marks it close-on-exec.\nfunc Dup(fd int) (int, error) {\n\treturn dupCloseOnExec(fd)\n}\n\n// tryDupCloexec indicates whether F_DUPFD_CLOEXEC should be used.\n// If the kernel doesn't support it, this is set to false.\nvar tryDupCloexec atomic.Bool\n\nfunc init() {\n\ttryDupCloexec.Store(true)\n}\n\n// dupCloseOnExec duplicates the given fd and marks it close-on-exec.\nfunc dupCloseOnExec(fd int) (int, error) {\n\tif tryDupCloexec.Load() {\n\t\tr, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)\n\t\tif err == nil {\n\t\t\treturn r, nil\n\t\t}\n\t\tswitch err.(syscall.Errno) {\n\t\tcase unix.EINVAL, unix.ENOSYS:\n\t\t\t// Old kernel, or js/wasm (which returns\n\t\t\t// ENOSYS). Fall back to the portable way from\n\t\t\t// now on.\n\t\t\ttryDupCloexec.Store(false)\n\t\tdefault:\n\t\t\treturn -1, err\n\t\t}\n\t}\n\treturn dupCloseOnExecOld(fd)\n}\n\n// dupCloseOnExecOld is the traditional way to dup an fd and\n// set its O_CLOEXEC bit, using two system calls.\nfunc dupCloseOnExecOld(fd int) (int, error) {\n\tsyscall.ForkLock.RLock()\n\tdefer syscall.ForkLock.RUnlock()\n\tnewFD, err := syscall.Dup(fd)\n\tif err != nil {\n\t\treturn -1, err\n\t}\n\tsyscall.CloseOnExec(newFD)\n\treturn newFD, nil\n}\n"
  },
  {
    "path": "pkg/socket/sock_bsd.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n// Copyright (c) 2017 Ma Weiwei, Max Riveiro\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"runtime\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc maxListenerBacklog() int {\n\tvar (\n\t\tn   uint32\n\t\terr error\n\t)\n\tswitch runtime.GOOS {\n\tcase \"darwin\":\n\t\tn, err = unix.SysctlUint32(\"kern.ipc.somaxconn\")\n\tcase \"freebsd\":\n\t\tn, err = unix.SysctlUint32(\"kern.ipc.soacceptqueue\")\n\t}\n\tif n == 0 || err != nil {\n\t\treturn unix.SOMAXCONN\n\t}\n\t// FreeBSD stores the backlog in a uint16, as does Linux.\n\t// Assume the other BSDs do too. Truncate number to avoid wrapping.\n\t// See issue 5030.\n\tif n > 1<<16-1 {\n\t\tn = 1<<16 - 1\n\t}\n\treturn int(n)\n}\n"
  },
  {
    "path": "pkg/socket/sock_cloexec.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build dragonfly || freebsd || linux\n\npackage socket\n\nimport \"golang.org/x/sys/unix\"\n\nfunc sysSocket(family, sotype, proto int) (int, error) {\n\treturn unix.Socket(family, sotype|unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC, proto)\n}\n\nfunc sysAccept(fd int) (nfd int, sa unix.Sockaddr, err error) {\n\treturn unix.Accept4(fd, unix.SOCK_NONBLOCK|unix.SOCK_CLOEXEC)\n}\n"
  },
  {
    "path": "pkg/socket/sock_linux.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n// Copyright (c) 2017 Ma Weiwei, Max Riveiro\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage socket\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc maxListenerBacklog() int {\n\tfd, err := os.Open(\"/proc/sys/net/core/somaxconn\")\n\tif err != nil {\n\t\treturn unix.SOMAXCONN\n\t}\n\tdefer fd.Close() //nolint:errcheck\n\n\trd := bufio.NewReader(fd)\n\tline, err := rd.ReadString('\\n')\n\tif err != nil {\n\t\treturn unix.SOMAXCONN\n\t}\n\n\tf := strings.Fields(line)\n\tif len(f) < 1 {\n\t\treturn unix.SOMAXCONN\n\t}\n\n\tn, err := strconv.Atoi(f[0])\n\tif err != nil || n == 0 {\n\t\treturn unix.SOMAXCONN\n\t}\n\n\t// Linux stores the backlog in a uint16.\n\t// Truncate number to avoid wrapping.\n\t// See issue 5030.\n\tif n > 1<<16-1 {\n\t\tn = 1<<16 - 1\n\t}\n\n\treturn n\n}\n"
  },
  {
    "path": "pkg/socket/sock_posix.go",
    "content": "// Copyright (c) 2022 The Gnet Authors. All rights reserved.\n// Copyright 2009 The Go Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"net\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc ipToSockaddrInet4(ip net.IP, port int) (unix.SockaddrInet4, error) {\n\tif len(ip) == 0 {\n\t\tip = net.IPv4zero\n\t}\n\tip4 := ip.To4()\n\tif ip4 == nil {\n\t\treturn unix.SockaddrInet4{}, &net.AddrError{Err: \"non-IPv4 address\", Addr: ip.String()}\n\t}\n\tsa := unix.SockaddrInet4{Port: port}\n\tcopy(sa.Addr[:], ip4)\n\treturn sa, nil\n}\n\nfunc ipToSockaddrInet6(ip net.IP, port int, zone string) (unix.SockaddrInet6, error) {\n\t// In general, an IP wildcard address, which is either\n\t// \"0.0.0.0\" or \"::\", means the entire IP addressing\n\t// space. For some historical reason, it is used to\n\t// specify \"any available address\" on some operations\n\t// of IP node.\n\t//\n\t// When the IP node supports IPv4-mapped IPv6 address,\n\t// we allow a listener to listen to the wildcard\n\t// address of both IP addressing spaces by specifying\n\t// IPv6 wildcard address.\n\tif len(ip) == 0 || ip.Equal(net.IPv4zero) {\n\t\tip = net.IPv6zero\n\t}\n\t// We accept any IPv6 address including IPv4-mapped\n\t// IPv6 address.\n\tip6 := ip.To16()\n\tif ip6 == nil {\n\t\treturn unix.SockaddrInet6{}, &net.AddrError{Err: \"non-IPv6 address\", Addr: ip.String()}\n\t}\n\n\tsa := unix.SockaddrInet6{Port: port}\n\tcopy(sa.Addr[:], ip6)\n\tiface, err := net.InterfaceByName(zone)\n\tif err != nil {\n\t\treturn sa, nil\n\t}\n\tsa.ZoneId = uint32(iface.Index)\n\n\treturn sa, nil\n}\n\nfunc ipToSockaddr(family int, ip net.IP, port int, zone string) (unix.Sockaddr, error) {\n\tswitch family {\n\tcase syscall.AF_INET:\n\t\tsa, err := ipToSockaddrInet4(ip, port)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &sa, nil\n\tcase syscall.AF_INET6:\n\t\tsa, err := ipToSockaddrInet6(ip, port, zone)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &sa, nil\n\t}\n\treturn nil, &net.AddrError{Err: \"invalid address family\", Addr: ip.String()}\n}\n"
  },
  {
    "path": "pkg/socket/sockaddr.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n// Copyright (c) 2012 The Go Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//     https://github.com/libp2p/go-sockaddr?tab=BSD-3-Clause-1-ov-file#readme\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"net\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/bs\"\n\tbsPool \"github.com/panjf2000/gnet/v2/pkg/pool/byteslice\"\n)\n\n// NetAddrToSockaddr converts a net.Addr to a Sockaddr.\n// Returns nil if the input is invalid or conversion is not possible.\nfunc NetAddrToSockaddr(addr net.Addr) unix.Sockaddr {\n\tswitch addr := addr.(type) {\n\tcase *net.IPAddr:\n\t\treturn IPAddrToSockaddr(addr)\n\tcase *net.TCPAddr:\n\t\treturn TCPAddrToSockaddr(addr)\n\tcase *net.UDPAddr:\n\t\treturn UDPAddrToSockaddr(addr)\n\tcase *net.UnixAddr:\n\t\tsa, _ := UnixAddrToSockaddr(addr)\n\t\treturn sa\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// IPAddrToSockaddr converts a net.IPAddr to a Sockaddr.\n// Returns nil if conversion fails.\nfunc IPAddrToSockaddr(addr *net.IPAddr) unix.Sockaddr {\n\treturn IPToSockaddr(addr.IP, 0, addr.Zone)\n}\n\n// TCPAddrToSockaddr converts a net.TCPAddr to a Sockaddr.\n// Returns nil if conversion fails.\nfunc TCPAddrToSockaddr(addr *net.TCPAddr) unix.Sockaddr {\n\treturn IPToSockaddr(addr.IP, addr.Port, addr.Zone)\n}\n\n// UDPAddrToSockaddr converts a net.UDPAddr to a Sockaddr.\n// Returns nil if conversion fails.\nfunc UDPAddrToSockaddr(addr *net.UDPAddr) unix.Sockaddr {\n\treturn IPToSockaddr(addr.IP, addr.Port, addr.Zone)\n}\n\n// IPToSockaddr converts a net.IP (with optional IPv6 Zone) to a Sockaddr\n// Returns nil if conversion fails.\nfunc IPToSockaddr(ip net.IP, port int, zone string) unix.Sockaddr {\n\t// Unspecified?\n\tif ip == nil {\n\t\tif zone != \"\" {\n\t\t\treturn &unix.SockaddrInet6{Port: port, ZoneId: uint32(ip6ZoneToInt(zone))}\n\t\t}\n\t\treturn &unix.SockaddrInet4{Port: port}\n\t}\n\n\t// Valid IPv4?\n\tif ip4 := ip.To4(); ip4 != nil && zone == \"\" {\n\t\tsa := unix.SockaddrInet4{Port: port}\n\t\tcopy(sa.Addr[:], ip4) // last 4 bytes\n\t\treturn &sa\n\t}\n\n\t// Valid IPv6 address?\n\tif ip6 := ip.To16(); ip6 != nil {\n\t\tsa := unix.SockaddrInet6{Port: port, ZoneId: uint32(ip6ZoneToInt(zone))}\n\t\tcopy(sa.Addr[:], ip6)\n\t\treturn &sa\n\t}\n\n\treturn nil\n}\n\n// UnixAddrToSockaddr converts a net.UnixAddr to a Sockaddr, and returns\n// the type (unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET)\n// Returns (nil, 0) if conversion fails.\nfunc UnixAddrToSockaddr(addr *net.UnixAddr) (unix.Sockaddr, int) {\n\tt := 0\n\tswitch addr.Net {\n\tcase \"unix\":\n\t\tt = unix.SOCK_STREAM\n\tcase \"unixgram\":\n\t\tt = unix.SOCK_DGRAM\n\tcase \"unixpacket\":\n\t\tt = unix.SOCK_SEQPACKET\n\tdefault:\n\t\treturn nil, 0\n\t}\n\treturn &unix.SockaddrUnix{Name: addr.Name}, t\n}\n\n// SockaddrToTCPOrUnixAddr converts a unix.Sockaddr to a net.TCPAddr or net.UnixAddr.\n// Returns nil if conversion fails.\nfunc SockaddrToTCPOrUnixAddr(sa unix.Sockaddr) net.Addr {\n\tswitch sa := sa.(type) {\n\tcase *unix.SockaddrInet4:\n\t\treturn &net.TCPAddr{IP: sa.Addr[0:], Port: sa.Port}\n\tcase *unix.SockaddrInet6:\n\t\treturn &net.TCPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: ip6ZoneToString(sa.ZoneId)}\n\tcase *unix.SockaddrUnix:\n\t\treturn &net.UnixAddr{Name: sa.Name, Net: \"unix\"}\n\t}\n\treturn nil\n}\n\n// SockaddrToUDPAddr converts a unix.Sockaddr to a net.UDPAddr\n// Returns nil if conversion fails.\nfunc SockaddrToUDPAddr(sa unix.Sockaddr) net.Addr {\n\tswitch sa := sa.(type) {\n\tcase *unix.SockaddrInet4:\n\t\treturn &net.UDPAddr{IP: sa.Addr[0:], Port: sa.Port}\n\tcase *unix.SockaddrInet6:\n\t\treturn &net.UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: ip6ZoneToString(sa.ZoneId)}\n\t}\n\treturn nil\n}\n\n// ip6ZoneToInt converts an IP6 Zone net string to a unix int.\n// Returns 0 if zone is \"\".\nfunc ip6ZoneToInt(zone string) int {\n\tif zone == \"\" {\n\t\treturn 0\n\t}\n\tif ifi, err := net.InterfaceByName(zone); err == nil {\n\t\treturn ifi.Index\n\t}\n\tn, _, _ := dtoi(zone, 0)\n\treturn n\n}\n\n// ip6ZoneToString converts an IP6 Zone unix int to a net string,\n// Returns \"\" if zone is 0.\nfunc ip6ZoneToString(zone uint32) string {\n\tif zone == 0 {\n\t\treturn \"\"\n\t}\n\tif ifi, err := net.InterfaceByIndex(int(zone)); err == nil {\n\t\treturn ifi.Name\n\t}\n\treturn itod(uint(zone))\n}\n\n// itod converts uint to a decimal string.\nfunc itod(v uint) string {\n\tif v == 0 { // avoid string allocation\n\t\treturn \"0\"\n\t}\n\t// Assemble decimal in reverse order.\n\tbuf := bsPool.Get(32)\n\ti := len(buf) - 1\n\tfor ; v > 0; v /= 10 {\n\t\tbuf[i] = byte(v%10 + '0')\n\t\ti--\n\t}\n\treturn bs.BytesToString(buf[i:])\n}\n\n// Bigger than we need, not too big to worry about overflow.\nconst big = 0xFFFFFF\n\n// Decimal to integer starting at &s[i0].\n// Returns number, new offset, success.\nfunc dtoi(s string, i0 int) (n int, i int, ok bool) {\n\tn = 0\n\tfor i = i0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {\n\t\tn = n*10 + int(s[i]-'0')\n\t\tif n >= big {\n\t\t\treturn 0, i, false\n\t\t}\n\t}\n\tif i == i0 {\n\t\treturn 0, i, false\n\t}\n\treturn n, i, true\n}\n"
  },
  {
    "path": "pkg/socket/socket.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n// Copyright (c) 2017 Max Riveiro\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\n// Package socket provides some handy socket-related functions.\npackage socket\n\nimport (\n\t\"net\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// Option is used for setting an option on socket.\ntype Option[T int | string] struct {\n\tSetSockOpt func(int, T) error\n\tOpt        T\n}\n\nfunc execSockOpts[T int | string](fd int, opts []Option[T]) error {\n\tfor _, opt := range opts {\n\t\tif err := opt.SetSockOpt(fd, opt.Opt); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// TCPSocket creates a TCP socket and returns a file descriptor that refers to it.\n// The given socket options will be set on the returned file descriptor.\nfunc TCPSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) {\n\treturn tcpSocket(proto, addr, passive, sockOptInts, sockOptStrs)\n}\n\n// UDPSocket creates a UDP socket and returns a file descriptor that refers to it.\n// The given socket options will be set on the returned file descriptor.\nfunc UDPSocket(proto, addr string, connect bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) {\n\treturn udpSocket(proto, addr, connect, sockOptInts, sockOptStrs)\n}\n\n// UnixSocket creates a Unix socket and returns a file descriptor that refers to it.\n// The given socket options will be set on the returned file descriptor.\nfunc UnixSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (int, net.Addr, error) {\n\treturn udsSocket(proto, addr, passive, sockOptInts, sockOptStrs)\n}\n\n// Accept accepts the next incoming socket along with setting\n// O_NONBLOCK and O_CLOEXEC flags on it.\nfunc Accept(fd int) (int, unix.Sockaddr, error) {\n\treturn sysAccept(fd)\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_bsd.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build dragonfly || freebsd || netbsd || openbsd\n\npackage socket\n\nimport errorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\n// SetBindToDevice is not implemented on *BSD because there is\n// no equivalent of Linux's SO_BINDTODEVICE.\nfunc SetBindToDevice(_ int, _ string) error {\n\treturn errorx.ErrUnsupportedOp\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_darwin.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage socket\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\n// SetKeepAlivePeriod enables the SO_KEEPALIVE option on the socket and sets\n// TCP_KEEPIDLE/TCP_KEEPALIVE to the specified duration in seconds, TCP_KEEPCNT\n// to 5, and TCP_KEEPINTVL to secs/5.\nfunc SetKeepAlivePeriod(fd, secs int) error {\n\tif secs <= 0 {\n\t\treturn errors.New(\"invalid time duration\")\n\t}\n\n\tinterval := secs / 5\n\tif interval == 0 {\n\t\tinterval = 1\n\t}\n\n\treturn SetKeepAlive(fd, true, secs, interval, 5)\n}\n\n// SetKeepAlive enables/disables the TCP keepalive feature on the socket.\nfunc SetKeepAlive(fd int, enabled bool, idle, intvl, cnt int) error {\n\tif enabled && (idle <= 0 || intvl <= 0 || cnt <= 0) {\n\t\treturn errors.New(\"invalid time duration\")\n\t}\n\n\tvar on int\n\tif enabled {\n\t\ton = 1\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_KEEPALIVE, on); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif !enabled {\n\t\t// If keepalive is disabled, ignore the TCP_KEEP* options.\n\t\treturn nil\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPALIVE, idle); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPINTVL, intvl); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPCNT, cnt))\n}\n\n// SetBindToDevice is not implemented on macOS because there is\n// no equivalent of Linux's SO_BINDTODEVICE.\nfunc SetBindToDevice(_ int, _ string) error {\n\treturn errorx.ErrUnsupportedOp\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_freebsd.go",
    "content": "/*\n * Copyright (c) 2024 The Gnet Authors. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\npackage socket\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// SetReuseport enables SO_REUSEPORT_LB option on socket.\nfunc SetReuseport(fd, reusePort int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT_LB, reusePort))\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_linux.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage socket\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// SetBindToDevice binds the socket to a specific network interface.\n//\n// SO_BINDTODEVICE on Linux works in both directions: only process packets\n// received from the particular interface along with sending them through\n// that interface, instead of following the default route.\nfunc SetBindToDevice(fd int, ifname string) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.BindToDevice(fd, ifname))\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_openbsd.go",
    "content": "// Copyright (c) 2024 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage socket\n\nimport errorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\n// SetKeepAlivePeriod is not implemented on OpenBSD because there are\n// no equivalents of Linux's TCP_KEEPIDLE, TCP_KEEPINTVL, and TCP_KEEPCNT.\nfunc SetKeepAlivePeriod(_, _ int) error {\n\t// OpenBSD has no user-settable per-socket TCP keepalive options.\n\treturn errorx.ErrUnsupportedOp\n}\n\n// SetKeepAlive is not implemented on OpenBSD.\nfunc SetKeepAlive(_ int, _ bool, _, _, _ int) error {\n\t// OpenBSD has no user-settable per-socket TCP keepalive options.\n\treturn errorx.ErrUnsupportedOp\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_posix.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\n// SetNoDelay controls whether the operating system should delay\n// packet transmission in hopes of sending fewer packets (Nagle's algorithm).\n//\n// The default is true (no delay), meaning that data is\n// sent as soon as possible after a Write.\nfunc SetNoDelay(fd, noDelay int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_NODELAY, noDelay))\n}\n\n// SetRecvBuffer sets the size of the operating system's\n// receive buffer associated with the connection.\nfunc SetRecvBuffer(fd, size int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, size))\n}\n\n// SetSendBuffer sets the size of the operating system's\n// transmit buffer associated with the connection.\nfunc SetSendBuffer(fd, size int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, size))\n}\n\n// SetReuseAddr enables SO_REUSEADDR option on socket.\nfunc SetReuseAddr(fd, reuseAddr int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, reuseAddr))\n}\n\n// SetIPv6Only restricts a IPv6 socket to only process IPv6 requests or both IPv4 and IPv6 requests.\nfunc SetIPv6Only(fd, ipv6only int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, ipv6only))\n}\n\n// SetLinger sets the behavior of Close on a connection which still\n// has data waiting to be sent or to be acknowledged.\n//\n// If sec < 0 (the default), the operating system finishes sending the\n// data in the background.\n//\n// If sec == 0, the operating system discards any unsent or\n// unacknowledged data.\n//\n// If sec > 0, the data is sent in the background as with sec < 0. On\n// some operating systems after sec seconds have elapsed any remaining\n// unsent data may be discarded.\nfunc SetLinger(fd, sec int) error {\n\tvar l unix.Linger\n\tif sec >= 0 {\n\t\tl.Onoff = 1\n\t\tl.Linger = int32(sec)\n\t} else {\n\t\tl.Onoff = 0\n\t\tl.Linger = 0\n\t}\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptLinger(fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &l))\n}\n\n// SetMulticastMembership returns with a socket option function based on the IP\n// version. Returns nil when multicast membership cannot be applied.\nfunc SetMulticastMembership(proto string, udpAddr *net.UDPAddr) func(int, int) error {\n\tudpVersion, err := determineUDPProto(proto, udpAddr)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tswitch udpVersion {\n\tcase \"udp4\":\n\t\treturn func(fd int, ifIndex int) error {\n\t\t\treturn SetIPv4MulticastMembership(fd, udpAddr.IP, ifIndex)\n\t\t}\n\tcase \"udp6\":\n\t\treturn func(fd int, ifIndex int) error {\n\t\t\treturn SetIPv6MulticastMembership(fd, udpAddr.IP, ifIndex)\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// SetIPv4MulticastMembership joins fd to the specified multicast IPv4 address.\n// ifIndex is the index of the interface where the multicast datagrams will be\n// received. If ifIndex is 0 then the operating system will choose the default,\n// it is usually needed when the host has multiple network interfaces configured.\nfunc SetIPv4MulticastMembership(fd int, mcast net.IP, ifIndex int) error {\n\t// Multicast interfaces are selected by IP address on IPv4 (and by index on IPv6)\n\tip, err := interfaceFirstIPv4Addr(ifIndex)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmreq := &unix.IPMreq{}\n\tcopy(mreq.Multiaddr[:], mcast.To4())\n\tcopy(mreq.Interface[:], ip.To4())\n\n\tif ifIndex > 0 {\n\t\tif err := os.NewSyscallError(\"setsockopt\", unix.SetsockoptInet4Addr(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, mreq.Interface)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := os.NewSyscallError(\"setsockopt\", unix.SetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, 0)); err != nil {\n\t\treturn err\n\t}\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptIPMreq(fd, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq))\n}\n\n// SetIPv6MulticastMembership joins fd to the specified multicast IPv6 address.\n// ifIndex is the index of the interface where the multicast datagrams will be\n// received. If ifIndex is 0 then the operating system will choose the default,\n// it is usually needed when the host has multiple network interfaces configured.\nfunc SetIPv6MulticastMembership(fd int, mcast net.IP, ifIndex int) error {\n\tmreq := &unix.IPv6Mreq{}\n\tmreq.Interface = uint32(ifIndex)\n\tcopy(mreq.Multiaddr[:], mcast.To16())\n\n\tif ifIndex > 0 {\n\t\tif err := os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, ifIndex)); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_LOOP, 0)); err != nil {\n\t\treturn err\n\t}\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptIPv6Mreq(fd, syscall.IPPROTO_IPV6, syscall.IPV6_JOIN_GROUP, mreq))\n}\n\n// interfaceFirstIPv4Addr returns the first IPv4 address of the interface.\nfunc interfaceFirstIPv4Addr(ifIndex int) (net.IP, error) {\n\tif ifIndex == 0 {\n\t\treturn net.IP([]byte{0, 0, 0, 0}), nil\n\t}\n\tiface, err := net.InterfaceByIndex(ifIndex)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\taddrs, err := iface.Addrs()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, addr := range addrs {\n\t\tip, _, err := net.ParseCIDR(addr.String())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ip.To4() != nil {\n\t\t\treturn ip, nil\n\t\t}\n\t}\n\treturn nil, errors.ErrNoIPv4AddressOnInterface\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_unix.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build dragonfly || freebsd || linux || netbsd\n\npackage socket\n\nimport (\n\t\"errors\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// SetKeepAlivePeriod enables the SO_KEEPALIVE option on the socket and sets\n// TCP_KEEPIDLE/TCP_KEEPALIVE to the specified duration in seconds, TCP_KEEPCNT\n// to 5, and TCP_KEEPINTVL to secs/5.\nfunc SetKeepAlivePeriod(fd, secs int) error {\n\tif secs <= 0 {\n\t\treturn errors.New(\"invalid time duration\")\n\t}\n\n\tinterval := secs / 5\n\tif interval == 0 {\n\t\tinterval = 1\n\t}\n\n\treturn SetKeepAlive(fd, true, secs, interval, 5)\n}\n\n// SetKeepAlive enables/disables the TCP keepalive feature on the socket.\nfunc SetKeepAlive(fd int, enabled bool, idle, intvl, cnt int) error {\n\tif enabled && (idle <= 0 || intvl <= 0 || cnt <= 0) {\n\t\treturn errors.New(\"invalid time duration\")\n\t}\n\n\tvar on int\n\tif enabled {\n\t\ton = 1\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_KEEPALIVE, on); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif !enabled {\n\t\t// If keepalive is disabled, ignore the TCP_KEEP* options.\n\t\treturn nil\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPIDLE, idle); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\tif err := unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPINTVL, intvl); err != nil {\n\t\treturn os.NewSyscallError(\"setsockopt\", err)\n\t}\n\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_KEEPCNT, cnt))\n}\n"
  },
  {
    "path": "pkg/socket/sockopts_unix1.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// SetReuseport enables SO_REUSEPORT option on socket.\nfunc SetReuseport(fd, reusePort int) error {\n\treturn os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, reusePort))\n}\n"
  },
  {
    "path": "pkg/socket/sys_cloexec.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"syscall\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc sysSocket(family, sotype, proto int) (fd int, err error) {\n\tsyscall.ForkLock.RLock()\n\tif fd, err = unix.Socket(family, sotype, proto); err == nil {\n\t\tunix.CloseOnExec(fd)\n\t}\n\tsyscall.ForkLock.RUnlock()\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\t_ = unix.Close(fd)\n\t}\n\treturn\n}\n\nfunc sysAccept(fd int) (nfd int, sa unix.Sockaddr, err error) {\n\tsyscall.ForkLock.RLock()\n\tif nfd, sa, err = unix.Accept(fd); err == nil {\n\t\tunix.CloseOnExec(nfd)\n\t}\n\tsyscall.ForkLock.RUnlock()\n\tif err != nil {\n\t\treturn\n\t}\n\tif err = unix.SetNonblock(nfd, true); err != nil {\n\t\t_ = unix.Close(nfd)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/socket/tcp_socket.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n// Copyright (c) 2017 Max Riveiro\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\nvar listenerBacklogMaxSize = maxListenerBacklog()\n\n// GetTCPSockAddr the structured addresses based on the protocol and raw address.\nfunc GetTCPSockAddr(proto, addr string) (sa unix.Sockaddr, family int, tcpAddr *net.TCPAddr, ipv6only bool, err error) {\n\tvar tcpVersion string\n\n\ttcpAddr, err = net.ResolveTCPAddr(proto, addr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\ttcpVersion, err = determineTCPProto(proto, tcpAddr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch tcpVersion {\n\tcase \"tcp4\":\n\t\tfamily = unix.AF_INET\n\t\tsa, err = ipToSockaddr(family, tcpAddr.IP, tcpAddr.Port, \"\")\n\tcase \"tcp6\":\n\t\tipv6only = true\n\t\tfallthrough\n\tcase \"tcp\":\n\t\tfamily = unix.AF_INET6\n\t\tsa, err = ipToSockaddr(family, tcpAddr.IP, tcpAddr.Port, tcpAddr.Zone)\n\tdefault:\n\t\terr = errorx.ErrUnsupportedProtocol\n\t}\n\n\treturn\n}\n\nfunc determineTCPProto(proto string, addr *net.TCPAddr) (string, error) {\n\t// If the protocol is set to \"tcp\", we try to determine the actual protocol\n\t// version from the size of the resolved IP address. Otherwise, we simple use\n\t// the protocol given to us by the caller.\n\n\tif addr.IP.To4() != nil {\n\t\treturn \"tcp4\", nil\n\t}\n\n\tif addr.IP.To16() != nil {\n\t\treturn \"tcp6\", nil\n\t}\n\n\tswitch proto {\n\tcase \"tcp\", \"tcp4\", \"tcp6\":\n\t\treturn proto, nil\n\t}\n\n\treturn \"\", errorx.ErrUnsupportedTCPProtocol\n}\n\n// tcpSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint.\nfunc tcpSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) {\n\tvar (\n\t\tfamily   int\n\t\tipv6only bool\n\t\tsa       unix.Sockaddr\n\t)\n\n\tif sa, family, netAddr, ipv6only, err = GetTCPSockAddr(proto, addr); err != nil {\n\t\treturn\n\t}\n\n\tif fd, err = sysSocket(family, unix.SOCK_STREAM, unix.IPPROTO_TCP); err != nil {\n\t\terr = os.NewSyscallError(\"socket\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t// Ignore EINPROGRESS for non-blocking socket connect, should be processed by caller\n\t\t\tif errors.Is(err, unix.EINPROGRESS) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = unix.Close(fd)\n\t\t}\n\t}()\n\n\tif family == unix.AF_INET6 && ipv6only {\n\t\tif err = SetIPv6Only(fd, 1); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err = execSockOpts(fd, sockOptInts); err != nil {\n\t\treturn\n\t}\n\tif err = execSockOpts(fd, sockOptStrs); err != nil {\n\t\treturn\n\t}\n\n\tif passive {\n\t\tif err = os.NewSyscallError(\"bind\", unix.Bind(fd, sa)); err != nil {\n\t\t\treturn\n\t\t}\n\t\t// Set backlog size to the maximum.\n\t\terr = os.NewSyscallError(\"listen\", unix.Listen(fd, listenerBacklogMaxSize))\n\t} else {\n\t\terr = os.NewSyscallError(\"connect\", unix.Connect(fd, sa))\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/socket/udp_socket.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n// Copyright (c) 2017 Max Riveiro\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\n// GetUDPSockAddr the structured addresses based on the protocol and raw address.\nfunc GetUDPSockAddr(proto, addr string) (sa unix.Sockaddr, family int, udpAddr *net.UDPAddr, ipv6only bool, err error) {\n\tvar udpVersion string\n\n\tudpAddr, err = net.ResolveUDPAddr(proto, addr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tudpVersion, err = determineUDPProto(proto, udpAddr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch udpVersion {\n\tcase \"udp4\":\n\t\tfamily = unix.AF_INET\n\t\tsa, err = ipToSockaddr(family, udpAddr.IP, udpAddr.Port, \"\")\n\tcase \"udp6\":\n\t\tipv6only = true\n\t\tfallthrough\n\tcase \"udp\":\n\t\tfamily = unix.AF_INET6\n\t\tsa, err = ipToSockaddr(family, udpAddr.IP, udpAddr.Port, udpAddr.Zone)\n\tdefault:\n\t\terr = errorx.ErrUnsupportedProtocol\n\t}\n\n\treturn\n}\n\nfunc determineUDPProto(proto string, addr *net.UDPAddr) (string, error) {\n\t// If the protocol is set to \"udp\", we try to determine the actual protocol\n\t// version from the size of the resolved IP address. Otherwise, we simple use\n\t// the protocol given to us by the caller.\n\n\tif addr.IP.To4() != nil {\n\t\treturn \"udp4\", nil\n\t}\n\n\tif addr.IP.To16() != nil {\n\t\treturn \"udp6\", nil\n\t}\n\n\tswitch proto {\n\tcase \"udp\", \"udp4\", \"udp6\":\n\t\treturn proto, nil\n\t}\n\n\treturn \"\", errorx.ErrUnsupportedUDPProtocol\n}\n\n// udpSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint.\nfunc udpSocket(proto, addr string, connect bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) {\n\tvar (\n\t\tfamily   int\n\t\tipv6only bool\n\t\tsa       unix.Sockaddr\n\t)\n\n\tif sa, family, netAddr, ipv6only, err = GetUDPSockAddr(proto, addr); err != nil {\n\t\treturn\n\t}\n\n\tif fd, err = sysSocket(family, unix.SOCK_DGRAM, unix.IPPROTO_UDP); err != nil {\n\t\terr = os.NewSyscallError(\"socket\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t// Ignore EINPROGRESS for non-blocking socket connect, should be processed by caller\n\t\t\tif errors.Is(err, unix.EINPROGRESS) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = unix.Close(fd)\n\t\t}\n\t}()\n\n\tif family == unix.AF_INET6 && ipv6only {\n\t\tif err = SetIPv6Only(fd, 1); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Allow broadcast.\n\tif err = os.NewSyscallError(\"setsockopt\", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)); err != nil {\n\t\treturn\n\t}\n\n\tif err = execSockOpts(fd, sockOptInts); err != nil {\n\t\treturn\n\t}\n\tif err = execSockOpts(fd, sockOptStrs); err != nil {\n\t\treturn\n\t}\n\n\tif connect {\n\t\terr = os.NewSyscallError(\"connect\", unix.Connect(fd, sa))\n\t} else {\n\t\terr = os.NewSyscallError(\"bind\", unix.Bind(fd, sa))\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "pkg/socket/unix_socket.go",
    "content": "// Copyright (c) 2020 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd\n\npackage socket\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\n// GetUnixSockAddr the structured addresses based on the protocol and raw address.\nfunc GetUnixSockAddr(proto, addr string) (sa unix.Sockaddr, family int, unixAddr *net.UnixAddr, err error) {\n\tunixAddr, err = net.ResolveUnixAddr(proto, addr)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch unixAddr.Network() {\n\tcase \"unix\":\n\t\tsa, family = &unix.SockaddrUnix{Name: unixAddr.Name}, unix.AF_UNIX\n\tdefault:\n\t\terr = errorx.ErrUnsupportedUDSProtocol\n\t}\n\n\treturn\n}\n\n// udsSocket creates an endpoint for communication and returns a file descriptor that refers to that endpoint.\nfunc udsSocket(proto, addr string, passive bool, sockOptInts []Option[int], sockOptStrs []Option[string]) (fd int, netAddr net.Addr, err error) {\n\tvar (\n\t\tfamily int\n\t\tsa     unix.Sockaddr\n\t)\n\n\tif sa, family, netAddr, err = GetUnixSockAddr(proto, addr); err != nil {\n\t\treturn\n\t}\n\n\tif fd, err = sysSocket(family, unix.SOCK_STREAM, 0); err != nil {\n\t\terr = os.NewSyscallError(\"socket\", err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t// Ignore EINPROGRESS for non-blocking socket connect, should be processed by caller\n\t\t\t// though there is less situation for EINPROGRESS when using unix socket\n\t\t\tif errors.Is(err, unix.EINPROGRESS) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = unix.Close(fd)\n\t\t}\n\t}()\n\n\tif err = execSockOpts(fd, sockOptInts); err != nil {\n\t\treturn\n\t}\n\tif err = execSockOpts(fd, sockOptStrs); err != nil {\n\t\treturn\n\t}\n\n\tif passive {\n\t\tif err = os.NewSyscallError(\"bind\", unix.Bind(fd, sa)); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\t// Set backlog size to the maximum.\n\t\terr = os.NewSyscallError(\"listen\", unix.Listen(fd, listenerBacklogMaxSize))\n\t} else {\n\t\terr = os.NewSyscallError(\"connect\", unix.Connect(fd, sa))\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "reactor_default.go",
    "content": "// Copyright (c) 2019 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && !poll_opt\n\npackage gnet\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n\t\"github.com/panjf2000/gnet/v2/pkg/netpoll\"\n)\n\nfunc (el *eventloop) rotate() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling(el.accept0)\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"main reactor is exiting in terms of the demand from user, %v\", err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"main reactor is exiting due to error: %v\", err)\n\t}\n\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n\nfunc (el *eventloop) orbit() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling(func(fd int, ev netpoll.IOEvent, flags netpoll.IOFlags) error {\n\t\tc := el.connections.getConn(fd)\n\t\tif c == nil {\n\t\t\t// For kqueue, this might happen when the connection has already been closed,\n\t\t\t// the file descriptor will be deleted from kqueue automatically as documented\n\t\t\t// in the manual pages.\n\t\t\t// For epoll, it somehow notified with an event for a stale fd that is not in\n\t\t\t// our connection set. We need to explicitly delete it from the epoll set.\n\t\t\t// Also print a warning log for this kind of irregularity.\n\t\t\tel.getLogger().Warnf(\"received event[fd=%d|ev=%d|flags=%d] of a stale connection from event-loop(%d)\", fd, ev, flags, el.idx)\n\t\t\treturn el.poller.Delete(fd)\n\t\t}\n\t\treturn c.processIO(fd, ev, flags)\n\t})\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"event-loop(%d) is exiting in terms of the demand from user, %v\", el.idx, err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"event-loop(%d) is exiting due to error: %v\", el.idx, err)\n\t}\n\n\tel.closeConns()\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n\nfunc (el *eventloop) run() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling(func(fd int, ev netpoll.IOEvent, flags netpoll.IOFlags) error {\n\t\tc := el.connections.getConn(fd)\n\t\tif c == nil {\n\t\t\tif _, ok := el.listeners[fd]; ok {\n\t\t\t\treturn el.accept(fd, ev, flags)\n\t\t\t}\n\t\t\t// For kqueue, this might happen when the connection has already been closed,\n\t\t\t// the file descriptor will be deleted from kqueue automatically as documented\n\t\t\t// in the manual pages.\n\t\t\t// For epoll, it somehow notified with an event for a stale fd that is not in\n\t\t\t// our connection set. We need to explicitly delete it from the epoll set.\n\t\t\t// Also print a warning log for this kind of irregularity.\n\t\t\tel.getLogger().Warnf(\"received event[fd=%d|ev=%d|flags=%d] of a stale connection from event-loop(%d)\", fd, ev, flags, el.idx)\n\t\t\treturn el.poller.Delete(fd)\n\t\t}\n\t\treturn c.processIO(fd, ev, flags)\n\t})\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"event-loop(%d) is exiting in terms of the demand from user, %v\", el.idx, err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"event-loop(%d) is exiting due to error: %v\", el.idx, err)\n\t}\n\n\tel.closeConns()\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n"
  },
  {
    "path": "reactor_ultimate.go",
    "content": "// Copyright (c) 2021 The Gnet Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n//go:build (darwin || dragonfly || freebsd || linux || netbsd || openbsd) && poll_opt\n\npackage gnet\n\nimport (\n\t\"errors\"\n\t\"runtime\"\n\n\terrorx \"github.com/panjf2000/gnet/v2/pkg/errors\"\n)\n\nfunc (el *eventloop) rotate() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling()\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"main reactor is exiting in terms of the demand from user, %v\", err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"main reactor is exiting due to error: %v\", err)\n\t}\n\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n\nfunc (el *eventloop) orbit() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling()\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"event-loop(%d) is exiting in terms of the demand from user, %v\", el.idx, err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"event-loop(%d) is exiting due to error: %v\", el.idx, err)\n\t}\n\n\tel.closeConns()\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n\nfunc (el *eventloop) run() error {\n\tif el.engine.opts.LockOSThread {\n\t\truntime.LockOSThread()\n\t\tdefer runtime.UnlockOSThread()\n\t}\n\n\terr := el.poller.Polling()\n\tif errors.Is(err, errorx.ErrEngineShutdown) {\n\t\tel.getLogger().Debugf(\"event-loop(%d) is exiting in terms of the demand from user, %v\", el.idx, err)\n\t\terr = nil\n\t} else if err != nil {\n\t\tel.getLogger().Errorf(\"event-loop(%d) is exiting due to error: %v\", el.idx, err)\n\t}\n\n\tel.closeConns()\n\tel.engine.shutdown(err)\n\n\treturn err\n}\n"
  }
]