Full Code of panjf2000/ants for AI

dev d10300552713 cached
41 files
192.0 KB
56.1k tokens
257 symbols
1 requests
Download .txt
Showing preview only (203K chars total). Download the full file or copy to clipboard to get everything.
Repository: panjf2000/ants
Branch: dev
Commit: d10300552713
Files: 41
Total size: 192.0 KB

Directory structure:
gitextract_dkc_uffz/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yaml
│   │   ├── feature-request.yaml
│   │   └── question.yaml
│   ├── pull_request_template.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── codeql.yml
│       ├── pull-request.yml
│       ├── release-drafter.yml
│       ├── stale-bot.yml
│       └── test.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── README_ZH.md
├── ants.go
├── ants_benchmark_test.go
├── ants_test.go
├── example_test.go
├── go.mod
├── go.sum
├── multipool.go
├── multipool_func.go
├── multipool_func_generic.go
├── options.go
├── pkg/
│   └── sync/
│       ├── spinlock.go
│       ├── spinlock_test.go
│       └── sync.go
├── pool.go
├── pool_func.go
├── pool_func_generic.go
├── worker.go
├── worker_func.go
├── worker_func_generic.go
├── worker_loop_queue.go
├── worker_loop_queue_test.go
├── worker_queue.go
├── worker_stack.go
└── worker_stack_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [panjf2000]
patreon: panjf2000
open_collective: panjf2000
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yaml
================================================
name: Bug Report
description: Oops!..., it's a bug.
title: "[Bug]: "
labels: ["bug"]
assignees:
  - panjf2000
body:
  - type: markdown
    id: tips
    attributes:
      value: |
        ## Before you go any further
        - 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.
        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
  - type: checkboxes
    id: checklist
    attributes:
      label: Actions I've taken before I'm here
      description: Make sure you have tried the following ways but still the problem has not been solved.
      options:
        - label: I've thoroughly read the documentations on this issue but still have no clue.
          required: true
        - label: I've searched the current list of Github issues but didn't find any duplicate issues that have been solved.
          required: true
        - label: I've searched the internet with this issue, but haven't found anything helpful.
          required: true
    validations:
      required: true
  - type: textarea
    id: bug-report
    attributes:
      label: What happened?
      description: Describe (and illustrate) the bug that you encountered precisely.
      placeholder: Please describe what happened and how it happened, the more details you provide, the faster the bug gets fixed.
    validations:
      required: true
  - type: dropdown
    id: major-version
    attributes:
      label: Major version of ants
      description: What major version of ants are you running?
      options:
        - v2
        - v1
    validations:
      required: true
  - type: input
    id: specific-version
    attributes:
      label: Specific version of ants
      description: What's the specific version of ants?
      placeholder: "For example: v2.6.0"
    validations:
      required: true
  - type: dropdown
    id: os
    attributes:
      label: Operating system
      multiple: true
      options:
        - Linux
        - macOS
        - Windows
        - BSD
    validations:
      required: true
  - type: input
    id: os-version
    attributes:
      label: OS version
      description: What's the specific version of OS?
      placeholder: "Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64"
    validations:
      required: true
  - type: input
    id: go-version
    attributes:
      label: Go version
      description: What's the specific version of Go?
      placeholder: "Run `go version` command to get the info, for example: go1.20.5 linux/amd64"
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Relevant log output
      description: Please copy and paste any relevant log output.
      render: shell
    validations:
      required: true
  - type: textarea
    id: code
    attributes:
      label: Reproducer
      description: Please provide the minimal code to reproduce the bug.
      render: go
  - type: textarea
    id: how-to-reproduce
    attributes:
      label: How to Reproduce
      description: Steps to reproduce the result.
      placeholder: Tell us step by step how we can replicate this bug and what we should see in the end.
      value: |
        Steps to reproduce the behavior:
        1. Go to '....'
        2. Click on '....'
        3. Do '....'
        4. See '....'
    validations:
      required: true
  - type: dropdown
    id: bug-in-latest-code
    attributes:
      label: Does this issue reproduce with the latest release?
      description: Is this bug still present in the latest version?
      options:
        - It can reproduce with the latest release
        - It gets fixed in the latest release
        - I haven't verified it with the latest release
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yaml
================================================
name: Feature Request
description: Propose an idea to make ants even better.
title: "[Feature]: "
labels: ["proposal", "enhancement"]
assignees:
  - panjf2000
body:
  - type: markdown
    id: tips
    attributes:
      value: |
        ## Before you go any further
        - 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.
        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
  - type: textarea
    id: feature-request
    attributes:
      label: Description of new feature
      description: Make a concise but clear description about this new feature.
      placeholder: Describe this new feature with critical details.
    validations:
      required: true
  - type: textarea
    id: feature-scenario
    attributes:
      label: Scenarios for new feature
      description: Explain why you need this feature and what scenarios can benefit from it.
      placeholder: Please try to convince us that this new feature makes sense, also it will improve ants.
    validations:
      required: true
  - type: dropdown
    id: breaking-changes
    attributes:
      label: Breaking changes or not?
      description: Is this new feature going to break the backward compatibility of the existing APIs?
      options:
        - "Yes"
        - "No"
    validations:
      required: true
  - type: textarea
    id: code
    attributes:
      label: Code snippets (optional)
      description: Illustrate this new feature with source code, what it looks like in code.
      render: go
  - type: textarea
    id: feature-alternative
    attributes:
      label: Alternatives for new feature
      description: Alternatives in your mind in case that the feature can't be done for some reasons.
      placeholder: A concise but clear description of any alternative solutions or features you had in mind.
      value: None.
  - type: textarea
    id: additional-context
    attributes:
      label: Additional context (optional)
      description: Any additional context about this new feature we should know.
      placeholder: What else should we know before we start discussing this new feature?
      value: None.


================================================
FILE: .github/ISSUE_TEMPLATE/question.yaml
================================================
name: Question
description: Ask questions about ants.
title: "[Question]: "
labels: ["question", "help wanted"]
body:
  - type: markdown
    id: tips
    attributes:
      value: |
        ## Before you go any further
        - 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.
        - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc).
  - type: textarea
    id: question
    attributes:
      label: Questions with details
      description: What do you want to know?
      placeholder: Describe your question with critical details here.
    validations:
      required: true
  - type: textarea
    id: code
    attributes:
      label: Code snippets (optional)
      description: Illustrate your question with source code if needed.
      render: go


================================================
FILE: .github/pull_request_template.md
================================================
<!--
Thank you for contributing to `ants`! Please fill this out to help us review your pull request more efficiently.

Was 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.

It is not uncommon for pull requests to go through several, iterative reviews. Please be patient with us! Every reviewer is a volunteer, and each has their own style.
-->

## 1. Are you opening this pull request for bug-fixs, optimizations or new feature?



## 2. Please describe how these code changes achieve your intention.
<!-- Please be specific. Motivate the problem, and justify why this is the best solution. -->



## 3. Please link to the relevant issues (if any).
<!-- This adds crucial context to your change. -->



## 4. Which documentation changes (if any) need to be made/updated because of this PR?
<!-- 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. -->



## 4. Checklist

- [ ] I have squashed all insignificant commits.
- [ ] I have commented my code for explaining package types, values, functions, and non-obvious lines.
- [ ] I have written unit tests and verified that all tests passes (if needed).
- [ ] I have documented feature info on the README (only when this PR is adding a new feature).
- [ ] (optional) I am willing to help maintain this change if there are issues with it later.


================================================
FILE: .github/release-drafter.yml
================================================
name-template: Ants v$RESOLVED_VERSION
tag-template: v$RESOLVED_VERSION
categories:
    - title: 🧨 Breaking changes
      labels:
          - breaking changes
    - title: 🚀 Features
      labels:
          - proposal
          - new feature
    - title: 🛩 Enhancements
      labels:
          - enhancement
          - optimization
    - title: 🐛 Bugfixes
      labels:
          - bug
    - title: 📚 Documentation
      labels:
          - doc
          - docs
    - title: 🗃 Misc
      labels:
          - chores
change-template: '- $TITLE (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
  major:
    labels:
      - major
  minor:
    labels:
      - minor
      - new feature
      - proposal
  patch:
    labels:
      - patch
      - bug
      - dependencies
  default: patch
autolabeler:
  - label: bug
    title:
      - /fix/i
      - /bug/i
      - /resolve/i
  - label: docs
    files:
      - '*.md'
    title:
      - /doc/i
      - /README/i
  - label: enhancement
    title:
      - /opt:/i
      - /refactor/i
      - /optimize/i
      - /improve/i
      - /update/i
      - /remove/i
      - /delete/i
  - label: optimization
    title:
      - /opt:/i
      - /refactor/i
      - /optimize/i
      - /improve/i
      - /update/i
      - /remove/i
      - /delete/i
  - label: new feature
    title:
      - /feat:/i
      - /feature/i
      - /implement/i
      - /add/i
      - /minor/i
  - label: dependencies
    title:
      - /dependencies/i
      - /upgrade/i
      - /bump up/i
  - label: chores
    title:
      - /chore/i
      - /misc/i
      - /cleanup/i
      - /clean up/i
  - label: major
    title:
      - /major:/i
  - label: minor
    title:
      - /minor:/i
  - label: patch
    title:
      - /patch:/i
template: |
    ## Changelogs
    
    $CHANGES

    **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION
  
    Thanks to all these contributors: $CONTRIBUTORS for making this release possible.


================================================
FILE: .github/workflows/codeql.yml
================================================
name: "Code scanning"

on:
  push:
    branches:
      - master
      - dev
    paths-ignore:
      - '**.md'
      - '**.yml'
      - '**.yaml'
      - '!.github/workflows/codeql.yml'
  pull_request:
    branches:
      - master
      - dev
    paths-ignore:
      - '**.md'
      - '**.yml'
      - '**.yaml'
      - '!.github/workflows/codeql.yml'
  schedule:
    #        ┌───────────── minute (0 - 59)
    #        │  ┌───────────── hour (0 - 23)
    #        │  │ ┌───────────── day of the month (1 - 31)
    #        │  │ │ ┌───────────── month (1 - 12 or JAN-DEC)
    #        │  │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
    #        │  │ │ │ │
    #        │  │ │ │ │
    #        │  │ │ │ │
    #        *  * * * *
    - cron: '30 4 * * 6'

jobs:
  CodeQL-Build:
    # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
    runs-on: ubuntu-latest

    permissions:
      # required for all workflows
      security-events: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 2

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: go

      # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
      # If this step fails, then you should remove it and run the build manually (see below).
      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      # ℹ️ Command-line programs to run using the OS shell.
      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

      # ✏️ If the Autobuild fails above, remove it and uncomment the following
      #    three lines and modify them (or add more) to build your code if your
      #    project uses a compiled language

      #- run: |
      #     make bootstrap
      #     make release

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3

================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Check pull request target
on:
  pull_request:
    types:
      - opened
      - reopened
      - synchronize
    branches:
      - master
jobs:
  check-branches:
    runs-on: ubuntu-latest
    steps:
      - name: Check target branch
        run: |
          if [ ${{ github.head_ref }} != "dev" ]; then
            echo "Only pull requests from dev branch are only allowed to be merged into master branch."
            exit 1
          fi


================================================
FILE: .github/workflows/release-drafter.yml
================================================
name: Release Drafter

on:
  push:
    # branches to consider in the event; optional, defaults to all
    branches:
      - master
  # pull_request event is required only for autolabeler
  pull_request:
    # Only following types are handled by the action, but one can default to all as well
    types: [opened, reopened, synchronize]
  # pull_request_target event is required for autolabeler to support PRs from forks
  # pull_request_target:
  #   types: [opened, reopened, synchronize]

permissions:
  contents: read

jobs:
  update_release_draft:
    permissions:
      # write permission is required to create a github release
      contents: write
      # write permission is required for autolabeler
      # otherwise, read permission is required at least
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      # (Optional) GitHub Enterprise requires GHE_HOST variable set
      #- name: Set GHE_HOST
      #  run: |
      #    echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV

      # Drafts your next Release notes as Pull Requests are merged into "master"
      - uses: release-drafter/release-drafter@v6
        # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
        # with:
        #   config-name: my-config.yml
        #   disable-autolabeler: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/stale-bot.yml
================================================
name: Monitor inactive issues and PRs
on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch:

jobs:
  stale-issues:
    runs-on: ubuntu-latest
    permissions:
      actions: write
      issues: write
      pull-requests: write
    steps:
      - uses: actions/stale@v9
        with:
          operations-per-run: 50
          days-before-issue-stale: 30
          days-before-issue-close: 7
          stale-issue-label: 'stale'
          stale-issue-message: |
            This issue is marked as stale because it has been open for 30 days with no activity.
            
            You should take one of the following actions:
            - Manually close this issue if it is no longer relevant
            - Comment if you have more information to share
            
            This issue will be automatically closed in 7 days if no further activity occurs.
          close-issue-message: |
            This issue was closed because it has been inactive for 7 days since being marked as stale.

            If you believe this is a false alarm, please leave a comment for it or open a new issue, you can also reopen this issue directly if you have permission.
          days-before-pr-stale: 21
          days-before-pr-close: 7
          stale-pr-label: 'stale'
          stale-pr-message: |
            This PR is marked as stale because it has been open for 21 days with no activity.
            
            You should take one of the following actions:
            - Manually close this PR if it is no longer relevant
            - Push new commits or comment if you have more information to share
            
            This PR will be automatically closed in 7 days if no further activity occurs.
          close-pr-message: |
            This PR was closed because it has been inactive for 7 days since being marked as stale.

            If you believe this is a false alarm, feel free to reopen this PR or create a new one.
          repo-token: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/test.yml
================================================
name: Run tests

on:
  push:
    branches:
      - master
      - dev
    paths-ignore:
      - '**.md'
      - '**.yml'
      - '**.yaml'
      - 'examples/*'
      - '!.github/workflows/test.yml'
  pull_request:
    branches:
      - master
      - dev
    paths-ignore:
      - '**.md'
      - '**.yml'
      - '**.yaml'
      - 'examples/*'
      - '!.github/workflows/test.yml'

env:
  GO111MODULE: on
  GOPROXY: "https://proxy.golang.org"

jobs:
  lint:
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
    name: Run golangci-lint
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '^1.20'
          cache: false

      - name: Setup and run golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: v1.62.2
          args: --timeout 5m -v -E gofumpt -E gocritic -E misspell -E revive -E godot
  test:
    needs: lint
    strategy:
      fail-fast: false
      matrix:
        go: [1.19, 1.26]
        os: [ubuntu-latest, macos-latest, windows-latest]
    name: Go ${{ matrix.go }} @ ${{ matrix.os }}
    runs-on: ${{ matrix.os}}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          ref: ${{ github.ref }}

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ matrix.go }}

      - name: Print Go environment
        id: go-env
        run: |
          printf "Using go at: $(which go)\n"
          printf "Go version: $(go version)\n"
          printf "\n\nGo environment:\n\n"
          go env
          printf "\n\nSystem environment:\n\n"
          env
          # Calculate the short SHA1 hash of the git commit
          echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
          echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT

      - name: Run unit tests and integrated tests
        run: go test -v -race -coverprofile="codecov.report" -covermode=atomic

      - name: Upload code coverage report to Codecov
        uses: codecov/codecov-action@v5
        with:
          files: ./codecov.report
          flags: unittests
          name: codecov-ants
          fail_ci_if_error: true
          verbose: true
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

.idea

.DS_Store


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at panjf2000@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing 

## With issues:
  - Use the search tool before opening a new issue.
  - Please provide source code and commit sha if you found a bug.
  - Review existing issues and provide feedback or react to them.

## With pull requests:
  - Open your pull request against `dev`.
  - 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-fixs.
  - Your pull request should have no more than two commits, if not, you should squash them.
  - It should pass all tests in the available continuous integrations systems such as TravisCI.
  - You should add/modify tests to cover your proposed code changes.
  - If your pull request contains a new feature, please document it on the README.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Andy Pan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
<b>A goroutine pool for Go</b>
<br/><br/>
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
<br/>
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.19-30dff3?style=flat-square&logo=go" /></a>
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
</p>

English | [中文](README_ZH.md)

## 📖 Introduction

Library `ants` implements a goroutine pool with fixed capacity, managing and recycling a massive number of goroutines, allowing developers to limit the number of goroutines in your concurrent programs.

## 🚀 Features:

- Managing and recycling a massive number of goroutines automatically
- Purging overdue goroutines periodically
- Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool, etc.
- Handle panic gracefully to prevent programs from crash
- Efficient in memory usage and it may even achieve ***higher performance*** than unlimited goroutines in Go
- Nonblocking mechanism
- Preallocated memory (ring buffer, optional)

## 💡 How `ants` works

### Flow Diagram

<p align="center">
<img width="1011" alt="ants-flowchart-en" src="https://user-images.githubusercontent.com/7496278/66396509-7b42e700-ea0c-11e9-8612-b71a4b734683.png">
</p>

### Activity Diagrams

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-1.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-2.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-3.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-4.png)

## 🧰 How to install

### For `ants` v1

``` powershell
go get -u github.com/panjf2000/ants
```

### For `ants` v2 (with GO111MODULE=on)

```powershell
go get -u github.com/panjf2000/ants/v2
```

## 🛠 How to use
Check out [the examples](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples) for basic usage.

### Functional options for pool

`ants.Options`contains all optional configurations of the ants pool, which allows you to customize the goroutine pool by invoking option functions to set up each configuration in `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` method.

Check out [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) and [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option) for more details.

### Customize pool capacity

`ants` supports customizing the capacity of the pool. You can call the `NewPool` method to instantiate a `Pool` with a given capacity, as follows:

``` go
p, _ := ants.NewPool(10000)
```

### Submit tasks
Tasks can be submitted by calling `ants.Submit`
```go
ants.Submit(func(){})
```

### Tune pool capacity at runtime
You can tune the capacity of `ants` pool at runtime with `ants.Tune`:

``` go
pool.Tune(1000) // Tune its capacity to 1000
pool.Tune(100000) // Tune its capacity to 100000
```

Don't worry about the contention problems in this case, the method here is thread-safe (or should be called goroutine-safe).

### Pre-malloc goroutine queue in pool

`ants` allows you to pre-allocate the memory of the goroutine queue in the pool, which may get a performance enhancement under some special certain circumstances such as the scenario that requires a pool with ultra-large capacity, meanwhile, each task in goroutine lasts for a long time, in this case, pre-mallocing will reduce a lot of memory allocation in goroutine queue.

```go
// ants will pre-malloc the whole capacity of pool when calling ants.NewPool.
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
```

### Release pool

```go
pool.Release()
```

or

```go
pool.ReleaseTimeout(time.Second * 3)
```

### Reboot pool

```go
// A pool that has been released can be still used after calling the Reboot().
pool.Reboot()
```

## ⚙️ About sequence

All tasks submitted to `ants` pool will not be guaranteed to be addressed in order, because those tasks scatter among a series of concurrent workers, thus those tasks would be executed concurrently.

## 👏 Contributors

Please read our [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `ants`!

<a href="https://github.com/panjf2000/ants/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=panjf2000/ants" />
</a>

## 📄 License

The source code in `ants` is available under the [MIT License](/LICENSE).

## 📚 Relevant Articles

-  [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
-  [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
-  [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
-  [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)

## 🖥 Use cases

### business corporations & open-source organizations

Trusted by the following corporations/organizations.

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.tencent.com/">
          <img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.bytedance.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://tieba.baidu.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://weibo.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.tencentmusic.com/en-us/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/tencent-music-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.futuhk.com/en/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.shopify.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.wechat.com/en/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.baidu.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.360.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.huaweicloud.com/intl/en-us/" target="_blank">
          <img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/%E7%BB%84%E4%BB%B6%E9%AA%8C%E8%AF%81/pep-common-header/logo-en.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.matrixorigin.io/" target="_blank">
          <img src="https://www.matrixorigin.io/_next/static/media/logo-light-en.b8e29d17.svg" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://adguard-dns.io/" target="_blank">
          <img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://bk.tencent.com/" target="_blank">
          <img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.alibabacloud.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/aliyun-intl-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.zuoyebang.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.antgroup.com/en/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://zilliz.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
        </a>
      </td>
      </td>
      <td align="center" valign="middle">
        <a href="https://amap.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.apache.org/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.coze.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/coze-logo.png" width="250" />
        </a>
      </td>
    </tr>
  </tbody>
</table>

If you're also using `ants` in production, please help us enrich this list by opening a pull request.

### open-source software

The open-source projects below do concurrent programming with the help of `ants`.

- [gnet](https://github.com/panjf2000/gnet):  A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go.
- [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications.
- [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal.
- [TDengine](https://github.com/taosdata/TDengine): TDengine is an open source, high-performance, cloud native time-series database optimized for Internet of Things (IoT), Connected Cars, and Industrial IoT.
- [siyuan](https://github.com/siyuan-note/siyuan): SiYuan is a local-first personal knowledge management system that supports complete offline use, as well as end-to-end encrypted synchronization.
- [BillionMail](https://github.com/aaPanel/BillionMail): A future open-source Mail server, Email marketing platform designed to help businesses and individuals manage their email campaigns with ease.
- [WeKnora](https://github.com/Tencent/WeKnora): An LLM-powered framework designed for deep document understanding and semantic retrieval, especially for handling complex, heterogeneous documents.
- [coze-loop](https://github.com/coze-dev/coze-loop): A developer-oriented, platform-level solution focused on the development and operation of AI agents.
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne is a future-oriented hyper-converged cloud and edge native DBMS that supports transactional, analytical, and streaming workloads with a simplified and distributed database engine, across multiple data centers, clouds, edges and other heterogeneous infrastructures.
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): BlueKing Container Service (BCS, same below) is a container management and orchestration platform for the micro-services under the BlueKing ecosystem.
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
- [openGemini](https://github.com/openGemini/openGemini): openGemini is an open-source,cloud-native time-series database(TSDB) that can be widely used in IoT, Internet of Vehicles(IoV), O&M monitoring, and industrial Internet scenarios.
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
- [trpc-go](https://github.com/trpc-group/trpc-go): A pluggable, high-performance RPC framework written in Golang.
- [motan-go](https://github.com/weibocom/motan-go): Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services. motan-go is the golang implementation of Motan.

#### All use cases:

- [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)

- [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)

If you have `ants` integrated into projects, feel free to open a pull request refreshing this list of use cases.

## 🔋 JetBrains OS licenses

`ants` has been being developed with GoLand under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here.

<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>

## ☕️ Buy me a coffee

> 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.

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a target="_blank" href="https://buymeacoffee.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="Buy me a coffee" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a target="_blank" href="https://www.patreon.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a target="_blank" href="https://opencollective.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
        </a>
      </td>
    </tr>
  </tbody>
</table>

## 🔋 Sponsorship

<p>
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="201px">
  </a>
</p>


================================================
FILE: README_ZH.md
================================================
<p align="center">
<img src="https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png" />
<b>Go 语言的 goroutine 池</b>
<br/><br/>
<a title="Build Status" target="_blank" href="https://github.com/panjf2000/ants/actions?query=workflow%3ATests"><img src="https://img.shields.io/github/actions/workflow/status/panjf2000/ants/test.yml?branch=master&style=flat-square&logo=github-actions" /></a>
<a title="Codecov" target="_blank" href="https://codecov.io/gh/panjf2000/ants"><img src="https://img.shields.io/codecov/c/github/panjf2000/ants?style=flat-square&logo=codecov" /></a>
<a title="Release" target="_blank" href="https://github.com/panjf2000/ants/releases"><img src="https://img.shields.io/github/v/release/panjf2000/ants.svg?color=161823&style=flat-square&logo=smartthings" /></a>
<a title="Tag" target="_blank" href="https://github.com/panjf2000/ants/tags"><img src="https://img.shields.io/github/v/tag/panjf2000/ants?color=%23ff8936&logo=fitbit&style=flat-square" /></a>
<br/>
<a title="Minimum Go Version" target="_blank" href="https://github.com/panjf2000/gnet"><img src="https://img.shields.io/badge/go-%3E%3D1.19-30dff3?style=flat-square&logo=go" /></a>
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/panjf2000/ants"><img src="https://goreportcard.com/badge/github.com/panjf2000/ants?style=flat-square" /></a>
<a title="Doc for ants" target="_blank" href="https://pkg.go.dev/github.com/panjf2000/ants/v2?tab=doc"><img src="https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs" /></a>
<a title="Mentioned in Awesome Go" target="_blank" href="https://github.com/avelino/awesome-go#goroutines"><img src="https://awesome.re/mentioned-badge-flat.svg" /></a>
</p>

[英文](README.md) | 中文

## 📖 简介

`ants` 是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。

## 🚀 功能:

- 自动调度海量的 goroutines,复用 goroutines
- 定期清理过期的 goroutines,进一步节省资源
- 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等
- 优雅处理 panic,防止程序崩溃
- 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能***
- 非阻塞机制
- 预分配内存 (环形队列,可选)

## 💡 `ants` 是如何运行的

### 流程图

<p align="center">
<img width="845" alt="ants-flowchart-cn" src="https://user-images.githubusercontent.com/7496278/66396519-7ed66e00-ea0c-11e9-9c1a-5ca54bbd61eb.png">
</p>

### 动态图

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-1.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-2.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-3.png)

![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-4.png)

## 🧰 安装

### 使用 `ants` v1 版本:

``` powershell
go get -u github.com/panjf2000/ants
```

### 使用 `ants` v2 版本 (开启 GO111MODULE=on):

```powershell
go get -u github.com/panjf2000/ants/v2
```

## 🛠 使用
基本的使用请查看[示例](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples).

### Pool 配置

通过在调用 `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` 之时使用各种 optional function,可以设置 `ants.Options` 中各个配置项的值,然后用它来定制化 goroutine pool。

更多细节请查看 [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) 和 [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option)


### 自定义 pool 容量
`ants` 支持实例化使用者自己的一个 Pool,指定具体的 pool 容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 `Pool`,如下:

``` go
p, _ := ants.NewPool(10000)
```

### 任务提交

提交任务通过调用 `ants.Submit` 方法:
```go
ants.Submit(func(){})
```

### 动态调整 goroutine 池容量
需要动态调整 pool 容量可以通过调用 `ants.Tune`:

``` go
pool.Tune(1000) // Tune its capacity to 1000
pool.Tune(100000) // Tune its capacity to 100000
```

该方法是线程安全的。

### 预先分配 goroutine 队列内存

`ants` 支持预先为 pool 分配容量的内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。

```go
// 提前分配的 pool 容量的内存空间
p, _ := ants.NewPool(100000, ants.WithPreAlloc(true))
```

### 释放 Pool

```go
pool.Release()
```

或者

```go
pool.ReleaseTimeout(time.Second * 3)
```

### 重启 Pool

```go
// 只要调用 Reboot() 方法,就可以重新激活一个之前已经被销毁掉的池,并且投入使用。
pool.Reboot()
```

## ⚙️ 关于任务执行顺序

`ants` 并不保证提交的任务被执行的顺序,执行的顺序也不是和提交的顺序保持一致,因为在 `ants` 是并发地处理所有提交的任务,提交的任务会被分派到正在并发运行的 workers 上去,因此那些任务将会被并发且无序地被执行。

## 👏 贡献者

请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `ants` 贡献过代码的开发者!

<a href="https://github.com/panjf2000/ants/graphs/contributors">
  <img src="https://contrib.rocks/image?repo=panjf2000/ants" />
</a>

## 📄 证书

`ants` 的源码允许用户在遵循 [MIT 开源证书](/LICENSE) 规则的前提下使用。

## 📚 相关文章

-  [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool)
-  [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5)
-  [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool)
-  [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple)

## 🖥 用户案例

### 商业公司和开源组织

以下公司/组织在生产环境上使用了 `ants`。

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.tencent.com/">
          <img src="https://res.strikefreedom.top/static_res/logos/tencent_logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.bytedance.com/zh/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/ByteDance_Logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://tieba.baidu.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/baidu-tieba-logo.png" width="300" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://weibo.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/weibo-logo.png" width="300" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.tencentmusic.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/tencent-music-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.futuhk.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/futu-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.shopify.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/shopify-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://weixin.qq.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/wechat-logo.png" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.baidu.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/baidu-mobile-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.360.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/360-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.huaweicloud.com/" target="_blank">
          <img src="https://res-static.hc-cdn.cn/cloudbu-site/china/zh-cn/wangxue/header/logo.svg" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://matrixorigin.cn/" target="_blank">
          <img src="https://www.matrixorigin.cn/_next/static/media/logo-light-zh.16ed7ea0.svg" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://adguard-dns.io/" target="_blank">
          <img src="https://cdn.adtidy.org/website/images/AdGuardDNS_black.svg" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://bk.tencent.com/" target="_blank">
          <img src="https://static.apiseven.com/2022/11/14/6371adab14119.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://cn.aliyun.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/aliyun-cn-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.zuoyebang.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/zuoyebang-logo.jpeg" width="300" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.antgroup.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/ant-group-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://zilliz.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/zilliz-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://amap.com/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/amap-logo.png" width="250" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://www.apache.org/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/asf-estd-1999-logo.jpg" width="250" />
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.coze.cn/" target="_blank">
          <img src="https://res.strikefreedom.top/static_res/logos/coze-logo-cn.png" width="250" />
        </a>
      </td>
    </tr>
  </tbody>
</table>
如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。

### 开源软件

这些开源项目借助 `ants` 进行并发编程。

- [gnet](https://github.com/panjf2000/gnet):  gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。
- [milvus](https://github.com/milvus-io/milvus): 一个高度灵活、可靠且速度极快的云原生开源向量数据库。
- [nps](https://github.com/ehang-io/nps): 一款轻量级、高性能、功能强大的内网穿透代理服务器。
- [TDengine](https://github.com/taosdata/TDengine): TDengine 是一款开源、高性能、云原生的时序数据库 (Time-Series Database, TSDB)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。
- [siyuan](https://github.com/siyuan-note/siyuan): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。
- [BillionMail](https://github.com/aaPanel/BillionMail): BillionMail 是一个未来的开源邮件服务器和电子邮件营销平台,旨在帮助企业和个人轻松管理他们的电子邮件营销活动。
- [WeKnora](https://github.com/Tencent/WeKnora): 一款基于大语言模型(LLM)的文档理解与语义检索框架,专为结构复杂、内容异构的文档场景而打造。
- [coze-loop](https://github.com/coze-dev/coze-loop): Coze Loop 是一个面向开发者,专注于 AI Agent 开发与运维的平台级解决方案。
- [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security.
- [jitsu](https://github.com/jitsucom/jitsu/tree/master): An open-source Segment alternative. Fully-scriptable data ingestion engine for modern data teams. Set-up a real-time data pipeline in minutes, not days.
- [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images.
- [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection.
- [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork.
- [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing.
- [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools.
- [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne 是一款面向未来的超融合异构云原生数据库,通过超融合数据引擎支持事务/分析/流处理等混合工作负载,通过异构云原生架构支持跨机房协同/多地协同/云边协同。简化开发运维,消简数据碎片,打破数据的系统、位置和创新边界。
- [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): 蓝鲸容器管理平台(Blueking Container Service)定位于打造云原生技术和业务实际应用场景之间的桥梁;聚焦于复杂应用场景的容器化部署技术方案的研发、整合和产品化;致力于为游戏等复杂应用提供一站式、低门槛的容器编排和服务治理服务。
- [trueblocks-core](https://github.com/TrueBlocks/trueblocks-core): TrueBlocks improves access to blockchain data for any EVM-compatible chain (particularly Ethereum mainnet) while remaining entirely local.
- [openGemini](https://github.com/openGemini/openGemini): openGemini 是华为云开源的一款云原生分布式时序数据库,可广泛应用于物联网、车联网、运维监控、工业互联网等业务场景,具备卓越的读写性能和高效的数据分析能力,采用类SQL查询语言,无第三方软件依赖、安装简单、部署灵活、运维便捷。
- [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control.
- [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。
- [vanus](https://github.com/vanus-labs/vanus): Vanus is a Serverless, event streaming system with processing capabilities. It easily connects SaaS, Cloud Services, and Databases to help users build next-gen Event-driven Applications.
- [trpc-go](https://github.com/trpc-group/trpc-go): 一个 Go 实现的可插拔的高性能 RPC 框架。
- [motan-go](https://github.com/weibocom/motan-go): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。

#### 所有案例:

- [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2)

- [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw)

如果你的项目也在使用 `ants`,欢迎给我提 Pull Request 来更新这份用户案例列表。

## 🔋 JetBrains 开源证书支持

`ants` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。

<a href="https://www.jetbrains.com/?from=ants" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo."></a>

## ☕️ 打赏

> 当您通过以下方式进行捐赠时,请务必留下姓名、GitHub 账号或其他社交媒体账号,以便我将其添加到捐赠者名单中,以表谢意。

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a target="_blank" href="https://buymeacoffee.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/bmc_qr.png" width="250" alt="Buy me coffee" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a target="_blank" href="https://www.patreon.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/patreon_logo.png" width="250" alt="Patreon" />
        </a>
      </td>
      <td align="center" valign="middle">
        <a target="_blank" href="https://opencollective.com/panjf2000">
          <img src="https://res.strikefreedom.top/static_res/logos/open-collective-logo.png" width="250" alt="OpenCollective" />
        </a>
      </td>
    </tr>
  </tbody>
</table>

## 🔋 赞助商

<p>
  <a href="https://www.digitalocean.com/">
    <img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="201px">
  </a>
</p>


================================================
FILE: ants.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// Package ants implements an efficient and reliable goroutine pool for Go.
//
// With ants, Go applications are able to limit the number of active goroutines,
// recycle goroutines efficiently, and reduce the memory footprint significantly.
// Package ants is extremely useful in the scenarios where a massive number of
// goroutines are created and destroyed frequently, such as highly-concurrent
// batch processing systems, HTTP servers, services of asynchronous tasks, etc.
package ants

import (
	"context"
	"errors"
	"log"
	"math"
	"os"
	"runtime"
	"sync"
	"sync/atomic"
	"time"

	syncx "github.com/panjf2000/ants/v2/pkg/sync"
)

const (
	// DefaultAntsPoolSize is the default capacity for a default goroutine pool.
	DefaultAntsPoolSize = math.MaxInt32

	// DefaultCleanIntervalTime is the interval time to clean up goroutines.
	DefaultCleanIntervalTime = time.Second
)

const (
	// OPENED represents that the pool is opened.
	OPENED = iota

	// CLOSED represents that the pool is closed.
	CLOSED
)

var (
	// ErrLackPoolFunc will be returned when invokers don't provide function for pool.
	ErrLackPoolFunc = errors.New("must provide function for pool")

	// ErrInvalidPoolExpiry will be returned when setting a negative number as the periodic duration to purge goroutines.
	ErrInvalidPoolExpiry = errors.New("invalid expiry for pool")

	// ErrPoolClosed will be returned when submitting task to a closed pool.
	ErrPoolClosed = errors.New("this pool has been closed")

	// ErrPoolOverload will be returned when the pool is full and no workers available.
	ErrPoolOverload = errors.New("too many goroutines blocked on submit or Nonblocking is set")

	// ErrInvalidPreAllocSize will be returned when trying to set up a negative capacity under PreAlloc mode.
	ErrInvalidPreAllocSize = errors.New("can not set up a negative capacity under PreAlloc mode")

	// ErrTimeout will be returned after the operations timed out.
	ErrTimeout = errors.New("operation timed out")

	// ErrInvalidPoolIndex will be returned when trying to retrieve a pool with an invalid index.
	ErrInvalidPoolIndex = errors.New("invalid pool index")

	// ErrInvalidLoadBalancingStrategy will be returned when trying to create a MultiPool with an invalid load-balancing strategy.
	ErrInvalidLoadBalancingStrategy = errors.New("invalid load-balancing strategy")

	// ErrInvalidMultiPoolSize  will be returned when trying to create a MultiPool with an invalid size.
	ErrInvalidMultiPoolSize = errors.New("invalid size for multiple pool")

	// workerChanCap determines whether the channel of a worker should be a buffered channel
	// to get the best performance. Inspired by fasthttp at
	// https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139
	workerChanCap = func() int {
		// Use blocking channel if GOMAXPROCS=1.
		// This switches context from sender to receiver immediately,
		// which results in higher performance (under go1.5 at least).
		if runtime.GOMAXPROCS(0) == 1 {
			return 0
		}

		// Use non-blocking workerChan if GOMAXPROCS>1,
		// since otherwise the sender might be dragged down if the receiver is CPU-bound.
		return 1
	}()

	defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds))

	// Init an instance pool when importing ants.
	defaultAntsPool, _ = NewPool(DefaultAntsPoolSize)
)

// Submit submits a task to pool.
func Submit(task func()) error {
	return defaultAntsPool.Submit(task)
}

// Running returns the number of the currently running goroutines.
func Running() int {
	return defaultAntsPool.Running()
}

// Cap returns the capacity of this default pool.
func Cap() int {
	return defaultAntsPool.Cap()
}

// Free returns the available goroutines to work.
func Free() int {
	return defaultAntsPool.Free()
}

// Release Closes the default pool.
func Release() {
	defaultAntsPool.Release()
}

// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
func ReleaseTimeout(timeout time.Duration) error {
	return defaultAntsPool.ReleaseTimeout(timeout)
}

// ReleaseContext is like Release but with a context, it waits all workers to exit before the context is done.
//
// Note that if the context is nil, it is the same as Release,
// just return immediately without waiting for all workers to exit.
func ReleaseContext(ctx context.Context) error {
	return defaultAntsPool.ReleaseContext(ctx)
}

// Reboot reboots the default pool.
func Reboot() {
	defaultAntsPool.Reboot()
}

// Logger is used for logging formatted messages.
type Logger interface {
	// Printf must have the same semantics as log.Printf.
	Printf(format string, args ...any)
}

// poolCommon contains all common fields for other sophisticated pools.
type poolCommon struct {
	// capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to
	// avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool
	// which submits a new task to the same pool.
	capacity int32

	// running is the number of the currently running goroutines.
	running int32

	// lock for protecting the worker queue.
	lock sync.Locker

	// workers is a slice that store the available workers.
	workers workerQueue

	// state is used to notice the pool to closed itself.
	state int32

	// cond for waiting to get an idle worker.
	cond *sync.Cond

	// done is used to indicate that all workers are done.
	allDone chan struct{}
	// once is used to make sure the pool is closed just once.
	once *sync.Once

	// workerCache speeds up the obtainment of a usable worker in function:retrieveWorker.
	workerCache sync.Pool

	// waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock
	waiting int32

	purgeDone int32
	purgeCtx  context.Context
	stopPurge context.CancelFunc

	ticktockDone int32
	ticktockCtx  context.Context
	stopTicktock context.CancelFunc

	now int64

	options *Options
}

func newPool(size int, options ...Option) (*poolCommon, error) {
	if size <= 0 {
		size = -1
	}

	opts := loadOptions(options...)

	if !opts.DisablePurge {
		if expiry := opts.ExpiryDuration; expiry < 0 {
			return nil, ErrInvalidPoolExpiry
		} else if expiry == 0 {
			opts.ExpiryDuration = DefaultCleanIntervalTime
		}
	}

	if opts.Logger == nil {
		opts.Logger = defaultLogger
	}

	p := &poolCommon{
		capacity: int32(size),
		allDone:  make(chan struct{}),
		lock:     syncx.NewSpinLock(),
		once:     &sync.Once{},
		options:  opts,
	}
	if p.options.PreAlloc {
		if size == -1 {
			return nil, ErrInvalidPreAllocSize
		}
		p.workers = newWorkerQueue(queueTypeLoopQueue, size)
	} else {
		p.workers = newWorkerQueue(queueTypeStack, 0)
	}

	p.cond = sync.NewCond(p.lock)

	p.goPurge()
	p.goTicktock()

	return p, nil
}

// purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger.
func (p *poolCommon) purgeStaleWorkers() {
	ticker := time.NewTicker(p.options.ExpiryDuration)

	defer func() {
		ticker.Stop()
		atomic.StoreInt32(&p.purgeDone, 1)
	}()

	purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot()
	for {
		select {
		case <-purgeCtx.Done():
			return
		case <-ticker.C:
		}

		if p.IsClosed() {
			break
		}

		var isDormant bool
		p.lock.Lock()
		staleWorkers := p.workers.refresh(p.options.ExpiryDuration)
		n := p.Running()
		isDormant = n == 0 || n == len(staleWorkers)
		p.lock.Unlock()

		// Clean up the stale workers.
		for i := range staleWorkers {
			staleWorkers[i].finish()
			staleWorkers[i] = nil
		}

		// There might be a situation where all workers have been cleaned up (no worker is running),
		// while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers.
		if isDormant && p.Waiting() > 0 {
			p.cond.Broadcast()
		}
	}
}

const nowTimeUpdateInterval = 500 * time.Millisecond

// ticktock is a goroutine that updates the current time in the pool regularly.
func (p *poolCommon) ticktock() {
	ticker := time.NewTicker(nowTimeUpdateInterval)
	defer func() {
		ticker.Stop()
		atomic.StoreInt32(&p.ticktockDone, 1)
	}()

	ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot()
	for {
		select {
		case <-ticktockCtx.Done():
			return
		case <-ticker.C:
		}

		if p.IsClosed() {
			break
		}

		atomic.StoreInt64(&p.now, time.Now().UnixNano())
	}
}

func (p *poolCommon) goPurge() {
	if p.options.DisablePurge {
		return
	}

	// Start a goroutine to clean up expired workers periodically.
	p.purgeCtx, p.stopPurge = context.WithCancel(context.Background())
	go p.purgeStaleWorkers()
}

func (p *poolCommon) goTicktock() {
	atomic.StoreInt64(&p.now, time.Now().UnixNano())
	p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background())
	go p.ticktock()
}

func (p *poolCommon) nowTime() int64 {
	return atomic.LoadInt64(&p.now)
}

// Running returns the number of workers currently running.
func (p *poolCommon) Running() int {
	return int(atomic.LoadInt32(&p.running))
}

// Free returns the number of available workers, -1 indicates this pool is unlimited.
func (p *poolCommon) Free() int {
	c := p.Cap()
	if c < 0 {
		return -1
	}
	return c - p.Running()
}

// Waiting returns the number of tasks waiting to be executed.
func (p *poolCommon) Waiting() int {
	return int(atomic.LoadInt32(&p.waiting))
}

// Cap returns the capacity of this pool.
func (p *poolCommon) Cap() int {
	return int(atomic.LoadInt32(&p.capacity))
}

// Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool.
func (p *poolCommon) Tune(size int) {
	capacity := p.Cap()
	if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc {
		return
	}
	atomic.StoreInt32(&p.capacity, int32(size))
	if size > capacity {
		if size-capacity == 1 {
			p.cond.Signal()
			return
		}
		p.cond.Broadcast()
	}
}

// IsClosed indicates whether the pool is closed.
func (p *poolCommon) IsClosed() bool {
	return atomic.LoadInt32(&p.state) == CLOSED
}

// Release closes this pool and releases the worker queue.
func (p *poolCommon) Release() {
	if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) {
		return
	}

	if p.stopPurge != nil {
		p.stopPurge()
		p.stopPurge = nil
	}
	if p.stopTicktock != nil {
		p.stopTicktock()
		p.stopTicktock = nil
	}

	p.lock.Lock()
	p.workers.reset()
	p.lock.Unlock()

	// There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent
	// those callers blocking infinitely.
	p.cond.Broadcast()
}

// ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out.
func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	err := p.ReleaseContext(ctx)
	if errors.Is(err, context.DeadlineExceeded) {
		return ErrTimeout
	}
	return err
}

// ReleaseContext is like Release but with a context, it waits all workers to exit before the context is done.
//
// Note that if the context is nil, it is the same as Release,
// just return immediately without waiting for all workers to exit.
func (p *poolCommon) ReleaseContext(ctx context.Context) error {
	if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil {
		return ErrPoolClosed
	}

	p.Release()

	// Don't wait for all workers to exit, just return immediately if the context is nil.
	if ctx == nil {
		return nil
	}

	var purgeCh <-chan struct{}
	if !p.options.DisablePurge {
		purgeCh = p.purgeCtx.Done()
	} else {
		purgeCh = p.allDone
	}

	if p.Running() == 0 {
		p.once.Do(func() {
			close(p.allDone)
		})
	}

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-p.allDone:
			<-purgeCh
			<-p.ticktockCtx.Done()
			if p.Running() == 0 &&
				(p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) &&
				atomic.LoadInt32(&p.ticktockDone) == 1 {
				return nil
			}
		}
	}
}

// Reboot reboots a closed pool, it does nothing if the pool is not closed.
// If you intend to reboot a closed pool, use ReleaseTimeout() instead of
// Release() to ensure that all workers are stopped and resource are released
// before rebooting, otherwise you may run into data race.
func (p *poolCommon) Reboot() {
	if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) {
		atomic.StoreInt32(&p.purgeDone, 0)
		p.goPurge()
		atomic.StoreInt32(&p.ticktockDone, 0)
		p.goTicktock()
		p.allDone = make(chan struct{})
		p.once = &sync.Once{}
	}
}

func (p *poolCommon) addRunning(delta int) int {
	return int(atomic.AddInt32(&p.running, int32(delta)))
}

func (p *poolCommon) addWaiting(delta int) {
	atomic.AddInt32(&p.waiting, int32(delta))
}

// retrieveWorker returns an available worker to run the tasks.
func (p *poolCommon) retrieveWorker() (w worker, err error) {
	p.lock.Lock()

retry:
	// First try to fetch the worker from the queue.
	if w = p.workers.detach(); w != nil {
		p.lock.Unlock()
		return
	}

	// If the worker queue is empty, and we don't run out of the pool capacity,
	// then just spawn a new worker goroutine.
	if capacity := p.Cap(); capacity == -1 || capacity > p.Running() {
		w = p.workerCache.Get().(worker)
		w.run()
		p.lock.Unlock()
		return
	}

	// Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value.
	if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) {
		p.lock.Unlock()
		return nil, ErrPoolOverload
	}

	// Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool.
	p.addWaiting(1)
	p.cond.Wait() // block and wait for an available worker
	p.addWaiting(-1)

	if p.IsClosed() {
		p.lock.Unlock()
		return nil, ErrPoolClosed
	}

	goto retry
}

// revertWorker puts a worker back into free pool, recycling the goroutines.
func (p *poolCommon) revertWorker(worker worker) bool {
	if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() {
		p.cond.Broadcast()
		return false
	}

	worker.setLastUsedTime(p.nowTime())

	p.lock.Lock()
	// To avoid memory leaks, add a double check in the lock scope.
	// Issue: https://github.com/panjf2000/ants/issues/113
	if p.IsClosed() {
		p.lock.Unlock()
		return false
	}
	if err := p.workers.insert(worker); err != nil {
		p.lock.Unlock()
		return false
	}
	// Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue.
	p.cond.Signal()
	p.lock.Unlock()

	return true
}


================================================
FILE: ants_benchmark_test.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants_test

import (
	"runtime"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"golang.org/x/sync/errgroup"

	"github.com/panjf2000/ants/v2"
)

const (
	RunTimes           = 1e6
	PoolCap            = 5e4
	BenchParam         = 10
	DefaultExpiredTime = 10 * time.Second
)

func demoFunc() {
	time.Sleep(time.Duration(BenchParam) * time.Millisecond)
}

func demoPoolFunc(args any) {
	n := args.(int)
	time.Sleep(time.Duration(n) * time.Millisecond)
}

func demoPoolFuncInt(n int) {
	time.Sleep(time.Duration(n) * time.Millisecond)
}

var stopLongRunningFunc int32

func longRunningFunc() {
	for atomic.LoadInt32(&stopLongRunningFunc) == 0 {
		runtime.Gosched()
	}
}

func longRunningPoolFunc(arg any) {
	<-arg.(chan struct{})
}

func longRunningPoolFuncCh(ch chan struct{}) {
	<-ch
}

func BenchmarkGoroutines(b *testing.B) {
	var wg sync.WaitGroup
	for i := 0; i < b.N; i++ {
		wg.Add(RunTimes)
		for j := 0; j < RunTimes; j++ {
			go func() {
				demoFunc()
				wg.Done()
			}()
		}
		wg.Wait()
	}
}

func BenchmarkChannel(b *testing.B) {
	var wg sync.WaitGroup
	sema := make(chan struct{}, PoolCap)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		wg.Add(RunTimes)
		for j := 0; j < RunTimes; j++ {
			sema <- struct{}{}
			go func() {
				demoFunc()
				<-sema
				wg.Done()
			}()
		}
		wg.Wait()
	}
}

func BenchmarkErrGroup(b *testing.B) {
	var wg sync.WaitGroup
	var pool errgroup.Group
	pool.SetLimit(PoolCap)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		wg.Add(RunTimes)
		for j := 0; j < RunTimes; j++ {
			pool.Go(func() error {
				demoFunc()
				wg.Done()
				return nil
			})
		}
		wg.Wait()
	}
}

func BenchmarkAntsPool(b *testing.B) {
	var wg sync.WaitGroup
	p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.Release()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		wg.Add(RunTimes)
		for j := 0; j < RunTimes; j++ {
			_ = p.Submit(func() {
				demoFunc()
				wg.Done()
			})
		}
		wg.Wait()
	}
}

func BenchmarkAntsMultiPool(b *testing.B) {
	var wg sync.WaitGroup
	p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		wg.Add(RunTimes)
		for j := 0; j < RunTimes; j++ {
			_ = p.Submit(func() {
				demoFunc()
				wg.Done()
			})
		}
		wg.Wait()
	}
}

func BenchmarkGoroutinesThroughput(b *testing.B) {
	for i := 0; i < b.N; i++ {
		for j := 0; j < RunTimes; j++ {
			go demoFunc()
		}
	}
}

func BenchmarkSemaphoreThroughput(b *testing.B) {
	sema := make(chan struct{}, PoolCap)
	for i := 0; i < b.N; i++ {
		for j := 0; j < RunTimes; j++ {
			sema <- struct{}{}
			go func() {
				demoFunc()
				<-sema
			}()
		}
	}
}

func BenchmarkAntsPoolThroughput(b *testing.B) {
	p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.Release()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < RunTimes; j++ {
			_ = p.Submit(demoFunc)
		}
	}
}

func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
	p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		for j := 0; j < RunTimes; j++ {
			_ = p.Submit(demoFunc)
		}
	}
}

func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
	p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.Release()

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_ = p.Submit(demoFunc)
		}
	})
}

func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {
	p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime))
	defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck

	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			_ = p.Submit(demoFunc)
		}
	})
}


================================================
FILE: ants_test.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants_test

import (
	"context"
	"log"
	"os"
	"runtime"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/panjf2000/ants/v2"
)

const (
	_   = 1 << (10 * iota)
	KiB // 1024
	MiB // 1048576
)

const (
	Param    = 100
	AntsSize = 1000
	TestSize = 10000
	n        = 100000
)

var curMem uint64

// TestAntsPoolWaitToGetWorker is used to test waiting to get worker.
func TestAntsPoolWaitToGetWorker(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPool(AntsSize)
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Submit(func() {
			demoPoolFunc(Param)
			wg.Done()
		})
	}
	wg.Wait()
	t.Logf("pool, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPoolWaitToGetWorkerPreMalloc(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPool(AntsSize, ants.WithPreAlloc(true))
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Submit(func() {
			demoPoolFunc(Param)
			wg.Done()
		})
	}
	wg.Wait()
	t.Logf("pool, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// TestAntsPoolWithFuncWaitToGetWorker is used to test waiting to get worker.
func TestAntsPoolWithFuncWaitToGetWorker(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) {
		demoPoolFunc(i)
		wg.Done()
	})
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Invoke(Param)
	}
	wg.Wait()
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// TestAntsPoolWithFuncGenericWaitToGetWorker is used to test waiting to get worker.
func TestAntsPoolWithFuncGenericWaitToGetWorker(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) {
		demoPoolFuncInt(i)
		wg.Done()
	})
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Invoke(Param)
	}
	wg.Wait()
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPoolWithFuncWaitToGetWorkerPreMalloc(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) {
		demoPoolFunc(i)
		wg.Done()
	}, ants.WithPreAlloc(true))
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Invoke(Param)
	}
	wg.Wait()
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPoolWithFuncGenericWaitToGetWorkerPreMalloc(t *testing.T) {
	var wg sync.WaitGroup
	p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) {
		demoPoolFuncInt(i)
		wg.Done()
	}, ants.WithPreAlloc(true))
	defer p.Release()

	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = p.Invoke(Param)
	}
	wg.Wait()
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// TestAntsPoolGetWorkerFromCache is used to test getting worker from sync.Pool.
func TestAntsPoolGetWorkerFromCache(t *testing.T) {
	p, _ := ants.NewPool(TestSize)
	defer p.Release()

	for i := 0; i < AntsSize; i++ {
		_ = p.Submit(demoFunc)
	}
	time.Sleep(2 * ants.DefaultCleanIntervalTime)
	_ = p.Submit(demoFunc)
	t.Logf("pool, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// TestAntsPoolWithFuncGetWorkerFromCache is used to test getting worker from sync.Pool.
func TestAntsPoolWithFuncGetWorkerFromCache(t *testing.T) {
	dur := 10
	p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc)
	defer p.Release()

	for i := 0; i < AntsSize; i++ {
		_ = p.Invoke(dur)
	}
	time.Sleep(2 * ants.DefaultCleanIntervalTime)
	_ = p.Invoke(dur)
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// TestAntsPoolWithFuncGenericGetWorkerFromCache is used to test getting worker from sync.Pool.
func TestAntsPoolWithFuncGenericGetWorkerFromCache(t *testing.T) {
	dur := 10
	p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt)
	defer p.Release()

	for i := 0; i < AntsSize; i++ {
		_ = p.Invoke(dur)
	}
	time.Sleep(2 * ants.DefaultCleanIntervalTime)
	_ = p.Invoke(dur)
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPoolWithFuncGetWorkerFromCachePreMalloc(t *testing.T) {
	dur := 10
	p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true))
	defer p.Release()

	for i := 0; i < AntsSize; i++ {
		_ = p.Invoke(dur)
	}
	time.Sleep(2 * ants.DefaultCleanIntervalTime)
	_ = p.Invoke(dur)
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPoolWithFuncGenericGetWorkerFromCachePreMalloc(t *testing.T) {
	dur := 10
	p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true))
	defer p.Release()

	for i := 0; i < AntsSize; i++ {
		_ = p.Invoke(dur)
	}
	time.Sleep(2 * ants.DefaultCleanIntervalTime)
	_ = p.Invoke(dur)
	t.Logf("pool with func, running workers number:%d", p.Running())
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

// Contrast between goroutines without a pool and goroutines with ants pool.

func TestNoPool(t *testing.T) {
	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() {
			demoFunc()
			wg.Done()
		}()
	}

	wg.Wait()
	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestAntsPool(t *testing.T) {
	defer ants.Release()
	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		_ = ants.Submit(func() {
			demoFunc()
			wg.Done()
		})
	}
	wg.Wait()

	t.Logf("pool, capacity:%d", ants.Cap())
	t.Logf("pool, running workers number:%d", ants.Running())
	t.Logf("pool, free workers number:%d", ants.Free())

	mem := runtime.MemStats{}
	runtime.ReadMemStats(&mem)
	curMem = mem.TotalAlloc/MiB - curMem
	t.Logf("memory usage:%d MB", curMem)
}

func TestPanicHandler(t *testing.T) {
	var panicCounter int64
	var wg sync.WaitGroup
	p0, err := ants.NewPool(10, ants.WithPanicHandler(func(p any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
		t.Logf("catch panic with PanicHandler: %v", p)
	}))
	require.NoErrorf(t, err, "create new pool failed: %v", err)
	defer p0.Release()
	wg.Add(1)
	_ = p0.Submit(func() {
		panic("Oops!")
	})
	wg.Wait()
	c := atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic")

	p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPanicHandler(func(_ any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
	}))
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p1.Release()
	wg.Add(1)
	_ = p1.Invoke("Oops!")
	wg.Wait()
	c = atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")

	p2, err := ants.NewPoolWithFuncGeneric(10, func(s string) { panic(s) }, ants.WithPanicHandler(func(_ any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
	}))
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p2.Release()
	wg.Add(1)
	_ = p2.Invoke("Oops!")
	wg.Wait()
	c = atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p2.Running(), "pool should be empty after panic")
}

func TestPanicHandlerPreMalloc(t *testing.T) {
	var panicCounter int64
	var wg sync.WaitGroup
	p0, err := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithPanicHandler(func(p any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
		t.Logf("catch panic with PanicHandler: %v", p)
	}))
	require.NoErrorf(t, err, "create new pool failed: %v", err)
	defer p0.Release()
	wg.Add(1)
	_ = p0.Submit(func() {
		panic("Oops!")
	})
	wg.Wait()
	c := atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic")

	p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
	}))
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p1.Release()
	wg.Add(1)
	_ = p1.Invoke("Oops!")
	wg.Wait()
	c = atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")

	p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) {
		defer wg.Done()
		atomic.AddInt64(&panicCounter, 1)
	}))
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p2.Release()
	wg.Add(1)
	_ = p2.Invoke("Oops!")
	wg.Wait()
	c = atomic.LoadInt64(&panicCounter)
	require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c)
	require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic")
}

func TestPoolPanicWithoutHandler(t *testing.T) {
	p0, err := ants.NewPool(10)
	require.NoErrorf(t, err, "create new pool failed: %v", err)
	defer p0.Release()
	_ = p0.Submit(func() {
		panic("Oops!")
	})

	p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) })
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p1.Release()
	_ = p1.Invoke("Oops!")

	p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) })
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p2.Release()
	_ = p2.Invoke("Oops!")
}

func TestPoolPanicWithoutHandlerPreMalloc(t *testing.T) {
	p0, err := ants.NewPool(10, ants.WithPreAlloc(true))
	require.NoErrorf(t, err, "create new pool failed: %v", err)
	defer p0.Release()
	_ = p0.Submit(func() {
		panic("Oops!")
	})

	p1, err := ants.NewPoolWithFunc(10, func(p any) {
		panic(p)
	})
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p1.Release()
	_ = p1.Invoke("Oops!")

	p2, err := ants.NewPoolWithFuncGeneric(10, func(p any) {
		panic(p)
	})
	require.NoErrorf(t, err, "create new pool with func failed: %v", err)
	defer p2.Release()
	_ = p2.Invoke("Oops!")
}

func TestPurgePool(t *testing.T) {
	size := 500
	ch := make(chan struct{})

	p, err := ants.NewPool(size)
	require.NoErrorf(t, err, "create TimingPool failed: %v", err)
	defer p.Release()

	for i := 0; i < size; i++ {
		j := i + 1
		_ = p.Submit(func() {
			<-ch
			d := j % 100
			time.Sleep(time.Duration(d) * time.Millisecond)
		})
	}
	require.EqualValuesf(t, size, p.Running(), "pool should be full, expected: %d, but got: %d", size, p.Running())

	close(ch)
	time.Sleep(5 * ants.DefaultCleanIntervalTime)
	require.EqualValuesf(t, 0, p.Running(), "pool should be empty after purge, but got %d", p.Running())

	ch = make(chan struct{})
	f := func(i any) {
		<-ch
		d := i.(int) % 100
		time.Sleep(time.Duration(d) * time.Millisecond)
	}

	p1, err := ants.NewPoolWithFunc(size, f)
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p1.Release()

	for i := 0; i < size; i++ {
		_ = p1.Invoke(i)
	}
	require.EqualValuesf(t, size, p1.Running(), "pool should be full, expected: %d, but got: %d", size, p1.Running())

	close(ch)
	time.Sleep(5 * ants.DefaultCleanIntervalTime)
	require.EqualValuesf(t, 0, p1.Running(), "pool should be empty after purge, but got %d", p1.Running())

	ch = make(chan struct{})
	f1 := func(i int) {
		<-ch
		d := i % 100
		time.Sleep(time.Duration(d) * time.Millisecond)
	}

	p2, err := ants.NewPoolWithFuncGeneric(size, f1)
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p2.Release()

	for i := 0; i < size; i++ {
		_ = p2.Invoke(i)
	}
	require.EqualValuesf(t, size, p2.Running(), "pool should be full, expected: %d, but got: %d", size, p2.Running())

	close(ch)
	time.Sleep(5 * ants.DefaultCleanIntervalTime)
	require.EqualValuesf(t, 0, p2.Running(), "pool should be empty after purge, but got %d", p2.Running())
}

func TestPurgePreMallocPool(t *testing.T) {
	p, err := ants.NewPool(10, ants.WithPreAlloc(true))
	require.NoErrorf(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	_ = p.Submit(demoFunc)
	time.Sleep(3 * ants.DefaultCleanIntervalTime)
	require.EqualValues(t, 0, p.Running(), "all p should be purged")

	p1, err := ants.NewPoolWithFunc(10, demoPoolFunc)
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p1.Release()
	_ = p1.Invoke(1)
	time.Sleep(3 * ants.DefaultCleanIntervalTime)
	require.EqualValues(t, 0, p1.Running(), "all p should be purged")

	p2, err := ants.NewPoolWithFuncGeneric(10, demoPoolFuncInt)
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p2.Release()
	_ = p2.Invoke(1)
	time.Sleep(3 * ants.DefaultCleanIntervalTime)
	require.EqualValues(t, 0, p2.Running(), "all p should be purged")
}

func TestNonblockingSubmit(t *testing.T) {
	poolSize := 10
	p, err := ants.NewPool(poolSize, ants.WithNonblocking(true))
	require.NoErrorf(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Submit(longRunningFunc), "nonblocking submit when pool is not full shouldn't return error")
	}
	ch := make(chan struct{})
	ch1 := make(chan struct{})
	f := func() {
		<-ch
		close(ch1)
	}
	// p is full now.
	require.NoError(t, p.Submit(f), "nonblocking submit when pool is not full shouldn't return error")
	require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload,
		"nonblocking submit when pool is full should get an ants.ErrPoolOverload")
	// interrupt f to get an available worker
	close(ch)
	<-ch1
	require.NoError(t, p.Submit(demoFunc), "nonblocking submit when pool is not full shouldn't return error")
}

func TestMaxBlockingSubmit(t *testing.T) {
	poolSize := 10
	p, err := ants.NewPool(poolSize, ants.WithMaxBlockingTasks(1))
	require.NoErrorf(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Submit(longRunningFunc), "submit when pool is not full shouldn't return error")
	}
	ch := make(chan struct{})
	f := func() {
		<-ch
	}
	// p is full now.
	require.NoError(t, p.Submit(f), "submit when pool is not full shouldn't return error")
	var wg sync.WaitGroup
	wg.Add(1)
	errCh := make(chan error, 1)
	go func() {
		// should be blocked. blocking num == 1
		if err := p.Submit(demoFunc); err != nil {
			errCh <- err
		}
		wg.Done()
	}()
	time.Sleep(1 * time.Second)
	// already reached max blocking limit
	require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload,
		"blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload")
	// interrupt f to make blocking submit successful.
	close(ch)
	wg.Wait()
	select {
	case <-errCh:
		t.Fatalf("blocking submit when pool is full should not return error")
	default:
	}
}

func TestNonblockingSubmitWithFunc(t *testing.T) {
	poolSize := 10
	ch := make(chan struct{})
	var wg sync.WaitGroup
	p, err := ants.NewPoolWithFunc(poolSize, func(i any) {
		longRunningPoolFunc(i)
		wg.Done()
	}, ants.WithNonblocking(true))
	require.NoError(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	wg.Add(poolSize)
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	}
	// p is full now.
	require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload,
		"nonblocking submit when pool is full should get an ants.ErrPoolOverload")
	// interrupt f to get an available worker
	close(ch)
	wg.Wait()
	wg.Add(1)
	require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	wg.Wait()
}

func TestNonblockingSubmitWithFuncGeneric(t *testing.T) {
	poolSize := 10
	var wg sync.WaitGroup
	p, err := ants.NewPoolWithFuncGeneric(poolSize, func(ch chan struct{}) {
		longRunningPoolFuncCh(ch)
		wg.Done()
	}, ants.WithNonblocking(true))
	require.NoError(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	ch := make(chan struct{})
	wg.Add(poolSize)
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	}
	// p is full now.
	require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload,
		"nonblocking submit when pool is full should get an ants.ErrPoolOverload")
	// interrupt f to get an available worker
	close(ch)
	wg.Wait()
	wg.Add(1)
	require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error")
	wg.Wait()
}

func TestMaxBlockingSubmitWithFunc(t *testing.T) {
	ch := make(chan struct{})
	poolSize := 10
	p, err := ants.NewPoolWithFunc(poolSize, longRunningPoolFunc, ants.WithMaxBlockingTasks(1))
	require.NoError(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
	}
	// p is full now.
	require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
	var wg sync.WaitGroup
	wg.Add(1)
	errCh := make(chan error, 1)
	go func() {
		// should be blocked. blocking num == 1
		if err := p.Invoke(ch); err != nil {
			errCh <- err
		}
		wg.Done()
	}()
	time.Sleep(1 * time.Second)
	// already reached max blocking limit
	require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload,
		"blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err)
	// interrupt one func to make blocking submit successful.
	close(ch)
	wg.Wait()
	select {
	case <-errCh:
		t.Fatalf("blocking submit when pool is full should not return error")
	default:
	}
}

func TestMaxBlockingSubmitWithFuncGeneric(t *testing.T) {
	poolSize := 10
	p, err := ants.NewPoolWithFuncGeneric(poolSize, longRunningPoolFuncCh, ants.WithMaxBlockingTasks(1))
	require.NoError(t, err, "create TimingPool failed: %v", err)
	defer p.Release()
	ch := make(chan struct{})
	for i := 0; i < poolSize-1; i++ {
		require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
	}
	// p is full now.
	require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error")
	var wg sync.WaitGroup
	wg.Add(1)
	errCh := make(chan error, 1)
	go func() {
		// should be blocked. blocking num == 1
		if err := p.Invoke(ch); err != nil {
			errCh <- err
		}
		wg.Done()
	}()
	time.Sleep(1 * time.Second)
	// already reached max blocking limit
	require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload,
		"blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err)
	// interrupt one func to make blocking submit successful.
	close(ch)
	wg.Wait()
	select {
	case <-errCh:
		t.Fatalf("blocking submit when pool is full should not return error")
	default:
	}
}

func TestRebootDefaultPool(t *testing.T) {
	defer ants.Release()
	ants.Reboot() // should do nothing inside
	var wg sync.WaitGroup
	wg.Add(1)
	_ = ants.Submit(func() {
		demoFunc()
		wg.Done()
	})
	wg.Wait()
	require.NoError(t, ants.ReleaseTimeout(time.Second))
	require.ErrorIsf(t, ants.Submit(nil), ants.ErrPoolClosed, "pool should be closed")
	ants.Reboot()
	wg.Add(1)
	require.NoError(t, ants.Submit(func() { wg.Done() }), "pool should be rebooted")
	wg.Wait()
}

func TestRebootNewPool(t *testing.T) {
	var wg sync.WaitGroup
	p, err := ants.NewPool(10)
	require.NoErrorf(t, err, "create Pool failed: %v", err)
	defer p.Release()
	wg.Add(1)
	_ = p.Submit(func() {
		demoFunc()
		wg.Done()
	})
	wg.Wait()
	require.NoError(t, p.ReleaseTimeout(time.Second))
	require.ErrorIsf(t, p.Submit(nil), ants.ErrPoolClosed, "pool should be closed")
	p.Reboot()
	wg.Add(1)
	require.NoError(t, p.Submit(func() { wg.Done() }), "pool should be rebooted")
	wg.Wait()

	p1, err := ants.NewPoolWithFunc(10, func(i any) {
		demoPoolFunc(i)
		wg.Done()
	})
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p1.Release()
	wg.Add(1)
	_ = p1.Invoke(1)
	wg.Wait()
	require.NoError(t, p1.ReleaseTimeout(time.Second))
	require.ErrorIsf(t, p1.Invoke(nil), ants.ErrPoolClosed, "pool should be closed")
	p1.Reboot()
	wg.Add(1)
	require.NoError(t, p1.Invoke(1), "pool should be rebooted")
	wg.Wait()

	p2, err := ants.NewPoolWithFuncGeneric(10, func(i int) {
		demoPoolFuncInt(i)
		wg.Done()
	})
	require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err)
	defer p2.Release()
	wg.Add(1)
	_ = p2.Invoke(1)
	wg.Wait()
	require.NoError(t, p2.ReleaseTimeout(time.Second))
	require.ErrorIsf(t, p2.Invoke(1), ants.ErrPoolClosed, "pool should be closed")
	p2.Reboot()
	wg.Add(1)
	require.NoError(t, p2.Invoke(1), "pool should be rebooted")
	wg.Wait()
}

func TestInfinitePool(t *testing.T) {
	c := make(chan struct{})
	p, _ := ants.NewPool(-1)
	_ = p.Submit(func() {
		_ = p.Submit(func() {
			<-c
		})
	})
	c <- struct{}{}
	if n := p.Running(); n != 2 {
		t.Errorf("expect 2 workers running, but got %d", n)
	}
	if n := p.Free(); n != -1 {
		t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
	}
	p.Tune(10)
	if capacity := p.Cap(); capacity != -1 {
		t.Fatalf("expect capacity: -1 but got %d", capacity)
	}
	var err error
	_, err = ants.NewPool(-1, ants.WithPreAlloc(true))
	require.ErrorIs(t, err, ants.ErrInvalidPreAllocSize)
}

func testPoolWithDisablePurge(t *testing.T, p *ants.Pool, numWorker int, waitForPurge time.Duration) {
	sig := make(chan struct{})
	var wg1, wg2 sync.WaitGroup
	wg1.Add(numWorker)
	wg2.Add(numWorker)
	for i := 0; i < numWorker; i++ {
		_ = p.Submit(func() {
			wg1.Done()
			<-sig
			wg2.Done()
		})
	}
	wg1.Wait()

	runningCnt := p.Running()
	require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
	freeCnt := p.Free()
	require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)

	// Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism,
	// we should see that all workers are still running after the sleep.
	close(sig)
	wg2.Wait()
	time.Sleep(waitForPurge + waitForPurge/2)

	runningCnt = p.Running()
	require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
	freeCnt = p.Free()
	require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)

	err := p.ReleaseTimeout(waitForPurge + waitForPurge/2)
	require.NoErrorf(t, err, "release pool failed: %v", err)

	runningCnt = p.Running()
	require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt)
	freeCnt = p.Free()
	require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt)
}

func TestWithDisablePurgePool(t *testing.T) {
	numWorker := 10
	p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true))
	testPoolWithDisablePurge(t, p, numWorker, ants.DefaultCleanIntervalTime)
}

func TestWithDisablePurgeAndWithExpirationPool(t *testing.T) {
	numWorker := 10
	expiredDuration := time.Millisecond * 100
	p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration))
	testPoolWithDisablePurge(t, p, numWorker, expiredDuration)
}

func testPoolFuncWithDisablePurge(t *testing.T, p *ants.PoolWithFunc, numWorker int, wg1, wg2 *sync.WaitGroup, sig chan struct{}, waitForPurge time.Duration) {
	for i := 0; i < numWorker; i++ {
		_ = p.Invoke(i)
	}
	wg1.Wait()

	runningCnt := p.Running()
	require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
	freeCnt := p.Free()
	require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)

	// Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism,
	// we should see that all workers are still running after the sleep.
	close(sig)
	wg2.Wait()
	time.Sleep(waitForPurge + waitForPurge/2)

	runningCnt = p.Running()
	require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt)
	freeCnt = p.Free()
	require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt)

	err := p.ReleaseTimeout(waitForPurge + waitForPurge/2)
	require.NoErrorf(t, err, "release pool failed: %v", err)

	runningCnt = p.Running()
	require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt)
	freeCnt = p.Free()
	require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt)
}

func TestWithDisablePurgePoolFunc(t *testing.T) {
	numWorker := 10
	sig := make(chan struct{})
	var wg1, wg2 sync.WaitGroup
	wg1.Add(numWorker)
	wg2.Add(numWorker)
	p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) {
		wg1.Done()
		<-sig
		wg2.Done()
	}, ants.WithDisablePurge(true))
	testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, ants.DefaultCleanIntervalTime)
}

func TestWithDisablePurgeAndWithExpirationPoolFunc(t *testing.T) {
	numWorker := 2
	sig := make(chan struct{})
	var wg1, wg2 sync.WaitGroup
	wg1.Add(numWorker)
	wg2.Add(numWorker)
	expiredDuration := time.Millisecond * 100
	p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) {
		wg1.Done()
		<-sig
		wg2.Done()
	}, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration))
	testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, expiredDuration)
}

func TestInfinitePoolWithFunc(t *testing.T) {
	c := make(chan struct{})
	p, err := ants.NewPoolWithFunc(-1, func(i any) {
		demoPoolFunc(i)
		<-c
	})
	require.NoErrorf(t, err, "create pool with func failed: %v", err)
	defer p.Release()
	_ = p.Invoke(10)
	_ = p.Invoke(10)
	c <- struct{}{}
	c <- struct{}{}
	if n := p.Running(); n != 2 {
		t.Errorf("expect 2 workers running, but got %d", n)
	}
	if n := p.Free(); n != -1 {
		t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
	}
	p.Tune(10)
	if capacity := p.Cap(); capacity != -1 {
		t.Fatalf("expect capacity: -1 but got %d", capacity)
	}
	_, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithPreAlloc(true))
	require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err)
}

func TestInfinitePoolWithFuncGeneric(t *testing.T) {
	c := make(chan struct{})
	p, err := ants.NewPoolWithFuncGeneric(-1, func(i int) {
		demoPoolFuncInt(i)
		<-c
	})
	require.NoErrorf(t, err, "create pool with func failed: %v", err)
	defer p.Release()
	_ = p.Invoke(10)
	_ = p.Invoke(10)
	c <- struct{}{}
	c <- struct{}{}
	if n := p.Running(); n != 2 {
		t.Errorf("expect 2 workers running, but got %d", n)
	}
	if n := p.Free(); n != -1 {
		t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n)
	}
	p.Tune(10)
	if capacity := p.Cap(); capacity != -1 {
		t.Fatalf("expect capacity: -1 but got %d", capacity)
	}
	_, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithPreAlloc(true))
	require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err)
}

func TestReleaseWhenRunningPool(t *testing.T) {
	var wg sync.WaitGroup
	p, err := ants.NewPool(1)
	require.NoErrorf(t, err, "create pool failed: %v", err)
	wg.Add(2)
	go func() {
		t.Log("start aaa")
		defer func() {
			wg.Done()
			t.Log("stop aaa")
		}()
		for i := 0; i < 30; i++ {
			j := i
			_ = p.Submit(func() {
				t.Log("do task", j)
				time.Sleep(1 * time.Second)
			})
		}
	}()

	go func() {
		t.Log("start bbb")
		defer func() {
			wg.Done()
			t.Log("stop bbb")
		}()
		for i := 100; i < 130; i++ {
			j := i
			_ = p.Submit(func() {
				t.Log("do task", j)
				time.Sleep(1 * time.Second)
			})
		}
	}()

	time.Sleep(3 * time.Second)
	p.Release()
	t.Log("wait for all goroutines to exit...")
	wg.Wait()
}

func TestReleaseWhenRunningPoolWithFunc(t *testing.T) {
	var wg sync.WaitGroup
	p, err := ants.NewPoolWithFunc(1, func(i any) {
		t.Log("do task", i)
		time.Sleep(1 * time.Second)
	})
	require.NoErrorf(t, err, "create pool with func failed: %v", err)

	wg.Add(2)
	go func() {
		t.Log("start aaa")
		defer func() {
			wg.Done()
			t.Log("stop aaa")
		}()
		for i := 0; i < 30; i++ {
			_ = p.Invoke(i)
		}
	}()

	go func() {
		t.Log("start bbb")
		defer func() {
			wg.Done()
			t.Log("stop bbb")
		}()
		for i := 100; i < 130; i++ {
			_ = p.Invoke(i)
		}
	}()

	time.Sleep(3 * time.Second)
	p.Release()
	t.Log("wait for all goroutines to exit...")
	wg.Wait()
}

func TestReleaseWhenRunningPoolWithFuncGeneric(t *testing.T) {
	var wg sync.WaitGroup
	p, err := ants.NewPoolWithFuncGeneric(1, func(i int) {
		t.Log("do task", i)
		time.Sleep(1 * time.Second)
	})
	require.NoErrorf(t, err, "create pool with func failed: %v", err)
	wg.Add(2)

	go func() {
		t.Log("start aaa")
		defer func() {
			wg.Done()
			t.Log("stop aaa")
		}()
		for i := 0; i < 30; i++ {
			_ = p.Invoke(i)
		}
	}()

	go func() {
		t.Log("start bbb")
		defer func() {
			wg.Done()
			t.Log("stop bbb")
		}()
		for i := 100; i < 130; i++ {
			_ = p.Invoke(i)
		}
	}()

	time.Sleep(3 * time.Second)
	p.Release()
	t.Log("wait for all goroutines to exit...")
	wg.Wait()
}

func TestRestCodeCoverage(t *testing.T) {
	_, err := ants.NewPool(-1, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	_, err = ants.NewPool(1, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	_, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	_, err = ants.NewPoolWithFunc(1, demoPoolFunc, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	_, err = ants.NewPoolWithFunc(1, nil, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrLackPoolFunc)
	_, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	_, err = ants.NewPoolWithFuncGeneric(1, demoPoolFuncInt, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)
	var fn func(i int)
	_, err = ants.NewPoolWithFuncGeneric(1, fn, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrLackPoolFunc)

	options := ants.Options{}
	options.ExpiryDuration = time.Duration(10) * time.Second
	options.Nonblocking = true
	options.PreAlloc = true
	poolOpts, _ := ants.NewPool(1, ants.WithOptions(options))
	t.Logf("Pool with options, capacity: %d", poolOpts.Cap())

	p0, _ := ants.NewPool(TestSize, ants.WithLogger(log.New(os.Stderr, "", log.LstdFlags)))
	defer func() {
		_ = p0.Submit(demoFunc)
	}()
	defer p0.Release()
	for i := 0; i < n; i++ {
		_ = p0.Submit(demoFunc)
	}
	t.Logf("pool, capacity:%d", p0.Cap())
	t.Logf("pool, running workers number:%d", p0.Running())
	t.Logf("pool, free workers number:%d", p0.Free())
	p0.Tune(TestSize)
	p0.Tune(TestSize / 10)
	t.Logf("pool, after tuning capacity, capacity:%d, running:%d", p0.Cap(), p0.Running())

	p1, _ := ants.NewPool(TestSize, ants.WithPreAlloc(true))
	defer func() {
		_ = p1.Submit(demoFunc)
	}()
	defer p1.Release()
	for i := 0; i < n; i++ {
		_ = p1.Submit(demoFunc)
	}
	t.Logf("pre-malloc pool, capacity:%d", p1.Cap())
	t.Logf("pre-malloc pool, running workers number:%d", p1.Running())
	t.Logf("pre-malloc pool, free workers number:%d", p1.Free())
	p1.Tune(TestSize)
	p1.Tune(TestSize / 10)
	t.Logf("pre-malloc pool, after tuning capacity, capacity:%d, running:%d", p1.Cap(), p1.Running())

	p2, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc)
	defer func() {
		_ = p2.Invoke(Param)
	}()
	defer p2.Release()
	for i := 0; i < n; i++ {
		_ = p2.Invoke(Param)
	}
	time.Sleep(ants.DefaultCleanIntervalTime)
	t.Logf("pool with func, capacity:%d", p2.Cap())
	t.Logf("pool with func, running workers number:%d", p2.Running())
	t.Logf("pool with func, free workers number:%d", p2.Free())
	p2.Tune(TestSize)
	p2.Tune(TestSize / 10)
	t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p2.Cap(), p2.Running())

	p3, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt)
	defer func() {
		_ = p3.Invoke(Param)
	}()
	defer p3.Release()
	for i := 0; i < n; i++ {
		_ = p3.Invoke(Param)
	}
	time.Sleep(ants.DefaultCleanIntervalTime)
	t.Logf("pool with func, capacity:%d", p3.Cap())
	t.Logf("pool with func, running workers number:%d", p3.Running())
	t.Logf("pool with func, free workers number:%d", p3.Free())
	p3.Tune(TestSize)
	p3.Tune(TestSize / 10)
	t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p3.Cap(), p3.Running())

	p4, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true))
	defer func() {
		_ = p4.Invoke(Param)
	}()
	defer p4.Release()
	for i := 0; i < n; i++ {
		_ = p4.Invoke(Param)
	}
	time.Sleep(ants.DefaultCleanIntervalTime)
	t.Logf("pre-malloc pool with func, capacity:%d", p4.Cap())
	t.Logf("pre-malloc pool with func, running workers number:%d", p4.Running())
	t.Logf("pre-malloc pool with func, free workers number:%d", p4.Free())
	p4.Tune(TestSize)
	p4.Tune(TestSize / 10)
	t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p4.Cap(),
		p4.Running())

	p5, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true))
	defer func() {
		_ = p5.Invoke(Param)
	}()
	defer p5.Release()
	for i := 0; i < n; i++ {
		_ = p5.Invoke(Param)
	}
	time.Sleep(ants.DefaultCleanIntervalTime)
	t.Logf("pre-malloc pool with func, capacity:%d", p5.Cap())
	t.Logf("pre-malloc pool with func, running workers number:%d", p5.Running())
	t.Logf("pre-malloc pool with func, free workers number:%d", p5.Free())
	p5.Tune(TestSize)
	p5.Tune(TestSize / 10)
	t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p5.Cap(),
		p5.Running())
}

func TestPoolTuneScaleUp(t *testing.T) {
	c := make(chan struct{})
	// Test Pool
	p, _ := ants.NewPool(2)
	for i := 0; i < 2; i++ {
		_ = p.Submit(func() {
			<-c
		})
	}
	n := p.Running()
	require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", p.Running())
	// test pool tune scale up one
	p.Tune(3)
	_ = p.Submit(func() {
		<-c
	})
	n = p.Running()
	require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
	// test pool tune scale up multiple
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = p.Submit(func() {
				<-c
			})
		}()
	}
	p.Tune(8)
	wg.Wait()
	n = p.Running()
	require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
	for i := 0; i < 8; i++ {
		c <- struct{}{}
	}
	p.Release()

	// Test PoolWithFunc
	pf, _ := ants.NewPoolWithFunc(2, func(_ any) {
		<-c
	})
	for i := 0; i < 2; i++ {
		_ = pf.Invoke(1)
	}
	n = pf.Running()
	require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n)
	// test pool tune scale up one
	pf.Tune(3)
	_ = pf.Invoke(1)
	n = pf.Running()
	require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
	// test pool tune scale up multiple
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = pf.Invoke(1)
		}()
	}
	pf.Tune(8)
	wg.Wait()
	n = pf.Running()
	require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
	for i := 0; i < 8; i++ {
		c <- struct{}{}
	}
	pf.Release()

	// Test PoolWithFuncGeneric
	pfg, _ := ants.NewPoolWithFuncGeneric(2, func(_ int) {
		<-c
	})
	for i := 0; i < 2; i++ {
		_ = pfg.Invoke(1)
	}
	n = pfg.Running()
	require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n)
	// test pool tune scale up one
	pfg.Tune(3)
	_ = pfg.Invoke(1)
	n = pfg.Running()
	require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n)
	// test pool tune scale up multiple
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			_ = pfg.Invoke(1)
		}()
	}
	pfg.Tune(8)
	wg.Wait()
	n = pfg.Running()
	require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n)
	for i := 0; i < 8; i++ {
		c <- struct{}{}
	}
	close(c)
	pfg.Release()
}

func TestReleaseTimeout(t *testing.T) {
	p, err := ants.NewPool(10)
	require.NoError(t, err)
	for i := 0; i < 5; i++ {
		_ = p.Submit(func() {
			time.Sleep(time.Second)
		})
	}
	require.NotZero(t, p.Running())
	err = p.ReleaseTimeout(2 * time.Second)
	require.NoError(t, err)

	pf, err := ants.NewPoolWithFunc(10, func(i any) {
		dur := i.(time.Duration)
		time.Sleep(dur)
	})
	require.NoError(t, err)
	for i := 0; i < 5; i++ {
		_ = pf.Invoke(time.Second)
	}
	require.NotZero(t, pf.Running())
	err = pf.ReleaseTimeout(2 * time.Second)
	require.NoError(t, err)

	pfg, err := ants.NewPoolWithFuncGeneric(10, func(d time.Duration) {
		time.Sleep(d)
	})
	require.NoError(t, err)
	for i := 0; i < 5; i++ {
		_ = pfg.Invoke(time.Second)
	}
	require.NotZero(t, pfg.Running())
	err = pfg.ReleaseTimeout(2 * time.Second)
	require.NoError(t, err)
}

func TestDefaultPoolReleaseTimeout(t *testing.T) {
	ants.Reboot() // should do nothing inside
	for i := 0; i < 5; i++ {
		_ = ants.Submit(func() {
			time.Sleep(time.Second)
		})
	}
	require.NotZero(t, ants.Running())
	err := ants.ReleaseTimeout(2 * time.Second)
	require.NoError(t, err)
}

func TestDefaultPoolReleaseContext(t *testing.T) {
	ants.Reboot()
	for i := 0; i < 5; i++ {
		_ = ants.Submit(func() {
			time.Sleep(time.Second)
		})
	}
	require.NotZero(t, ants.Running())
	err := ants.ReleaseContext(context.Background())
	require.NoError(t, err)
}

func TestReleaseContextWithNil(t *testing.T) {
	p, err := ants.NewPool(10)
	require.NoError(t, err)
	for i := 0; i < 5; i++ {
		_ = p.Submit(func() {
			time.Sleep(time.Second)
		})
	}
	require.NotZero(t, p.Running())

	// Passing nil context should release immediately without waiting for workers to exit.
	err = p.ReleaseContext(nil) //nolint:staticcheck
	require.NoError(t, err)
	require.True(t, p.IsClosed())
}

func TestMultiPool(t *testing.T) {
	_, err := ants.NewMultiPool(-1, 10, 8)
	require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
	_, err = ants.NewMultiPool(10, -1, 8)
	require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
	_, err = ants.NewMultiPool(10, 10, ants.RoundRobin, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)

	mp, err := ants.NewMultiPool(10, 5, ants.RoundRobin)
	testFn := func() {
		for i := 0; i < 50; i++ {
			err = mp.Submit(longRunningFunc)
			require.NoError(t, err)
		}
		require.EqualValues(t, mp.Waiting(), 0)
		_, err = mp.WaitingByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.WaitingByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Running())
		_, err = mp.RunningByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.RunningByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 0, mp.Free())
		_, err = mp.FreeByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.FreeByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Cap())
		require.False(t, mp.IsClosed())
		for i := 0; i < 10; i++ {
			n, _ := mp.WaitingByIndex(i)
			require.EqualValues(t, 0, n)
			n, _ = mp.RunningByIndex(i)
			require.EqualValues(t, 5, n)
			n, _ = mp.FreeByIndex(i)
			require.EqualValues(t, 0, n)
		}
		atomic.StoreInt32(&stopLongRunningFunc, 1)
		require.NoError(t, mp.ReleaseTimeout(3*time.Second))
		require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
		require.ErrorIs(t, mp.Submit(nil), ants.ErrPoolClosed)
		require.Zero(t, mp.Running())
		require.True(t, mp.IsClosed())
		atomic.StoreInt32(&stopLongRunningFunc, 0)
	}
	testFn()

	mp.Reboot()
	testFn()

	mp, err = ants.NewMultiPool(10, 5, ants.LeastTasks)
	testFn()

	mp.Reboot()
	testFn()

	mp.Tune(10)
}

func TestMultiPoolWithFunc(t *testing.T) {
	_, err := ants.NewMultiPoolWithFunc(-1, 10, longRunningPoolFunc, 8)
	require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
	_, err = ants.NewMultiPoolWithFunc(10, -1, longRunningPoolFunc, 8)
	require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
	_, err = ants.NewMultiPoolWithFunc(10, 10, longRunningPoolFunc, ants.RoundRobin, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)

	ch := make(chan struct{})
	mp, err := ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.RoundRobin)
	testFn := func() {
		for i := 0; i < 50; i++ {
			err = mp.Invoke(ch)
			require.NoError(t, err)
		}
		require.EqualValues(t, mp.Waiting(), 0)
		_, err = mp.WaitingByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.WaitingByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Running())
		_, err = mp.RunningByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.RunningByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 0, mp.Free())
		_, err = mp.FreeByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.FreeByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Cap())
		require.False(t, mp.IsClosed())
		for i := 0; i < 10; i++ {
			n, _ := mp.WaitingByIndex(i)
			require.EqualValues(t, 0, n)
			n, _ = mp.RunningByIndex(i)
			require.EqualValues(t, 5, n)
			n, _ = mp.FreeByIndex(i)
			require.EqualValues(t, 0, n)
		}
		close(ch)
		require.NoError(t, mp.ReleaseTimeout(3*time.Second))
		require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
		require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed)
		require.Zero(t, mp.Running())
		require.True(t, mp.IsClosed())
		ch = make(chan struct{})
	}
	testFn()

	mp.Reboot()
	testFn()

	mp, err = ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.LeastTasks)
	testFn()

	mp.Reboot()
	testFn()

	mp.Tune(10)
}

func TestMultiPoolWithFuncGeneric(t *testing.T) {
	_, err := ants.NewMultiPoolWithFuncGeneric(-1, 10, longRunningPoolFuncCh, 8)
	require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize)
	_, err = ants.NewMultiPoolWithFuncGeneric(10, -1, longRunningPoolFuncCh, 8)
	require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy)
	_, err = ants.NewMultiPoolWithFuncGeneric(10, 10, longRunningPoolFuncCh, ants.RoundRobin, ants.WithExpiryDuration(-1))
	require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry)

	ch := make(chan struct{})
	mp, err := ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.RoundRobin)
	testFn := func() {
		for i := 0; i < 50; i++ {
			err = mp.Invoke(ch)
			require.NoError(t, err)
		}
		require.EqualValues(t, mp.Waiting(), 0)
		_, err = mp.WaitingByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.WaitingByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Running())
		_, err = mp.RunningByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.RunningByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 0, mp.Free())
		_, err = mp.FreeByIndex(-1)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		_, err = mp.FreeByIndex(11)
		require.ErrorIs(t, err, ants.ErrInvalidPoolIndex)
		require.EqualValues(t, 50, mp.Cap())
		require.False(t, mp.IsClosed())
		for i := 0; i < 10; i++ {
			n, _ := mp.WaitingByIndex(i)
			require.EqualValues(t, 0, n)
			n, _ = mp.RunningByIndex(i)
			require.EqualValues(t, 5, n)
			n, _ = mp.FreeByIndex(i)
			require.EqualValues(t, 0, n)
		}
		close(ch)
		require.NoError(t, mp.ReleaseTimeout(3*time.Second))
		require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed)
		require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed)
		require.Zero(t, mp.Running())
		require.True(t, mp.IsClosed())
		ch = make(chan struct{})
	}
	testFn()

	mp.Reboot()
	testFn()

	mp, err = ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.LeastTasks)
	testFn()

	mp.Reboot()
	testFn()

	mp.Tune(10)
}

func TestMultiPoolReleaseContext(t *testing.T) {
	mp, err := ants.NewMultiPool(10, 5, ants.RoundRobin)
	require.NoError(t, err)

	for i := 0; i < 50; i++ {
		err = mp.Submit(longRunningFunc)
		require.NoError(t, err)
	}
	require.EqualValues(t, 50, mp.Running())

	// Signal workers to stop, then release with a background context.
	atomic.StoreInt32(&stopLongRunningFunc, 1)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())
	atomic.StoreInt32(&stopLongRunningFunc, 0)

	// Calling ReleaseContext on a closed pool should return ErrPoolClosed.
	require.ErrorIs(t, mp.ReleaseContext(context.Background()), ants.ErrPoolClosed)

	// Test with LeastTasks strategy.
	mp, err = ants.NewMultiPool(10, 5, ants.LeastTasks)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Submit(longRunningFunc)
		require.NoError(t, err)
	}
	require.EqualValues(t, 50, mp.Running())

	atomic.StoreInt32(&stopLongRunningFunc, 1)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())
	atomic.StoreInt32(&stopLongRunningFunc, 0)

	// Test that a cancelled context returns an error.
	mp, err = ants.NewMultiPool(10, 5, ants.RoundRobin)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Submit(longRunningFunc)
		require.NoError(t, err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel() // cancel immediately
	err = mp.ReleaseContext(ctx)
	require.Error(t, err)
	atomic.StoreInt32(&stopLongRunningFunc, 1)
	require.Eventually(t, func() bool {
		return mp.Running() == 0
	}, 3*time.Second, 100*time.Millisecond)
	atomic.StoreInt32(&stopLongRunningFunc, 0)

	// Test reboot after ReleaseContext.
	mp, err = ants.NewMultiPool(10, 5, ants.RoundRobin)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Submit(longRunningFunc)
		require.NoError(t, err)
	}
	atomic.StoreInt32(&stopLongRunningFunc, 1)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	atomic.StoreInt32(&stopLongRunningFunc, 0)

	mp.Reboot()
	require.False(t, mp.IsClosed())
	for i := 0; i < 50; i++ {
		err = mp.Submit(longRunningFunc)
		require.NoError(t, err)
	}
	require.EqualValues(t, 50, mp.Running())
	atomic.StoreInt32(&stopLongRunningFunc, 1)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	atomic.StoreInt32(&stopLongRunningFunc, 0)
}

func TestMultiPoolWithFuncReleaseContext(t *testing.T) {
	ch := make(chan struct{})
	mp, err := ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.RoundRobin)
	require.NoError(t, err)

	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	require.EqualValues(t, 50, mp.Running())

	close(ch)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())

	// Calling ReleaseContext on a closed pool should return ErrPoolClosed.
	require.ErrorIs(t, mp.ReleaseContext(context.Background()), ants.ErrPoolClosed)

	// Test with LeastTasks strategy.
	ch = make(chan struct{})
	mp, err = ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.LeastTasks)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	close(ch)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())

	// Test that a cancelled context returns an error.
	ch = make(chan struct{})
	mp, err = ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.RoundRobin)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err = mp.ReleaseContext(ctx)
	require.Error(t, err)
	close(ch)
	require.Eventually(t, func() bool {
		return mp.Running() == 0
	}, 3*time.Second, 100*time.Millisecond)
}

func TestMultiPoolWithFuncGenericReleaseContext(t *testing.T) {
	ch := make(chan struct{})
	mp, err := ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.RoundRobin)
	require.NoError(t, err)

	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	require.EqualValues(t, 50, mp.Running())

	close(ch)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())

	// Calling ReleaseContext on a closed pool should return ErrPoolClosed.
	require.ErrorIs(t, mp.ReleaseContext(context.Background()), ants.ErrPoolClosed)

	// Test with LeastTasks strategy.
	ch = make(chan struct{})
	mp, err = ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.LeastTasks)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	close(ch)
	err = mp.ReleaseContext(context.Background())
	require.NoError(t, err)
	require.Zero(t, mp.Running())
	require.True(t, mp.IsClosed())

	// Test that a cancelled context returns an error.
	ch = make(chan struct{})
	mp, err = ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.RoundRobin)
	require.NoError(t, err)
	for i := 0; i < 50; i++ {
		err = mp.Invoke(ch)
		require.NoError(t, err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	err = mp.ReleaseContext(ctx)
	require.Error(t, err)
	close(ch)
	require.Eventually(t, func() bool {
		return mp.Running() == 0
	}, 3*time.Second, 100*time.Millisecond)
}

func TestRebootNewPoolCalc(t *testing.T) {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	pool, err := ants.NewPool(10)
	require.NoError(t, err)
	defer pool.Release()
	// Use the default pool.
	for i := 0; i < runTimes; i++ {
		j := i
		_ = pool.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	require.EqualValues(t, 499500, sum, "The result should be 499500")

	atomic.StoreInt32(&sum, 0)
	wg.Add(runTimes)
	err = pool.ReleaseTimeout(time.Second) // use both Release and ReleaseTimeout will occur panic
	require.NoError(t, err)
	pool.Reboot()

	for i := 0; i < runTimes; i++ {
		j := i
		_ = pool.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	require.EqualValues(t, 499500, sum, "The result should be 499500")
}

func TestRebootNewPoolWithPreAllocCalc(t *testing.T) {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	pool, err := ants.NewPool(10, ants.WithPreAlloc(true))
	require.NoError(t, err)
	defer pool.Release()
	// Use the default pool.
	for i := 0; i < runTimes; i++ {
		j := i
		_ = pool.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	require.EqualValues(t, 499500, sum, "The result should be 499500")

	atomic.StoreInt32(&sum, 0)
	err = pool.ReleaseTimeout(time.Second)
	require.NoError(t, err)
	pool.Reboot()

	wg.Add(runTimes)
	for i := 0; i < runTimes; i++ {
		j := i
		_ = pool.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	require.EqualValues(t, 499500, sum, "The result should be 499500")
}


================================================
FILE: example_test.go
================================================
/*
 * Copyright (c) 2025. Andy Pan. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants_test

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"github.com/panjf2000/ants/v2"
)

var (
	sum int32
	wg  sync.WaitGroup
)

func incSum(i any) {
	incSumInt(i.(int32))
}

func incSumInt(i int32) {
	atomic.AddInt32(&sum, i)
	wg.Done()
}

func ExamplePool() {
	ants.Reboot() // ensure the default pool is available

	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)
	// Use the default pool.
	for i := 0; i < runTimes; i++ {
		j := i
		_ = ants.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	fmt.Printf("The result is %d\n", sum)

	atomic.StoreInt32(&sum, 0)
	wg.Add(runTimes)
	// Use the new pool.
	pool, _ := ants.NewPool(10)
	defer pool.Release()
	for i := 0; i < runTimes; i++ {
		j := i
		_ = pool.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()
	fmt.Printf("The result is %d\n", sum)

	// Output:
	// The result is 499500
	// The result is 499500
}

func ExamplePoolWithFunc() {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	pool, _ := ants.NewPoolWithFunc(10, incSum)
	defer pool.Release()

	for i := 0; i < runTimes; i++ {
		_ = pool.Invoke(int32(i))
	}
	wg.Wait()

	fmt.Printf("The result is %d\n", sum)

	// Output: The result is 499500
}

func ExamplePoolWithFuncGeneric() {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	pool, _ := ants.NewPoolWithFuncGeneric(10, incSumInt)
	defer pool.Release()

	for i := 0; i < runTimes; i++ {
		_ = pool.Invoke(int32(i))
	}
	wg.Wait()

	fmt.Printf("The result is %d\n", sum)

	// Output: The result is 499500
}

func ExampleMultiPool() {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	mp, _ := ants.NewMultiPool(10, runTimes/10, ants.RoundRobin)
	defer mp.ReleaseTimeout(time.Second) // nolint:errcheck

	for i := 0; i < runTimes; i++ {
		j := i
		_ = mp.Submit(func() {
			incSumInt(int32(j))
		})
	}
	wg.Wait()

	fmt.Printf("The result is %d\n", sum)

	// Output: The result is 499500
}

func ExampleMultiPoolWithFunc() {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	mp, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, incSum, ants.RoundRobin)
	defer mp.ReleaseTimeout(time.Second) // nolint:errcheck

	for i := 0; i < runTimes; i++ {
		_ = mp.Invoke(int32(i))
	}
	wg.Wait()

	fmt.Printf("The result is %d\n", sum)

	// Output: The result is 499500
}

func ExampleMultiPoolWithFuncGeneric() {
	atomic.StoreInt32(&sum, 0)
	runTimes := 1000
	wg.Add(runTimes)

	mp, _ := ants.NewMultiPoolWithFuncGeneric(10, runTimes/10, incSumInt, ants.RoundRobin)
	defer mp.ReleaseTimeout(time.Second) // nolint:errcheck

	for i := 0; i < runTimes; i++ {
		_ = mp.Invoke(int32(i))
	}
	wg.Wait()

	fmt.Printf("The result is %d\n", sum)

	// Output: The result is 499500
}


================================================
FILE: go.mod
================================================
module github.com/panjf2000/ants/v2

go 1.19

require (
	github.com/stretchr/testify v1.10.0
	golang.org/x/sync v0.11.0
)

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: multipool.go
================================================
// MIT License

// Copyright (c) 2023 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"context"
	"errors"
	"fmt"
	"math"
	"strings"
	"sync/atomic"
	"time"

	"golang.org/x/sync/errgroup"
)

// LoadBalancingStrategy represents the type of load-balancing algorithm.
type LoadBalancingStrategy int

const (
	// RoundRobin distributes task to a list of pools in rotation.
	RoundRobin LoadBalancingStrategy = 1 << (iota + 1)

	// LeastTasks always selects the pool with the least number of pending tasks.
	LeastTasks
)

type contextReleaser interface {
	ReleaseContext(ctx context.Context) error
}

func releasePools(ctx context.Context, pools []contextReleaser) error {
	errCh := make(chan error, len(pools))
	var wg errgroup.Group
	for i, pool := range pools {
		func(p contextReleaser, idx int) {
			wg.Go(func() error {
				err := p.ReleaseContext(ctx)
				if err != nil {
					err = fmt.Errorf("pool %d: %v", idx, err)
				}
				errCh <- err
				return err
			})
		}(pool, i)
	}

	_ = wg.Wait()

	var errStr strings.Builder
	for i := 0; i < len(pools); i++ {
		if err := <-errCh; err != nil {
			errStr.WriteString(err.Error())
			errStr.WriteString(" | ")
		}
	}

	if errStr.Len() == 0 {
		return nil
	}

	return errors.New(strings.TrimSuffix(errStr.String(), " | "))
}

// MultiPool consists of multiple pools, from which you will benefit the
// performance improvement on basis of the fine-grained locking that reduces
// the lock contention.
// MultiPool is a good fit for the scenario where you have a large number of
// tasks to submit, and you don't want the single pool to be the bottleneck.
type MultiPool struct {
	pools []*Pool
	index uint32
	state int32
	lbs   LoadBalancingStrategy
}

// NewMultiPool instantiates a MultiPool with a size of the pool list and a size
// per pool, and the load-balancing strategy.
func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) {
	if size <= 0 {
		return nil, ErrInvalidMultiPoolSize
	}

	if lbs != RoundRobin && lbs != LeastTasks {
		return nil, ErrInvalidLoadBalancingStrategy
	}
	pools := make([]*Pool, size)
	for i := 0; i < size; i++ {
		pool, err := NewPool(sizePerPool, options...)
		if err != nil {
			return nil, err
		}
		pools[i] = pool
	}
	return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
}

func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
	switch lbs {
	case RoundRobin:
		return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
	case LeastTasks:
		leastTasks := 1<<31 - 1
		for i, pool := range mp.pools {
			if n := pool.Running(); n < leastTasks {
				leastTasks = n
				idx = i
			}
		}
		return
	}
	return -1
}

// Submit submits a task to a pool selected by the load-balancing strategy.
func (mp *MultiPool) Submit(task func()) (err error) {
	if mp.IsClosed() {
		return ErrPoolClosed
	}
	if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil {
		return
	}
	if err == ErrPoolOverload && mp.lbs == RoundRobin {
		return mp.pools[mp.next(LeastTasks)].Submit(task)
	}
	return
}

// Running returns the number of the currently running workers across all pools.
func (mp *MultiPool) Running() (n int) {
	for _, pool := range mp.pools {
		n += pool.Running()
	}
	return
}

// RunningByIndex returns the number of the currently running workers in the specific pool.
func (mp *MultiPool) RunningByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Running(), nil
}

// Free returns the number of available workers across all pools.
func (mp *MultiPool) Free() (n int) {
	for _, pool := range mp.pools {
		n += pool.Free()
	}
	return
}

// FreeByIndex returns the number of available workers in the specific pool.
func (mp *MultiPool) FreeByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Free(), nil
}

// Waiting returns the number of the currently waiting tasks across all pools.
func (mp *MultiPool) Waiting() (n int) {
	for _, pool := range mp.pools {
		n += pool.Waiting()
	}
	return
}

// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
func (mp *MultiPool) WaitingByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Waiting(), nil
}

// Cap returns the capacity of this multi-pool.
func (mp *MultiPool) Cap() (n int) {
	for _, pool := range mp.pools {
		n += pool.Cap()
	}
	return
}

// Tune resizes each pool in multi-pool.
//
// Note that this method doesn't resize the overall
// capacity of multi-pool.
func (mp *MultiPool) Tune(size int) {
	for _, pool := range mp.pools {
		pool.Tune(size)
	}
}

// IsClosed indicates whether the multi-pool is closed.
func (mp *MultiPool) IsClosed() bool {
	return atomic.LoadInt32(&mp.state) == CLOSED
}

// ReleaseTimeout closes the multi-pool with a timeout,
// it waits all pools to be closed before timing out.
func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	return mp.ReleaseContext(ctx)
}

// ReleaseContext closes the multi-pool with a context,
// it waits all pools to be closed before the context is done.
func (mp *MultiPool) ReleaseContext(ctx context.Context) error {
	if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
		return ErrPoolClosed
	}

	pools := make([]contextReleaser, len(mp.pools))
	for i, p := range mp.pools {
		pools[i] = p
	}
	return releasePools(ctx, pools)
}

// Reboot reboots a released multi-pool.
func (mp *MultiPool) Reboot() {
	if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
		atomic.StoreUint32(&mp.index, 0)
		for _, pool := range mp.pools {
			pool.Reboot()
		}
	}
}


================================================
FILE: multipool_func.go
================================================
// MIT License

// Copyright (c) 2023 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"context"
	"math"
	"sync/atomic"
	"time"
)

// MultiPoolWithFunc consists of multiple pools, from which you will benefit the
// performance improvement on basis of the fine-grained locking that reduces
// the lock contention.
// MultiPoolWithFunc is a good fit for the scenario where you have a large number of
// tasks to submit, and you don't want the single pool to be the bottleneck.
type MultiPoolWithFunc struct {
	pools []*PoolWithFunc
	index uint32
	state int32
	lbs   LoadBalancingStrategy
}

// NewMultiPoolWithFunc instantiates a MultiPoolWithFunc with a size of the pool list and a size
// per pool, and the load-balancing strategy.
func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) {
	if size <= 0 {
		return nil, ErrInvalidMultiPoolSize
	}

	if lbs != RoundRobin && lbs != LeastTasks {
		return nil, ErrInvalidLoadBalancingStrategy
	}
	pools := make([]*PoolWithFunc, size)
	for i := 0; i < size; i++ {
		pool, err := NewPoolWithFunc(sizePerPool, fn, options...)
		if err != nil {
			return nil, err
		}
		pools[i] = pool
	}
	return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
}

func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
	switch lbs {
	case RoundRobin:
		return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
	case LeastTasks:
		leastTasks := 1<<31 - 1
		for i, pool := range mp.pools {
			if n := pool.Running(); n < leastTasks {
				leastTasks = n
				idx = i
			}
		}
		return
	}
	return -1
}

// Invoke submits a task to a pool selected by the load-balancing strategy.
func (mp *MultiPoolWithFunc) Invoke(args any) (err error) {
	if mp.IsClosed() {
		return ErrPoolClosed
	}

	if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
		return
	}
	if err == ErrPoolOverload && mp.lbs == RoundRobin {
		return mp.pools[mp.next(LeastTasks)].Invoke(args)
	}
	return
}

// Running returns the number of the currently running workers across all pools.
func (mp *MultiPoolWithFunc) Running() (n int) {
	for _, pool := range mp.pools {
		n += pool.Running()
	}
	return
}

// RunningByIndex returns the number of the currently running workers in the specific pool.
func (mp *MultiPoolWithFunc) RunningByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Running(), nil
}

// Free returns the number of available workers across all pools.
func (mp *MultiPoolWithFunc) Free() (n int) {
	for _, pool := range mp.pools {
		n += pool.Free()
	}
	return
}

// FreeByIndex returns the number of available workers in the specific pool.
func (mp *MultiPoolWithFunc) FreeByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Free(), nil
}

// Waiting returns the number of the currently waiting tasks across all pools.
func (mp *MultiPoolWithFunc) Waiting() (n int) {
	for _, pool := range mp.pools {
		n += pool.Waiting()
	}
	return
}

// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
func (mp *MultiPoolWithFunc) WaitingByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Waiting(), nil
}

// Cap returns the capacity of this multi-pool.
func (mp *MultiPoolWithFunc) Cap() (n int) {
	for _, pool := range mp.pools {
		n += pool.Cap()
	}
	return
}

// Tune resizes each pool in multi-pool.
//
// Note that this method doesn't resize the overall
// capacity of multi-pool.
func (mp *MultiPoolWithFunc) Tune(size int) {
	for _, pool := range mp.pools {
		pool.Tune(size)
	}
}

// IsClosed indicates whether the multi-pool is closed.
func (mp *MultiPoolWithFunc) IsClosed() bool {
	return atomic.LoadInt32(&mp.state) == CLOSED
}

// ReleaseTimeout closes the multi-pool with a timeout,
// it waits all pools to be closed before timing out.
func (mp *MultiPoolWithFunc) ReleaseTimeout(timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	return mp.ReleaseContext(ctx)
}

// ReleaseContext closes the multi-pool with a context,
// it waits all pools to be closed before the context is done.
func (mp *MultiPoolWithFunc) ReleaseContext(ctx context.Context) error {
	if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
		return ErrPoolClosed
	}

	pools := make([]contextReleaser, len(mp.pools))
	for i, p := range mp.pools {
		pools[i] = p
	}
	return releasePools(ctx, pools)
}

// Reboot reboots a released multi-pool.
func (mp *MultiPoolWithFunc) Reboot() {
	if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
		atomic.StoreUint32(&mp.index, 0)
		for _, pool := range mp.pools {
			pool.Reboot()
		}
	}
}


================================================
FILE: multipool_func_generic.go
================================================
// MIT License

// Copyright (c) 2025 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"context"
	"math"
	"sync/atomic"
	"time"
)

// MultiPoolWithFuncGeneric is the generic version of MultiPoolWithFunc.
type MultiPoolWithFuncGeneric[T any] struct {
	pools []*PoolWithFuncGeneric[T]
	index uint32
	state int32
	lbs   LoadBalancingStrategy
}

// NewMultiPoolWithFuncGeneric instantiates a MultiPoolWithFunc with a size of the pool list and a size
// per pool, and the load-balancing strategy.
func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFuncGeneric[T], error) {
	if size <= 0 {
		return nil, ErrInvalidMultiPoolSize
	}

	if lbs != RoundRobin && lbs != LeastTasks {
		return nil, ErrInvalidLoadBalancingStrategy
	}
	pools := make([]*PoolWithFuncGeneric[T], size)
	for i := 0; i < size; i++ {
		pool, err := NewPoolWithFuncGeneric(sizePerPool, fn, options...)
		if err != nil {
			return nil, err
		}
		pools[i] = pool
	}
	return &MultiPoolWithFuncGeneric[T]{pools: pools, index: math.MaxUint32, lbs: lbs}, nil
}

func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (idx int) {
	switch lbs {
	case RoundRobin:
		return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools)))
	case LeastTasks:
		leastTasks := 1<<31 - 1
		for i, pool := range mp.pools {
			if n := pool.Running(); n < leastTasks {
				leastTasks = n
				idx = i
			}
		}
		return
	}
	return -1
}

// Invoke submits a task to a pool selected by the load-balancing strategy.
func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) {
	if mp.IsClosed() {
		return ErrPoolClosed
	}

	if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil {
		return
	}
	if err == ErrPoolOverload && mp.lbs == RoundRobin {
		return mp.pools[mp.next(LeastTasks)].Invoke(args)
	}
	return
}

// Running returns the number of the currently running workers across all pools.
func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) {
	for _, pool := range mp.pools {
		n += pool.Running()
	}
	return
}

// RunningByIndex returns the number of the currently running workers in the specific pool.
func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Running(), nil
}

// Free returns the number of available workers across all pools.
func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) {
	for _, pool := range mp.pools {
		n += pool.Free()
	}
	return
}

// FreeByIndex returns the number of available workers in the specific pool.
func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Free(), nil
}

// Waiting returns the number of the currently waiting tasks across all pools.
func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) {
	for _, pool := range mp.pools {
		n += pool.Waiting()
	}
	return
}

// WaitingByIndex returns the number of the currently waiting tasks in the specific pool.
func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, error) {
	if idx < 0 || idx >= len(mp.pools) {
		return -1, ErrInvalidPoolIndex
	}
	return mp.pools[idx].Waiting(), nil
}

// Cap returns the capacity of this multi-pool.
func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) {
	for _, pool := range mp.pools {
		n += pool.Cap()
	}
	return
}

// Tune resizes each pool in multi-pool.
//
// Note that this method doesn't resize the overall
// capacity of multi-pool.
func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) {
	for _, pool := range mp.pools {
		pool.Tune(size)
	}
}

// IsClosed indicates whether the multi-pool is closed.
func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool {
	return atomic.LoadInt32(&mp.state) == CLOSED
}

// ReleaseTimeout closes the multi-pool with a timeout,
// it waits all pools to be closed before timing out.
func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Duration) error {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	return mp.ReleaseContext(ctx)
}

// ReleaseContext closes the multi-pool with a context,
// it waits all pools to be closed before the context is done.
func (mp *MultiPoolWithFuncGeneric[T]) ReleaseContext(ctx context.Context) error {
	if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) {
		return ErrPoolClosed
	}

	pools := make([]contextReleaser, len(mp.pools))
	for i, p := range mp.pools {
		pools[i] = p
	}
	return releasePools(ctx, pools)
}

// Reboot reboots a released multi-pool.
func (mp *MultiPoolWithFuncGeneric[T]) Reboot() {
	if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) {
		atomic.StoreUint32(&mp.index, 0)
		for _, pool := range mp.pools {
			pool.Reboot()
		}
	}
}


================================================
FILE: options.go
================================================
/*
 * Copyright (c) 2018. Andy Pan. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import "time"

// Option represents the optional function.
type Option func(opts *Options)

func loadOptions(options ...Option) *Options {
	opts := new(Options)
	for _, option := range options {
		option(opts)
	}
	return opts
}

// Options contains all options which will be applied when instantiating an ants pool.
type Options struct {
	// ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers,
	// the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been
	// used for more than `ExpiryDuration`.
	ExpiryDuration time.Duration

	// PreAlloc indicates whether to make memory pre-allocation when initializing Pool.
	PreAlloc bool

	// Max number of goroutine blocking on pool.Submit.
	// 0 (default value) means no such limit.
	MaxBlockingTasks int

	// When Nonblocking is true, Pool.Submit will never be blocked.
	// ErrPoolOverload will be returned when Pool.Submit cannot be done at once.
	// When Nonblocking is true, MaxBlockingTasks is inoperative.
	Nonblocking bool

	// PanicHandler is used to handle panics from each worker goroutine.
	// If nil, the default behavior is to capture the value given to panic
	// and resume normal execution and print that value along with the
	// stack trace of the goroutine
	PanicHandler func(any)

	// Logger is the customized logger for logging info, if it is not set,
	// default standard logger from log package is used.
	Logger Logger

	// When DisablePurge is true, workers are not purged and are resident.
	DisablePurge bool
}

// WithOptions accepts the whole Options config.
func WithOptions(options Options) Option {
	return func(opts *Options) {
		*opts = options
	}
}

// WithExpiryDuration sets up the interval time of cleaning up goroutines.
func WithExpiryDuration(expiryDuration time.Duration) Option {
	return func(opts *Options) {
		opts.ExpiryDuration = expiryDuration
	}
}

// WithPreAlloc indicates whether it should malloc for workers.
func WithPreAlloc(preAlloc bool) Option {
	return func(opts *Options) {
		opts.PreAlloc = preAlloc
	}
}

// WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool.
func WithMaxBlockingTasks(maxBlockingTasks int) Option {
	return func(opts *Options) {
		opts.MaxBlockingTasks = maxBlockingTasks
	}
}

// WithNonblocking indicates that pool will return nil when there is no available workers.
func WithNonblocking(nonblocking bool) Option {
	return func(opts *Options) {
		opts.Nonblocking = nonblocking
	}
}

// WithPanicHandler sets up panic handler.
func WithPanicHandler(panicHandler func(any)) Option {
	return func(opts *Options) {
		opts.PanicHandler = panicHandler
	}
}

// WithLogger sets up a customized logger.
func WithLogger(logger Logger) Option {
	return func(opts *Options) {
		opts.Logger = logger
	}
}

// WithDisablePurge indicates whether we turn off automatically purge.
func WithDisablePurge(disable bool) Option {
	return func(opts *Options) {
		opts.DisablePurge = disable
	}
}


================================================
FILE: pkg/sync/spinlock.go
================================================
// Copyright 2019 Andy Pan & Dietoad. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package sync

import (
	"runtime"
	"sync"
	"sync/atomic"
)

type spinLock uint32

const maxBackoff = 16

func (sl *spinLock) Lock() {
	backoff := 1
	for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
		// Leverage the exponential backoff algorithm, see https://en.wikipedia.org/wiki/Exponential_backoff.
		for i := 0; i < backoff; i++ {
			runtime.Gosched()
		}
		if backoff < maxBackoff {
			backoff <<= 1
		}
	}
}

func (sl *spinLock) Unlock() {
	atomic.StoreUint32((*uint32)(sl), 0)
}

// NewSpinLock instantiates a spin-lock.
func NewSpinLock() sync.Locker {
	return new(spinLock)
}


================================================
FILE: pkg/sync/spinlock_test.go
================================================
// Copyright 2021 Andy Pan & Dietoad. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package sync

import (
	"runtime"
	"sync"
	"sync/atomic"
	"testing"
)

/*
Benchmark result for three types of locks:
	goos: darwin
	goarch: arm64
	pkg: github.com/panjf2000/ants/v2/pkg/sync
	BenchmarkMutex-10              	10452573	       111.1 ns/op	       0 B/op	       0 allocs/op
	BenchmarkSpinLock-10           	58953211	        18.01 ns/op	       0 B/op	       0 allocs/op
	BenchmarkBackOffSpinLock-10    	100000000	        10.81 ns/op	       0 B/op	       0 allocs/op
*/

type originSpinLock uint32

func (sl *originSpinLock) Lock() {
	for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) {
		runtime.Gosched()
	}
}

func (sl *originSpinLock) Unlock() {
	atomic.StoreUint32((*uint32)(sl), 0)
}

func NewOriginSpinLock() sync.Locker {
	return new(originSpinLock)
}

func BenchmarkMutex(b *testing.B) {
	m := sync.Mutex{}
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			m.Lock()
			//nolint:staticcheck
			m.Unlock()
		}
	})
}

func BenchmarkSpinLock(b *testing.B) {
	spin := NewOriginSpinLock()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			spin.Lock()
			//nolint:staticcheck
			spin.Unlock()
		}
	})
}

func BenchmarkBackOffSpinLock(b *testing.B) {
	spin := NewSpinLock()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			spin.Lock()
			//nolint:staticcheck
			spin.Unlock()
		}
	})
}


================================================
FILE: pkg/sync/sync.go
================================================
/*
 * Copyright (c) 2025. Andy Pan. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// Package sync provides some handy implementations for synchronization access.
// At the moment, there is only an implementation of spin-lock.
package sync


================================================
FILE: pool.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

// Pool is a goroutine pool that limits and recycles a mass of goroutines.
// The pool capacity can be fixed or unlimited.
type Pool struct {
	*poolCommon
}

// Submit submits a task to the pool.
//
// Note that you are allowed to call Pool.Submit() from the current Pool.Submit(),
// but what calls for special attention is that you will get blocked with the last
// Pool.Submit() call once the current Pool runs out of its capacity, and to avoid this,
// you should instantiate a Pool with ants.WithNonblocking(true).
func (p *Pool) Submit(task func()) error {
	if p.IsClosed() {
		return ErrPoolClosed
	}

	w, err := p.retrieveWorker()
	if w != nil {
		w.inputFunc(task)
	}
	return err
}

// NewPool instantiates a Pool with customized options.
func NewPool(size int, options ...Option) (*Pool, error) {
	pc, err := newPool(size, options...)
	if err != nil {
		return nil, err
	}

	pool := &Pool{poolCommon: pc}
	pool.workerCache.New = func() any {
		return &goWorker{
			pool: pool,
			task: make(chan func(), workerChanCap),
		}
	}

	return pool, nil
}


================================================
FILE: pool_func.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

// PoolWithFunc is like Pool but accepts a unified function for all goroutines to execute.
type PoolWithFunc struct {
	*poolCommon

	// fn is the unified function for processing tasks.
	fn func(any)
}

// Invoke passes arguments to the pool.
//
// Note that you are allowed to call Pool.Invoke() from the current Pool.Invoke(),
// but what calls for special attention is that you will get blocked with the last
// Pool.Invoke() call once the current Pool runs out of its capacity, and to avoid this,
// you should instantiate a PoolWithFunc with ants.WithNonblocking(true).
func (p *PoolWithFunc) Invoke(arg any) error {
	if p.IsClosed() {
		return ErrPoolClosed
	}

	w, err := p.retrieveWorker()
	if w != nil {
		w.inputArg(arg)
	}
	return err
}

// NewPoolWithFunc instantiates a PoolWithFunc with customized options.
func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) {
	if pf == nil {
		return nil, ErrLackPoolFunc
	}

	pc, err := newPool(size, options...)
	if err != nil {
		return nil, err
	}

	pool := &PoolWithFunc{
		poolCommon: pc,
		fn:         pf,
	}

	pool.workerCache.New = func() any {
		return &goWorkerWithFunc{
			pool: pool,
			arg:  make(chan any, workerChanCap),
		}
	}

	return pool, nil
}


================================================
FILE: pool_func_generic.go
================================================
// MIT License

// Copyright (c) 2025 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

// PoolWithFuncGeneric is the generic version of PoolWithFunc.
type PoolWithFuncGeneric[T any] struct {
	*poolCommon

	// fn is the unified function for processing tasks.
	fn func(T)
}

// Invoke passes the argument to the pool to start a new task.
func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error {
	if p.IsClosed() {
		return ErrPoolClosed
	}

	w, err := p.retrieveWorker()
	if w != nil {
		w.(*goWorkerWithFuncGeneric[T]).arg <- arg
	}
	return err
}

// NewPoolWithFuncGeneric instantiates a PoolWithFuncGeneric[T] with customized options.
func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Option) (*PoolWithFuncGeneric[T], error) {
	if pf == nil {
		return nil, ErrLackPoolFunc
	}

	pc, err := newPool(size, options...)
	if err != nil {
		return nil, err
	}

	pool := &PoolWithFuncGeneric[T]{
		poolCommon: pc,
		fn:         pf,
	}

	pool.workerCache.New = func() any {
		return &goWorkerWithFuncGeneric[T]{
			pool: pool,
			arg:  make(chan T, workerChanCap),
			exit: make(chan struct{}, 1),
		}
	}

	return pool, nil
}


================================================
FILE: worker.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"runtime/debug"
)

// goWorker is the actual executor who runs the tasks,
// it starts a goroutine that accepts tasks and
// performs function calls.
type goWorker struct {
	worker

	// pool who owns this worker.
	pool *Pool

	// task is a job should be done.
	task chan func()

	// lastUsed will be updated when putting a worker back into queue.
	lastUsed int64
}

// run starts a goroutine to repeat the process
// that performs the function calls.
func (w *goWorker) run() {
	w.pool.addRunning(1)
	go func() {
		defer func() {
			if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
				w.pool.once.Do(func() {
					close(w.pool.allDone)
				})
			}
			w.pool.workerCache.Put(w)
			if p := recover(); p != nil {
				if ph := w.pool.options.PanicHandler; ph != nil {
					ph(p)
				} else {
					w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
				}
			}
			// Call Signal() here in case there are goroutines waiting for available workers.
			w.pool.cond.Signal()
		}()

		for fn := range w.task {
			if fn == nil {
				return
			}
			fn()
			if ok := w.pool.revertWorker(w); !ok {
				return
			}
		}
	}()
}

func (w *goWorker) finish() {
	w.task <- nil
}

func (w *goWorker) lastUsedTime() int64 {
	return w.lastUsed
}

func (w *goWorker) setLastUsedTime(t int64) {
	w.lastUsed = t
}

func (w *goWorker) inputFunc(fn func()) {
	w.task <- fn
}


================================================
FILE: worker_func.go
================================================
// MIT License

// Copyright (c) 2018 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"runtime/debug"
)

// goWorkerWithFunc is the actual executor who runs the tasks,
// it starts a goroutine that accepts tasks and
// performs function calls.
type goWorkerWithFunc struct {
	worker

	// pool who owns this worker.
	pool *PoolWithFunc

	// arg is the argument for the function.
	arg chan any

	// lastUsed will be updated when putting a worker back into queue.
	lastUsed int64
}

// run starts a goroutine to repeat the process
// that performs the function calls.
func (w *goWorkerWithFunc) run() {
	w.pool.addRunning(1)
	go func() {
		defer func() {
			if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
				w.pool.once.Do(func() {
					close(w.pool.allDone)
				})
			}
			w.pool.workerCache.Put(w)
			if p := recover(); p != nil {
				if ph := w.pool.options.PanicHandler; ph != nil {
					ph(p)
				} else {
					w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
				}
			}
			// Call Signal() here in case there are goroutines waiting for available workers.
			w.pool.cond.Signal()
		}()

		for arg := range w.arg {
			if arg == nil {
				return
			}
			w.pool.fn(arg)
			if ok := w.pool.revertWorker(w); !ok {
				return
			}
		}
	}()
}

func (w *goWorkerWithFunc) finish() {
	w.arg <- nil
}

func (w *goWorkerWithFunc) lastUsedTime() int64 {
	return w.lastUsed
}

func (w *goWorkerWithFunc) setLastUsedTime(t int64) {
	w.lastUsed = t
}

func (w *goWorkerWithFunc) inputArg(arg any) {
	w.arg <- arg
}


================================================
FILE: worker_func_generic.go
================================================
// MIT License

// Copyright (c) 2025 Andy Pan

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

package ants

import (
	"runtime/debug"
)

// goWorkerWithFunc is the actual executor who runs the tasks,
// it starts a goroutine that accepts tasks and
// performs function calls.
type goWorkerWithFuncGeneric[T any] struct {
	worker

	// pool who owns this worker.
	pool *PoolWithFuncGeneric[T]

	// arg is a job should be done.
	arg chan T

	// exit signals the goroutine to exit.
	exit chan struct{}

	// lastUsed will be updated when putting a worker back into queue.
	lastUsed int64
}

// run starts a goroutine to repeat the process
// that performs the function calls.
func (w *goWorkerWithFuncGeneric[T]) run() {
	w.pool.addRunning(1)
	go func() {
		defer func() {
			if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() {
				w.pool.once.Do(func() {
					close(w.pool.allDone)
				})
			}
			w.pool.workerCache.Put(w)
			if p := recover(); p != nil {
				if ph := w.pool.options.PanicHandler; ph != nil {
					ph(p)
				} else {
					w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack())
				}
			}
			// Call Signal() here in case there are goroutines waiting for available workers.
			w.pool.cond.Signal()
		}()

		for {
			select {
			case <-w.exit:
				return
			case arg := <-w.arg:
				w.pool.fn(arg)
				if ok := w.pool.revertWorker(w); !ok {
					return
				}
			}
		}
	}()
}

func (w *goWorkerWithFuncGeneric[T]) finish() {
	w.exit <- struct{}{}
}

func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() int64 {
	return w.lastUsed
}

func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t int64) {
	w.lastUsed = t
}


================================================
FILE: worker_loop_queue.go
================================================
/*
 * Copyright (c) 2019. Ants Authors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import "time"

type loopQueue struct {
	items  []worker
	expiry []worker
	head   int
	tail   int
	size   int
	isFull bool
}

func newWorkerLoopQueue(size int) *loopQueue {
	if size <= 0 {
		return nil
	}
	return &loopQueue{
		items: make([]worker, size),
		size:  size,
	}
}

func (wq *loopQueue) len() int {
	if wq.size == 0 || wq.isEmpty() {
		return 0
	}

	if wq.head == wq.tail && wq.isFull {
		return wq.size
	}

	if wq.tail > wq.head {
		return wq.tail - wq.head
	}

	return wq.size - wq.head + wq.tail
}

func (wq *loopQueue) isEmpty() bool {
	return wq.head == wq.tail && !wq.isFull
}

func (wq *loopQueue) insert(w worker) error {
	if wq.isFull {
		return errQueueIsFull
	}
	wq.items[wq.tail] = w
	wq.tail = (wq.tail + 1) % wq.size

	if wq.tail == wq.head {
		wq.isFull = true
	}

	return nil
}

func (wq *loopQueue) detach() worker {
	if wq.isEmpty() {
		return nil
	}

	w := wq.items[wq.head]
	wq.items[wq.head] = nil
	wq.head = (wq.head + 1) % wq.size

	wq.isFull = false

	return w
}

func (wq *loopQueue) refresh(duration time.Duration) []worker {
	expiryTime := time.Now().Add(-duration).UnixNano()
	index := wq.binarySearch(expiryTime)
	if index == -1 {
		return nil
	}
	wq.expiry = wq.expiry[:0]

	if wq.head <= index {
		wq.expiry = append(wq.expiry, wq.items[wq.head:index+1]...)
		for i := wq.head; i < index+1; i++ {
			wq.items[i] = nil
		}
	} else {
		wq.expiry = append(wq.expiry, wq.items[0:index+1]...)
		wq.expiry = append(wq.expiry, wq.items[wq.head:]...)
		for i := 0; i < index+1; i++ {
			wq.items[i] = nil
		}
		for i := wq.head; i < wq.size; i++ {
			wq.items[i] = nil
		}
	}
	head := (index + 1) % wq.size
	wq.head = head
	if len(wq.expiry) > 0 {
		wq.isFull = false
	}

	return wq.expiry
}

func (wq *loopQueue) binarySearch(expiryTime int64) int {
	var mid, nlen, basel, tmid int
	nlen = len(wq.items)

	// if no need to remove work, return -1
	if wq.isEmpty() || expiryTime < wq.items[wq.head].lastUsedTime() {
		return -1
	}

	// example
	// size = 8, head = 7, tail = 4
	// [ 2, 3, 4, 5, nil, nil, nil,  1]  true position
	//   0  1  2  3    4   5     6   7
	//              tail          head
	//
	//   1  2  3  4  nil nil   nil   0   mapped position
	//            r                  l

	// base algorithm is a copy from worker_stack
	// map head and tail to effective left and right
	r := (wq.tail - 1 - wq.head + nlen) % nlen
	basel = wq.head
	l := 0
	for l <= r {
		mid = l + ((r - l) >> 1) // avoid overflow when computing mid
		// calculate true mid position from mapped mid position
		tmid = (mid + basel + nlen) % nlen
		if expiryTime < wq.items[tmid].lastUsedTime() {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
	// return true position from mapped position
	return (r + basel + nlen) % nlen
}

func (wq *loopQueue) reset() {
	if wq.isEmpty() {
		return
	}

retry:
	if w := wq.detach(); w != nil {
		w.finish()
		goto retry
	}
	wq.head = 0
	wq.tail = 0
}


================================================
FILE: worker_loop_queue_test.go
================================================
/*
 * Copyright (c) 2019. Ants Authors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestNewLoopQueue(t *testing.T) {
	size := 100
	q := newWorkerLoopQueue(size)
	require.EqualValues(t, 0, q.len(), "Len error")
	require.Equal(t, true, q.isEmpty(), "IsEmpty error")
	require.Nil(t, q.detach(), "Dequeue error")

	require.Nil(t, newWorkerLoopQueue(0))
}

func TestLoopQueue(t *testing.T) {
	size := 10
	q := newWorkerLoopQueue(size)

	for i := 0; i < 5; i++ {
		err := q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
		if err != nil {
			break
		}
	}
	require.EqualValues(t, 5, q.len(), "Len error")
	_ = q.detach()
	require.EqualValues(t, 4, q.len(), "Len error")

	time.Sleep(time.Second)

	for i := 0; i < 6; i++ {
		err := q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
		if err != nil {
			break
		}
	}
	require.EqualValues(t, 10, q.len(), "Len error")

	err := q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	require.Error(t, err, "Enqueue, error")

	q.refresh(time.Second)
	require.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len())
}

func TestRotatedQueueSearch(t *testing.T) {
	size := 10
	q := newWorkerLoopQueue(size)

	currTime := time.Now().UnixNano()

	// 1
	expiry1 := currTime
	currTime++
	_ = q.insert(&goWorker{lastUsed: currTime})

	require.EqualValues(t, 0, q.binarySearch(currTime), "index should be 0")
	require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")

	// 2
	currTime++
	expiry2 := currTime
	currTime++
	_ = q.insert(&goWorker{lastUsed: currTime})

	require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1")

	require.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0")

	require.EqualValues(t, 1, q.binarySearch(currTime), "index should be 1")

	// more
	for i := 0; i < 5; i++ {
		currTime++
		_ = q.insert(&goWorker{lastUsed: currTime})
	}

	currTime++
	expiry3 := currTime
	_ = q.insert(&goWorker{lastUsed: expiry3})

	var err error
	for err != errQueueIsFull {
		currTime++
		err = q.insert(&goWorker{lastUsed: currTime})
	}

	require.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7")

	// rotate
	for i := 0; i < 6; i++ {
		_ = q.detach()
	}

	currTime++
	expiry4 := currTime
	_ = q.insert(&goWorker{lastUsed: expiry4})

	for i := 0; i < 4; i++ {
		currTime++
		_ = q.insert(&goWorker{lastUsed: currTime})
	}
	//	head = 6, tail = 5, insert direction ->
	// [expiry4, time, time, time,  time, nil/tail,  time/head, time, time, time]
	require.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0")

	for i := 0; i < 3; i++ {
		_ = q.detach()
	}
	currTime++
	expiry5 := currTime
	_ = q.insert(&goWorker{lastUsed: expiry5})

	//	head = 6, tail = 5, insert direction ->
	// [expiry4, time, time, time,  time, expiry5,  nil/tail, nil, nil, time/head]
	require.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5")

	for i := 0; i < 3; i++ {
		currTime++
		_ = q.insert(&goWorker{lastUsed: currTime})
	}
	//	head = 9, tail = 9, insert direction ->
	// [expiry4, time, time, time,  time, expiry5,  time, time, time, time/head/tail]
	require.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1")

	require.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9")
	require.EqualValues(t, 8, q.binarySearch(currTime), "index should be 8")
}

func TestRetrieveExpiry(t *testing.T) {
	size := 10
	q := newWorkerLoopQueue(size)
	expirew := make([]worker, 0)
	u, _ := time.ParseDuration("1s")

	// test [ time+1s, time+1s, time+1s, time+1s, time+1s, time, time, time, time, time]
	for i := 0; i < size/2; i++ {
		_ = q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	}
	expirew = append(expirew, q.items[:size/2]...)
	time.Sleep(u)

	for i := 0; i < size/2; i++ {
		_ = q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	}
	workers := q.refresh(u)

	require.EqualValues(t, expirew, workers, "expired workers aren't right")

	// test [ time, time, time, time, time, time+1s, time+1s, time+1s, time+1s, time+1s]
	time.Sleep(u)

	for i := 0; i < size/2; i++ {
		_ = q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	}
	expirew = expirew[:0]
	expirew = append(expirew, q.items[size/2:]...)

	workers2 := q.refresh(u)

	require.EqualValues(t, expirew, workers2, "expired workers aren't right")

	// test [ time+1s, time+1s, time+1s, nil, nil, time+1s, time+1s, time+1s, time+1s, time+1s]
	for i := 0; i < size/2; i++ {
		_ = q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	}
	for i := 0; i < size/2; i++ {
		_ = q.detach()
	}
	for i := 0; i < 3; i++ {
		_ = q.insert(&goWorker{lastUsed: time.Now().UnixNano()})
	}
	time.Sleep(u)

	expirew = expirew[:0]
	expirew = append(expirew, q.items[0:3]...)
	expirew = append(expirew, q.items[size/2:]...)

	workers3 := q.refresh(u)

	require.EqualValues(t, expirew, workers3, "expired workers aren't right")
}


================================================
FILE: worker_queue.go
================================================
/*
 * Copyright (c) 2019. Ants Authors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import (
	"errors"
	"time"
)

// errQueueIsFull will be returned when the worker queue is full.
var errQueueIsFull = errors.New("the queue is full")

type worker interface {
	run()
	finish()
	lastUsedTime() int64
	setLastUsedTime(t int64)
	inputFunc(func())
	inputArg(any)
}

type workerQueue interface {
	len() int
	isEmpty() bool
	insert(worker) error
	detach() worker
	refresh(duration time.Duration) []worker // clean up the stale workers and return them
	reset()
}

type queueType int

const (
	queueTypeStack queueType = 1 << iota
	queueTypeLoopQueue
)

func newWorkerQueue(qType queueType, size int) workerQueue {
	switch qType {
	case queueTypeStack:
		return newWorkerStack(size)
	case queueTypeLoopQueue:
		return newWorkerLoopQueue(size)
	default:
		return newWorkerStack(size)
	}
}


================================================
FILE: worker_stack.go
================================================
/*
 * Copyright (c) 2019. Ants Authors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import "time"

type workerStack struct {
	items  []worker
	expiry []worker
}

func newWorkerStack(size int) *workerStack {
	return &workerStack{
		items: make([]worker, 0, size),
	}
}

func (ws *workerStack) len() int {
	return len(ws.items)
}

func (ws *workerStack) isEmpty() bool {
	return len(ws.items) == 0
}

func (ws *workerStack) insert(w worker) error {
	ws.items = append(ws.items, w)
	return nil
}

func (ws *workerStack) detach() worker {
	l := ws.len()
	if l == 0 {
		return nil
	}

	w := ws.items[l-1]
	ws.items[l-1] = nil // avoid memory leaks
	ws.items = ws.items[:l-1]

	return w
}

func (ws *workerStack) refresh(duration time.Duration) []worker {
	n := ws.len()
	if n == 0 {
		return nil
	}

	expiryTime := time.Now().Add(-duration).UnixNano()
	index := ws.binarySearch(0, n-1, expiryTime)

	ws.expiry = ws.expiry[:0]
	if index != -1 {
		ws.expiry = append(ws.expiry, ws.items[:index+1]...)
		m := copy(ws.items, ws.items[index+1:])
		for i := m; i < n; i++ {
			ws.items[i] = nil
		}
		ws.items = ws.items[:m]
	}
	return ws.expiry
}

func (ws *workerStack) binarySearch(l, r int, expiryTime int64) int {
	for l <= r {
		mid := l + ((r - l) >> 1) // avoid overflow when computing mid
		if expiryTime < ws.items[mid].lastUsedTime() {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
	return r
}

func (ws *workerStack) reset() {
	for i := 0; i < ws.len(); i++ {
		ws.items[i].finish()
		ws.items[i] = nil
	}
	ws.items = ws.items[:0]
}


================================================
FILE: worker_stack_test.go
================================================
/*
 * Copyright (c) 2019. Ants Authors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package ants

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestNewWorkerStack(t *testing.T) {
	size := 100
	q := newWorkerStack(size)
	require.EqualValues(t, 0, q.len(), "Len error")
	require.Equal(t, true, q.isEmpty(), "IsEmpty error")
	require.Nil(t, q.detach(), "Dequeue error")
}

func TestWorkerStack(t *testing.T) {
	q := newWorkerQueue(queueType(-1), 0)

	for i := 0; i < 5; i++ {
		err := q.insert(&goWorker{lastUs
Download .txt
gitextract_dkc_uffz/

├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yaml
│   │   ├── feature-request.yaml
│   │   └── question.yaml
│   ├── pull_request_template.md
│   ├── release-drafter.yml
│   └── workflows/
│       ├── codeql.yml
│       ├── pull-request.yml
│       ├── release-drafter.yml
│       ├── stale-bot.yml
│       └── test.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── README_ZH.md
├── ants.go
├── ants_benchmark_test.go
├── ants_test.go
├── example_test.go
├── go.mod
├── go.sum
├── multipool.go
├── multipool_func.go
├── multipool_func_generic.go
├── options.go
├── pkg/
│   └── sync/
│       ├── spinlock.go
│       ├── spinlock_test.go
│       └── sync.go
├── pool.go
├── pool_func.go
├── pool_func_generic.go
├── worker.go
├── worker_func.go
├── worker_func_generic.go
├── worker_loop_queue.go
├── worker_loop_queue_test.go
├── worker_queue.go
├── worker_stack.go
└── worker_stack_test.go
Download .txt
SYMBOL INDEX (257 symbols across 21 files)

FILE: ants.go
  constant DefaultAntsPoolSize (line 48) | DefaultAntsPoolSize = math.MaxInt32
  constant DefaultCleanIntervalTime (line 51) | DefaultCleanIntervalTime = time.Second
  constant OPENED (line 56) | OPENED = iota
  constant CLOSED (line 59) | CLOSED
  function Submit (line 113) | func Submit(task func()) error {
  function Running (line 118) | func Running() int {
  function Cap (line 123) | func Cap() int {
  function Free (line 128) | func Free() int {
  function Release (line 133) | func Release() {
  function ReleaseTimeout (line 138) | func ReleaseTimeout(timeout time.Duration) error {
  function ReleaseContext (line 146) | func ReleaseContext(ctx context.Context) error {
  function Reboot (line 151) | func Reboot() {
  type Logger (line 156) | type Logger interface
  type poolCommon (line 162) | type poolCommon struct
    method purgeStaleWorkers (line 251) | func (p *poolCommon) purgeStaleWorkers() {
    method ticktock (line 295) | func (p *poolCommon) ticktock() {
    method goPurge (line 318) | func (p *poolCommon) goPurge() {
    method goTicktock (line 328) | func (p *poolCommon) goTicktock() {
    method nowTime (line 334) | func (p *poolCommon) nowTime() int64 {
    method Running (line 339) | func (p *poolCommon) Running() int {
    method Free (line 344) | func (p *poolCommon) Free() int {
    method Waiting (line 353) | func (p *poolCommon) Waiting() int {
    method Cap (line 358) | func (p *poolCommon) Cap() int {
    method Tune (line 363) | func (p *poolCommon) Tune(size int) {
    method IsClosed (line 379) | func (p *poolCommon) IsClosed() bool {
    method Release (line 384) | func (p *poolCommon) Release() {
    method ReleaseTimeout (line 408) | func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error {
    method ReleaseContext (line 422) | func (p *poolCommon) ReleaseContext(ctx context.Context) error {
    method Reboot (line 467) | func (p *poolCommon) Reboot() {
    method addRunning (line 478) | func (p *poolCommon) addRunning(delta int) int {
    method addWaiting (line 482) | func (p *poolCommon) addWaiting(delta int) {
    method retrieveWorker (line 487) | func (p *poolCommon) retrieveWorker() (w worker, err error) {
    method revertWorker (line 526) | func (p *poolCommon) revertWorker(worker worker) bool {
  function newPool (line 207) | func newPool(size int, options ...Option) (*poolCommon, error) {
  constant nowTimeUpdateInterval (line 292) | nowTimeUpdateInterval = 500 * time.Millisecond

FILE: ants_benchmark_test.go
  constant RunTimes (line 38) | RunTimes           = 1e6
  constant PoolCap (line 39) | PoolCap            = 5e4
  constant BenchParam (line 40) | BenchParam         = 10
  constant DefaultExpiredTime (line 41) | DefaultExpiredTime = 10 * time.Second
  function demoFunc (line 44) | func demoFunc() {
  function demoPoolFunc (line 48) | func demoPoolFunc(args any) {
  function demoPoolFuncInt (line 53) | func demoPoolFuncInt(n int) {
  function longRunningFunc (line 59) | func longRunningFunc() {
  function longRunningPoolFunc (line 65) | func longRunningPoolFunc(arg any) {
  function longRunningPoolFuncCh (line 69) | func longRunningPoolFuncCh(ch chan struct{}) {
  function BenchmarkGoroutines (line 73) | func BenchmarkGoroutines(b *testing.B) {
  function BenchmarkChannel (line 87) | func BenchmarkChannel(b *testing.B) {
  function BenchmarkErrGroup (line 106) | func BenchmarkErrGroup(b *testing.B) {
  function BenchmarkAntsPool (line 125) | func BenchmarkAntsPool(b *testing.B) {
  function BenchmarkAntsMultiPool (line 143) | func BenchmarkAntsMultiPool(b *testing.B) {
  function BenchmarkGoroutinesThroughput (line 161) | func BenchmarkGoroutinesThroughput(b *testing.B) {
  function BenchmarkSemaphoreThroughput (line 169) | func BenchmarkSemaphoreThroughput(b *testing.B) {
  function BenchmarkAntsPoolThroughput (line 182) | func BenchmarkAntsPoolThroughput(b *testing.B) {
  function BenchmarkAntsMultiPoolThroughput (line 194) | func BenchmarkAntsMultiPoolThroughput(b *testing.B) {
  function BenchmarkParallelAntsPoolThroughput (line 206) | func BenchmarkParallelAntsPoolThroughput(b *testing.B) {
  function BenchmarkParallelAntsMultiPoolThroughput (line 218) | func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) {

FILE: ants_test.go
  constant _ (line 41) | _   = 1 << (10 * iota)
  constant KiB (line 42) | KiB
  constant MiB (line 43) | MiB
  constant Param (line 47) | Param    = 100
  constant AntsSize (line 48) | AntsSize = 1000
  constant TestSize (line 49) | TestSize = 10000
  constant n (line 50) | n        = 100000
  function TestAntsPoolWaitToGetWorker (line 56) | func TestAntsPoolWaitToGetWorker(t *testing.T) {
  function TestAntsPoolWaitToGetWorkerPreMalloc (line 76) | func TestAntsPoolWaitToGetWorkerPreMalloc(t *testing.T) {
  function TestAntsPoolWithFuncWaitToGetWorker (line 97) | func TestAntsPoolWithFuncWaitToGetWorker(t *testing.T) {
  function TestAntsPoolWithFuncGenericWaitToGetWorker (line 118) | func TestAntsPoolWithFuncGenericWaitToGetWorker(t *testing.T) {
  function TestAntsPoolWithFuncWaitToGetWorkerPreMalloc (line 138) | func TestAntsPoolWithFuncWaitToGetWorkerPreMalloc(t *testing.T) {
  function TestAntsPoolWithFuncGenericWaitToGetWorkerPreMalloc (line 158) | func TestAntsPoolWithFuncGenericWaitToGetWorkerPreMalloc(t *testing.T) {
  function TestAntsPoolGetWorkerFromCache (line 179) | func TestAntsPoolGetWorkerFromCache(t *testing.T) {
  function TestAntsPoolWithFuncGetWorkerFromCache (line 196) | func TestAntsPoolWithFuncGetWorkerFromCache(t *testing.T) {
  function TestAntsPoolWithFuncGenericGetWorkerFromCache (line 214) | func TestAntsPoolWithFuncGenericGetWorkerFromCache(t *testing.T) {
  function TestAntsPoolWithFuncGetWorkerFromCachePreMalloc (line 231) | func TestAntsPoolWithFuncGetWorkerFromCachePreMalloc(t *testing.T) {
  function TestAntsPoolWithFuncGenericGetWorkerFromCachePreMalloc (line 248) | func TestAntsPoolWithFuncGenericGetWorkerFromCachePreMalloc(t *testing.T) {
  function TestNoPool (line 267) | func TestNoPool(t *testing.T) {
  function TestAntsPool (line 284) | func TestAntsPool(t *testing.T) {
  function TestPanicHandler (line 306) | func TestPanicHandler(t *testing.T) {
  function TestPanicHandlerPreMalloc (line 352) | func TestPanicHandlerPreMalloc(t *testing.T) {
  function TestPoolPanicWithoutHandler (line 398) | func TestPoolPanicWithoutHandler(t *testing.T) {
  function TestPoolPanicWithoutHandlerPreMalloc (line 417) | func TestPoolPanicWithoutHandlerPreMalloc(t *testing.T) {
  function TestPurgePool (line 440) | func TestPurgePool(t *testing.T) {
  function TestPurgePreMallocPool (line 503) | func TestPurgePreMallocPool(t *testing.T) {
  function TestNonblockingSubmit (line 526) | func TestNonblockingSubmit(t *testing.T) {
  function TestMaxBlockingSubmit (line 550) | func TestMaxBlockingSubmit(t *testing.T) {
  function TestNonblockingSubmitWithFunc (line 588) | func TestNonblockingSubmitWithFunc(t *testing.T) {
  function TestNonblockingSubmitWithFuncGeneric (line 614) | func TestNonblockingSubmitWithFuncGeneric(t *testing.T) {
  function TestMaxBlockingSubmitWithFunc (line 640) | func TestMaxBlockingSubmitWithFunc(t *testing.T) {
  function TestMaxBlockingSubmitWithFuncGeneric (line 675) | func TestMaxBlockingSubmitWithFuncGeneric(t *testing.T) {
  function TestRebootDefaultPool (line 710) | func TestRebootDefaultPool(t *testing.T) {
  function TestRebootNewPool (line 728) | func TestRebootNewPool(t *testing.T) {
  function TestInfinitePool (line 779) | func TestInfinitePool(t *testing.T) {
  function testPoolWithDisablePurge (line 803) | func testPoolWithDisablePurge(t *testing.T, p *ants.Pool, numWorker int,...
  function TestWithDisablePurgePool (line 842) | func TestWithDisablePurgePool(t *testing.T) {
  function TestWithDisablePurgeAndWithExpirationPool (line 848) | func TestWithDisablePurgeAndWithExpirationPool(t *testing.T) {
  function testPoolFuncWithDisablePurge (line 855) | func testPoolFuncWithDisablePurge(t *testing.T, p *ants.PoolWithFunc, nu...
  function TestWithDisablePurgePoolFunc (line 886) | func TestWithDisablePurgePoolFunc(t *testing.T) {
  function TestWithDisablePurgeAndWithExpirationPoolFunc (line 900) | func TestWithDisablePurgeAndWithExpirationPoolFunc(t *testing.T) {
  function TestInfinitePoolWithFunc (line 915) | func TestInfinitePoolWithFunc(t *testing.T) {
  function TestInfinitePoolWithFuncGeneric (line 941) | func TestInfinitePoolWithFuncGeneric(t *testing.T) {
  function TestReleaseWhenRunningPool (line 967) | func TestReleaseWhenRunningPool(t *testing.T) {
  function TestReleaseWhenRunningPoolWithFunc (line 1008) | func TestReleaseWhenRunningPoolWithFunc(t *testing.T) {
  function TestReleaseWhenRunningPoolWithFuncGeneric (line 1045) | func TestReleaseWhenRunningPoolWithFuncGeneric(t *testing.T) {
  function TestRestCodeCoverage (line 1082) | func TestRestCodeCoverage(t *testing.T) {
  function TestPoolTuneScaleUp (line 1205) | func TestPoolTuneScaleUp(t *testing.T) {
  function TestReleaseTimeout (line 1307) | func TestReleaseTimeout(t *testing.T) {
  function TestDefaultPoolReleaseTimeout (line 1343) | func TestDefaultPoolReleaseTimeout(t *testing.T) {
  function TestDefaultPoolReleaseContext (line 1355) | func TestDefaultPoolReleaseContext(t *testing.T) {
  function TestReleaseContextWithNil (line 1367) | func TestReleaseContextWithNil(t *testing.T) {
  function TestMultiPool (line 1383) | func TestMultiPool(t *testing.T) {
  function TestMultiPoolWithFunc (line 1444) | func TestMultiPoolWithFunc(t *testing.T) {
  function TestMultiPoolWithFuncGeneric (line 1506) | func TestMultiPoolWithFuncGeneric(t *testing.T) {
  function TestMultiPoolReleaseContext (line 1568) | func TestMultiPoolReleaseContext(t *testing.T) {
  function TestMultiPoolWithFuncReleaseContext (line 1647) | func TestMultiPoolWithFuncReleaseContext(t *testing.T) {
  function TestMultiPoolWithFuncGenericReleaseContext (line 1699) | func TestMultiPoolWithFuncGenericReleaseContext(t *testing.T) {
  function TestRebootNewPoolCalc (line 1751) | func TestRebootNewPoolCalc(t *testing.T) {
  function TestRebootNewPoolWithPreAllocCalc (line 1785) | func TestRebootNewPoolWithPreAllocCalc(t *testing.T) {

FILE: example_test.go
  function incSum (line 39) | func incSum(i any) {
  function incSumInt (line 43) | func incSumInt(i int32) {
  function ExamplePool (line 48) | func ExamplePool() {
  function ExamplePoolWithFunc (line 83) | func ExamplePoolWithFunc() {
  function ExamplePoolWithFuncGeneric (line 101) | func ExamplePoolWithFuncGeneric() {
  function ExampleMultiPool (line 119) | func ExampleMultiPool() {
  function ExampleMultiPoolWithFunc (line 140) | func ExampleMultiPoolWithFunc() {
  function ExampleMultiPoolWithFuncGeneric (line 158) | func ExampleMultiPoolWithFuncGeneric() {

FILE: multipool.go
  type LoadBalancingStrategy (line 38) | type LoadBalancingStrategy
  constant RoundRobin (line 42) | RoundRobin LoadBalancingStrategy = 1 << (iota + 1)
  constant LeastTasks (line 45) | LeastTasks
  type contextReleaser (line 48) | type contextReleaser interface
  function releasePools (line 52) | func releasePools(ctx context.Context, pools []contextReleaser) error {
  type MultiPool (line 90) | type MultiPool struct
    method next (line 118) | func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) {
    method Submit (line 136) | func (mp *MultiPool) Submit(task func()) (err error) {
    method Running (line 150) | func (mp *MultiPool) Running() (n int) {
    method RunningByIndex (line 158) | func (mp *MultiPool) RunningByIndex(idx int) (int, error) {
    method Free (line 166) | func (mp *MultiPool) Free() (n int) {
    method FreeByIndex (line 174) | func (mp *MultiPool) FreeByIndex(idx int) (int, error) {
    method Waiting (line 182) | func (mp *MultiPool) Waiting() (n int) {
    method WaitingByIndex (line 190) | func (mp *MultiPool) WaitingByIndex(idx int) (int, error) {
    method Cap (line 198) | func (mp *MultiPool) Cap() (n int) {
    method Tune (line 209) | func (mp *MultiPool) Tune(size int) {
    method IsClosed (line 216) | func (mp *MultiPool) IsClosed() bool {
    method ReleaseTimeout (line 222) | func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error {
    method ReleaseContext (line 230) | func (mp *MultiPool) ReleaseContext(ctx context.Context) error {
    method Reboot (line 243) | func (mp *MultiPool) Reboot() {
  function NewMultiPool (line 99) | func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, opti...

FILE: multipool_func.go
  type MultiPoolWithFunc (line 37) | type MultiPoolWithFunc struct
    method next (line 65) | func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) {
    method Invoke (line 83) | func (mp *MultiPoolWithFunc) Invoke(args any) (err error) {
    method Running (line 98) | func (mp *MultiPoolWithFunc) Running() (n int) {
    method RunningByIndex (line 106) | func (mp *MultiPoolWithFunc) RunningByIndex(idx int) (int, error) {
    method Free (line 114) | func (mp *MultiPoolWithFunc) Free() (n int) {
    method FreeByIndex (line 122) | func (mp *MultiPoolWithFunc) FreeByIndex(idx int) (int, error) {
    method Waiting (line 130) | func (mp *MultiPoolWithFunc) Waiting() (n int) {
    method WaitingByIndex (line 138) | func (mp *MultiPoolWithFunc) WaitingByIndex(idx int) (int, error) {
    method Cap (line 146) | func (mp *MultiPoolWithFunc) Cap() (n int) {
    method Tune (line 157) | func (mp *MultiPoolWithFunc) Tune(size int) {
    method IsClosed (line 164) | func (mp *MultiPoolWithFunc) IsClosed() bool {
    method ReleaseTimeout (line 170) | func (mp *MultiPoolWithFunc) ReleaseTimeout(timeout time.Duration) err...
    method ReleaseContext (line 178) | func (mp *MultiPoolWithFunc) ReleaseContext(ctx context.Context) error {
    method Reboot (line 191) | func (mp *MultiPoolWithFunc) Reboot() {
  function NewMultiPoolWithFunc (line 46) | func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadB...

FILE: multipool_func_generic.go
  type MultiPoolWithFuncGeneric (line 33) | type MultiPoolWithFuncGeneric struct
  function NewMultiPoolWithFuncGeneric (line 42) | func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T...
  method next (line 61) | func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (...
  method Invoke (line 79) | func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) {
  method Running (line 94) | func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) {
  method RunningByIndex (line 102) | func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, err...
  method Free (line 110) | func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) {
  method FreeByIndex (line 118) | func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) {
  method Waiting (line 126) | func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) {
  method WaitingByIndex (line 134) | func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, err...
  method Cap (line 142) | func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) {
  method Tune (line 153) | func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) {
  method IsClosed (line 160) | func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool {
  method ReleaseTimeout (line 166) | func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Durat...
  method ReleaseContext (line 174) | func (mp *MultiPoolWithFuncGeneric[T]) ReleaseContext(ctx context.Contex...
  method Reboot (line 187) | func (mp *MultiPoolWithFuncGeneric[T]) Reboot() {

FILE: options.go
  type Option (line 28) | type Option
  function loadOptions (line 30) | func loadOptions(options ...Option) *Options {
  type Options (line 39) | type Options struct
  function WithOptions (line 72) | func WithOptions(options Options) Option {
  function WithExpiryDuration (line 79) | func WithExpiryDuration(expiryDuration time.Duration) Option {
  function WithPreAlloc (line 86) | func WithPreAlloc(preAlloc bool) Option {
  function WithMaxBlockingTasks (line 93) | func WithMaxBlockingTasks(maxBlockingTasks int) Option {
  function WithNonblocking (line 100) | func WithNonblocking(nonblocking bool) Option {
  function WithPanicHandler (line 107) | func WithPanicHandler(panicHandler func(any)) Option {
  function WithLogger (line 114) | func WithLogger(logger Logger) Option {
  function WithDisablePurge (line 121) | func WithDisablePurge(disable bool) Option {

FILE: pkg/sync/spinlock.go
  type spinLock (line 13) | type spinLock
    method Lock (line 17) | func (sl *spinLock) Lock() {
    method Unlock (line 30) | func (sl *spinLock) Unlock() {
  constant maxBackoff (line 15) | maxBackoff = 16
  function NewSpinLock (line 35) | func NewSpinLock() sync.Locker {

FILE: pkg/sync/spinlock_test.go
  type originSpinLock (line 24) | type originSpinLock
    method Lock (line 26) | func (sl *originSpinLock) Lock() {
    method Unlock (line 32) | func (sl *originSpinLock) Unlock() {
  function NewOriginSpinLock (line 36) | func NewOriginSpinLock() sync.Locker {
  function BenchmarkMutex (line 40) | func BenchmarkMutex(b *testing.B) {
  function BenchmarkSpinLock (line 51) | func BenchmarkSpinLock(b *testing.B) {
  function BenchmarkBackOffSpinLock (line 62) | func BenchmarkBackOffSpinLock(b *testing.B) {

FILE: pool.go
  type Pool (line 27) | type Pool struct
    method Submit (line 37) | func (p *Pool) Submit(task func()) error {
  function NewPool (line 50) | func NewPool(size int, options ...Option) (*Pool, error) {

FILE: pool_func.go
  type PoolWithFunc (line 26) | type PoolWithFunc struct
    method Invoke (line 39) | func (p *PoolWithFunc) Invoke(arg any) error {
  function NewPoolWithFunc (line 52) | func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWi...

FILE: pool_func_generic.go
  type PoolWithFuncGeneric (line 26) | type PoolWithFuncGeneric struct
  method Invoke (line 34) | func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error {
  function NewPoolWithFuncGeneric (line 47) | func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Opti...

FILE: worker.go
  type goWorker (line 32) | type goWorker struct
    method run (line 47) | func (w *goWorker) run() {
    method finish (line 80) | func (w *goWorker) finish() {
    method lastUsedTime (line 84) | func (w *goWorker) lastUsedTime() int64 {
    method setLastUsedTime (line 88) | func (w *goWorker) setLastUsedTime(t int64) {
    method inputFunc (line 92) | func (w *goWorker) inputFunc(fn func()) {

FILE: worker_func.go
  type goWorkerWithFunc (line 32) | type goWorkerWithFunc struct
    method run (line 47) | func (w *goWorkerWithFunc) run() {
    method finish (line 80) | func (w *goWorkerWithFunc) finish() {
    method lastUsedTime (line 84) | func (w *goWorkerWithFunc) lastUsedTime() int64 {
    method setLastUsedTime (line 88) | func (w *goWorkerWithFunc) setLastUsedTime(t int64) {
    method inputArg (line 92) | func (w *goWorkerWithFunc) inputArg(arg any) {

FILE: worker_func_generic.go
  type goWorkerWithFuncGeneric (line 32) | type goWorkerWithFuncGeneric struct
  method run (line 50) | func (w *goWorkerWithFuncGeneric[T]) run() {
  method finish (line 85) | func (w *goWorkerWithFuncGeneric[T]) finish() {
  method lastUsedTime (line 89) | func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() int64 {
  method setLastUsedTime (line 93) | func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t int64) {

FILE: worker_loop_queue.go
  type loopQueue (line 27) | type loopQueue struct
    method len (line 46) | func (wq *loopQueue) len() int {
    method isEmpty (line 62) | func (wq *loopQueue) isEmpty() bool {
    method insert (line 66) | func (wq *loopQueue) insert(w worker) error {
    method detach (line 80) | func (wq *loopQueue) detach() worker {
    method refresh (line 94) | func (wq *loopQueue) refresh(duration time.Duration) []worker {
    method binarySearch (line 126) | func (wq *loopQueue) binarySearch(expiryTime int64) int {
    method reset (line 163) | func (wq *loopQueue) reset() {
  function newWorkerLoopQueue (line 36) | func newWorkerLoopQueue(size int) *loopQueue {

FILE: worker_loop_queue_test.go
  function TestNewLoopQueue (line 32) | func TestNewLoopQueue(t *testing.T) {
  function TestLoopQueue (line 42) | func TestLoopQueue(t *testing.T) {
  function TestRotatedQueueSearch (line 73) | func TestRotatedQueueSearch(t *testing.T) {
  function TestRetrieveExpiry (line 157) | func TestRetrieveExpiry(t *testing.T) {

FILE: worker_queue.go
  type worker (line 33) | type worker interface
  type workerQueue (line 42) | type workerQueue interface
  type queueType (line 51) | type queueType
  constant queueTypeStack (line 54) | queueTypeStack queueType = 1 << iota
  constant queueTypeLoopQueue (line 55) | queueTypeLoopQueue
  function newWorkerQueue (line 58) | func newWorkerQueue(qType queueType, size int) workerQueue {

FILE: worker_stack.go
  type workerStack (line 27) | type workerStack struct
    method len (line 38) | func (ws *workerStack) len() int {
    method isEmpty (line 42) | func (ws *workerStack) isEmpty() bool {
    method insert (line 46) | func (ws *workerStack) insert(w worker) error {
    method detach (line 51) | func (ws *workerStack) detach() worker {
    method refresh (line 64) | func (ws *workerStack) refresh(duration time.Duration) []worker {
    method binarySearch (line 85) | func (ws *workerStack) binarySearch(l, r int, expiryTime int64) int {
    method reset (line 97) | func (ws *workerStack) reset() {
  function newWorkerStack (line 32) | func newWorkerStack(size int) *workerStack {

FILE: worker_stack_test.go
  function TestNewWorkerStack (line 32) | func TestNewWorkerStack(t *testing.T) {
  function TestWorkerStack (line 40) | func TestWorkerStack(t *testing.T) {
  function TestSearch (line 73) | func TestSearch(t *testing.T) {
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (211K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 575,
    "preview": "# These are supported funding model platforms\n\ngithub: [panjf2000]\npatreon: panjf2000\nopen_collective: panjf2000\nko_fi: "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yaml",
    "chars": 4037,
    "preview": "name: Bug Report\ndescription: Oops!..., it's a bug.\ntitle: \"[Bug]: \"\nlabels: [\"bug\"]\nassignees:\n  - panjf2000\nbody:\n  - "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yaml",
    "chars": 2391,
    "preview": "name: Feature Request\ndescription: Propose an idea to make ants even better.\ntitle: \"[Feature]: \"\nlabels: [\"proposal\", \""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.yaml",
    "chars": 1028,
    "preview": "name: Question\ndescription: Ask questions about ants.\ntitle: \"[Question]: \"\nlabels: [\"question\", \"help wanted\"]\nbody:\n  "
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 1531,
    "preview": "<!--\nThank you for contributing to `ants`! Please fill this out to help us review your pull request more efficiently.\n\nW"
  },
  {
    "path": ".github/release-drafter.yml",
    "chars": 2075,
    "preview": "name-template: Ants v$RESOLVED_VERSION\ntag-template: v$RESOLVED_VERSION\ncategories:\n    - title: 🧨 Breaking changes\n    "
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 2039,
    "preview": "name: \"Code scanning\"\n\non:\n  push:\n    branches:\n      - master\n      - dev\n    paths-ignore:\n      - '**.md'\n      - '*"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "chars": 446,
    "preview": "name: Check pull request target\non:\n  pull_request:\n    types:\n      - opened\n      - reopened\n      - synchronize\n    b"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "chars": 1403,
    "preview": "name: Release Drafter\n\non:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n    "
  },
  {
    "path": ".github/workflows/stale-bot.yml",
    "chars": 1997,
    "preview": "name: Monitor inactive issues and PRs\non:\n  schedule:\n    - cron: '0 0 * * *'\n  workflow_dispatch:\n\njobs:\n  stale-issues"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 2436,
    "preview": "name: Run tests\n\non:\n  push:\n    branches:\n      - master\n      - dev\n    paths-ignore:\n      - '**.md'\n      - '**.yml'"
  },
  {
    "path": ".gitignore",
    "chars": 287,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3351,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 815,
    "preview": "# Contributing \n\n## With issues:\n  - Use the search tool before opening a new issue.\n  - Please provide source code and "
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2018 Andy Pan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "README.md",
    "chars": 18004,
    "preview": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png\" />\n<b>A goroutine p"
  },
  {
    "path": "README_ZH.md",
    "chars": 14995,
    "preview": "<p align=\"center\">\n<img src=\"https://raw.githubusercontent.com/panjf2000/logos/master/ants/logo.png\" />\n<b>Go 语言的 gorout"
  },
  {
    "path": "ants.go",
    "chars": 15639,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "ants_benchmark_test.go",
    "chars": 5051,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "ants_test.go",
    "chars": 53268,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "example_test.go",
    "chars": 3872,
    "preview": "/*\n * Copyright (c) 2025. Andy Pan. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "go.mod",
    "chars": 269,
    "preview": "module github.com/panjf2000/ants/v2\n\ngo 1.19\n\nrequire (\n\tgithub.com/stretchr/testify v1.10.0\n\tgolang.org/x/sync v0.11.0\n"
  },
  {
    "path": "go.sum",
    "chars": 1038,
    "preview": "github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.m"
  },
  {
    "path": "multipool.go",
    "chars": 6876,
    "preview": "// MIT License\n\n// Copyright (c) 2023 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "multipool_func.go",
    "chars": 5951,
    "preview": "// MIT License\n\n// Copyright (c) 2023 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "multipool_func_generic.go",
    "chars": 5898,
    "preview": "// MIT License\n\n// Copyright (c) 2025 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "options.go",
    "chars": 4192,
    "preview": "/*\n * Copyright (c) 2018. Andy Pan. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "pkg/sync/spinlock.go",
    "chars": 757,
    "preview": "// Copyright 2019 Andy Pan & Dietoad. All rights reserved.\n// Use of this source code is governed by an MIT-style\n// lic"
  },
  {
    "path": "pkg/sync/spinlock_test.go",
    "chars": 1492,
    "preview": "// Copyright 2021 Andy Pan & Dietoad. All rights reserved.\n// Use of this source code is governed by an MIT-style\n// lic"
  },
  {
    "path": "pkg/sync/sync.go",
    "chars": 1294,
    "preview": "/*\n * Copyright (c) 2025. Andy Pan. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "pool.go",
    "chars": 2193,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "pool_func.go",
    "chars": 2383,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "pool_func_generic.go",
    "chars": 2183,
    "preview": "// MIT License\n\n// Copyright (c) 2025 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "worker.go",
    "chars": 2528,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "worker_func.go",
    "chars": 2603,
    "preview": "// MIT License\n\n// Copyright (c) 2018 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "worker_func_generic.go",
    "chars": 2681,
    "preview": "// MIT License\n\n// Copyright (c) 2025 Andy Pan\n\n// Permission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "worker_loop_queue.go",
    "chars": 4063,
    "preview": "/*\n * Copyright (c) 2019. Ants Authors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any "
  },
  {
    "path": "worker_loop_queue_test.go",
    "chars": 5965,
    "preview": "/*\n * Copyright (c) 2019. Ants Authors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any "
  },
  {
    "path": "worker_queue.go",
    "chars": 1949,
    "preview": "/*\n * Copyright (c) 2019. Ants Authors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any "
  },
  {
    "path": "worker_stack.go",
    "chars": 2607,
    "preview": "/*\n * Copyright (c) 2019. Ants Authors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any "
  },
  {
    "path": "worker_stack_test.go",
    "chars": 3366,
    "preview": "/*\n * Copyright (c) 2019. Ants Authors. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any "
  }
]

About this extraction

This page contains the full source code of the panjf2000/ants GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (192.0 KB), approximately 56.1k tokens, and a symbol index with 257 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!