├── .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 /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [panjf2000] 4 | patreon: panjf2000 5 | open_collective: panjf2000 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Oops!..., it's a bug. 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: 6 | - panjf2000 7 | body: 8 | - type: markdown 9 | id: tips 10 | attributes: 11 | value: | 12 | ## Before you go any further 13 | - Please read [*How To Ask Questions The Smart Way*](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. 14 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc). 15 | - type: checkboxes 16 | id: checklist 17 | attributes: 18 | label: Actions I've taken before I'm here 19 | description: Make sure you have tried the following ways but still the problem has not been solved. 20 | options: 21 | - label: I've thoroughly read the documentations on this issue but still have no clue. 22 | required: true 23 | - label: I've searched the current list of Github issues but didn't find any duplicate issues that have been solved. 24 | required: true 25 | - label: I've searched the internet with this issue, but haven't found anything helpful. 26 | required: true 27 | validations: 28 | required: true 29 | - type: textarea 30 | id: bug-report 31 | attributes: 32 | label: What happened? 33 | description: Describe (and illustrate) the bug that you encountered precisely. 34 | placeholder: Please describe what happened and how it happened, the more details you provide, the faster the bug gets fixed. 35 | validations: 36 | required: true 37 | - type: dropdown 38 | id: major-version 39 | attributes: 40 | label: Major version of ants 41 | description: What major version of ants are you running? 42 | options: 43 | - v2 44 | - v1 45 | validations: 46 | required: true 47 | - type: input 48 | id: specific-version 49 | attributes: 50 | label: Specific version of ants 51 | description: What's the specific version of ants? 52 | placeholder: "For example: v2.6.0" 53 | validations: 54 | required: true 55 | - type: dropdown 56 | id: os 57 | attributes: 58 | label: Operating system 59 | multiple: true 60 | options: 61 | - Linux 62 | - macOS 63 | - Windows 64 | - BSD 65 | validations: 66 | required: true 67 | - type: input 68 | id: os-version 69 | attributes: 70 | label: OS version 71 | description: What's the specific version of OS? 72 | placeholder: "Run `uname -srm` command to get the info, for example: Darwin 21.5.0 arm64, Linux 5.4.0-137-generic x86_64" 73 | validations: 74 | required: true 75 | - type: input 76 | id: go-version 77 | attributes: 78 | label: Go version 79 | description: What's the specific version of Go? 80 | placeholder: "Run `go version` command to get the info, for example: go1.20.5 linux/amd64" 81 | validations: 82 | required: true 83 | - type: textarea 84 | id: logs 85 | attributes: 86 | label: Relevant log output 87 | description: Please copy and paste any relevant log output. 88 | render: shell 89 | validations: 90 | required: true 91 | - type: textarea 92 | id: code 93 | attributes: 94 | label: Reproducer 95 | description: Please provide the minimal code to reproduce the bug. 96 | render: go 97 | - type: textarea 98 | id: how-to-reproduce 99 | attributes: 100 | label: How to Reproduce 101 | description: Steps to reproduce the result. 102 | placeholder: Tell us step by step how we can replicate this bug and what we should see in the end. 103 | value: | 104 | Steps to reproduce the behavior: 105 | 1. Go to '....' 106 | 2. Click on '....' 107 | 3. Do '....' 108 | 4. See '....' 109 | validations: 110 | required: true 111 | - type: dropdown 112 | id: bug-in-latest-code 113 | attributes: 114 | label: Does this issue reproduce with the latest release? 115 | description: Is this bug still present in the latest version? 116 | options: 117 | - It can reproduce with the latest release 118 | - It gets fixed in the latest release 119 | - I haven't verified it with the latest release 120 | validations: 121 | required: true 122 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Propose an idea to make ants even better. 3 | title: "[Feature]: " 4 | labels: ["proposal", "enhancement"] 5 | assignees: 6 | - panjf2000 7 | body: 8 | - type: markdown 9 | id: tips 10 | attributes: 11 | value: | 12 | ## Before you go any further 13 | - Please read [*How To Ask Questions The Smart Way*](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. 14 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc). 15 | - type: textarea 16 | id: feature-request 17 | attributes: 18 | label: Description of new feature 19 | description: Make a concise but clear description about this new feature. 20 | placeholder: Describe this new feature with critical details. 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: feature-scenario 25 | attributes: 26 | label: Scenarios for new feature 27 | description: Explain why you need this feature and what scenarios can benefit from it. 28 | placeholder: Please try to convince us that this new feature makes sense, also it will improve ants. 29 | validations: 30 | required: true 31 | - type: dropdown 32 | id: breaking-changes 33 | attributes: 34 | label: Breaking changes or not? 35 | description: Is this new feature going to break the backward compatibility of the existing APIs? 36 | options: 37 | - "Yes" 38 | - "No" 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: code 43 | attributes: 44 | label: Code snippets (optional) 45 | description: Illustrate this new feature with source code, what it looks like in code. 46 | render: go 47 | - type: textarea 48 | id: feature-alternative 49 | attributes: 50 | label: Alternatives for new feature 51 | description: Alternatives in your mind in case that the feature can't be done for some reasons. 52 | placeholder: A concise but clear description of any alternative solutions or features you had in mind. 53 | value: None. 54 | - type: textarea 55 | id: additional-context 56 | attributes: 57 | label: Additional context (optional) 58 | description: Any additional context about this new feature we should know. 59 | placeholder: What else should we know before we start discussing this new feature? 60 | value: None. 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yaml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask questions about ants. 3 | title: "[Question]: " 4 | labels: ["question", "help wanted"] 5 | body: 6 | - type: markdown 7 | id: tips 8 | attributes: 9 | value: | 10 | ## Before you go any further 11 | - Please read [*How To Ask Questions The Smart Way*](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. 12 | - Keep in mind that there is always welcome to ask questions on [Discord](https://discord.gg/Cuy7KPaWQc). 13 | - type: textarea 14 | id: question 15 | attributes: 16 | label: Questions with details 17 | description: What do you want to know? 18 | placeholder: Describe your question with critical details here. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: code 23 | attributes: 24 | label: Code snippets (optional) 25 | description: Illustrate your question with source code if needed. 26 | render: go 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## 1. Are you opening this pull request for bug-fixs, optimizations or new feature? 10 | 11 | 12 | 13 | ## 2. Please describe how these code changes achieve your intention. 14 | 15 | 16 | 17 | 18 | ## 3. Please link to the relevant issues (if any). 19 | 20 | 21 | 22 | 23 | ## 4. Which documentation changes (if any) need to be made/updated because of this PR? 24 | 25 | 26 | 27 | 28 | ## 4. Checklist 29 | 30 | - [ ] I have squashed all insignificant commits. 31 | - [ ] I have commented my code for explaining package types, values, functions, and non-obvious lines. 32 | - [ ] I have written unit tests and verified that all tests passes (if needed). 33 | - [ ] I have documented feature info on the README (only when this PR is adding a new feature). 34 | - [ ] (optional) I am willing to help maintain this change if there are issues with it later. 35 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: Ants v$RESOLVED_VERSION 2 | tag-template: v$RESOLVED_VERSION 3 | categories: 4 | - title: 🧨 Breaking changes 5 | labels: 6 | - breaking changes 7 | - title: 🚀 Features 8 | labels: 9 | - proposal 10 | - new feature 11 | - title: 🛩 Enhancements 12 | labels: 13 | - enhancement 14 | - optimization 15 | - title: 🐛 Bugfixes 16 | labels: 17 | - bug 18 | - title: 📚 Documentation 19 | labels: 20 | - doc 21 | - docs 22 | - title: 🗃 Misc 23 | labels: 24 | - chores 25 | change-template: '- $TITLE (#$NUMBER)' 26 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 27 | version-resolver: 28 | major: 29 | labels: 30 | - major 31 | minor: 32 | labels: 33 | - minor 34 | - new feature 35 | - proposal 36 | patch: 37 | labels: 38 | - patch 39 | - bug 40 | - dependencies 41 | default: patch 42 | autolabeler: 43 | - label: bug 44 | title: 45 | - /fix/i 46 | - /bug/i 47 | - /resolve/i 48 | - label: docs 49 | files: 50 | - '*.md' 51 | title: 52 | - /doc/i 53 | - /README/i 54 | - label: enhancement 55 | title: 56 | - /opt:/i 57 | - /refactor/i 58 | - /optimize/i 59 | - /improve/i 60 | - /update/i 61 | - /remove/i 62 | - /delete/i 63 | - label: optimization 64 | title: 65 | - /opt:/i 66 | - /refactor/i 67 | - /optimize/i 68 | - /improve/i 69 | - /update/i 70 | - /remove/i 71 | - /delete/i 72 | - label: new feature 73 | title: 74 | - /feat:/i 75 | - /feature/i 76 | - /implement/i 77 | - /add/i 78 | - /minor/i 79 | - label: dependencies 80 | title: 81 | - /dependencies/i 82 | - /upgrade/i 83 | - /bump up/i 84 | - label: chores 85 | title: 86 | - /chore/i 87 | - /misc/i 88 | - /cleanup/i 89 | - /clean up/i 90 | - label: major 91 | title: 92 | - /major:/i 93 | - label: minor 94 | title: 95 | - /minor:/i 96 | - label: patch 97 | title: 98 | - /patch:/i 99 | template: | 100 | ## Changelogs 101 | 102 | $CHANGES 103 | 104 | **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION 105 | 106 | Thanks to all these contributors: $CONTRIBUTORS for making this release possible. 107 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | paths-ignore: 9 | - '**.md' 10 | - '**.yml' 11 | - '**.yaml' 12 | - '!.github/workflows/codeql.yml' 13 | pull_request: 14 | branches: 15 | - master 16 | - dev 17 | paths-ignore: 18 | - '**.md' 19 | - '**.yml' 20 | - '**.yaml' 21 | - '!.github/workflows/codeql.yml' 22 | schedule: 23 | # ┌───────────── minute (0 - 59) 24 | # │ ┌───────────── hour (0 - 23) 25 | # │ │ ┌───────────── day of the month (1 - 31) 26 | # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) 27 | # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) 28 | # │ │ │ │ │ 29 | # │ │ │ │ │ 30 | # │ │ │ │ │ 31 | # * * * * * 32 | - cron: '30 4 * * 6' 33 | 34 | jobs: 35 | CodeQL-Build: 36 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 37 | runs-on: ubuntu-latest 38 | 39 | permissions: 40 | # required for all workflows 41 | security-events: write 42 | 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | with: 47 | fetch-depth: 2 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v3 52 | with: 53 | languages: go 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below). 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v3 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 64 | # three lines and modify them (or add more) to build your code if your 65 | # project uses a compiled language 66 | 67 | #- run: | 68 | # make bootstrap 69 | # make release 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v3 -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Check pull request target 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - reopened 7 | - synchronize 8 | branches: 9 | - master 10 | jobs: 11 | check-branches: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check target branch 15 | run: | 16 | if [ ${{ github.head_ref }} != "dev" ]; then 17 | echo "Only pull requests from dev branch are only allowed to be merged into master branch." 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | # pull_request_target event is required for autolabeler to support PRs from forks 13 | # pull_request_target: 14 | # types: [opened, reopened, synchronize] 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | update_release_draft: 21 | permissions: 22 | # write permission is required to create a github release 23 | contents: write 24 | # write permission is required for autolabeler 25 | # otherwise, read permission is required at least 26 | pull-requests: write 27 | runs-on: ubuntu-latest 28 | steps: 29 | # (Optional) GitHub Enterprise requires GHE_HOST variable set 30 | #- name: Set GHE_HOST 31 | # run: | 32 | # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV 33 | 34 | # Drafts your next Release notes as Pull Requests are merged into "master" 35 | - uses: release-drafter/release-drafter@v6 36 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 37 | # with: 38 | # config-name: my-config.yml 39 | # disable-autolabeler: true 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/stale-bot.yml: -------------------------------------------------------------------------------- 1 | name: Monitor inactive issues and PRs 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | workflow_dispatch: 6 | 7 | jobs: 8 | stale-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | actions: write 12 | issues: write 13 | pull-requests: write 14 | steps: 15 | - uses: actions/stale@v9 16 | with: 17 | operations-per-run: 50 18 | days-before-issue-stale: 30 19 | days-before-issue-close: 7 20 | stale-issue-label: 'stale' 21 | stale-issue-message: | 22 | This issue is marked as stale because it has been open for 30 days with no activity. 23 | 24 | You should take one of the following actions: 25 | - Manually close this issue if it is no longer relevant 26 | - Comment if you have more information to share 27 | 28 | This issue will be automatically closed in 7 days if no further activity occurs. 29 | close-issue-message: | 30 | This issue was closed because it has been inactive for 7 days since being marked as stale. 31 | 32 | 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. 33 | days-before-pr-stale: 21 34 | days-before-pr-close: 7 35 | stale-pr-label: 'stale' 36 | stale-pr-message: | 37 | This PR is marked as stale because it has been open for 21 days with no activity. 38 | 39 | You should take one of the following actions: 40 | - Manually close this PR if it is no longer relevant 41 | - Push new commits or comment if you have more information to share 42 | 43 | This PR will be automatically closed in 7 days if no further activity occurs. 44 | close-pr-message: | 45 | This PR was closed because it has been inactive for 7 days since being marked as stale. 46 | 47 | If you believe this is a false alarm, feel free to reopen this PR or create a new one. 48 | repo-token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | paths-ignore: 9 | - '**.md' 10 | - '**.yml' 11 | - '**.yaml' 12 | - 'examples/*' 13 | - '!.github/workflows/test.yml' 14 | pull_request: 15 | branches: 16 | - master 17 | - dev 18 | paths-ignore: 19 | - '**.md' 20 | - '**.yml' 21 | - '**.yaml' 22 | - 'examples/*' 23 | - '!.github/workflows/test.yml' 24 | 25 | env: 26 | GO111MODULE: on 27 | GOPROXY: "https://proxy.golang.org" 28 | 29 | jobs: 30 | lint: 31 | strategy: 32 | matrix: 33 | os: 34 | - ubuntu-latest 35 | - macos-latest 36 | name: Run golangci-lint 37 | runs-on: ${{ matrix.os }} 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | - name: Setup Go 43 | uses: actions/setup-go@v5 44 | with: 45 | go-version: '^1.20' 46 | cache: false 47 | 48 | - name: Setup and run golangci-lint 49 | uses: golangci/golangci-lint-action@v6 50 | with: 51 | version: v1.62.2 52 | args: --timeout 5m -v -E gofumpt -E gocritic -E misspell -E revive -E godot 53 | test: 54 | needs: lint 55 | strategy: 56 | fail-fast: false 57 | matrix: 58 | go: [1.18, 1.23] 59 | os: [ubuntu-latest, macos-latest, windows-latest] 60 | name: Go ${{ matrix.go }} @ ${{ matrix.os }} 61 | runs-on: ${{ matrix.os}} 62 | steps: 63 | - name: Checkout repository 64 | uses: actions/checkout@v4 65 | with: 66 | ref: ${{ github.ref }} 67 | 68 | - name: Setup Go 69 | uses: actions/setup-go@v5 70 | with: 71 | go-version: ${{ matrix.go }} 72 | 73 | - name: Print Go environment 74 | id: go-env 75 | run: | 76 | printf "Using go at: $(which go)\n" 77 | printf "Go version: $(go version)\n" 78 | printf "\n\nGo environment:\n\n" 79 | go env 80 | printf "\n\nSystem environment:\n\n" 81 | env 82 | # Calculate the short SHA1 hash of the git commit 83 | echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT 84 | echo "GO_CACHE=$(go env GOCACHE)" >> $GITHUB_OUTPUT 85 | 86 | - name: Run unit tests and integrated tests 87 | run: go test -v -race -coverprofile="codecov.report" -covermode=atomic 88 | 89 | - name: Upload code coverage report to Codecov 90 | uses: codecov/codecov-action@v5 91 | with: 92 | files: ./codecov.report 93 | flags: unittests 94 | name: codecov-ants 95 | fail_ci_if_error: true 96 | verbose: true 97 | env: 98 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at panjf2000@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## With issues: 4 | - Use the search tool before opening a new issue. 5 | - Please provide source code and commit sha if you found a bug. 6 | - Review existing issues and provide feedback or react to them. 7 | 8 | ## With pull requests: 9 | - Open your pull request against `dev`. 10 | - 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. 11 | - Your pull request should have no more than two commits, if not, you should squash them. 12 | - It should pass all tests in the available continuous integrations systems such as TravisCI. 13 | - You should add/modify tests to cover your proposed code changes. 14 | - If your pull request contains a new feature, please document it on the README. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andy Pan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | A goroutine pool for Go 4 |

5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 |

15 | 16 | English | [中文](README_ZH.md) 17 | 18 | ## 📖 Introduction 19 | 20 | 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. 21 | 22 | ## 🚀 Features: 23 | 24 | - Managing and recycling a massive number of goroutines automatically 25 | - Purging overdue goroutines periodically 26 | - Abundant APIs: submitting tasks, getting the number of running goroutines, tuning the capacity of the pool dynamically, releasing the pool, rebooting the pool, etc. 27 | - Handle panic gracefully to prevent programs from crash 28 | - Efficient in memory usage and it may even achieve ***higher performance*** than unlimited goroutines in Go 29 | - Nonblocking mechanism 30 | - Preallocated memory (ring buffer, optional) 31 | 32 | ## 💡 How `ants` works 33 | 34 | ### Flow Diagram 35 | 36 |

37 | ants-flowchart-en 38 |

39 | 40 | ### Activity Diagrams 41 | 42 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-1.png) 43 | 44 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-2.png) 45 | 46 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-3.png) 47 | 48 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-4.png) 49 | 50 | ## 🧰 How to install 51 | 52 | ### For `ants` v1 53 | 54 | ``` powershell 55 | go get -u github.com/panjf2000/ants 56 | ``` 57 | 58 | ### For `ants` v2 (with GO111MODULE=on) 59 | 60 | ```powershell 61 | go get -u github.com/panjf2000/ants/v2 62 | ``` 63 | 64 | ## 🛠 How to use 65 | Check out [the examples](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples) for basic usage. 66 | 67 | ### Functional options for pool 68 | 69 | `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. 70 | 71 | 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. 72 | 73 | ### Customize pool capacity 74 | 75 | `ants` supports customizing the capacity of the pool. You can call the `NewPool` method to instantiate a `Pool` with a given capacity, as follows: 76 | 77 | ``` go 78 | p, _ := ants.NewPool(10000) 79 | ``` 80 | 81 | ### Submit tasks 82 | Tasks can be submitted by calling `ants.Submit` 83 | ```go 84 | ants.Submit(func(){}) 85 | ``` 86 | 87 | ### Tune pool capacity at runtime 88 | You can tune the capacity of `ants` pool at runtime with `ants.Tune`: 89 | 90 | ``` go 91 | pool.Tune(1000) // Tune its capacity to 1000 92 | pool.Tune(100000) // Tune its capacity to 100000 93 | ``` 94 | 95 | Don't worry about the contention problems in this case, the method here is thread-safe (or should be called goroutine-safe). 96 | 97 | ### Pre-malloc goroutine queue in pool 98 | 99 | `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. 100 | 101 | ```go 102 | // ants will pre-malloc the whole capacity of pool when calling ants.NewPool. 103 | p, _ := ants.NewPool(100000, ants.WithPreAlloc(true)) 104 | ``` 105 | 106 | ### Release pool 107 | 108 | ```go 109 | pool.Release() 110 | ``` 111 | 112 | or 113 | 114 | ```go 115 | pool.ReleaseTimeout(time.Second * 3) 116 | ``` 117 | 118 | ### Reboot pool 119 | 120 | ```go 121 | // A pool that has been released can be still used after calling the Reboot(). 122 | pool.Reboot() 123 | ``` 124 | 125 | ## ⚙️ About sequence 126 | 127 | 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. 128 | 129 | ## 👏 Contributors 130 | 131 | Please read our [Contributing Guidelines](CONTRIBUTING.md) before opening a PR and thank you to all the developers who already made contributions to `ants`! 132 | 133 | 134 | 135 | 136 | 137 | ## 📄 License 138 | 139 | The source code in `ants` is available under the [MIT License](/LICENSE). 140 | 141 | ## 📚 Relevant Articles 142 | 143 | - [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool) 144 | - [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5) 145 | - [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool) 146 | - [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple) 147 | 148 | ## 🖥 Use cases 149 | 150 | ### business corporations 151 | 152 | Trusted by the following corporations/organizations. 153 | 154 | 155 | 156 | 157 | 162 | 167 | 172 | 177 | 178 | 179 | 184 | 189 | 194 | 199 | 200 | 201 | 206 | 211 | 216 | 221 | 222 | 223 | 228 | 233 | 238 | 243 | 244 | 245 | 250 | 255 | 256 | 261 | 262 | 263 |
158 | 159 | 160 | 161 | 163 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 173 | 174 | 175 | 176 |
180 | 181 | 182 | 183 | 185 | 186 | 187 | 188 | 190 | 191 | 192 | 193 | 195 | 196 | 197 | 198 |
202 | 203 | 204 | 205 | 207 | 208 | 209 | 210 | 212 | 213 | 214 | 215 | 217 | 218 | 219 | 220 |
224 | 225 | 226 | 227 | 229 | 230 | 231 | 232 | 234 | 235 | 236 | 237 | 239 | 240 | 241 | 242 |
246 | 247 | 248 | 249 | 251 | 252 | 253 | 254 | 257 | 258 | 259 | 260 |
264 | 265 | If you're also using `ants` in production, please help us enrich this list by opening a pull request. 266 | 267 | ### open-source software 268 | 269 | The open-source projects below do concurrent programming with the help of `ants`. 270 | 271 | - [gnet](https://github.com/panjf2000/gnet): A high-performance, lightweight, non-blocking, event-driven networking framework written in pure Go. 272 | - [milvus](https://github.com/milvus-io/milvus): An open-source vector database for scalable similarity search and AI applications. 273 | - [nps](https://github.com/ehang-io/nps): A lightweight, high-performance, powerful intranet penetration proxy server, with a powerful web management terminal. 274 | - [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. 275 | - [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. 276 | - [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security. 277 | - [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. 278 | - [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images. 279 | - [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection. 280 | - [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork. 281 | - [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing. 282 | - [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools. 283 | - [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. 284 | - [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. 285 | - [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. 286 | - [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. 287 | - [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control. 288 | - [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。 289 | - [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. 290 | - [trpc-go](https://github.com/trpc-group/trpc-go): A pluggable, high-performance RPC framework written in Golang. 291 | - [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. 292 | 293 | #### All use cases: 294 | 295 | - [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2) 296 | 297 | - [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw) 298 | 299 | If you have `ants` integrated into projects, feel free to open a pull request refreshing this list of use cases. 300 | 301 | ## 🔋 JetBrains OS licenses 302 | 303 | `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. 304 | 305 | JetBrains logo. 306 | 307 | ## 💰 Backers 308 | 309 | Support us with a monthly donation and help us continue our activities. 310 | 311 | 312 | 313 | ## 💎 Sponsors 314 | 315 | Become a bronze sponsor with a monthly donation of $10 and get your logo on our README on GitHub. 316 | 317 | 318 | 319 | ## ☕️ Buy me a coffee 320 | 321 | > 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. 322 | 323 |    324 |    325 |    326 | 327 | ## 🔋 Sponsorship 328 | 329 |

330 | 331 | 332 | 333 |

334 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Go 语言的 goroutine 池 4 |

5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 |

15 | 16 | [英文](README.md) | 中文 17 | 18 | ## 📖 简介 19 | 20 | `ants` 是一个高性能的 goroutine 池,实现了对大规模 goroutine 的调度管理、goroutine 复用,允许使用者在开发并发程序的时候限制 goroutine 数量,复用资源,达到更高效执行任务的效果。 21 | 22 | ## 🚀 功能: 23 | 24 | - 自动调度海量的 goroutines,复用 goroutines 25 | - 定期清理过期的 goroutines,进一步节省资源 26 | - 提供了大量实用的接口:任务提交、获取运行中的 goroutine 数量、动态调整 Pool 大小、释放 Pool、重启 Pool 等 27 | - 优雅处理 panic,防止程序崩溃 28 | - 资源复用,极大节省内存使用量;在大规模批量并发任务场景下甚至可能比 Go 语言的无限制 goroutine 并发具有***更高的性能*** 29 | - 非阻塞机制 30 | - 预分配内存 (环形队列,可选) 31 | 32 | ## 💡 `ants` 是如何运行的 33 | 34 | ### 流程图 35 | 36 |

37 | ants-flowchart-cn 38 |

39 | 40 | ### 动态图 41 | 42 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-1.png) 43 | 44 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-2.png) 45 | 46 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-3.png) 47 | 48 | ![](https://raw.githubusercontent.com/panjf2000/illustrations/master/go/ants-pool-4.png) 49 | 50 | ## 🧰 安装 51 | 52 | ### 使用 `ants` v1 版本: 53 | 54 | ``` powershell 55 | go get -u github.com/panjf2000/ants 56 | ``` 57 | 58 | ### 使用 `ants` v2 版本 (开启 GO111MODULE=on): 59 | 60 | ```powershell 61 | go get -u github.com/panjf2000/ants/v2 62 | ``` 63 | 64 | ## 🛠 使用 65 | 基本的使用请查看[示例](https://pkg.go.dev/github.com/panjf2000/ants/v2#pkg-examples). 66 | 67 | ### Pool 配置 68 | 69 | 通过在调用 `NewPool`/`NewPoolWithFunc`/`NewPoolWithFuncGeneric` 之时使用各种 optional function,可以设置 `ants.Options` 中各个配置项的值,然后用它来定制化 goroutine pool。 70 | 71 | 更多细节请查看 [ants.Options](https://pkg.go.dev/github.com/panjf2000/ants/v2#Options) 和 [ants.Option](https://pkg.go.dev/github.com/panjf2000/ants/v2#Option) 72 | 73 | 74 | ### 自定义 pool 容量 75 | `ants` 支持实例化使用者自己的一个 Pool,指定具体的 pool 容量;通过调用 `NewPool` 方法可以实例化一个新的带有指定容量的 `Pool`,如下: 76 | 77 | ``` go 78 | p, _ := ants.NewPool(10000) 79 | ``` 80 | 81 | ### 任务提交 82 | 83 | 提交任务通过调用 `ants.Submit` 方法: 84 | ```go 85 | ants.Submit(func(){}) 86 | ``` 87 | 88 | ### 动态调整 goroutine 池容量 89 | 需要动态调整 pool 容量可以通过调用 `ants.Tune`: 90 | 91 | ``` go 92 | pool.Tune(1000) // Tune its capacity to 1000 93 | pool.Tune(100000) // Tune its capacity to 100000 94 | ``` 95 | 96 | 该方法是线程安全的。 97 | 98 | ### 预先分配 goroutine 队列内存 99 | 100 | `ants` 支持预先为 pool 分配容量的内存, 这个功能可以在某些特定的场景下提高 goroutine 池的性能。比如, 有一个场景需要一个超大容量的池,而且每个 goroutine 里面的任务都是耗时任务,这种情况下,预先分配 goroutine 队列内存将会减少不必要的内存重新分配。 101 | 102 | ```go 103 | // 提前分配的 pool 容量的内存空间 104 | p, _ := ants.NewPool(100000, ants.WithPreAlloc(true)) 105 | ``` 106 | 107 | ### 释放 Pool 108 | 109 | ```go 110 | pool.Release() 111 | ``` 112 | 113 | 或者 114 | 115 | ```go 116 | pool.ReleaseTimeout(time.Second * 3) 117 | ``` 118 | 119 | ### 重启 Pool 120 | 121 | ```go 122 | // 只要调用 Reboot() 方法,就可以重新激活一个之前已经被销毁掉的池,并且投入使用。 123 | pool.Reboot() 124 | ``` 125 | 126 | ## ⚙️ 关于任务执行顺序 127 | 128 | `ants` 并不保证提交的任务被执行的顺序,执行的顺序也不是和提交的顺序保持一致,因为在 `ants` 是并发地处理所有提交的任务,提交的任务会被分派到正在并发运行的 workers 上去,因此那些任务将会被并发且无序地被执行。 129 | 130 | ## 👏 贡献者 131 | 132 | 请在提 PR 之前仔细阅读 [Contributing Guidelines](CONTRIBUTING.md),感谢那些为 `ants` 贡献过代码的开发者! 133 | 134 | 135 | 136 | 137 | 138 | ## 📄 证书 139 | 140 | `ants` 的源码允许用户在遵循 [MIT 开源证书](/LICENSE) 规则的前提下使用。 141 | 142 | ## 📚 相关文章 143 | 144 | - [Goroutine 并发调度模型深度解析之手撸一个高性能 goroutine 池](https://taohuawu.club/high-performance-implementation-of-goroutine-pool) 145 | - [Visually Understanding Worker Pool](https://medium.com/coinmonks/visually-understanding-worker-pool-48a83b7fc1f5) 146 | - [The Case For A Go Worker Pool](https://brandur.org/go-worker-pool) 147 | - [Go Concurrency - GoRoutines, Worker Pools and Throttling Made Simple](https://twin.sh/articles/39/go-concurrency-goroutines-worker-pools-and-throttling-made-simple) 148 | 149 | ## 🖥 用户案例 150 | 151 | ### 商业公司 152 | 153 | 以下公司/组织在生产环境上使用了 `ants`。 154 | 155 | 156 | 157 | 158 | 163 | 168 | 173 | 178 | 179 | 180 | 185 | 190 | 195 | 200 | 201 | 202 | 207 | 212 | 217 | 222 | 223 | 224 | 229 | 234 | 239 | 244 | 245 | 246 | 251 | 256 | 261 | 262 | 263 |
159 | 160 | 161 | 162 | 164 | 165 | 166 | 167 | 169 | 170 | 171 | 172 | 174 | 175 | 176 | 177 |
181 | 182 | 183 | 184 | 186 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 196 | 197 | 198 | 199 |
203 | 204 | 205 | 206 | 208 | 209 | 210 | 211 | 213 | 214 | 215 | 216 | 218 | 219 | 220 | 221 |
225 | 226 | 227 | 228 | 230 | 231 | 232 | 233 | 235 | 236 | 237 | 238 | 240 | 241 | 242 | 243 |
247 | 248 | 249 | 250 | 252 | 253 | 254 | 255 | 257 | 258 | 259 | 260 |
264 | 265 | 如果你也正在生产环境上使用 `ants`,欢迎提 PR 来丰富这份列表。 266 | 267 | ### 开源软件 268 | 269 | 这些开源项目借助 `ants` 进行并发编程。 270 | 271 | - [gnet](https://github.com/panjf2000/gnet): gnet 是一个高性能、轻量级、非阻塞的事件驱动 Go 网络框架。 272 | - [milvus](https://github.com/milvus-io/milvus): 一个高度灵活、可靠且速度极快的云原生开源向量数据库。 273 | - [nps](https://github.com/ehang-io/nps): 一款轻量级、高性能、功能强大的内网穿透代理服务器。 274 | - [TDengine](https://github.com/taosdata/TDengine): TDengine 是一款开源、高性能、云原生的时序数据库 (Time-Series Database, TSDB)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。 275 | - [siyuan](https://github.com/siyuan-note/siyuan): 思源笔记是一款本地优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。 276 | - [osmedeus](https://github.com/j3ssie/osmedeus): A Workflow Engine for Offensive Security. 277 | - [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. 278 | - [triangula](https://github.com/RH12503/triangula): Generate high-quality triangulated and polygonal art from images. 279 | - [teler](https://github.com/kitabisa/teler): Real-time HTTP Intrusion Detection. 280 | - [bsc](https://github.com/binance-chain/bsc): A Binance Smart Chain client based on the go-ethereum fork. 281 | - [jaeles](https://github.com/jaeles-project/jaeles): The Swiss Army knife for automated Web Application Testing. 282 | - [devlake](https://github.com/apache/incubator-devlake): The open-source dev data platform & dashboard for your DevOps tools. 283 | - [matrixone](https://github.com/matrixorigin/matrixone): MatrixOne 是一款面向未来的超融合异构云原生数据库,通过超融合数据引擎支持事务/分析/流处理等混合工作负载,通过异构云原生架构支持跨机房协同/多地协同/云边协同。简化开发运维,消简数据碎片,打破数据的系统、位置和创新边界。 284 | - [bk-bcs](https://github.com/TencentBlueKing/bk-bcs): 蓝鲸容器管理平台(Blueking Container Service)定位于打造云原生技术和业务实际应用场景之间的桥梁;聚焦于复杂应用场景的容器化部署技术方案的研发、整合和产品化;致力于为游戏等复杂应用提供一站式、低门槛的容器编排和服务治理服务。 285 | - [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. 286 | - [openGemini](https://github.com/openGemini/openGemini): openGemini 是华为云开源的一款云原生分布式时序数据库,可广泛应用于物联网、车联网、运维监控、工业互联网等业务场景,具备卓越的读写性能和高效的数据分析能力,采用类SQL查询语言,无第三方软件依赖、安装简单、部署灵活、运维便捷。 287 | - [AdGuardDNS](https://github.com/AdguardTeam/AdGuardDNS): AdGuard DNS is an alternative solution for tracker blocking, privacy protection, and parental control. 288 | - [WatchAD2.0](https://github.com/Qihoo360/WatchAD2.0): WatchAD2.0 是 360 信息安全中心开发的一款针对域安全的日志分析与监控系统,它可以收集所有域控上的事件日志、网络流量,通过特征匹配、协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。 289 | - [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. 290 | - [trpc-go](https://github.com/trpc-group/trpc-go): 一个 Go 实现的可插拔的高性能 RPC 框架。 291 | - [motan-go](https://github.com/weibocom/motan-go): Motan 是一套高性能、易于使用的分布式远程服务调用 (RPC) 框架。motan-go 是 motan 的 Go 语言实现。 292 | 293 | #### 所有案例: 294 | 295 | - [Repositories that depend on ants/v2](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY2ODgxMjg2) 296 | 297 | - [Repositories that depend on ants/v1](https://github.com/panjf2000/ants/network/dependents?package_id=UGFja2FnZS0yMjY0ODMzNjEw) 298 | 299 | 如果你的项目也在使用 `ants`,欢迎给我提 Pull Request 来更新这份用户案例列表。 300 | 301 | ## 🔋 JetBrains 开源证书支持 302 | 303 | `ants` 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 **free JetBrains Open Source license(s)** 正版免费授权,在此表达我的谢意。 304 | 305 | JetBrains logo. 306 | 307 | ## 💰 支持 308 | 309 | 如果有意向,可以通过每个月定量的少许捐赠来支持这个项目。 310 | 311 | 312 | 313 | ## 💎 赞助 314 | 315 | 每月定量捐赠 10 刀即可成为本项目的赞助者,届时您的 logo 或者 link 可以展示在本项目的 README 上。 316 | 317 | 318 | 319 | ## ☕️ 打赏 320 | 321 | > 当您通过以下方式进行捐赠时,请务必留下姓名、GitHub 账号或其他社交媒体账号,以便我将其添加到捐赠者名单中,以表谢意。 322 | 323 |    324 |    325 |    326 | 327 | ## 🔋 赞助商 328 | 329 |

330 | 331 | 332 | 333 |

334 | -------------------------------------------------------------------------------- /ants.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | // Package ants implements an efficient and reliable goroutine pool for Go. 24 | // 25 | // With ants, Go applications are able to limit the number of active goroutines, 26 | // recycle goroutines efficiently, and reduce the memory footprint significantly. 27 | // Package ants is extremely useful in the scenarios where a massive number of 28 | // goroutines are created and destroyed frequently, such as highly-concurrent 29 | // batch processing systems, HTTP servers, services of asynchronous tasks, etc. 30 | package ants 31 | 32 | import ( 33 | "context" 34 | "errors" 35 | "log" 36 | "math" 37 | "os" 38 | "runtime" 39 | "sync" 40 | "sync/atomic" 41 | "time" 42 | 43 | syncx "github.com/panjf2000/ants/v2/pkg/sync" 44 | ) 45 | 46 | const ( 47 | // DefaultAntsPoolSize is the default capacity for a default goroutine pool. 48 | DefaultAntsPoolSize = math.MaxInt32 49 | 50 | // DefaultCleanIntervalTime is the interval time to clean up goroutines. 51 | DefaultCleanIntervalTime = time.Second 52 | ) 53 | 54 | const ( 55 | // OPENED represents that the pool is opened. 56 | OPENED = iota 57 | 58 | // CLOSED represents that the pool is closed. 59 | CLOSED 60 | ) 61 | 62 | var ( 63 | // ErrLackPoolFunc will be returned when invokers don't provide function for pool. 64 | ErrLackPoolFunc = errors.New("must provide function for pool") 65 | 66 | // ErrInvalidPoolExpiry will be returned when setting a negative number as the periodic duration to purge goroutines. 67 | ErrInvalidPoolExpiry = errors.New("invalid expiry for pool") 68 | 69 | // ErrPoolClosed will be returned when submitting task to a closed pool. 70 | ErrPoolClosed = errors.New("this pool has been closed") 71 | 72 | // ErrPoolOverload will be returned when the pool is full and no workers available. 73 | ErrPoolOverload = errors.New("too many goroutines blocked on submit or Nonblocking is set") 74 | 75 | // ErrInvalidPreAllocSize will be returned when trying to set up a negative capacity under PreAlloc mode. 76 | ErrInvalidPreAllocSize = errors.New("can not set up a negative capacity under PreAlloc mode") 77 | 78 | // ErrTimeout will be returned after the operations timed out. 79 | ErrTimeout = errors.New("operation timed out") 80 | 81 | // ErrInvalidPoolIndex will be returned when trying to retrieve a pool with an invalid index. 82 | ErrInvalidPoolIndex = errors.New("invalid pool index") 83 | 84 | // ErrInvalidLoadBalancingStrategy will be returned when trying to create a MultiPool with an invalid load-balancing strategy. 85 | ErrInvalidLoadBalancingStrategy = errors.New("invalid load-balancing strategy") 86 | 87 | // ErrInvalidMultiPoolSize will be returned when trying to create a MultiPool with an invalid size. 88 | ErrInvalidMultiPoolSize = errors.New("invalid size for multiple pool") 89 | 90 | // workerChanCap determines whether the channel of a worker should be a buffered channel 91 | // to get the best performance. Inspired by fasthttp at 92 | // https://github.com/valyala/fasthttp/blob/master/workerpool.go#L139 93 | workerChanCap = func() int { 94 | // Use blocking channel if GOMAXPROCS=1. 95 | // This switches context from sender to receiver immediately, 96 | // which results in higher performance (under go1.5 at least). 97 | if runtime.GOMAXPROCS(0) == 1 { 98 | return 0 99 | } 100 | 101 | // Use non-blocking workerChan if GOMAXPROCS>1, 102 | // since otherwise the sender might be dragged down if the receiver is CPU-bound. 103 | return 1 104 | }() 105 | 106 | defaultLogger = Logger(log.New(os.Stderr, "[ants]: ", log.LstdFlags|log.Lmsgprefix|log.Lmicroseconds)) 107 | 108 | // Init an instance pool when importing ants. 109 | defaultAntsPool, _ = NewPool(DefaultAntsPoolSize) 110 | ) 111 | 112 | // Submit submits a task to pool. 113 | func Submit(task func()) error { 114 | return defaultAntsPool.Submit(task) 115 | } 116 | 117 | // Running returns the number of the currently running goroutines. 118 | func Running() int { 119 | return defaultAntsPool.Running() 120 | } 121 | 122 | // Cap returns the capacity of this default pool. 123 | func Cap() int { 124 | return defaultAntsPool.Cap() 125 | } 126 | 127 | // Free returns the available goroutines to work. 128 | func Free() int { 129 | return defaultAntsPool.Free() 130 | } 131 | 132 | // Release Closes the default pool. 133 | func Release() { 134 | defaultAntsPool.Release() 135 | } 136 | 137 | // ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out. 138 | func ReleaseTimeout(timeout time.Duration) error { 139 | return defaultAntsPool.ReleaseTimeout(timeout) 140 | } 141 | 142 | // Reboot reboots the default pool. 143 | func Reboot() { 144 | defaultAntsPool.Reboot() 145 | } 146 | 147 | // Logger is used for logging formatted messages. 148 | type Logger interface { 149 | // Printf must have the same semantics as log.Printf. 150 | Printf(format string, args ...any) 151 | } 152 | 153 | // poolCommon contains all common fields for other sophisticated pools. 154 | type poolCommon struct { 155 | // capacity of the pool, a negative value means that the capacity of pool is limitless, an infinite pool is used to 156 | // avoid potential issue of endless blocking caused by nested usage of a pool: submitting a task to pool 157 | // which submits a new task to the same pool. 158 | capacity int32 159 | 160 | // running is the number of the currently running goroutines. 161 | running int32 162 | 163 | // lock for protecting the worker queue. 164 | lock sync.Locker 165 | 166 | // workers is a slice that store the available workers. 167 | workers workerQueue 168 | 169 | // state is used to notice the pool to closed itself. 170 | state int32 171 | 172 | // cond for waiting to get an idle worker. 173 | cond *sync.Cond 174 | 175 | // done is used to indicate that all workers are done. 176 | allDone chan struct{} 177 | // once is used to make sure the pool is closed just once. 178 | once *sync.Once 179 | 180 | // workerCache speeds up the obtainment of a usable worker in function:retrieveWorker. 181 | workerCache sync.Pool 182 | 183 | // waiting is the number of goroutines already been blocked on pool.Submit(), protected by pool.lock 184 | waiting int32 185 | 186 | purgeDone int32 187 | purgeCtx context.Context 188 | stopPurge context.CancelFunc 189 | 190 | ticktockDone int32 191 | ticktockCtx context.Context 192 | stopTicktock context.CancelFunc 193 | 194 | now atomic.Value 195 | 196 | options *Options 197 | } 198 | 199 | func newPool(size int, options ...Option) (*poolCommon, error) { 200 | if size <= 0 { 201 | size = -1 202 | } 203 | 204 | opts := loadOptions(options...) 205 | 206 | if !opts.DisablePurge { 207 | if expiry := opts.ExpiryDuration; expiry < 0 { 208 | return nil, ErrInvalidPoolExpiry 209 | } else if expiry == 0 { 210 | opts.ExpiryDuration = DefaultCleanIntervalTime 211 | } 212 | } 213 | 214 | if opts.Logger == nil { 215 | opts.Logger = defaultLogger 216 | } 217 | 218 | p := &poolCommon{ 219 | capacity: int32(size), 220 | allDone: make(chan struct{}), 221 | lock: syncx.NewSpinLock(), 222 | once: &sync.Once{}, 223 | options: opts, 224 | } 225 | if p.options.PreAlloc { 226 | if size == -1 { 227 | return nil, ErrInvalidPreAllocSize 228 | } 229 | p.workers = newWorkerQueue(queueTypeLoopQueue, size) 230 | } else { 231 | p.workers = newWorkerQueue(queueTypeStack, 0) 232 | } 233 | 234 | p.cond = sync.NewCond(p.lock) 235 | 236 | p.goPurge() 237 | p.goTicktock() 238 | 239 | return p, nil 240 | } 241 | 242 | // purgeStaleWorkers clears stale workers periodically, it runs in an individual goroutine, as a scavenger. 243 | func (p *poolCommon) purgeStaleWorkers() { 244 | ticker := time.NewTicker(p.options.ExpiryDuration) 245 | 246 | defer func() { 247 | ticker.Stop() 248 | atomic.StoreInt32(&p.purgeDone, 1) 249 | }() 250 | 251 | purgeCtx := p.purgeCtx // copy to the local variable to avoid race from Reboot() 252 | for { 253 | select { 254 | case <-purgeCtx.Done(): 255 | return 256 | case <-ticker.C: 257 | } 258 | 259 | if p.IsClosed() { 260 | break 261 | } 262 | 263 | var isDormant bool 264 | p.lock.Lock() 265 | staleWorkers := p.workers.refresh(p.options.ExpiryDuration) 266 | n := p.Running() 267 | isDormant = n == 0 || n == len(staleWorkers) 268 | p.lock.Unlock() 269 | 270 | // Clean up the stale workers. 271 | for i := range staleWorkers { 272 | staleWorkers[i].finish() 273 | staleWorkers[i] = nil 274 | } 275 | 276 | // There might be a situation where all workers have been cleaned up (no worker is running), 277 | // while some invokers still are stuck in p.cond.Wait(), then we need to awake those invokers. 278 | if isDormant && p.Waiting() > 0 { 279 | p.cond.Broadcast() 280 | } 281 | } 282 | } 283 | 284 | const nowTimeUpdateInterval = 500 * time.Millisecond 285 | 286 | // ticktock is a goroutine that updates the current time in the pool regularly. 287 | func (p *poolCommon) ticktock() { 288 | ticker := time.NewTicker(nowTimeUpdateInterval) 289 | defer func() { 290 | ticker.Stop() 291 | atomic.StoreInt32(&p.ticktockDone, 1) 292 | }() 293 | 294 | ticktockCtx := p.ticktockCtx // copy to the local variable to avoid race from Reboot() 295 | for { 296 | select { 297 | case <-ticktockCtx.Done(): 298 | return 299 | case <-ticker.C: 300 | } 301 | 302 | if p.IsClosed() { 303 | break 304 | } 305 | 306 | p.now.Store(time.Now()) 307 | } 308 | } 309 | 310 | func (p *poolCommon) goPurge() { 311 | if p.options.DisablePurge { 312 | return 313 | } 314 | 315 | // Start a goroutine to clean up expired workers periodically. 316 | p.purgeCtx, p.stopPurge = context.WithCancel(context.Background()) 317 | go p.purgeStaleWorkers() 318 | } 319 | 320 | func (p *poolCommon) goTicktock() { 321 | p.now.Store(time.Now()) 322 | p.ticktockCtx, p.stopTicktock = context.WithCancel(context.Background()) 323 | go p.ticktock() 324 | } 325 | 326 | func (p *poolCommon) nowTime() time.Time { 327 | return p.now.Load().(time.Time) 328 | } 329 | 330 | // Running returns the number of workers currently running. 331 | func (p *poolCommon) Running() int { 332 | return int(atomic.LoadInt32(&p.running)) 333 | } 334 | 335 | // Free returns the number of available workers, -1 indicates this pool is unlimited. 336 | func (p *poolCommon) Free() int { 337 | c := p.Cap() 338 | if c < 0 { 339 | return -1 340 | } 341 | return c - p.Running() 342 | } 343 | 344 | // Waiting returns the number of tasks waiting to be executed. 345 | func (p *poolCommon) Waiting() int { 346 | return int(atomic.LoadInt32(&p.waiting)) 347 | } 348 | 349 | // Cap returns the capacity of this pool. 350 | func (p *poolCommon) Cap() int { 351 | return int(atomic.LoadInt32(&p.capacity)) 352 | } 353 | 354 | // Tune changes the capacity of this pool, note that it is noneffective to the infinite or pre-allocation pool. 355 | func (p *poolCommon) Tune(size int) { 356 | capacity := p.Cap() 357 | if capacity == -1 || size <= 0 || size == capacity || p.options.PreAlloc { 358 | return 359 | } 360 | atomic.StoreInt32(&p.capacity, int32(size)) 361 | if size > capacity { 362 | if size-capacity == 1 { 363 | p.cond.Signal() 364 | return 365 | } 366 | p.cond.Broadcast() 367 | } 368 | } 369 | 370 | // IsClosed indicates whether the pool is closed. 371 | func (p *poolCommon) IsClosed() bool { 372 | return atomic.LoadInt32(&p.state) == CLOSED 373 | } 374 | 375 | // Release closes this pool and releases the worker queue. 376 | func (p *poolCommon) Release() { 377 | if !atomic.CompareAndSwapInt32(&p.state, OPENED, CLOSED) { 378 | return 379 | } 380 | 381 | if p.stopPurge != nil { 382 | p.stopPurge() 383 | p.stopPurge = nil 384 | } 385 | if p.stopTicktock != nil { 386 | p.stopTicktock() 387 | p.stopTicktock = nil 388 | } 389 | 390 | p.lock.Lock() 391 | p.workers.reset() 392 | p.lock.Unlock() 393 | 394 | // There might be some callers waiting in retrieveWorker(), so we need to wake them up to prevent 395 | // those callers blocking infinitely. 396 | p.cond.Broadcast() 397 | } 398 | 399 | // ReleaseTimeout is like Release but with a timeout, it waits all workers to exit before timing out. 400 | func (p *poolCommon) ReleaseTimeout(timeout time.Duration) error { 401 | if p.IsClosed() || (!p.options.DisablePurge && p.stopPurge == nil) || p.stopTicktock == nil { 402 | return ErrPoolClosed 403 | } 404 | 405 | p.Release() 406 | 407 | var purgeCh <-chan struct{} 408 | if !p.options.DisablePurge { 409 | purgeCh = p.purgeCtx.Done() 410 | } else { 411 | purgeCh = p.allDone 412 | } 413 | 414 | if p.Running() == 0 { 415 | p.once.Do(func() { 416 | close(p.allDone) 417 | }) 418 | } 419 | 420 | timer := time.NewTimer(timeout) 421 | defer timer.Stop() 422 | for { 423 | select { 424 | case <-timer.C: 425 | return ErrTimeout 426 | case <-p.allDone: 427 | <-purgeCh 428 | <-p.ticktockCtx.Done() 429 | if p.Running() == 0 && 430 | (p.options.DisablePurge || atomic.LoadInt32(&p.purgeDone) == 1) && 431 | atomic.LoadInt32(&p.ticktockDone) == 1 { 432 | return nil 433 | } 434 | } 435 | } 436 | } 437 | 438 | // Reboot reboots a closed pool, it does nothing if the pool is not closed. 439 | // If you intend to reboot a closed pool, use ReleaseTimeout() instead of 440 | // Release() to ensure that all workers are stopped and resource are released 441 | // before rebooting, otherwise you may run into data race. 442 | func (p *poolCommon) Reboot() { 443 | if atomic.CompareAndSwapInt32(&p.state, CLOSED, OPENED) { 444 | atomic.StoreInt32(&p.purgeDone, 0) 445 | p.goPurge() 446 | atomic.StoreInt32(&p.ticktockDone, 0) 447 | p.goTicktock() 448 | p.allDone = make(chan struct{}) 449 | p.once = &sync.Once{} 450 | } 451 | } 452 | 453 | func (p *poolCommon) addRunning(delta int) int { 454 | return int(atomic.AddInt32(&p.running, int32(delta))) 455 | } 456 | 457 | func (p *poolCommon) addWaiting(delta int) { 458 | atomic.AddInt32(&p.waiting, int32(delta)) 459 | } 460 | 461 | // retrieveWorker returns an available worker to run the tasks. 462 | func (p *poolCommon) retrieveWorker() (w worker, err error) { 463 | p.lock.Lock() 464 | 465 | retry: 466 | // First try to fetch the worker from the queue. 467 | if w = p.workers.detach(); w != nil { 468 | p.lock.Unlock() 469 | return 470 | } 471 | 472 | // If the worker queue is empty, and we don't run out of the pool capacity, 473 | // then just spawn a new worker goroutine. 474 | if capacity := p.Cap(); capacity == -1 || capacity > p.Running() { 475 | p.lock.Unlock() 476 | w = p.workerCache.Get().(worker) 477 | w.run() 478 | return 479 | } 480 | 481 | // Bail out early if it's in nonblocking mode or the number of pending callers reaches the maximum limit value. 482 | if p.options.Nonblocking || (p.options.MaxBlockingTasks != 0 && p.Waiting() >= p.options.MaxBlockingTasks) { 483 | p.lock.Unlock() 484 | return nil, ErrPoolOverload 485 | } 486 | 487 | // Otherwise, we'll have to keep them blocked and wait for at least one worker to be put back into pool. 488 | p.addWaiting(1) 489 | p.cond.Wait() // block and wait for an available worker 490 | p.addWaiting(-1) 491 | 492 | if p.IsClosed() { 493 | p.lock.Unlock() 494 | return nil, ErrPoolClosed 495 | } 496 | 497 | goto retry 498 | } 499 | 500 | // revertWorker puts a worker back into free pool, recycling the goroutines. 501 | func (p *poolCommon) revertWorker(worker worker) bool { 502 | if capacity := p.Cap(); (capacity > 0 && p.Running() > capacity) || p.IsClosed() { 503 | p.cond.Broadcast() 504 | return false 505 | } 506 | 507 | worker.setLastUsedTime(p.nowTime()) 508 | 509 | p.lock.Lock() 510 | // To avoid memory leaks, add a double check in the lock scope. 511 | // Issue: https://github.com/panjf2000/ants/issues/113 512 | if p.IsClosed() { 513 | p.lock.Unlock() 514 | return false 515 | } 516 | if err := p.workers.insert(worker); err != nil { 517 | p.lock.Unlock() 518 | return false 519 | } 520 | // Notify the invoker stuck in 'retrieveWorker()' of there is an available worker in the worker queue. 521 | p.cond.Signal() 522 | p.lock.Unlock() 523 | 524 | return true 525 | } 526 | -------------------------------------------------------------------------------- /ants_benchmark_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants_test 24 | 25 | import ( 26 | "runtime" 27 | "sync" 28 | "sync/atomic" 29 | "testing" 30 | "time" 31 | 32 | "golang.org/x/sync/errgroup" 33 | 34 | "github.com/panjf2000/ants/v2" 35 | ) 36 | 37 | const ( 38 | RunTimes = 1e6 39 | PoolCap = 5e4 40 | BenchParam = 10 41 | DefaultExpiredTime = 10 * time.Second 42 | ) 43 | 44 | func demoFunc() { 45 | time.Sleep(time.Duration(BenchParam) * time.Millisecond) 46 | } 47 | 48 | func demoPoolFunc(args any) { 49 | n := args.(int) 50 | time.Sleep(time.Duration(n) * time.Millisecond) 51 | } 52 | 53 | func demoPoolFuncInt(n int) { 54 | time.Sleep(time.Duration(n) * time.Millisecond) 55 | } 56 | 57 | var stopLongRunningFunc int32 58 | 59 | func longRunningFunc() { 60 | for atomic.LoadInt32(&stopLongRunningFunc) == 0 { 61 | runtime.Gosched() 62 | } 63 | } 64 | 65 | func longRunningPoolFunc(arg any) { 66 | <-arg.(chan struct{}) 67 | } 68 | 69 | func longRunningPoolFuncCh(ch chan struct{}) { 70 | <-ch 71 | } 72 | 73 | func BenchmarkGoroutines(b *testing.B) { 74 | var wg sync.WaitGroup 75 | for i := 0; i < b.N; i++ { 76 | wg.Add(RunTimes) 77 | for j := 0; j < RunTimes; j++ { 78 | go func() { 79 | demoFunc() 80 | wg.Done() 81 | }() 82 | } 83 | wg.Wait() 84 | } 85 | } 86 | 87 | func BenchmarkChannel(b *testing.B) { 88 | var wg sync.WaitGroup 89 | sema := make(chan struct{}, PoolCap) 90 | 91 | b.ResetTimer() 92 | for i := 0; i < b.N; i++ { 93 | wg.Add(RunTimes) 94 | for j := 0; j < RunTimes; j++ { 95 | sema <- struct{}{} 96 | go func() { 97 | demoFunc() 98 | <-sema 99 | wg.Done() 100 | }() 101 | } 102 | wg.Wait() 103 | } 104 | } 105 | 106 | func BenchmarkErrGroup(b *testing.B) { 107 | var wg sync.WaitGroup 108 | var pool errgroup.Group 109 | pool.SetLimit(PoolCap) 110 | 111 | b.ResetTimer() 112 | for i := 0; i < b.N; i++ { 113 | wg.Add(RunTimes) 114 | for j := 0; j < RunTimes; j++ { 115 | pool.Go(func() error { 116 | demoFunc() 117 | wg.Done() 118 | return nil 119 | }) 120 | } 121 | wg.Wait() 122 | } 123 | } 124 | 125 | func BenchmarkAntsPool(b *testing.B) { 126 | var wg sync.WaitGroup 127 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime)) 128 | defer p.Release() 129 | 130 | b.ResetTimer() 131 | for i := 0; i < b.N; i++ { 132 | wg.Add(RunTimes) 133 | for j := 0; j < RunTimes; j++ { 134 | _ = p.Submit(func() { 135 | demoFunc() 136 | wg.Done() 137 | }) 138 | } 139 | wg.Wait() 140 | } 141 | } 142 | 143 | func BenchmarkAntsMultiPool(b *testing.B) { 144 | var wg sync.WaitGroup 145 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime)) 146 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck 147 | 148 | b.ResetTimer() 149 | for i := 0; i < b.N; i++ { 150 | wg.Add(RunTimes) 151 | for j := 0; j < RunTimes; j++ { 152 | _ = p.Submit(func() { 153 | demoFunc() 154 | wg.Done() 155 | }) 156 | } 157 | wg.Wait() 158 | } 159 | } 160 | 161 | func BenchmarkGoroutinesThroughput(b *testing.B) { 162 | for i := 0; i < b.N; i++ { 163 | for j := 0; j < RunTimes; j++ { 164 | go demoFunc() 165 | } 166 | } 167 | } 168 | 169 | func BenchmarkSemaphoreThroughput(b *testing.B) { 170 | sema := make(chan struct{}, PoolCap) 171 | for i := 0; i < b.N; i++ { 172 | for j := 0; j < RunTimes; j++ { 173 | sema <- struct{}{} 174 | go func() { 175 | demoFunc() 176 | <-sema 177 | }() 178 | } 179 | } 180 | } 181 | 182 | func BenchmarkAntsPoolThroughput(b *testing.B) { 183 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime)) 184 | defer p.Release() 185 | 186 | b.ResetTimer() 187 | for i := 0; i < b.N; i++ { 188 | for j := 0; j < RunTimes; j++ { 189 | _ = p.Submit(demoFunc) 190 | } 191 | } 192 | } 193 | 194 | func BenchmarkAntsMultiPoolThroughput(b *testing.B) { 195 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime)) 196 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck 197 | 198 | b.ResetTimer() 199 | for i := 0; i < b.N; i++ { 200 | for j := 0; j < RunTimes; j++ { 201 | _ = p.Submit(demoFunc) 202 | } 203 | } 204 | } 205 | 206 | func BenchmarkParallelAntsPoolThroughput(b *testing.B) { 207 | p, _ := ants.NewPool(PoolCap, ants.WithExpiryDuration(DefaultExpiredTime)) 208 | defer p.Release() 209 | 210 | b.ResetTimer() 211 | b.RunParallel(func(pb *testing.PB) { 212 | for pb.Next() { 213 | _ = p.Submit(demoFunc) 214 | } 215 | }) 216 | } 217 | 218 | func BenchmarkParallelAntsMultiPoolThroughput(b *testing.B) { 219 | p, _ := ants.NewMultiPool(10, PoolCap/10, ants.RoundRobin, ants.WithExpiryDuration(DefaultExpiredTime)) 220 | defer p.ReleaseTimeout(DefaultExpiredTime) //nolint:errcheck 221 | 222 | b.ResetTimer() 223 | b.RunParallel(func(pb *testing.PB) { 224 | for pb.Next() { 225 | _ = p.Submit(demoFunc) 226 | } 227 | }) 228 | } 229 | -------------------------------------------------------------------------------- /ants_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants_test 24 | 25 | import ( 26 | "log" 27 | "os" 28 | "runtime" 29 | "sync" 30 | "sync/atomic" 31 | "testing" 32 | "time" 33 | 34 | "github.com/stretchr/testify/require" 35 | 36 | "github.com/panjf2000/ants/v2" 37 | ) 38 | 39 | const ( 40 | _ = 1 << (10 * iota) 41 | KiB // 1024 42 | MiB // 1048576 43 | ) 44 | 45 | const ( 46 | Param = 100 47 | AntsSize = 1000 48 | TestSize = 10000 49 | n = 100000 50 | ) 51 | 52 | var curMem uint64 53 | 54 | // TestAntsPoolWaitToGetWorker is used to test waiting to get worker. 55 | func TestAntsPoolWaitToGetWorker(t *testing.T) { 56 | var wg sync.WaitGroup 57 | p, _ := ants.NewPool(AntsSize) 58 | defer p.Release() 59 | 60 | for i := 0; i < n; i++ { 61 | wg.Add(1) 62 | _ = p.Submit(func() { 63 | demoPoolFunc(Param) 64 | wg.Done() 65 | }) 66 | } 67 | wg.Wait() 68 | t.Logf("pool, running workers number:%d", p.Running()) 69 | mem := runtime.MemStats{} 70 | runtime.ReadMemStats(&mem) 71 | curMem = mem.TotalAlloc/MiB - curMem 72 | t.Logf("memory usage:%d MB", curMem) 73 | } 74 | 75 | func TestAntsPoolWaitToGetWorkerPreMalloc(t *testing.T) { 76 | var wg sync.WaitGroup 77 | p, _ := ants.NewPool(AntsSize, ants.WithPreAlloc(true)) 78 | defer p.Release() 79 | 80 | for i := 0; i < n; i++ { 81 | wg.Add(1) 82 | _ = p.Submit(func() { 83 | demoPoolFunc(Param) 84 | wg.Done() 85 | }) 86 | } 87 | wg.Wait() 88 | t.Logf("pool, running workers number:%d", p.Running()) 89 | mem := runtime.MemStats{} 90 | runtime.ReadMemStats(&mem) 91 | curMem = mem.TotalAlloc/MiB - curMem 92 | t.Logf("memory usage:%d MB", curMem) 93 | } 94 | 95 | // TestAntsPoolWithFuncWaitToGetWorker is used to test waiting to get worker. 96 | func TestAntsPoolWithFuncWaitToGetWorker(t *testing.T) { 97 | var wg sync.WaitGroup 98 | p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) { 99 | demoPoolFunc(i) 100 | wg.Done() 101 | }) 102 | defer p.Release() 103 | 104 | for i := 0; i < n; i++ { 105 | wg.Add(1) 106 | _ = p.Invoke(Param) 107 | } 108 | wg.Wait() 109 | t.Logf("pool with func, running workers number:%d", p.Running()) 110 | mem := runtime.MemStats{} 111 | runtime.ReadMemStats(&mem) 112 | curMem = mem.TotalAlloc/MiB - curMem 113 | t.Logf("memory usage:%d MB", curMem) 114 | } 115 | 116 | // TestAntsPoolWithFuncGenericWaitToGetWorker is used to test waiting to get worker. 117 | func TestAntsPoolWithFuncGenericWaitToGetWorker(t *testing.T) { 118 | var wg sync.WaitGroup 119 | p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) { 120 | demoPoolFuncInt(i) 121 | wg.Done() 122 | }) 123 | defer p.Release() 124 | 125 | for i := 0; i < n; i++ { 126 | wg.Add(1) 127 | _ = p.Invoke(Param) 128 | } 129 | wg.Wait() 130 | t.Logf("pool with func, running workers number:%d", p.Running()) 131 | mem := runtime.MemStats{} 132 | runtime.ReadMemStats(&mem) 133 | curMem = mem.TotalAlloc/MiB - curMem 134 | t.Logf("memory usage:%d MB", curMem) 135 | } 136 | 137 | func TestAntsPoolWithFuncWaitToGetWorkerPreMalloc(t *testing.T) { 138 | var wg sync.WaitGroup 139 | p, _ := ants.NewPoolWithFunc(AntsSize, func(i any) { 140 | demoPoolFunc(i) 141 | wg.Done() 142 | }, ants.WithPreAlloc(true)) 143 | defer p.Release() 144 | 145 | for i := 0; i < n; i++ { 146 | wg.Add(1) 147 | _ = p.Invoke(Param) 148 | } 149 | wg.Wait() 150 | t.Logf("pool with func, running workers number:%d", p.Running()) 151 | mem := runtime.MemStats{} 152 | runtime.ReadMemStats(&mem) 153 | curMem = mem.TotalAlloc/MiB - curMem 154 | t.Logf("memory usage:%d MB", curMem) 155 | } 156 | 157 | func TestAntsPoolWithFuncGenericWaitToGetWorkerPreMalloc(t *testing.T) { 158 | var wg sync.WaitGroup 159 | p, _ := ants.NewPoolWithFuncGeneric(AntsSize, func(i int) { 160 | demoPoolFuncInt(i) 161 | wg.Done() 162 | }, ants.WithPreAlloc(true)) 163 | defer p.Release() 164 | 165 | for i := 0; i < n; i++ { 166 | wg.Add(1) 167 | _ = p.Invoke(Param) 168 | } 169 | wg.Wait() 170 | t.Logf("pool with func, running workers number:%d", p.Running()) 171 | mem := runtime.MemStats{} 172 | runtime.ReadMemStats(&mem) 173 | curMem = mem.TotalAlloc/MiB - curMem 174 | t.Logf("memory usage:%d MB", curMem) 175 | } 176 | 177 | // TestAntsPoolGetWorkerFromCache is used to test getting worker from sync.Pool. 178 | func TestAntsPoolGetWorkerFromCache(t *testing.T) { 179 | p, _ := ants.NewPool(TestSize) 180 | defer p.Release() 181 | 182 | for i := 0; i < AntsSize; i++ { 183 | _ = p.Submit(demoFunc) 184 | } 185 | time.Sleep(2 * ants.DefaultCleanIntervalTime) 186 | _ = p.Submit(demoFunc) 187 | t.Logf("pool, running workers number:%d", p.Running()) 188 | mem := runtime.MemStats{} 189 | runtime.ReadMemStats(&mem) 190 | curMem = mem.TotalAlloc/MiB - curMem 191 | t.Logf("memory usage:%d MB", curMem) 192 | } 193 | 194 | // TestAntsPoolWithFuncGetWorkerFromCache is used to test getting worker from sync.Pool. 195 | func TestAntsPoolWithFuncGetWorkerFromCache(t *testing.T) { 196 | dur := 10 197 | p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc) 198 | defer p.Release() 199 | 200 | for i := 0; i < AntsSize; i++ { 201 | _ = p.Invoke(dur) 202 | } 203 | time.Sleep(2 * ants.DefaultCleanIntervalTime) 204 | _ = p.Invoke(dur) 205 | t.Logf("pool with func, running workers number:%d", p.Running()) 206 | mem := runtime.MemStats{} 207 | runtime.ReadMemStats(&mem) 208 | curMem = mem.TotalAlloc/MiB - curMem 209 | t.Logf("memory usage:%d MB", curMem) 210 | } 211 | 212 | // TestAntsPoolWithFuncGenericGetWorkerFromCache is used to test getting worker from sync.Pool. 213 | func TestAntsPoolWithFuncGenericGetWorkerFromCache(t *testing.T) { 214 | dur := 10 215 | p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt) 216 | defer p.Release() 217 | 218 | for i := 0; i < AntsSize; i++ { 219 | _ = p.Invoke(dur) 220 | } 221 | time.Sleep(2 * ants.DefaultCleanIntervalTime) 222 | _ = p.Invoke(dur) 223 | t.Logf("pool with func, running workers number:%d", p.Running()) 224 | mem := runtime.MemStats{} 225 | runtime.ReadMemStats(&mem) 226 | curMem = mem.TotalAlloc/MiB - curMem 227 | t.Logf("memory usage:%d MB", curMem) 228 | } 229 | 230 | func TestAntsPoolWithFuncGetWorkerFromCachePreMalloc(t *testing.T) { 231 | dur := 10 232 | p, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true)) 233 | defer p.Release() 234 | 235 | for i := 0; i < AntsSize; i++ { 236 | _ = p.Invoke(dur) 237 | } 238 | time.Sleep(2 * ants.DefaultCleanIntervalTime) 239 | _ = p.Invoke(dur) 240 | t.Logf("pool with func, running workers number:%d", p.Running()) 241 | mem := runtime.MemStats{} 242 | runtime.ReadMemStats(&mem) 243 | curMem = mem.TotalAlloc/MiB - curMem 244 | t.Logf("memory usage:%d MB", curMem) 245 | } 246 | 247 | func TestAntsPoolWithFuncGenericGetWorkerFromCachePreMalloc(t *testing.T) { 248 | dur := 10 249 | p, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true)) 250 | defer p.Release() 251 | 252 | for i := 0; i < AntsSize; i++ { 253 | _ = p.Invoke(dur) 254 | } 255 | time.Sleep(2 * ants.DefaultCleanIntervalTime) 256 | _ = p.Invoke(dur) 257 | t.Logf("pool with func, running workers number:%d", p.Running()) 258 | mem := runtime.MemStats{} 259 | runtime.ReadMemStats(&mem) 260 | curMem = mem.TotalAlloc/MiB - curMem 261 | t.Logf("memory usage:%d MB", curMem) 262 | } 263 | 264 | // Contrast between goroutines without a pool and goroutines with ants pool. 265 | 266 | func TestNoPool(t *testing.T) { 267 | var wg sync.WaitGroup 268 | for i := 0; i < n; i++ { 269 | wg.Add(1) 270 | go func() { 271 | demoFunc() 272 | wg.Done() 273 | }() 274 | } 275 | 276 | wg.Wait() 277 | mem := runtime.MemStats{} 278 | runtime.ReadMemStats(&mem) 279 | curMem = mem.TotalAlloc/MiB - curMem 280 | t.Logf("memory usage:%d MB", curMem) 281 | } 282 | 283 | func TestAntsPool(t *testing.T) { 284 | defer ants.Release() 285 | var wg sync.WaitGroup 286 | for i := 0; i < n; i++ { 287 | wg.Add(1) 288 | _ = ants.Submit(func() { 289 | demoFunc() 290 | wg.Done() 291 | }) 292 | } 293 | wg.Wait() 294 | 295 | t.Logf("pool, capacity:%d", ants.Cap()) 296 | t.Logf("pool, running workers number:%d", ants.Running()) 297 | t.Logf("pool, free workers number:%d", ants.Free()) 298 | 299 | mem := runtime.MemStats{} 300 | runtime.ReadMemStats(&mem) 301 | curMem = mem.TotalAlloc/MiB - curMem 302 | t.Logf("memory usage:%d MB", curMem) 303 | } 304 | 305 | func TestPanicHandler(t *testing.T) { 306 | var panicCounter int64 307 | var wg sync.WaitGroup 308 | p0, err := ants.NewPool(10, ants.WithPanicHandler(func(p any) { 309 | defer wg.Done() 310 | atomic.AddInt64(&panicCounter, 1) 311 | t.Logf("catch panic with PanicHandler: %v", p) 312 | })) 313 | require.NoErrorf(t, err, "create new pool failed: %v", err) 314 | defer p0.Release() 315 | wg.Add(1) 316 | _ = p0.Submit(func() { 317 | panic("Oops!") 318 | }) 319 | wg.Wait() 320 | c := atomic.LoadInt64(&panicCounter) 321 | require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c) 322 | require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic") 323 | 324 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPanicHandler(func(_ any) { 325 | defer wg.Done() 326 | atomic.AddInt64(&panicCounter, 1) 327 | })) 328 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 329 | defer p1.Release() 330 | wg.Add(1) 331 | _ = p1.Invoke("Oops!") 332 | wg.Wait() 333 | c = atomic.LoadInt64(&panicCounter) 334 | require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c) 335 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic") 336 | 337 | p2, err := ants.NewPoolWithFuncGeneric(10, func(s string) { panic(s) }, ants.WithPanicHandler(func(_ any) { 338 | defer wg.Done() 339 | atomic.AddInt64(&panicCounter, 1) 340 | })) 341 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 342 | defer p2.Release() 343 | wg.Add(1) 344 | _ = p2.Invoke("Oops!") 345 | wg.Wait() 346 | c = atomic.LoadInt64(&panicCounter) 347 | require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c) 348 | require.EqualValues(t, 0, p2.Running(), "pool should be empty after panic") 349 | } 350 | 351 | func TestPanicHandlerPreMalloc(t *testing.T) { 352 | var panicCounter int64 353 | var wg sync.WaitGroup 354 | p0, err := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithPanicHandler(func(p any) { 355 | defer wg.Done() 356 | atomic.AddInt64(&panicCounter, 1) 357 | t.Logf("catch panic with PanicHandler: %v", p) 358 | })) 359 | require.NoErrorf(t, err, "create new pool failed: %v", err) 360 | defer p0.Release() 361 | wg.Add(1) 362 | _ = p0.Submit(func() { 363 | panic("Oops!") 364 | }) 365 | wg.Wait() 366 | c := atomic.LoadInt64(&panicCounter) 367 | require.EqualValuesf(t, 1, c, "panic handler didn't work, panicCounter: %d", c) 368 | require.EqualValues(t, 0, p0.Running(), "pool should be empty after panic") 369 | 370 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) { 371 | defer wg.Done() 372 | atomic.AddInt64(&panicCounter, 1) 373 | })) 374 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 375 | defer p1.Release() 376 | wg.Add(1) 377 | _ = p1.Invoke("Oops!") 378 | wg.Wait() 379 | c = atomic.LoadInt64(&panicCounter) 380 | require.EqualValuesf(t, 2, c, "panic handler didn't work, panicCounter: %d", c) 381 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic") 382 | 383 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) }, ants.WithPreAlloc(true), ants.WithPanicHandler(func(_ any) { 384 | defer wg.Done() 385 | atomic.AddInt64(&panicCounter, 1) 386 | })) 387 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 388 | defer p2.Release() 389 | wg.Add(1) 390 | _ = p2.Invoke("Oops!") 391 | wg.Wait() 392 | c = atomic.LoadInt64(&panicCounter) 393 | require.EqualValuesf(t, 3, c, "panic handler didn't work, panicCounter: %d", c) 394 | require.EqualValues(t, 0, p1.Running(), "pool should be empty after panic") 395 | } 396 | 397 | func TestPoolPanicWithoutHandler(t *testing.T) { 398 | p0, err := ants.NewPool(10) 399 | require.NoErrorf(t, err, "create new pool failed: %v", err) 400 | defer p0.Release() 401 | _ = p0.Submit(func() { 402 | panic("Oops!") 403 | }) 404 | 405 | p1, err := ants.NewPoolWithFunc(10, func(p any) { panic(p) }) 406 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 407 | defer p1.Release() 408 | _ = p1.Invoke("Oops!") 409 | 410 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p string) { panic(p) }) 411 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 412 | defer p2.Release() 413 | _ = p2.Invoke("Oops!") 414 | } 415 | 416 | func TestPoolPanicWithoutHandlerPreMalloc(t *testing.T) { 417 | p0, err := ants.NewPool(10, ants.WithPreAlloc(true)) 418 | require.NoErrorf(t, err, "create new pool failed: %v", err) 419 | defer p0.Release() 420 | _ = p0.Submit(func() { 421 | panic("Oops!") 422 | }) 423 | 424 | p1, err := ants.NewPoolWithFunc(10, func(p any) { 425 | panic(p) 426 | }) 427 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 428 | defer p1.Release() 429 | _ = p1.Invoke("Oops!") 430 | 431 | p2, err := ants.NewPoolWithFuncGeneric(10, func(p any) { 432 | panic(p) 433 | }) 434 | require.NoErrorf(t, err, "create new pool with func failed: %v", err) 435 | defer p2.Release() 436 | _ = p2.Invoke("Oops!") 437 | } 438 | 439 | func TestPurgePool(t *testing.T) { 440 | size := 500 441 | ch := make(chan struct{}) 442 | 443 | p, err := ants.NewPool(size) 444 | require.NoErrorf(t, err, "create TimingPool failed: %v", err) 445 | defer p.Release() 446 | 447 | for i := 0; i < size; i++ { 448 | j := i + 1 449 | _ = p.Submit(func() { 450 | <-ch 451 | d := j % 100 452 | time.Sleep(time.Duration(d) * time.Millisecond) 453 | }) 454 | } 455 | require.EqualValuesf(t, size, p.Running(), "pool should be full, expected: %d, but got: %d", size, p.Running()) 456 | 457 | close(ch) 458 | time.Sleep(5 * ants.DefaultCleanIntervalTime) 459 | require.EqualValuesf(t, 0, p.Running(), "pool should be empty after purge, but got %d", p.Running()) 460 | 461 | ch = make(chan struct{}) 462 | f := func(i any) { 463 | <-ch 464 | d := i.(int) % 100 465 | time.Sleep(time.Duration(d) * time.Millisecond) 466 | } 467 | 468 | p1, err := ants.NewPoolWithFunc(size, f) 469 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 470 | defer p1.Release() 471 | 472 | for i := 0; i < size; i++ { 473 | _ = p1.Invoke(i) 474 | } 475 | require.EqualValuesf(t, size, p1.Running(), "pool should be full, expected: %d, but got: %d", size, p1.Running()) 476 | 477 | close(ch) 478 | time.Sleep(5 * ants.DefaultCleanIntervalTime) 479 | require.EqualValuesf(t, 0, p1.Running(), "pool should be empty after purge, but got %d", p1.Running()) 480 | 481 | ch = make(chan struct{}) 482 | f1 := func(i int) { 483 | <-ch 484 | d := i % 100 485 | time.Sleep(time.Duration(d) * time.Millisecond) 486 | } 487 | 488 | p2, err := ants.NewPoolWithFuncGeneric(size, f1) 489 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 490 | defer p2.Release() 491 | 492 | for i := 0; i < size; i++ { 493 | _ = p2.Invoke(i) 494 | } 495 | require.EqualValuesf(t, size, p2.Running(), "pool should be full, expected: %d, but got: %d", size, p2.Running()) 496 | 497 | close(ch) 498 | time.Sleep(5 * ants.DefaultCleanIntervalTime) 499 | require.EqualValuesf(t, 0, p2.Running(), "pool should be empty after purge, but got %d", p2.Running()) 500 | } 501 | 502 | func TestPurgePreMallocPool(t *testing.T) { 503 | p, err := ants.NewPool(10, ants.WithPreAlloc(true)) 504 | require.NoErrorf(t, err, "create TimingPool failed: %v", err) 505 | defer p.Release() 506 | _ = p.Submit(demoFunc) 507 | time.Sleep(3 * ants.DefaultCleanIntervalTime) 508 | require.EqualValues(t, 0, p.Running(), "all p should be purged") 509 | 510 | p1, err := ants.NewPoolWithFunc(10, demoPoolFunc) 511 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 512 | defer p1.Release() 513 | _ = p1.Invoke(1) 514 | time.Sleep(3 * ants.DefaultCleanIntervalTime) 515 | require.EqualValues(t, 0, p1.Running(), "all p should be purged") 516 | 517 | p2, err := ants.NewPoolWithFuncGeneric(10, demoPoolFuncInt) 518 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 519 | defer p2.Release() 520 | _ = p2.Invoke(1) 521 | time.Sleep(3 * ants.DefaultCleanIntervalTime) 522 | require.EqualValues(t, 0, p2.Running(), "all p should be purged") 523 | } 524 | 525 | func TestNonblockingSubmit(t *testing.T) { 526 | poolSize := 10 527 | p, err := ants.NewPool(poolSize, ants.WithNonblocking(true)) 528 | require.NoErrorf(t, err, "create TimingPool failed: %v", err) 529 | defer p.Release() 530 | for i := 0; i < poolSize-1; i++ { 531 | require.NoError(t, p.Submit(longRunningFunc), "nonblocking submit when pool is not full shouldn't return error") 532 | } 533 | ch := make(chan struct{}) 534 | ch1 := make(chan struct{}) 535 | f := func() { 536 | <-ch 537 | close(ch1) 538 | } 539 | // p is full now. 540 | require.NoError(t, p.Submit(f), "nonblocking submit when pool is not full shouldn't return error") 541 | require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload, 542 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload") 543 | // interrupt f to get an available worker 544 | close(ch) 545 | <-ch1 546 | require.NoError(t, p.Submit(demoFunc), "nonblocking submit when pool is not full shouldn't return error") 547 | } 548 | 549 | func TestMaxBlockingSubmit(t *testing.T) { 550 | poolSize := 10 551 | p, err := ants.NewPool(poolSize, ants.WithMaxBlockingTasks(1)) 552 | require.NoErrorf(t, err, "create TimingPool failed: %v", err) 553 | defer p.Release() 554 | for i := 0; i < poolSize-1; i++ { 555 | require.NoError(t, p.Submit(longRunningFunc), "submit when pool is not full shouldn't return error") 556 | } 557 | ch := make(chan struct{}) 558 | f := func() { 559 | <-ch 560 | } 561 | // p is full now. 562 | require.NoError(t, p.Submit(f), "submit when pool is not full shouldn't return error") 563 | var wg sync.WaitGroup 564 | wg.Add(1) 565 | errCh := make(chan error, 1) 566 | go func() { 567 | // should be blocked. blocking num == 1 568 | if err := p.Submit(demoFunc); err != nil { 569 | errCh <- err 570 | } 571 | wg.Done() 572 | }() 573 | time.Sleep(1 * time.Second) 574 | // already reached max blocking limit 575 | require.ErrorIsf(t, p.Submit(demoFunc), ants.ErrPoolOverload, 576 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload") 577 | // interrupt f to make blocking submit successful. 578 | close(ch) 579 | wg.Wait() 580 | select { 581 | case <-errCh: 582 | t.Fatalf("blocking submit when pool is full should not return error") 583 | default: 584 | } 585 | } 586 | 587 | func TestNonblockingSubmitWithFunc(t *testing.T) { 588 | poolSize := 10 589 | ch := make(chan struct{}) 590 | var wg sync.WaitGroup 591 | p, err := ants.NewPoolWithFunc(poolSize, func(i any) { 592 | longRunningPoolFunc(i) 593 | wg.Done() 594 | }, ants.WithNonblocking(true)) 595 | require.NoError(t, err, "create TimingPool failed: %v", err) 596 | defer p.Release() 597 | wg.Add(poolSize) 598 | for i := 0; i < poolSize-1; i++ { 599 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 600 | } 601 | // p is full now. 602 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 603 | require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload, 604 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload") 605 | // interrupt f to get an available worker 606 | close(ch) 607 | wg.Wait() 608 | wg.Add(1) 609 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 610 | wg.Wait() 611 | } 612 | 613 | func TestNonblockingSubmitWithFuncGeneric(t *testing.T) { 614 | poolSize := 10 615 | var wg sync.WaitGroup 616 | p, err := ants.NewPoolWithFuncGeneric(poolSize, func(ch chan struct{}) { 617 | longRunningPoolFuncCh(ch) 618 | wg.Done() 619 | }, ants.WithNonblocking(true)) 620 | require.NoError(t, err, "create TimingPool failed: %v", err) 621 | defer p.Release() 622 | ch := make(chan struct{}) 623 | wg.Add(poolSize) 624 | for i := 0; i < poolSize-1; i++ { 625 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 626 | } 627 | // p is full now. 628 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 629 | require.ErrorIsf(t, p.Invoke(nil), ants.ErrPoolOverload, 630 | "nonblocking submit when pool is full should get an ants.ErrPoolOverload") 631 | // interrupt f to get an available worker 632 | close(ch) 633 | wg.Wait() 634 | wg.Add(1) 635 | require.NoError(t, p.Invoke(ch), "nonblocking submit when pool is not full shouldn't return error") 636 | wg.Wait() 637 | } 638 | 639 | func TestMaxBlockingSubmitWithFunc(t *testing.T) { 640 | ch := make(chan struct{}) 641 | poolSize := 10 642 | p, err := ants.NewPoolWithFunc(poolSize, longRunningPoolFunc, ants.WithMaxBlockingTasks(1)) 643 | require.NoError(t, err, "create TimingPool failed: %v", err) 644 | defer p.Release() 645 | for i := 0; i < poolSize-1; i++ { 646 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error") 647 | } 648 | // p is full now. 649 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error") 650 | var wg sync.WaitGroup 651 | wg.Add(1) 652 | errCh := make(chan error, 1) 653 | go func() { 654 | // should be blocked. blocking num == 1 655 | if err := p.Invoke(ch); err != nil { 656 | errCh <- err 657 | } 658 | wg.Done() 659 | }() 660 | time.Sleep(1 * time.Second) 661 | // already reached max blocking limit 662 | require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload, 663 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err) 664 | // interrupt one func to make blocking submit successful. 665 | close(ch) 666 | wg.Wait() 667 | select { 668 | case <-errCh: 669 | t.Fatalf("blocking submit when pool is full should not return error") 670 | default: 671 | } 672 | } 673 | 674 | func TestMaxBlockingSubmitWithFuncGeneric(t *testing.T) { 675 | poolSize := 10 676 | p, err := ants.NewPoolWithFuncGeneric(poolSize, longRunningPoolFuncCh, ants.WithMaxBlockingTasks(1)) 677 | require.NoError(t, err, "create TimingPool failed: %v", err) 678 | defer p.Release() 679 | ch := make(chan struct{}) 680 | for i := 0; i < poolSize-1; i++ { 681 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error") 682 | } 683 | // p is full now. 684 | require.NoError(t, p.Invoke(ch), "submit when pool is not full shouldn't return error") 685 | var wg sync.WaitGroup 686 | wg.Add(1) 687 | errCh := make(chan error, 1) 688 | go func() { 689 | // should be blocked. blocking num == 1 690 | if err := p.Invoke(ch); err != nil { 691 | errCh <- err 692 | } 693 | wg.Done() 694 | }() 695 | time.Sleep(1 * time.Second) 696 | // already reached max blocking limit 697 | require.ErrorIsf(t, p.Invoke(ch), ants.ErrPoolOverload, 698 | "blocking submit when pool reach max blocking submit should return ants.ErrPoolOverload: %v", err) 699 | // interrupt one func to make blocking submit successful. 700 | close(ch) 701 | wg.Wait() 702 | select { 703 | case <-errCh: 704 | t.Fatalf("blocking submit when pool is full should not return error") 705 | default: 706 | } 707 | } 708 | 709 | func TestRebootDefaultPool(t *testing.T) { 710 | defer ants.Release() 711 | ants.Reboot() // should do nothing inside 712 | var wg sync.WaitGroup 713 | wg.Add(1) 714 | _ = ants.Submit(func() { 715 | demoFunc() 716 | wg.Done() 717 | }) 718 | wg.Wait() 719 | require.NoError(t, ants.ReleaseTimeout(time.Second)) 720 | require.ErrorIsf(t, ants.Submit(nil), ants.ErrPoolClosed, "pool should be closed") 721 | ants.Reboot() 722 | wg.Add(1) 723 | require.NoError(t, ants.Submit(func() { wg.Done() }), "pool should be rebooted") 724 | wg.Wait() 725 | } 726 | 727 | func TestRebootNewPool(t *testing.T) { 728 | var wg sync.WaitGroup 729 | p, err := ants.NewPool(10) 730 | require.NoErrorf(t, err, "create Pool failed: %v", err) 731 | defer p.Release() 732 | wg.Add(1) 733 | _ = p.Submit(func() { 734 | demoFunc() 735 | wg.Done() 736 | }) 737 | wg.Wait() 738 | require.NoError(t, p.ReleaseTimeout(time.Second)) 739 | require.ErrorIsf(t, p.Submit(nil), ants.ErrPoolClosed, "pool should be closed") 740 | p.Reboot() 741 | wg.Add(1) 742 | require.NoError(t, p.Submit(func() { wg.Done() }), "pool should be rebooted") 743 | wg.Wait() 744 | 745 | p1, err := ants.NewPoolWithFunc(10, func(i any) { 746 | demoPoolFunc(i) 747 | wg.Done() 748 | }) 749 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 750 | defer p1.Release() 751 | wg.Add(1) 752 | _ = p1.Invoke(1) 753 | wg.Wait() 754 | require.NoError(t, p1.ReleaseTimeout(time.Second)) 755 | require.ErrorIsf(t, p1.Invoke(nil), ants.ErrPoolClosed, "pool should be closed") 756 | p1.Reboot() 757 | wg.Add(1) 758 | require.NoError(t, p1.Invoke(1), "pool should be rebooted") 759 | wg.Wait() 760 | 761 | p2, err := ants.NewPoolWithFuncGeneric(10, func(i int) { 762 | demoPoolFuncInt(i) 763 | wg.Done() 764 | }) 765 | require.NoErrorf(t, err, "create TimingPoolWithFunc failed: %v", err) 766 | defer p2.Release() 767 | wg.Add(1) 768 | _ = p2.Invoke(1) 769 | wg.Wait() 770 | require.NoError(t, p2.ReleaseTimeout(time.Second)) 771 | require.ErrorIsf(t, p2.Invoke(1), ants.ErrPoolClosed, "pool should be closed") 772 | p2.Reboot() 773 | wg.Add(1) 774 | require.NoError(t, p2.Invoke(1), "pool should be rebooted") 775 | wg.Wait() 776 | } 777 | 778 | func TestInfinitePool(t *testing.T) { 779 | c := make(chan struct{}) 780 | p, _ := ants.NewPool(-1) 781 | _ = p.Submit(func() { 782 | _ = p.Submit(func() { 783 | <-c 784 | }) 785 | }) 786 | c <- struct{}{} 787 | if n := p.Running(); n != 2 { 788 | t.Errorf("expect 2 workers running, but got %d", n) 789 | } 790 | if n := p.Free(); n != -1 { 791 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n) 792 | } 793 | p.Tune(10) 794 | if capacity := p.Cap(); capacity != -1 { 795 | t.Fatalf("expect capacity: -1 but got %d", capacity) 796 | } 797 | var err error 798 | _, err = ants.NewPool(-1, ants.WithPreAlloc(true)) 799 | require.ErrorIs(t, err, ants.ErrInvalidPreAllocSize) 800 | } 801 | 802 | func testPoolWithDisablePurge(t *testing.T, p *ants.Pool, numWorker int, waitForPurge time.Duration) { 803 | sig := make(chan struct{}) 804 | var wg1, wg2 sync.WaitGroup 805 | wg1.Add(numWorker) 806 | wg2.Add(numWorker) 807 | for i := 0; i < numWorker; i++ { 808 | _ = p.Submit(func() { 809 | wg1.Done() 810 | <-sig 811 | wg2.Done() 812 | }) 813 | } 814 | wg1.Wait() 815 | 816 | runningCnt := p.Running() 817 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt) 818 | freeCnt := p.Free() 819 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt) 820 | 821 | // Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism, 822 | // we should see that all workers are still running after the sleep. 823 | close(sig) 824 | wg2.Wait() 825 | time.Sleep(waitForPurge + waitForPurge/2) 826 | 827 | runningCnt = p.Running() 828 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt) 829 | freeCnt = p.Free() 830 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt) 831 | 832 | err := p.ReleaseTimeout(waitForPurge + waitForPurge/2) 833 | require.NoErrorf(t, err, "release pool failed: %v", err) 834 | 835 | runningCnt = p.Running() 836 | require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt) 837 | freeCnt = p.Free() 838 | require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt) 839 | } 840 | 841 | func TestWithDisablePurgePool(t *testing.T) { 842 | numWorker := 10 843 | p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true)) 844 | testPoolWithDisablePurge(t, p, numWorker, ants.DefaultCleanIntervalTime) 845 | } 846 | 847 | func TestWithDisablePurgeAndWithExpirationPool(t *testing.T) { 848 | numWorker := 10 849 | expiredDuration := time.Millisecond * 100 850 | p, _ := ants.NewPool(numWorker, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration)) 851 | testPoolWithDisablePurge(t, p, numWorker, expiredDuration) 852 | } 853 | 854 | func testPoolFuncWithDisablePurge(t *testing.T, p *ants.PoolWithFunc, numWorker int, wg1, wg2 *sync.WaitGroup, sig chan struct{}, waitForPurge time.Duration) { 855 | for i := 0; i < numWorker; i++ { 856 | _ = p.Invoke(i) 857 | } 858 | wg1.Wait() 859 | 860 | runningCnt := p.Running() 861 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt) 862 | freeCnt := p.Free() 863 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt) 864 | 865 | // Finish all tasks and sleep for a while to wait for purging, since we've disabled purge mechanism, 866 | // we should see that all workers are still running after the sleep. 867 | close(sig) 868 | wg2.Wait() 869 | time.Sleep(waitForPurge + waitForPurge/2) 870 | 871 | runningCnt = p.Running() 872 | require.EqualValuesf(t, numWorker, runningCnt, "expect %d workers running, but got %d", numWorker, runningCnt) 873 | freeCnt = p.Free() 874 | require.EqualValuesf(t, 0, freeCnt, "expect %d free workers, but got %d", 0, freeCnt) 875 | 876 | err := p.ReleaseTimeout(waitForPurge + waitForPurge/2) 877 | require.NoErrorf(t, err, "release pool failed: %v", err) 878 | 879 | runningCnt = p.Running() 880 | require.EqualValuesf(t, 0, runningCnt, "expect %d workers running, but got %d", 0, runningCnt) 881 | freeCnt = p.Free() 882 | require.EqualValuesf(t, numWorker, freeCnt, "expect %d free workers, but got %d", numWorker, freeCnt) 883 | } 884 | 885 | func TestWithDisablePurgePoolFunc(t *testing.T) { 886 | numWorker := 10 887 | sig := make(chan struct{}) 888 | var wg1, wg2 sync.WaitGroup 889 | wg1.Add(numWorker) 890 | wg2.Add(numWorker) 891 | p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) { 892 | wg1.Done() 893 | <-sig 894 | wg2.Done() 895 | }, ants.WithDisablePurge(true)) 896 | testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, ants.DefaultCleanIntervalTime) 897 | } 898 | 899 | func TestWithDisablePurgeAndWithExpirationPoolFunc(t *testing.T) { 900 | numWorker := 2 901 | sig := make(chan struct{}) 902 | var wg1, wg2 sync.WaitGroup 903 | wg1.Add(numWorker) 904 | wg2.Add(numWorker) 905 | expiredDuration := time.Millisecond * 100 906 | p, _ := ants.NewPoolWithFunc(numWorker, func(_ any) { 907 | wg1.Done() 908 | <-sig 909 | wg2.Done() 910 | }, ants.WithDisablePurge(true), ants.WithExpiryDuration(expiredDuration)) 911 | testPoolFuncWithDisablePurge(t, p, numWorker, &wg1, &wg2, sig, expiredDuration) 912 | } 913 | 914 | func TestInfinitePoolWithFunc(t *testing.T) { 915 | c := make(chan struct{}) 916 | p, err := ants.NewPoolWithFunc(-1, func(i any) { 917 | demoPoolFunc(i) 918 | <-c 919 | }) 920 | require.NoErrorf(t, err, "create pool with func failed: %v", err) 921 | defer p.Release() 922 | _ = p.Invoke(10) 923 | _ = p.Invoke(10) 924 | c <- struct{}{} 925 | c <- struct{}{} 926 | if n := p.Running(); n != 2 { 927 | t.Errorf("expect 2 workers running, but got %d", n) 928 | } 929 | if n := p.Free(); n != -1 { 930 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n) 931 | } 932 | p.Tune(10) 933 | if capacity := p.Cap(); capacity != -1 { 934 | t.Fatalf("expect capacity: -1 but got %d", capacity) 935 | } 936 | _, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithPreAlloc(true)) 937 | require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err) 938 | } 939 | 940 | func TestInfinitePoolWithFuncGeneric(t *testing.T) { 941 | c := make(chan struct{}) 942 | p, err := ants.NewPoolWithFuncGeneric(-1, func(i int) { 943 | demoPoolFuncInt(i) 944 | <-c 945 | }) 946 | require.NoErrorf(t, err, "create pool with func failed: %v", err) 947 | defer p.Release() 948 | _ = p.Invoke(10) 949 | _ = p.Invoke(10) 950 | c <- struct{}{} 951 | c <- struct{}{} 952 | if n := p.Running(); n != 2 { 953 | t.Errorf("expect 2 workers running, but got %d", n) 954 | } 955 | if n := p.Free(); n != -1 { 956 | t.Errorf("expect -1 of free workers by unlimited pool, but got %d", n) 957 | } 958 | p.Tune(10) 959 | if capacity := p.Cap(); capacity != -1 { 960 | t.Fatalf("expect capacity: -1 but got %d", capacity) 961 | } 962 | _, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithPreAlloc(true)) 963 | require.ErrorIsf(t, err, ants.ErrInvalidPreAllocSize, "expect ErrInvalidPreAllocSize but got %v", err) 964 | } 965 | 966 | func TestReleaseWhenRunningPool(t *testing.T) { 967 | var wg sync.WaitGroup 968 | p, err := ants.NewPool(1) 969 | require.NoErrorf(t, err, "create pool failed: %v", err) 970 | wg.Add(2) 971 | go func() { 972 | t.Log("start aaa") 973 | defer func() { 974 | wg.Done() 975 | t.Log("stop aaa") 976 | }() 977 | for i := 0; i < 30; i++ { 978 | j := i 979 | _ = p.Submit(func() { 980 | t.Log("do task", j) 981 | time.Sleep(1 * time.Second) 982 | }) 983 | } 984 | }() 985 | 986 | go func() { 987 | t.Log("start bbb") 988 | defer func() { 989 | wg.Done() 990 | t.Log("stop bbb") 991 | }() 992 | for i := 100; i < 130; i++ { 993 | j := i 994 | _ = p.Submit(func() { 995 | t.Log("do task", j) 996 | time.Sleep(1 * time.Second) 997 | }) 998 | } 999 | }() 1000 | 1001 | time.Sleep(3 * time.Second) 1002 | p.Release() 1003 | t.Log("wait for all goroutines to exit...") 1004 | wg.Wait() 1005 | } 1006 | 1007 | func TestReleaseWhenRunningPoolWithFunc(t *testing.T) { 1008 | var wg sync.WaitGroup 1009 | p, err := ants.NewPoolWithFunc(1, func(i any) { 1010 | t.Log("do task", i) 1011 | time.Sleep(1 * time.Second) 1012 | }) 1013 | require.NoErrorf(t, err, "create pool with func failed: %v", err) 1014 | 1015 | wg.Add(2) 1016 | go func() { 1017 | t.Log("start aaa") 1018 | defer func() { 1019 | wg.Done() 1020 | t.Log("stop aaa") 1021 | }() 1022 | for i := 0; i < 30; i++ { 1023 | _ = p.Invoke(i) 1024 | } 1025 | }() 1026 | 1027 | go func() { 1028 | t.Log("start bbb") 1029 | defer func() { 1030 | wg.Done() 1031 | t.Log("stop bbb") 1032 | }() 1033 | for i := 100; i < 130; i++ { 1034 | _ = p.Invoke(i) 1035 | } 1036 | }() 1037 | 1038 | time.Sleep(3 * time.Second) 1039 | p.Release() 1040 | t.Log("wait for all goroutines to exit...") 1041 | wg.Wait() 1042 | } 1043 | 1044 | func TestReleaseWhenRunningPoolWithFuncGeneric(t *testing.T) { 1045 | var wg sync.WaitGroup 1046 | p, err := ants.NewPoolWithFuncGeneric(1, func(i int) { 1047 | t.Log("do task", i) 1048 | time.Sleep(1 * time.Second) 1049 | }) 1050 | require.NoErrorf(t, err, "create pool with func failed: %v", err) 1051 | wg.Add(2) 1052 | 1053 | go func() { 1054 | t.Log("start aaa") 1055 | defer func() { 1056 | wg.Done() 1057 | t.Log("stop aaa") 1058 | }() 1059 | for i := 0; i < 30; i++ { 1060 | _ = p.Invoke(i) 1061 | } 1062 | }() 1063 | 1064 | go func() { 1065 | t.Log("start bbb") 1066 | defer func() { 1067 | wg.Done() 1068 | t.Log("stop bbb") 1069 | }() 1070 | for i := 100; i < 130; i++ { 1071 | _ = p.Invoke(i) 1072 | } 1073 | }() 1074 | 1075 | time.Sleep(3 * time.Second) 1076 | p.Release() 1077 | t.Log("wait for all goroutines to exit...") 1078 | wg.Wait() 1079 | } 1080 | 1081 | func TestRestCodeCoverage(t *testing.T) { 1082 | _, err := ants.NewPool(-1, ants.WithExpiryDuration(-1)) 1083 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1084 | _, err = ants.NewPool(1, ants.WithExpiryDuration(-1)) 1085 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1086 | _, err = ants.NewPoolWithFunc(-1, demoPoolFunc, ants.WithExpiryDuration(-1)) 1087 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1088 | _, err = ants.NewPoolWithFunc(1, demoPoolFunc, ants.WithExpiryDuration(-1)) 1089 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1090 | _, err = ants.NewPoolWithFunc(1, nil, ants.WithExpiryDuration(-1)) 1091 | require.ErrorIs(t, err, ants.ErrLackPoolFunc) 1092 | _, err = ants.NewPoolWithFuncGeneric(-1, demoPoolFuncInt, ants.WithExpiryDuration(-1)) 1093 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1094 | _, err = ants.NewPoolWithFuncGeneric(1, demoPoolFuncInt, ants.WithExpiryDuration(-1)) 1095 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1096 | var fn func(i int) 1097 | _, err = ants.NewPoolWithFuncGeneric(1, fn, ants.WithExpiryDuration(-1)) 1098 | require.ErrorIs(t, err, ants.ErrLackPoolFunc) 1099 | 1100 | options := ants.Options{} 1101 | options.ExpiryDuration = time.Duration(10) * time.Second 1102 | options.Nonblocking = true 1103 | options.PreAlloc = true 1104 | poolOpts, _ := ants.NewPool(1, ants.WithOptions(options)) 1105 | t.Logf("Pool with options, capacity: %d", poolOpts.Cap()) 1106 | 1107 | p0, _ := ants.NewPool(TestSize, ants.WithLogger(log.New(os.Stderr, "", log.LstdFlags))) 1108 | defer func() { 1109 | _ = p0.Submit(demoFunc) 1110 | }() 1111 | defer p0.Release() 1112 | for i := 0; i < n; i++ { 1113 | _ = p0.Submit(demoFunc) 1114 | } 1115 | t.Logf("pool, capacity:%d", p0.Cap()) 1116 | t.Logf("pool, running workers number:%d", p0.Running()) 1117 | t.Logf("pool, free workers number:%d", p0.Free()) 1118 | p0.Tune(TestSize) 1119 | p0.Tune(TestSize / 10) 1120 | t.Logf("pool, after tuning capacity, capacity:%d, running:%d", p0.Cap(), p0.Running()) 1121 | 1122 | p1, _ := ants.NewPool(TestSize, ants.WithPreAlloc(true)) 1123 | defer func() { 1124 | _ = p1.Submit(demoFunc) 1125 | }() 1126 | defer p1.Release() 1127 | for i := 0; i < n; i++ { 1128 | _ = p1.Submit(demoFunc) 1129 | } 1130 | t.Logf("pre-malloc pool, capacity:%d", p1.Cap()) 1131 | t.Logf("pre-malloc pool, running workers number:%d", p1.Running()) 1132 | t.Logf("pre-malloc pool, free workers number:%d", p1.Free()) 1133 | p1.Tune(TestSize) 1134 | p1.Tune(TestSize / 10) 1135 | t.Logf("pre-malloc pool, after tuning capacity, capacity:%d, running:%d", p1.Cap(), p1.Running()) 1136 | 1137 | p2, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc) 1138 | defer func() { 1139 | _ = p2.Invoke(Param) 1140 | }() 1141 | defer p2.Release() 1142 | for i := 0; i < n; i++ { 1143 | _ = p2.Invoke(Param) 1144 | } 1145 | time.Sleep(ants.DefaultCleanIntervalTime) 1146 | t.Logf("pool with func, capacity:%d", p2.Cap()) 1147 | t.Logf("pool with func, running workers number:%d", p2.Running()) 1148 | t.Logf("pool with func, free workers number:%d", p2.Free()) 1149 | p2.Tune(TestSize) 1150 | p2.Tune(TestSize / 10) 1151 | t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p2.Cap(), p2.Running()) 1152 | 1153 | p3, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt) 1154 | defer func() { 1155 | _ = p3.Invoke(Param) 1156 | }() 1157 | defer p3.Release() 1158 | for i := 0; i < n; i++ { 1159 | _ = p3.Invoke(Param) 1160 | } 1161 | time.Sleep(ants.DefaultCleanIntervalTime) 1162 | t.Logf("pool with func, capacity:%d", p3.Cap()) 1163 | t.Logf("pool with func, running workers number:%d", p3.Running()) 1164 | t.Logf("pool with func, free workers number:%d", p3.Free()) 1165 | p3.Tune(TestSize) 1166 | p3.Tune(TestSize / 10) 1167 | t.Logf("pool with func, after tuning capacity, capacity:%d, running:%d", p3.Cap(), p3.Running()) 1168 | 1169 | p4, _ := ants.NewPoolWithFunc(TestSize, demoPoolFunc, ants.WithPreAlloc(true)) 1170 | defer func() { 1171 | _ = p4.Invoke(Param) 1172 | }() 1173 | defer p4.Release() 1174 | for i := 0; i < n; i++ { 1175 | _ = p4.Invoke(Param) 1176 | } 1177 | time.Sleep(ants.DefaultCleanIntervalTime) 1178 | t.Logf("pre-malloc pool with func, capacity:%d", p4.Cap()) 1179 | t.Logf("pre-malloc pool with func, running workers number:%d", p4.Running()) 1180 | t.Logf("pre-malloc pool with func, free workers number:%d", p4.Free()) 1181 | p4.Tune(TestSize) 1182 | p4.Tune(TestSize / 10) 1183 | t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p4.Cap(), 1184 | p4.Running()) 1185 | 1186 | p5, _ := ants.NewPoolWithFuncGeneric(TestSize, demoPoolFuncInt, ants.WithPreAlloc(true)) 1187 | defer func() { 1188 | _ = p5.Invoke(Param) 1189 | }() 1190 | defer p5.Release() 1191 | for i := 0; i < n; i++ { 1192 | _ = p5.Invoke(Param) 1193 | } 1194 | time.Sleep(ants.DefaultCleanIntervalTime) 1195 | t.Logf("pre-malloc pool with func, capacity:%d", p5.Cap()) 1196 | t.Logf("pre-malloc pool with func, running workers number:%d", p5.Running()) 1197 | t.Logf("pre-malloc pool with func, free workers number:%d", p5.Free()) 1198 | p5.Tune(TestSize) 1199 | p5.Tune(TestSize / 10) 1200 | t.Logf("pre-malloc pool with func, after tuning capacity, capacity:%d, running:%d", p5.Cap(), 1201 | p5.Running()) 1202 | } 1203 | 1204 | func TestPoolTuneScaleUp(t *testing.T) { 1205 | c := make(chan struct{}) 1206 | // Test Pool 1207 | p, _ := ants.NewPool(2) 1208 | for i := 0; i < 2; i++ { 1209 | _ = p.Submit(func() { 1210 | <-c 1211 | }) 1212 | } 1213 | n := p.Running() 1214 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", p.Running()) 1215 | // test pool tune scale up one 1216 | p.Tune(3) 1217 | _ = p.Submit(func() { 1218 | <-c 1219 | }) 1220 | n = p.Running() 1221 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n) 1222 | // test pool tune scale up multiple 1223 | var wg sync.WaitGroup 1224 | for i := 0; i < 5; i++ { 1225 | wg.Add(1) 1226 | go func() { 1227 | defer wg.Done() 1228 | _ = p.Submit(func() { 1229 | <-c 1230 | }) 1231 | }() 1232 | } 1233 | p.Tune(8) 1234 | wg.Wait() 1235 | n = p.Running() 1236 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n) 1237 | for i := 0; i < 8; i++ { 1238 | c <- struct{}{} 1239 | } 1240 | p.Release() 1241 | 1242 | // Test PoolWithFunc 1243 | pf, _ := ants.NewPoolWithFunc(2, func(_ any) { 1244 | <-c 1245 | }) 1246 | for i := 0; i < 2; i++ { 1247 | _ = pf.Invoke(1) 1248 | } 1249 | n = pf.Running() 1250 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n) 1251 | // test pool tune scale up one 1252 | pf.Tune(3) 1253 | _ = pf.Invoke(1) 1254 | n = pf.Running() 1255 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n) 1256 | // test pool tune scale up multiple 1257 | for i := 0; i < 5; i++ { 1258 | wg.Add(1) 1259 | go func() { 1260 | defer wg.Done() 1261 | _ = pf.Invoke(1) 1262 | }() 1263 | } 1264 | pf.Tune(8) 1265 | wg.Wait() 1266 | n = pf.Running() 1267 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n) 1268 | for i := 0; i < 8; i++ { 1269 | c <- struct{}{} 1270 | } 1271 | pf.Release() 1272 | 1273 | // Test PoolWithFuncGeneric 1274 | pfg, _ := ants.NewPoolWithFuncGeneric(2, func(_ int) { 1275 | <-c 1276 | }) 1277 | for i := 0; i < 2; i++ { 1278 | _ = pfg.Invoke(1) 1279 | } 1280 | n = pfg.Running() 1281 | require.EqualValuesf(t, 2, n, "expect 2 workers running, but got %d", n) 1282 | // test pool tune scale up one 1283 | pfg.Tune(3) 1284 | _ = pfg.Invoke(1) 1285 | n = pfg.Running() 1286 | require.EqualValuesf(t, 3, n, "expect 3 workers running, but got %d", n) 1287 | // test pool tune scale up multiple 1288 | for i := 0; i < 5; i++ { 1289 | wg.Add(1) 1290 | go func() { 1291 | defer wg.Done() 1292 | _ = pfg.Invoke(1) 1293 | }() 1294 | } 1295 | pfg.Tune(8) 1296 | wg.Wait() 1297 | n = pfg.Running() 1298 | require.EqualValuesf(t, 8, n, "expect 8 workers running, but got %d", n) 1299 | for i := 0; i < 8; i++ { 1300 | c <- struct{}{} 1301 | } 1302 | close(c) 1303 | pfg.Release() 1304 | } 1305 | 1306 | func TestReleaseTimeout(t *testing.T) { 1307 | p, err := ants.NewPool(10) 1308 | require.NoError(t, err) 1309 | for i := 0; i < 5; i++ { 1310 | _ = p.Submit(func() { 1311 | time.Sleep(time.Second) 1312 | }) 1313 | } 1314 | require.NotZero(t, p.Running()) 1315 | err = p.ReleaseTimeout(2 * time.Second) 1316 | require.NoError(t, err) 1317 | 1318 | pf, err := ants.NewPoolWithFunc(10, func(i any) { 1319 | dur := i.(time.Duration) 1320 | time.Sleep(dur) 1321 | }) 1322 | require.NoError(t, err) 1323 | for i := 0; i < 5; i++ { 1324 | _ = pf.Invoke(time.Second) 1325 | } 1326 | require.NotZero(t, pf.Running()) 1327 | err = pf.ReleaseTimeout(2 * time.Second) 1328 | require.NoError(t, err) 1329 | 1330 | pfg, err := ants.NewPoolWithFuncGeneric(10, func(d time.Duration) { 1331 | time.Sleep(d) 1332 | }) 1333 | require.NoError(t, err) 1334 | for i := 0; i < 5; i++ { 1335 | _ = pfg.Invoke(time.Second) 1336 | } 1337 | require.NotZero(t, pfg.Running()) 1338 | err = pfg.ReleaseTimeout(2 * time.Second) 1339 | require.NoError(t, err) 1340 | } 1341 | 1342 | func TestDefaultPoolReleaseTimeout(t *testing.T) { 1343 | ants.Reboot() // should do nothing inside 1344 | for i := 0; i < 5; i++ { 1345 | _ = ants.Submit(func() { 1346 | time.Sleep(time.Second) 1347 | }) 1348 | } 1349 | require.NotZero(t, ants.Running()) 1350 | err := ants.ReleaseTimeout(2 * time.Second) 1351 | require.NoError(t, err) 1352 | } 1353 | 1354 | func TestMultiPool(t *testing.T) { 1355 | _, err := ants.NewMultiPool(-1, 10, 8) 1356 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize) 1357 | _, err = ants.NewMultiPool(10, -1, 8) 1358 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy) 1359 | _, err = ants.NewMultiPool(10, 10, ants.RoundRobin, ants.WithExpiryDuration(-1)) 1360 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1361 | 1362 | mp, err := ants.NewMultiPool(10, 5, ants.RoundRobin) 1363 | testFn := func() { 1364 | for i := 0; i < 50; i++ { 1365 | err = mp.Submit(longRunningFunc) 1366 | require.NoError(t, err) 1367 | } 1368 | require.EqualValues(t, mp.Waiting(), 0) 1369 | _, err = mp.WaitingByIndex(-1) 1370 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1371 | _, err = mp.WaitingByIndex(11) 1372 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1373 | require.EqualValues(t, 50, mp.Running()) 1374 | _, err = mp.RunningByIndex(-1) 1375 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1376 | _, err = mp.RunningByIndex(11) 1377 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1378 | require.EqualValues(t, 0, mp.Free()) 1379 | _, err = mp.FreeByIndex(-1) 1380 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1381 | _, err = mp.FreeByIndex(11) 1382 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1383 | require.EqualValues(t, 50, mp.Cap()) 1384 | require.False(t, mp.IsClosed()) 1385 | for i := 0; i < 10; i++ { 1386 | n, _ := mp.WaitingByIndex(i) 1387 | require.EqualValues(t, 0, n) 1388 | n, _ = mp.RunningByIndex(i) 1389 | require.EqualValues(t, 5, n) 1390 | n, _ = mp.FreeByIndex(i) 1391 | require.EqualValues(t, 0, n) 1392 | } 1393 | atomic.StoreInt32(&stopLongRunningFunc, 1) 1394 | require.NoError(t, mp.ReleaseTimeout(3*time.Second)) 1395 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed) 1396 | require.ErrorIs(t, mp.Submit(nil), ants.ErrPoolClosed) 1397 | require.Zero(t, mp.Running()) 1398 | require.True(t, mp.IsClosed()) 1399 | atomic.StoreInt32(&stopLongRunningFunc, 0) 1400 | } 1401 | testFn() 1402 | 1403 | mp.Reboot() 1404 | testFn() 1405 | 1406 | mp, err = ants.NewMultiPool(10, 5, ants.LeastTasks) 1407 | testFn() 1408 | 1409 | mp.Reboot() 1410 | testFn() 1411 | 1412 | mp.Tune(10) 1413 | } 1414 | 1415 | func TestMultiPoolWithFunc(t *testing.T) { 1416 | _, err := ants.NewMultiPoolWithFunc(-1, 10, longRunningPoolFunc, 8) 1417 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize) 1418 | _, err = ants.NewMultiPoolWithFunc(10, -1, longRunningPoolFunc, 8) 1419 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy) 1420 | _, err = ants.NewMultiPoolWithFunc(10, 10, longRunningPoolFunc, ants.RoundRobin, ants.WithExpiryDuration(-1)) 1421 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1422 | 1423 | ch := make(chan struct{}) 1424 | mp, err := ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.RoundRobin) 1425 | testFn := func() { 1426 | for i := 0; i < 50; i++ { 1427 | err = mp.Invoke(ch) 1428 | require.NoError(t, err) 1429 | } 1430 | require.EqualValues(t, mp.Waiting(), 0) 1431 | _, err = mp.WaitingByIndex(-1) 1432 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1433 | _, err = mp.WaitingByIndex(11) 1434 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1435 | require.EqualValues(t, 50, mp.Running()) 1436 | _, err = mp.RunningByIndex(-1) 1437 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1438 | _, err = mp.RunningByIndex(11) 1439 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1440 | require.EqualValues(t, 0, mp.Free()) 1441 | _, err = mp.FreeByIndex(-1) 1442 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1443 | _, err = mp.FreeByIndex(11) 1444 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1445 | require.EqualValues(t, 50, mp.Cap()) 1446 | require.False(t, mp.IsClosed()) 1447 | for i := 0; i < 10; i++ { 1448 | n, _ := mp.WaitingByIndex(i) 1449 | require.EqualValues(t, 0, n) 1450 | n, _ = mp.RunningByIndex(i) 1451 | require.EqualValues(t, 5, n) 1452 | n, _ = mp.FreeByIndex(i) 1453 | require.EqualValues(t, 0, n) 1454 | } 1455 | close(ch) 1456 | require.NoError(t, mp.ReleaseTimeout(3*time.Second)) 1457 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed) 1458 | require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed) 1459 | require.Zero(t, mp.Running()) 1460 | require.True(t, mp.IsClosed()) 1461 | ch = make(chan struct{}) 1462 | } 1463 | testFn() 1464 | 1465 | mp.Reboot() 1466 | testFn() 1467 | 1468 | mp, err = ants.NewMultiPoolWithFunc(10, 5, longRunningPoolFunc, ants.LeastTasks) 1469 | testFn() 1470 | 1471 | mp.Reboot() 1472 | testFn() 1473 | 1474 | mp.Tune(10) 1475 | } 1476 | 1477 | func TestMultiPoolWithFuncGeneric(t *testing.T) { 1478 | _, err := ants.NewMultiPoolWithFuncGeneric(-1, 10, longRunningPoolFuncCh, 8) 1479 | require.ErrorIs(t, err, ants.ErrInvalidMultiPoolSize) 1480 | _, err = ants.NewMultiPoolWithFuncGeneric(10, -1, longRunningPoolFuncCh, 8) 1481 | require.ErrorIs(t, err, ants.ErrInvalidLoadBalancingStrategy) 1482 | _, err = ants.NewMultiPoolWithFuncGeneric(10, 10, longRunningPoolFuncCh, ants.RoundRobin, ants.WithExpiryDuration(-1)) 1483 | require.ErrorIs(t, err, ants.ErrInvalidPoolExpiry) 1484 | 1485 | ch := make(chan struct{}) 1486 | mp, err := ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.RoundRobin) 1487 | testFn := func() { 1488 | for i := 0; i < 50; i++ { 1489 | err = mp.Invoke(ch) 1490 | require.NoError(t, err) 1491 | } 1492 | require.EqualValues(t, mp.Waiting(), 0) 1493 | _, err = mp.WaitingByIndex(-1) 1494 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1495 | _, err = mp.WaitingByIndex(11) 1496 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1497 | require.EqualValues(t, 50, mp.Running()) 1498 | _, err = mp.RunningByIndex(-1) 1499 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1500 | _, err = mp.RunningByIndex(11) 1501 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1502 | require.EqualValues(t, 0, mp.Free()) 1503 | _, err = mp.FreeByIndex(-1) 1504 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1505 | _, err = mp.FreeByIndex(11) 1506 | require.ErrorIs(t, err, ants.ErrInvalidPoolIndex) 1507 | require.EqualValues(t, 50, mp.Cap()) 1508 | require.False(t, mp.IsClosed()) 1509 | for i := 0; i < 10; i++ { 1510 | n, _ := mp.WaitingByIndex(i) 1511 | require.EqualValues(t, 0, n) 1512 | n, _ = mp.RunningByIndex(i) 1513 | require.EqualValues(t, 5, n) 1514 | n, _ = mp.FreeByIndex(i) 1515 | require.EqualValues(t, 0, n) 1516 | } 1517 | close(ch) 1518 | require.NoError(t, mp.ReleaseTimeout(3*time.Second)) 1519 | require.ErrorIs(t, mp.ReleaseTimeout(3*time.Second), ants.ErrPoolClosed) 1520 | require.ErrorIs(t, mp.Invoke(nil), ants.ErrPoolClosed) 1521 | require.Zero(t, mp.Running()) 1522 | require.True(t, mp.IsClosed()) 1523 | ch = make(chan struct{}) 1524 | } 1525 | testFn() 1526 | 1527 | mp.Reboot() 1528 | testFn() 1529 | 1530 | mp, err = ants.NewMultiPoolWithFuncGeneric(10, 5, longRunningPoolFuncCh, ants.LeastTasks) 1531 | testFn() 1532 | 1533 | mp.Reboot() 1534 | testFn() 1535 | 1536 | mp.Tune(10) 1537 | } 1538 | 1539 | func TestRebootNewPoolCalc(t *testing.T) { 1540 | atomic.StoreInt32(&sum, 0) 1541 | runTimes := 1000 1542 | wg.Add(runTimes) 1543 | 1544 | pool, err := ants.NewPool(10) 1545 | require.NoError(t, err) 1546 | defer pool.Release() 1547 | // Use the default pool. 1548 | for i := 0; i < runTimes; i++ { 1549 | j := i 1550 | _ = pool.Submit(func() { 1551 | incSumInt(int32(j)) 1552 | }) 1553 | } 1554 | wg.Wait() 1555 | require.EqualValues(t, 499500, sum, "The result should be 499500") 1556 | 1557 | atomic.StoreInt32(&sum, 0) 1558 | wg.Add(runTimes) 1559 | err = pool.ReleaseTimeout(time.Second) // use both Release and ReleaseTimeout will occur panic 1560 | require.NoError(t, err) 1561 | pool.Reboot() 1562 | 1563 | for i := 0; i < runTimes; i++ { 1564 | j := i 1565 | _ = pool.Submit(func() { 1566 | incSumInt(int32(j)) 1567 | }) 1568 | } 1569 | wg.Wait() 1570 | require.EqualValues(t, 499500, sum, "The result should be 499500") 1571 | } 1572 | 1573 | func TestRebootNewPoolWithPreAllocCalc(t *testing.T) { 1574 | atomic.StoreInt32(&sum, 0) 1575 | runTimes := 1000 1576 | wg.Add(runTimes) 1577 | 1578 | pool, err := ants.NewPool(10, ants.WithPreAlloc(true)) 1579 | require.NoError(t, err) 1580 | defer pool.Release() 1581 | // Use the default pool. 1582 | for i := 0; i < runTimes; i++ { 1583 | j := i 1584 | _ = pool.Submit(func() { 1585 | incSumInt(int32(j)) 1586 | }) 1587 | } 1588 | wg.Wait() 1589 | require.EqualValues(t, 499500, sum, "The result should be 499500") 1590 | 1591 | atomic.StoreInt32(&sum, 0) 1592 | err = pool.ReleaseTimeout(time.Second) 1593 | require.NoError(t, err) 1594 | pool.Reboot() 1595 | 1596 | wg.Add(runTimes) 1597 | for i := 0; i < runTimes; i++ { 1598 | j := i 1599 | _ = pool.Submit(func() { 1600 | incSumInt(int32(j)) 1601 | }) 1602 | } 1603 | wg.Wait() 1604 | require.EqualValues(t, 499500, sum, "The result should be 499500") 1605 | } 1606 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025. Andy Pan. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants_test 24 | 25 | import ( 26 | "fmt" 27 | "sync" 28 | "sync/atomic" 29 | "time" 30 | 31 | "github.com/panjf2000/ants/v2" 32 | ) 33 | 34 | var ( 35 | sum int32 36 | wg sync.WaitGroup 37 | ) 38 | 39 | func incSum(i any) { 40 | incSumInt(i.(int32)) 41 | } 42 | 43 | func incSumInt(i int32) { 44 | atomic.AddInt32(&sum, i) 45 | wg.Done() 46 | } 47 | 48 | func ExamplePool() { 49 | ants.Reboot() // ensure the default pool is available 50 | 51 | atomic.StoreInt32(&sum, 0) 52 | runTimes := 1000 53 | wg.Add(runTimes) 54 | // Use the default pool. 55 | for i := 0; i < runTimes; i++ { 56 | j := i 57 | _ = ants.Submit(func() { 58 | incSumInt(int32(j)) 59 | }) 60 | } 61 | wg.Wait() 62 | fmt.Printf("The result is %d\n", sum) 63 | 64 | atomic.StoreInt32(&sum, 0) 65 | wg.Add(runTimes) 66 | // Use the new pool. 67 | pool, _ := ants.NewPool(10) 68 | defer pool.Release() 69 | for i := 0; i < runTimes; i++ { 70 | j := i 71 | _ = pool.Submit(func() { 72 | incSumInt(int32(j)) 73 | }) 74 | } 75 | wg.Wait() 76 | fmt.Printf("The result is %d\n", sum) 77 | 78 | // Output: 79 | // The result is 499500 80 | // The result is 499500 81 | } 82 | 83 | func ExamplePoolWithFunc() { 84 | atomic.StoreInt32(&sum, 0) 85 | runTimes := 1000 86 | wg.Add(runTimes) 87 | 88 | pool, _ := ants.NewPoolWithFunc(10, incSum) 89 | defer pool.Release() 90 | 91 | for i := 0; i < runTimes; i++ { 92 | _ = pool.Invoke(int32(i)) 93 | } 94 | wg.Wait() 95 | 96 | fmt.Printf("The result is %d\n", sum) 97 | 98 | // Output: The result is 499500 99 | } 100 | 101 | func ExamplePoolWithFuncGeneric() { 102 | atomic.StoreInt32(&sum, 0) 103 | runTimes := 1000 104 | wg.Add(runTimes) 105 | 106 | pool, _ := ants.NewPoolWithFuncGeneric(10, incSumInt) 107 | defer pool.Release() 108 | 109 | for i := 0; i < runTimes; i++ { 110 | _ = pool.Invoke(int32(i)) 111 | } 112 | wg.Wait() 113 | 114 | fmt.Printf("The result is %d\n", sum) 115 | 116 | // Output: The result is 499500 117 | } 118 | 119 | func ExampleMultiPool() { 120 | atomic.StoreInt32(&sum, 0) 121 | runTimes := 1000 122 | wg.Add(runTimes) 123 | 124 | mp, _ := ants.NewMultiPool(10, runTimes/10, ants.RoundRobin) 125 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck 126 | 127 | for i := 0; i < runTimes; i++ { 128 | j := i 129 | _ = mp.Submit(func() { 130 | incSumInt(int32(j)) 131 | }) 132 | } 133 | wg.Wait() 134 | 135 | fmt.Printf("The result is %d\n", sum) 136 | 137 | // Output: The result is 499500 138 | } 139 | 140 | func ExampleMultiPoolWithFunc() { 141 | atomic.StoreInt32(&sum, 0) 142 | runTimes := 1000 143 | wg.Add(runTimes) 144 | 145 | mp, _ := ants.NewMultiPoolWithFunc(10, runTimes/10, incSum, ants.RoundRobin) 146 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck 147 | 148 | for i := 0; i < runTimes; i++ { 149 | _ = mp.Invoke(int32(i)) 150 | } 151 | wg.Wait() 152 | 153 | fmt.Printf("The result is %d\n", sum) 154 | 155 | // Output: The result is 499500 156 | } 157 | 158 | func ExampleMultiPoolWithFuncGeneric() { 159 | atomic.StoreInt32(&sum, 0) 160 | runTimes := 1000 161 | wg.Add(runTimes) 162 | 163 | mp, _ := ants.NewMultiPoolWithFuncGeneric(10, runTimes/10, incSumInt, ants.RoundRobin) 164 | defer mp.ReleaseTimeout(time.Second) // nolint:errcheck 165 | 166 | for i := 0; i < runTimes; i++ { 167 | _ = mp.Invoke(int32(i)) 168 | } 169 | wg.Wait() 170 | 171 | fmt.Printf("The result is %d\n", sum) 172 | 173 | // Output: The result is 499500 174 | } 175 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/panjf2000/ants/v2 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.10.0 7 | golang.org/x/sync v0.11.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 6 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 8 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /multipool.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "errors" 27 | "fmt" 28 | "math" 29 | "strings" 30 | "sync/atomic" 31 | "time" 32 | 33 | "golang.org/x/sync/errgroup" 34 | ) 35 | 36 | // LoadBalancingStrategy represents the type of load-balancing algorithm. 37 | type LoadBalancingStrategy int 38 | 39 | const ( 40 | // RoundRobin distributes task to a list of pools in rotation. 41 | RoundRobin LoadBalancingStrategy = 1 << (iota + 1) 42 | 43 | // LeastTasks always selects the pool with the least number of pending tasks. 44 | LeastTasks 45 | ) 46 | 47 | // MultiPool consists of multiple pools, from which you will benefit the 48 | // performance improvement on basis of the fine-grained locking that reduces 49 | // the lock contention. 50 | // MultiPool is a good fit for the scenario where you have a large number of 51 | // tasks to submit, and you don't want the single pool to be the bottleneck. 52 | type MultiPool struct { 53 | pools []*Pool 54 | index uint32 55 | state int32 56 | lbs LoadBalancingStrategy 57 | } 58 | 59 | // NewMultiPool instantiates a MultiPool with a size of the pool list and a size 60 | // per pool, and the load-balancing strategy. 61 | func NewMultiPool(size, sizePerPool int, lbs LoadBalancingStrategy, options ...Option) (*MultiPool, error) { 62 | if size <= 0 { 63 | return nil, ErrInvalidMultiPoolSize 64 | } 65 | 66 | if lbs != RoundRobin && lbs != LeastTasks { 67 | return nil, ErrInvalidLoadBalancingStrategy 68 | } 69 | pools := make([]*Pool, size) 70 | for i := 0; i < size; i++ { 71 | pool, err := NewPool(sizePerPool, options...) 72 | if err != nil { 73 | return nil, err 74 | } 75 | pools[i] = pool 76 | } 77 | return &MultiPool{pools: pools, index: math.MaxUint32, lbs: lbs}, nil 78 | } 79 | 80 | func (mp *MultiPool) next(lbs LoadBalancingStrategy) (idx int) { 81 | switch lbs { 82 | case RoundRobin: 83 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools))) 84 | case LeastTasks: 85 | leastTasks := 1<<31 - 1 86 | for i, pool := range mp.pools { 87 | if n := pool.Running(); n < leastTasks { 88 | leastTasks = n 89 | idx = i 90 | } 91 | } 92 | return 93 | } 94 | return -1 95 | } 96 | 97 | // Submit submits a task to a pool selected by the load-balancing strategy. 98 | func (mp *MultiPool) Submit(task func()) (err error) { 99 | if mp.IsClosed() { 100 | return ErrPoolClosed 101 | } 102 | if err = mp.pools[mp.next(mp.lbs)].Submit(task); err == nil { 103 | return 104 | } 105 | if err == ErrPoolOverload && mp.lbs == RoundRobin { 106 | return mp.pools[mp.next(LeastTasks)].Submit(task) 107 | } 108 | return 109 | } 110 | 111 | // Running returns the number of the currently running workers across all pools. 112 | func (mp *MultiPool) Running() (n int) { 113 | for _, pool := range mp.pools { 114 | n += pool.Running() 115 | } 116 | return 117 | } 118 | 119 | // RunningByIndex returns the number of the currently running workers in the specific pool. 120 | func (mp *MultiPool) RunningByIndex(idx int) (int, error) { 121 | if idx < 0 || idx >= len(mp.pools) { 122 | return -1, ErrInvalidPoolIndex 123 | } 124 | return mp.pools[idx].Running(), nil 125 | } 126 | 127 | // Free returns the number of available workers across all pools. 128 | func (mp *MultiPool) Free() (n int) { 129 | for _, pool := range mp.pools { 130 | n += pool.Free() 131 | } 132 | return 133 | } 134 | 135 | // FreeByIndex returns the number of available workers in the specific pool. 136 | func (mp *MultiPool) FreeByIndex(idx int) (int, error) { 137 | if idx < 0 || idx >= len(mp.pools) { 138 | return -1, ErrInvalidPoolIndex 139 | } 140 | return mp.pools[idx].Free(), nil 141 | } 142 | 143 | // Waiting returns the number of the currently waiting tasks across all pools. 144 | func (mp *MultiPool) Waiting() (n int) { 145 | for _, pool := range mp.pools { 146 | n += pool.Waiting() 147 | } 148 | return 149 | } 150 | 151 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool. 152 | func (mp *MultiPool) WaitingByIndex(idx int) (int, error) { 153 | if idx < 0 || idx >= len(mp.pools) { 154 | return -1, ErrInvalidPoolIndex 155 | } 156 | return mp.pools[idx].Waiting(), nil 157 | } 158 | 159 | // Cap returns the capacity of this multi-pool. 160 | func (mp *MultiPool) Cap() (n int) { 161 | for _, pool := range mp.pools { 162 | n += pool.Cap() 163 | } 164 | return 165 | } 166 | 167 | // Tune resizes each pool in multi-pool. 168 | // 169 | // Note that this method doesn't resize the overall 170 | // capacity of multi-pool. 171 | func (mp *MultiPool) Tune(size int) { 172 | for _, pool := range mp.pools { 173 | pool.Tune(size) 174 | } 175 | } 176 | 177 | // IsClosed indicates whether the multi-pool is closed. 178 | func (mp *MultiPool) IsClosed() bool { 179 | return atomic.LoadInt32(&mp.state) == CLOSED 180 | } 181 | 182 | // ReleaseTimeout closes the multi-pool with a timeout, 183 | // it waits all pools to be closed before timing out. 184 | func (mp *MultiPool) ReleaseTimeout(timeout time.Duration) error { 185 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) { 186 | return ErrPoolClosed 187 | } 188 | 189 | errCh := make(chan error, len(mp.pools)) 190 | var wg errgroup.Group 191 | for i, pool := range mp.pools { 192 | func(p *Pool, idx int) { 193 | wg.Go(func() error { 194 | err := p.ReleaseTimeout(timeout) 195 | if err != nil { 196 | err = fmt.Errorf("pool %d: %v", idx, err) 197 | } 198 | errCh <- err 199 | return err 200 | }) 201 | }(pool, i) 202 | } 203 | 204 | _ = wg.Wait() 205 | 206 | var errStr strings.Builder 207 | for i := 0; i < len(mp.pools); i++ { 208 | if err := <-errCh; err != nil { 209 | errStr.WriteString(err.Error()) 210 | errStr.WriteString(" | ") 211 | } 212 | } 213 | 214 | if errStr.Len() == 0 { 215 | return nil 216 | } 217 | 218 | return errors.New(strings.TrimSuffix(errStr.String(), " | ")) 219 | } 220 | 221 | // Reboot reboots a released multi-pool. 222 | func (mp *MultiPool) Reboot() { 223 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) { 224 | atomic.StoreUint32(&mp.index, 0) 225 | for _, pool := range mp.pools { 226 | pool.Reboot() 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /multipool_func.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2023 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "errors" 27 | "fmt" 28 | "math" 29 | "strings" 30 | "sync/atomic" 31 | "time" 32 | 33 | "golang.org/x/sync/errgroup" 34 | ) 35 | 36 | // MultiPoolWithFunc consists of multiple pools, from which you will benefit the 37 | // performance improvement on basis of the fine-grained locking that reduces 38 | // the lock contention. 39 | // MultiPoolWithFunc is a good fit for the scenario where you have a large number of 40 | // tasks to submit, and you don't want the single pool to be the bottleneck. 41 | type MultiPoolWithFunc struct { 42 | pools []*PoolWithFunc 43 | index uint32 44 | state int32 45 | lbs LoadBalancingStrategy 46 | } 47 | 48 | // NewMultiPoolWithFunc instantiates a MultiPoolWithFunc with a size of the pool list and a size 49 | // per pool, and the load-balancing strategy. 50 | func NewMultiPoolWithFunc(size, sizePerPool int, fn func(any), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFunc, error) { 51 | if size <= 0 { 52 | return nil, ErrInvalidMultiPoolSize 53 | } 54 | 55 | if lbs != RoundRobin && lbs != LeastTasks { 56 | return nil, ErrInvalidLoadBalancingStrategy 57 | } 58 | pools := make([]*PoolWithFunc, size) 59 | for i := 0; i < size; i++ { 60 | pool, err := NewPoolWithFunc(sizePerPool, fn, options...) 61 | if err != nil { 62 | return nil, err 63 | } 64 | pools[i] = pool 65 | } 66 | return &MultiPoolWithFunc{pools: pools, index: math.MaxUint32, lbs: lbs}, nil 67 | } 68 | 69 | func (mp *MultiPoolWithFunc) next(lbs LoadBalancingStrategy) (idx int) { 70 | switch lbs { 71 | case RoundRobin: 72 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools))) 73 | case LeastTasks: 74 | leastTasks := 1<<31 - 1 75 | for i, pool := range mp.pools { 76 | if n := pool.Running(); n < leastTasks { 77 | leastTasks = n 78 | idx = i 79 | } 80 | } 81 | return 82 | } 83 | return -1 84 | } 85 | 86 | // Invoke submits a task to a pool selected by the load-balancing strategy. 87 | func (mp *MultiPoolWithFunc) Invoke(args any) (err error) { 88 | if mp.IsClosed() { 89 | return ErrPoolClosed 90 | } 91 | 92 | if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil { 93 | return 94 | } 95 | if err == ErrPoolOverload && mp.lbs == RoundRobin { 96 | return mp.pools[mp.next(LeastTasks)].Invoke(args) 97 | } 98 | return 99 | } 100 | 101 | // Running returns the number of the currently running workers across all pools. 102 | func (mp *MultiPoolWithFunc) Running() (n int) { 103 | for _, pool := range mp.pools { 104 | n += pool.Running() 105 | } 106 | return 107 | } 108 | 109 | // RunningByIndex returns the number of the currently running workers in the specific pool. 110 | func (mp *MultiPoolWithFunc) RunningByIndex(idx int) (int, error) { 111 | if idx < 0 || idx >= len(mp.pools) { 112 | return -1, ErrInvalidPoolIndex 113 | } 114 | return mp.pools[idx].Running(), nil 115 | } 116 | 117 | // Free returns the number of available workers across all pools. 118 | func (mp *MultiPoolWithFunc) Free() (n int) { 119 | for _, pool := range mp.pools { 120 | n += pool.Free() 121 | } 122 | return 123 | } 124 | 125 | // FreeByIndex returns the number of available workers in the specific pool. 126 | func (mp *MultiPoolWithFunc) FreeByIndex(idx int) (int, error) { 127 | if idx < 0 || idx >= len(mp.pools) { 128 | return -1, ErrInvalidPoolIndex 129 | } 130 | return mp.pools[idx].Free(), nil 131 | } 132 | 133 | // Waiting returns the number of the currently waiting tasks across all pools. 134 | func (mp *MultiPoolWithFunc) Waiting() (n int) { 135 | for _, pool := range mp.pools { 136 | n += pool.Waiting() 137 | } 138 | return 139 | } 140 | 141 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool. 142 | func (mp *MultiPoolWithFunc) WaitingByIndex(idx int) (int, error) { 143 | if idx < 0 || idx >= len(mp.pools) { 144 | return -1, ErrInvalidPoolIndex 145 | } 146 | return mp.pools[idx].Waiting(), nil 147 | } 148 | 149 | // Cap returns the capacity of this multi-pool. 150 | func (mp *MultiPoolWithFunc) Cap() (n int) { 151 | for _, pool := range mp.pools { 152 | n += pool.Cap() 153 | } 154 | return 155 | } 156 | 157 | // Tune resizes each pool in multi-pool. 158 | // 159 | // Note that this method doesn't resize the overall 160 | // capacity of multi-pool. 161 | func (mp *MultiPoolWithFunc) Tune(size int) { 162 | for _, pool := range mp.pools { 163 | pool.Tune(size) 164 | } 165 | } 166 | 167 | // IsClosed indicates whether the multi-pool is closed. 168 | func (mp *MultiPoolWithFunc) IsClosed() bool { 169 | return atomic.LoadInt32(&mp.state) == CLOSED 170 | } 171 | 172 | // ReleaseTimeout closes the multi-pool with a timeout, 173 | // it waits all pools to be closed before timing out. 174 | func (mp *MultiPoolWithFunc) ReleaseTimeout(timeout time.Duration) error { 175 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) { 176 | return ErrPoolClosed 177 | } 178 | 179 | errCh := make(chan error, len(mp.pools)) 180 | var wg errgroup.Group 181 | for i, pool := range mp.pools { 182 | func(p *PoolWithFunc, idx int) { 183 | wg.Go(func() error { 184 | err := p.ReleaseTimeout(timeout) 185 | if err != nil { 186 | err = fmt.Errorf("pool %d: %v", idx, err) 187 | } 188 | errCh <- err 189 | return err 190 | }) 191 | }(pool, i) 192 | } 193 | 194 | _ = wg.Wait() 195 | 196 | var errStr strings.Builder 197 | for i := 0; i < len(mp.pools); i++ { 198 | if err := <-errCh; err != nil { 199 | errStr.WriteString(err.Error()) 200 | errStr.WriteString(" | ") 201 | } 202 | } 203 | 204 | if errStr.Len() == 0 { 205 | return nil 206 | } 207 | 208 | return errors.New(strings.TrimSuffix(errStr.String(), " | ")) 209 | } 210 | 211 | // Reboot reboots a released multi-pool. 212 | func (mp *MultiPoolWithFunc) Reboot() { 213 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) { 214 | atomic.StoreUint32(&mp.index, 0) 215 | for _, pool := range mp.pools { 216 | pool.Reboot() 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /multipool_func_generic.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2025 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "errors" 27 | "fmt" 28 | "math" 29 | "strings" 30 | "sync/atomic" 31 | "time" 32 | 33 | "golang.org/x/sync/errgroup" 34 | ) 35 | 36 | // MultiPoolWithFuncGeneric is the generic version of MultiPoolWithFunc. 37 | type MultiPoolWithFuncGeneric[T any] struct { 38 | pools []*PoolWithFuncGeneric[T] 39 | index uint32 40 | state int32 41 | lbs LoadBalancingStrategy 42 | } 43 | 44 | // NewMultiPoolWithFuncGeneric instantiates a MultiPoolWithFunc with a size of the pool list and a size 45 | // per pool, and the load-balancing strategy. 46 | func NewMultiPoolWithFuncGeneric[T any](size, sizePerPool int, fn func(T), lbs LoadBalancingStrategy, options ...Option) (*MultiPoolWithFuncGeneric[T], error) { 47 | if size <= 0 { 48 | return nil, ErrInvalidMultiPoolSize 49 | } 50 | 51 | if lbs != RoundRobin && lbs != LeastTasks { 52 | return nil, ErrInvalidLoadBalancingStrategy 53 | } 54 | pools := make([]*PoolWithFuncGeneric[T], size) 55 | for i := 0; i < size; i++ { 56 | pool, err := NewPoolWithFuncGeneric(sizePerPool, fn, options...) 57 | if err != nil { 58 | return nil, err 59 | } 60 | pools[i] = pool 61 | } 62 | return &MultiPoolWithFuncGeneric[T]{pools: pools, index: math.MaxUint32, lbs: lbs}, nil 63 | } 64 | 65 | func (mp *MultiPoolWithFuncGeneric[T]) next(lbs LoadBalancingStrategy) (idx int) { 66 | switch lbs { 67 | case RoundRobin: 68 | return int(atomic.AddUint32(&mp.index, 1) % uint32(len(mp.pools))) 69 | case LeastTasks: 70 | leastTasks := 1<<31 - 1 71 | for i, pool := range mp.pools { 72 | if n := pool.Running(); n < leastTasks { 73 | leastTasks = n 74 | idx = i 75 | } 76 | } 77 | return 78 | } 79 | return -1 80 | } 81 | 82 | // Invoke submits a task to a pool selected by the load-balancing strategy. 83 | func (mp *MultiPoolWithFuncGeneric[T]) Invoke(args T) (err error) { 84 | if mp.IsClosed() { 85 | return ErrPoolClosed 86 | } 87 | 88 | if err = mp.pools[mp.next(mp.lbs)].Invoke(args); err == nil { 89 | return 90 | } 91 | if err == ErrPoolOverload && mp.lbs == RoundRobin { 92 | return mp.pools[mp.next(LeastTasks)].Invoke(args) 93 | } 94 | return 95 | } 96 | 97 | // Running returns the number of the currently running workers across all pools. 98 | func (mp *MultiPoolWithFuncGeneric[T]) Running() (n int) { 99 | for _, pool := range mp.pools { 100 | n += pool.Running() 101 | } 102 | return 103 | } 104 | 105 | // RunningByIndex returns the number of the currently running workers in the specific pool. 106 | func (mp *MultiPoolWithFuncGeneric[T]) RunningByIndex(idx int) (int, error) { 107 | if idx < 0 || idx >= len(mp.pools) { 108 | return -1, ErrInvalidPoolIndex 109 | } 110 | return mp.pools[idx].Running(), nil 111 | } 112 | 113 | // Free returns the number of available workers across all pools. 114 | func (mp *MultiPoolWithFuncGeneric[T]) Free() (n int) { 115 | for _, pool := range mp.pools { 116 | n += pool.Free() 117 | } 118 | return 119 | } 120 | 121 | // FreeByIndex returns the number of available workers in the specific pool. 122 | func (mp *MultiPoolWithFuncGeneric[T]) FreeByIndex(idx int) (int, error) { 123 | if idx < 0 || idx >= len(mp.pools) { 124 | return -1, ErrInvalidPoolIndex 125 | } 126 | return mp.pools[idx].Free(), nil 127 | } 128 | 129 | // Waiting returns the number of the currently waiting tasks across all pools. 130 | func (mp *MultiPoolWithFuncGeneric[T]) Waiting() (n int) { 131 | for _, pool := range mp.pools { 132 | n += pool.Waiting() 133 | } 134 | return 135 | } 136 | 137 | // WaitingByIndex returns the number of the currently waiting tasks in the specific pool. 138 | func (mp *MultiPoolWithFuncGeneric[T]) WaitingByIndex(idx int) (int, error) { 139 | if idx < 0 || idx >= len(mp.pools) { 140 | return -1, ErrInvalidPoolIndex 141 | } 142 | return mp.pools[idx].Waiting(), nil 143 | } 144 | 145 | // Cap returns the capacity of this multi-pool. 146 | func (mp *MultiPoolWithFuncGeneric[T]) Cap() (n int) { 147 | for _, pool := range mp.pools { 148 | n += pool.Cap() 149 | } 150 | return 151 | } 152 | 153 | // Tune resizes each pool in multi-pool. 154 | // 155 | // Note that this method doesn't resize the overall 156 | // capacity of multi-pool. 157 | func (mp *MultiPoolWithFuncGeneric[T]) Tune(size int) { 158 | for _, pool := range mp.pools { 159 | pool.Tune(size) 160 | } 161 | } 162 | 163 | // IsClosed indicates whether the multi-pool is closed. 164 | func (mp *MultiPoolWithFuncGeneric[T]) IsClosed() bool { 165 | return atomic.LoadInt32(&mp.state) == CLOSED 166 | } 167 | 168 | // ReleaseTimeout closes the multi-pool with a timeout, 169 | // it waits all pools to be closed before timing out. 170 | func (mp *MultiPoolWithFuncGeneric[T]) ReleaseTimeout(timeout time.Duration) error { 171 | if !atomic.CompareAndSwapInt32(&mp.state, OPENED, CLOSED) { 172 | return ErrPoolClosed 173 | } 174 | 175 | errCh := make(chan error, len(mp.pools)) 176 | var wg errgroup.Group 177 | for i, pool := range mp.pools { 178 | func(p *PoolWithFuncGeneric[T], idx int) { 179 | wg.Go(func() error { 180 | err := p.ReleaseTimeout(timeout) 181 | if err != nil { 182 | err = fmt.Errorf("pool %d: %v", idx, err) 183 | } 184 | errCh <- err 185 | return err 186 | }) 187 | }(pool, i) 188 | } 189 | 190 | _ = wg.Wait() 191 | 192 | var errStr strings.Builder 193 | for i := 0; i < len(mp.pools); i++ { 194 | if err := <-errCh; err != nil { 195 | errStr.WriteString(err.Error()) 196 | errStr.WriteString(" | ") 197 | } 198 | } 199 | 200 | if errStr.Len() == 0 { 201 | return nil 202 | } 203 | 204 | return errors.New(strings.TrimSuffix(errStr.String(), " | ")) 205 | } 206 | 207 | // Reboot reboots a released multi-pool. 208 | func (mp *MultiPoolWithFuncGeneric[T]) Reboot() { 209 | if atomic.CompareAndSwapInt32(&mp.state, CLOSED, OPENED) { 210 | atomic.StoreUint32(&mp.index, 0) 211 | for _, pool := range mp.pools { 212 | pool.Reboot() 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Andy Pan. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import "time" 26 | 27 | // Option represents the optional function. 28 | type Option func(opts *Options) 29 | 30 | func loadOptions(options ...Option) *Options { 31 | opts := new(Options) 32 | for _, option := range options { 33 | option(opts) 34 | } 35 | return opts 36 | } 37 | 38 | // Options contains all options which will be applied when instantiating an ants pool. 39 | type Options struct { 40 | // ExpiryDuration is a period for the scavenger goroutine to clean up those expired workers, 41 | // the scavenger scans all workers every `ExpiryDuration` and clean up those workers that haven't been 42 | // used for more than `ExpiryDuration`. 43 | ExpiryDuration time.Duration 44 | 45 | // PreAlloc indicates whether to make memory pre-allocation when initializing Pool. 46 | PreAlloc bool 47 | 48 | // Max number of goroutine blocking on pool.Submit. 49 | // 0 (default value) means no such limit. 50 | MaxBlockingTasks int 51 | 52 | // When Nonblocking is true, Pool.Submit will never be blocked. 53 | // ErrPoolOverload will be returned when Pool.Submit cannot be done at once. 54 | // When Nonblocking is true, MaxBlockingTasks is inoperative. 55 | Nonblocking bool 56 | 57 | // PanicHandler is used to handle panics from each worker goroutine. 58 | // If nil, the default behavior is to capture the value given to panic 59 | // and resume normal execution and print that value along with the 60 | // stack trace of the goroutine 61 | PanicHandler func(any) 62 | 63 | // Logger is the customized logger for logging info, if it is not set, 64 | // default standard logger from log package is used. 65 | Logger Logger 66 | 67 | // When DisablePurge is true, workers are not purged and are resident. 68 | DisablePurge bool 69 | } 70 | 71 | // WithOptions accepts the whole Options config. 72 | func WithOptions(options Options) Option { 73 | return func(opts *Options) { 74 | *opts = options 75 | } 76 | } 77 | 78 | // WithExpiryDuration sets up the interval time of cleaning up goroutines. 79 | func WithExpiryDuration(expiryDuration time.Duration) Option { 80 | return func(opts *Options) { 81 | opts.ExpiryDuration = expiryDuration 82 | } 83 | } 84 | 85 | // WithPreAlloc indicates whether it should malloc for workers. 86 | func WithPreAlloc(preAlloc bool) Option { 87 | return func(opts *Options) { 88 | opts.PreAlloc = preAlloc 89 | } 90 | } 91 | 92 | // WithMaxBlockingTasks sets up the maximum number of goroutines that are blocked when it reaches the capacity of pool. 93 | func WithMaxBlockingTasks(maxBlockingTasks int) Option { 94 | return func(opts *Options) { 95 | opts.MaxBlockingTasks = maxBlockingTasks 96 | } 97 | } 98 | 99 | // WithNonblocking indicates that pool will return nil when there is no available workers. 100 | func WithNonblocking(nonblocking bool) Option { 101 | return func(opts *Options) { 102 | opts.Nonblocking = nonblocking 103 | } 104 | } 105 | 106 | // WithPanicHandler sets up panic handler. 107 | func WithPanicHandler(panicHandler func(any)) Option { 108 | return func(opts *Options) { 109 | opts.PanicHandler = panicHandler 110 | } 111 | } 112 | 113 | // WithLogger sets up a customized logger. 114 | func WithLogger(logger Logger) Option { 115 | return func(opts *Options) { 116 | opts.Logger = logger 117 | } 118 | } 119 | 120 | // WithDisablePurge indicates whether we turn off automatically purge. 121 | func WithDisablePurge(disable bool) Option { 122 | return func(opts *Options) { 123 | opts.DisablePurge = disable 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /pkg/sync/spinlock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Andy Pan & Dietoad. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sync 6 | 7 | import ( 8 | "runtime" 9 | "sync" 10 | "sync/atomic" 11 | ) 12 | 13 | type spinLock uint32 14 | 15 | const maxBackoff = 16 16 | 17 | func (sl *spinLock) Lock() { 18 | backoff := 1 19 | for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) { 20 | // Leverage the exponential backoff algorithm, see https://en.wikipedia.org/wiki/Exponential_backoff. 21 | for i := 0; i < backoff; i++ { 22 | runtime.Gosched() 23 | } 24 | if backoff < maxBackoff { 25 | backoff <<= 1 26 | } 27 | } 28 | } 29 | 30 | func (sl *spinLock) Unlock() { 31 | atomic.StoreUint32((*uint32)(sl), 0) 32 | } 33 | 34 | // NewSpinLock instantiates a spin-lock. 35 | func NewSpinLock() sync.Locker { 36 | return new(spinLock) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/sync/spinlock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Andy Pan & Dietoad. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package sync 6 | 7 | import ( 8 | "runtime" 9 | "sync" 10 | "sync/atomic" 11 | "testing" 12 | ) 13 | 14 | /* 15 | Benchmark result for three types of locks: 16 | goos: darwin 17 | goarch: arm64 18 | pkg: github.com/panjf2000/ants/v2/pkg/sync 19 | BenchmarkMutex-10 10452573 111.1 ns/op 0 B/op 0 allocs/op 20 | BenchmarkSpinLock-10 58953211 18.01 ns/op 0 B/op 0 allocs/op 21 | BenchmarkBackOffSpinLock-10 100000000 10.81 ns/op 0 B/op 0 allocs/op 22 | */ 23 | 24 | type originSpinLock uint32 25 | 26 | func (sl *originSpinLock) Lock() { 27 | for !atomic.CompareAndSwapUint32((*uint32)(sl), 0, 1) { 28 | runtime.Gosched() 29 | } 30 | } 31 | 32 | func (sl *originSpinLock) Unlock() { 33 | atomic.StoreUint32((*uint32)(sl), 0) 34 | } 35 | 36 | func NewOriginSpinLock() sync.Locker { 37 | return new(originSpinLock) 38 | } 39 | 40 | func BenchmarkMutex(b *testing.B) { 41 | m := sync.Mutex{} 42 | b.RunParallel(func(pb *testing.PB) { 43 | for pb.Next() { 44 | m.Lock() 45 | //nolint:staticcheck 46 | m.Unlock() 47 | } 48 | }) 49 | } 50 | 51 | func BenchmarkSpinLock(b *testing.B) { 52 | spin := NewOriginSpinLock() 53 | b.RunParallel(func(pb *testing.PB) { 54 | for pb.Next() { 55 | spin.Lock() 56 | //nolint:staticcheck 57 | spin.Unlock() 58 | } 59 | }) 60 | } 61 | 62 | func BenchmarkBackOffSpinLock(b *testing.B) { 63 | spin := NewSpinLock() 64 | b.RunParallel(func(pb *testing.PB) { 65 | for pb.Next() { 66 | spin.Lock() 67 | //nolint:staticcheck 68 | spin.Unlock() 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /pkg/sync/sync.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025. Andy Pan. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | // Package sync provides some handy implementations for synchronization access. 24 | // At the moment, there is only an implementation of spin-lock. 25 | package sync 26 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | // Pool is a goroutine pool that limits and recycles a mass of goroutines. 26 | // The pool capacity can be fixed or unlimited. 27 | type Pool struct { 28 | *poolCommon 29 | } 30 | 31 | // Submit submits a task to the pool. 32 | // 33 | // Note that you are allowed to call Pool.Submit() from the current Pool.Submit(), 34 | // but what calls for special attention is that you will get blocked with the last 35 | // Pool.Submit() call once the current Pool runs out of its capacity, and to avoid this, 36 | // you should instantiate a Pool with ants.WithNonblocking(true). 37 | func (p *Pool) Submit(task func()) error { 38 | if p.IsClosed() { 39 | return ErrPoolClosed 40 | } 41 | 42 | w, err := p.retrieveWorker() 43 | if w != nil { 44 | w.inputFunc(task) 45 | } 46 | return err 47 | } 48 | 49 | // NewPool instantiates a Pool with customized options. 50 | func NewPool(size int, options ...Option) (*Pool, error) { 51 | pc, err := newPool(size, options...) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | pool := &Pool{poolCommon: pc} 57 | pool.workerCache.New = func() any { 58 | return &goWorker{ 59 | pool: pool, 60 | task: make(chan func(), workerChanCap), 61 | } 62 | } 63 | 64 | return pool, nil 65 | } 66 | -------------------------------------------------------------------------------- /pool_func.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | // PoolWithFunc is like Pool but accepts a unified function for all goroutines to execute. 26 | type PoolWithFunc struct { 27 | *poolCommon 28 | 29 | // fn is the unified function for processing tasks. 30 | fn func(any) 31 | } 32 | 33 | // Invoke passes arguments to the pool. 34 | // 35 | // Note that you are allowed to call Pool.Invoke() from the current Pool.Invoke(), 36 | // but what calls for special attention is that you will get blocked with the last 37 | // Pool.Invoke() call once the current Pool runs out of its capacity, and to avoid this, 38 | // you should instantiate a PoolWithFunc with ants.WithNonblocking(true). 39 | func (p *PoolWithFunc) Invoke(arg any) error { 40 | if p.IsClosed() { 41 | return ErrPoolClosed 42 | } 43 | 44 | w, err := p.retrieveWorker() 45 | if w != nil { 46 | w.inputArg(arg) 47 | } 48 | return err 49 | } 50 | 51 | // NewPoolWithFunc instantiates a PoolWithFunc with customized options. 52 | func NewPoolWithFunc(size int, pf func(any), options ...Option) (*PoolWithFunc, error) { 53 | if pf == nil { 54 | return nil, ErrLackPoolFunc 55 | } 56 | 57 | pc, err := newPool(size, options...) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | pool := &PoolWithFunc{ 63 | poolCommon: pc, 64 | fn: pf, 65 | } 66 | 67 | pool.workerCache.New = func() any { 68 | return &goWorkerWithFunc{ 69 | pool: pool, 70 | arg: make(chan any, workerChanCap), 71 | } 72 | } 73 | 74 | return pool, nil 75 | } 76 | -------------------------------------------------------------------------------- /pool_func_generic.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2025 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | // PoolWithFuncGeneric is the generic version of PoolWithFunc. 26 | type PoolWithFuncGeneric[T any] struct { 27 | *poolCommon 28 | 29 | // fn is the unified function for processing tasks. 30 | fn func(T) 31 | } 32 | 33 | // Invoke passes the argument to the pool to start a new task. 34 | func (p *PoolWithFuncGeneric[T]) Invoke(arg T) error { 35 | if p.IsClosed() { 36 | return ErrPoolClosed 37 | } 38 | 39 | w, err := p.retrieveWorker() 40 | if w != nil { 41 | w.(*goWorkerWithFuncGeneric[T]).arg <- arg 42 | } 43 | return err 44 | } 45 | 46 | // NewPoolWithFuncGeneric instantiates a PoolWithFuncGeneric[T] with customized options. 47 | func NewPoolWithFuncGeneric[T any](size int, pf func(T), options ...Option) (*PoolWithFuncGeneric[T], error) { 48 | if pf == nil { 49 | return nil, ErrLackPoolFunc 50 | } 51 | 52 | pc, err := newPool(size, options...) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | pool := &PoolWithFuncGeneric[T]{ 58 | poolCommon: pc, 59 | fn: pf, 60 | } 61 | 62 | pool.workerCache.New = func() any { 63 | return &goWorkerWithFuncGeneric[T]{ 64 | pool: pool, 65 | arg: make(chan T, workerChanCap), 66 | exit: make(chan struct{}, 1), 67 | } 68 | } 69 | 70 | return pool, nil 71 | } 72 | -------------------------------------------------------------------------------- /worker.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "runtime/debug" 27 | "time" 28 | ) 29 | 30 | // goWorker is the actual executor who runs the tasks, 31 | // it starts a goroutine that accepts tasks and 32 | // performs function calls. 33 | type goWorker struct { 34 | worker 35 | 36 | // pool who owns this worker. 37 | pool *Pool 38 | 39 | // task is a job should be done. 40 | task chan func() 41 | 42 | // lastUsed will be updated when putting a worker back into queue. 43 | lastUsed time.Time 44 | } 45 | 46 | // run starts a goroutine to repeat the process 47 | // that performs the function calls. 48 | func (w *goWorker) run() { 49 | w.pool.addRunning(1) 50 | go func() { 51 | defer func() { 52 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() { 53 | w.pool.once.Do(func() { 54 | close(w.pool.allDone) 55 | }) 56 | } 57 | w.pool.workerCache.Put(w) 58 | if p := recover(); p != nil { 59 | if ph := w.pool.options.PanicHandler; ph != nil { 60 | ph(p) 61 | } else { 62 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack()) 63 | } 64 | } 65 | // Call Signal() here in case there are goroutines waiting for available workers. 66 | w.pool.cond.Signal() 67 | }() 68 | 69 | for fn := range w.task { 70 | if fn == nil { 71 | return 72 | } 73 | fn() 74 | if ok := w.pool.revertWorker(w); !ok { 75 | return 76 | } 77 | } 78 | }() 79 | } 80 | 81 | func (w *goWorker) finish() { 82 | w.task <- nil 83 | } 84 | 85 | func (w *goWorker) lastUsedTime() time.Time { 86 | return w.lastUsed 87 | } 88 | 89 | func (w *goWorker) setLastUsedTime(t time.Time) { 90 | w.lastUsed = t 91 | } 92 | 93 | func (w *goWorker) inputFunc(fn func()) { 94 | w.task <- fn 95 | } 96 | -------------------------------------------------------------------------------- /worker_func.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "runtime/debug" 27 | "time" 28 | ) 29 | 30 | // goWorkerWithFunc is the actual executor who runs the tasks, 31 | // it starts a goroutine that accepts tasks and 32 | // performs function calls. 33 | type goWorkerWithFunc struct { 34 | worker 35 | 36 | // pool who owns this worker. 37 | pool *PoolWithFunc 38 | 39 | // arg is the argument for the function. 40 | arg chan any 41 | 42 | // lastUsed will be updated when putting a worker back into queue. 43 | lastUsed time.Time 44 | } 45 | 46 | // run starts a goroutine to repeat the process 47 | // that performs the function calls. 48 | func (w *goWorkerWithFunc) run() { 49 | w.pool.addRunning(1) 50 | go func() { 51 | defer func() { 52 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() { 53 | w.pool.once.Do(func() { 54 | close(w.pool.allDone) 55 | }) 56 | } 57 | w.pool.workerCache.Put(w) 58 | if p := recover(); p != nil { 59 | if ph := w.pool.options.PanicHandler; ph != nil { 60 | ph(p) 61 | } else { 62 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack()) 63 | } 64 | } 65 | // Call Signal() here in case there are goroutines waiting for available workers. 66 | w.pool.cond.Signal() 67 | }() 68 | 69 | for arg := range w.arg { 70 | if arg == nil { 71 | return 72 | } 73 | w.pool.fn(arg) 74 | if ok := w.pool.revertWorker(w); !ok { 75 | return 76 | } 77 | } 78 | }() 79 | } 80 | 81 | func (w *goWorkerWithFunc) finish() { 82 | w.arg <- nil 83 | } 84 | 85 | func (w *goWorkerWithFunc) lastUsedTime() time.Time { 86 | return w.lastUsed 87 | } 88 | 89 | func (w *goWorkerWithFunc) setLastUsedTime(t time.Time) { 90 | w.lastUsed = t 91 | } 92 | 93 | func (w *goWorkerWithFunc) inputArg(arg any) { 94 | w.arg <- arg 95 | } 96 | -------------------------------------------------------------------------------- /worker_func_generic.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2025 Andy Pan 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | package ants 24 | 25 | import ( 26 | "runtime/debug" 27 | "time" 28 | ) 29 | 30 | // goWorkerWithFunc is the actual executor who runs the tasks, 31 | // it starts a goroutine that accepts tasks and 32 | // performs function calls. 33 | type goWorkerWithFuncGeneric[T any] struct { 34 | worker 35 | 36 | // pool who owns this worker. 37 | pool *PoolWithFuncGeneric[T] 38 | 39 | // arg is a job should be done. 40 | arg chan T 41 | 42 | // exit signals the goroutine to exit. 43 | exit chan struct{} 44 | 45 | // lastUsed will be updated when putting a worker back into queue. 46 | lastUsed time.Time 47 | } 48 | 49 | // run starts a goroutine to repeat the process 50 | // that performs the function calls. 51 | func (w *goWorkerWithFuncGeneric[T]) run() { 52 | w.pool.addRunning(1) 53 | go func() { 54 | defer func() { 55 | if w.pool.addRunning(-1) == 0 && w.pool.IsClosed() { 56 | w.pool.once.Do(func() { 57 | close(w.pool.allDone) 58 | }) 59 | } 60 | w.pool.workerCache.Put(w) 61 | if p := recover(); p != nil { 62 | if ph := w.pool.options.PanicHandler; ph != nil { 63 | ph(p) 64 | } else { 65 | w.pool.options.Logger.Printf("worker exits from panic: %v\n%s\n", p, debug.Stack()) 66 | } 67 | } 68 | // Call Signal() here in case there are goroutines waiting for available workers. 69 | w.pool.cond.Signal() 70 | }() 71 | 72 | for { 73 | select { 74 | case <-w.exit: 75 | return 76 | case arg := <-w.arg: 77 | w.pool.fn(arg) 78 | if ok := w.pool.revertWorker(w); !ok { 79 | return 80 | } 81 | } 82 | } 83 | }() 84 | } 85 | 86 | func (w *goWorkerWithFuncGeneric[T]) finish() { 87 | w.exit <- struct{}{} 88 | } 89 | 90 | func (w *goWorkerWithFuncGeneric[T]) lastUsedTime() time.Time { 91 | return w.lastUsed 92 | } 93 | 94 | func (w *goWorkerWithFuncGeneric[T]) setLastUsedTime(t time.Time) { 95 | w.lastUsed = t 96 | } 97 | -------------------------------------------------------------------------------- /worker_loop_queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. Ants Authors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import "time" 26 | 27 | type loopQueue struct { 28 | items []worker 29 | expiry []worker 30 | head int 31 | tail int 32 | size int 33 | isFull bool 34 | } 35 | 36 | func newWorkerLoopQueue(size int) *loopQueue { 37 | if size <= 0 { 38 | return nil 39 | } 40 | return &loopQueue{ 41 | items: make([]worker, size), 42 | size: size, 43 | } 44 | } 45 | 46 | func (wq *loopQueue) len() int { 47 | if wq.size == 0 || wq.isEmpty() { 48 | return 0 49 | } 50 | 51 | if wq.head == wq.tail && wq.isFull { 52 | return wq.size 53 | } 54 | 55 | if wq.tail > wq.head { 56 | return wq.tail - wq.head 57 | } 58 | 59 | return wq.size - wq.head + wq.tail 60 | } 61 | 62 | func (wq *loopQueue) isEmpty() bool { 63 | return wq.head == wq.tail && !wq.isFull 64 | } 65 | 66 | func (wq *loopQueue) insert(w worker) error { 67 | if wq.isFull { 68 | return errQueueIsFull 69 | } 70 | wq.items[wq.tail] = w 71 | wq.tail = (wq.tail + 1) % wq.size 72 | 73 | if wq.tail == wq.head { 74 | wq.isFull = true 75 | } 76 | 77 | return nil 78 | } 79 | 80 | func (wq *loopQueue) detach() worker { 81 | if wq.isEmpty() { 82 | return nil 83 | } 84 | 85 | w := wq.items[wq.head] 86 | wq.items[wq.head] = nil 87 | wq.head = (wq.head + 1) % wq.size 88 | 89 | wq.isFull = false 90 | 91 | return w 92 | } 93 | 94 | func (wq *loopQueue) refresh(duration time.Duration) []worker { 95 | expiryTime := time.Now().Add(-duration) 96 | index := wq.binarySearch(expiryTime) 97 | if index == -1 { 98 | return nil 99 | } 100 | wq.expiry = wq.expiry[:0] 101 | 102 | if wq.head <= index { 103 | wq.expiry = append(wq.expiry, wq.items[wq.head:index+1]...) 104 | for i := wq.head; i < index+1; i++ { 105 | wq.items[i] = nil 106 | } 107 | } else { 108 | wq.expiry = append(wq.expiry, wq.items[0:index+1]...) 109 | wq.expiry = append(wq.expiry, wq.items[wq.head:]...) 110 | for i := 0; i < index+1; i++ { 111 | wq.items[i] = nil 112 | } 113 | for i := wq.head; i < wq.size; i++ { 114 | wq.items[i] = nil 115 | } 116 | } 117 | head := (index + 1) % wq.size 118 | wq.head = head 119 | if len(wq.expiry) > 0 { 120 | wq.isFull = false 121 | } 122 | 123 | return wq.expiry 124 | } 125 | 126 | func (wq *loopQueue) binarySearch(expiryTime time.Time) int { 127 | var mid, nlen, basel, tmid int 128 | nlen = len(wq.items) 129 | 130 | // if no need to remove work, return -1 131 | if wq.isEmpty() || expiryTime.Before(wq.items[wq.head].lastUsedTime()) { 132 | return -1 133 | } 134 | 135 | // example 136 | // size = 8, head = 7, tail = 4 137 | // [ 2, 3, 4, 5, nil, nil, nil, 1] true position 138 | // 0 1 2 3 4 5 6 7 139 | // tail head 140 | // 141 | // 1 2 3 4 nil nil nil 0 mapped position 142 | // r l 143 | 144 | // base algorithm is a copy from worker_stack 145 | // map head and tail to effective left and right 146 | r := (wq.tail - 1 - wq.head + nlen) % nlen 147 | basel = wq.head 148 | l := 0 149 | for l <= r { 150 | mid = l + ((r - l) >> 1) // avoid overflow when computing mid 151 | // calculate true mid position from mapped mid position 152 | tmid = (mid + basel + nlen) % nlen 153 | if expiryTime.Before(wq.items[tmid].lastUsedTime()) { 154 | r = mid - 1 155 | } else { 156 | l = mid + 1 157 | } 158 | } 159 | // return true position from mapped position 160 | return (r + basel + nlen) % nlen 161 | } 162 | 163 | func (wq *loopQueue) reset() { 164 | if wq.isEmpty() { 165 | return 166 | } 167 | 168 | retry: 169 | if w := wq.detach(); w != nil { 170 | w.finish() 171 | goto retry 172 | } 173 | wq.head = 0 174 | wq.tail = 0 175 | } 176 | -------------------------------------------------------------------------------- /worker_loop_queue_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. Ants Authors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import ( 26 | "runtime" 27 | "testing" 28 | "time" 29 | 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | func TestNewLoopQueue(t *testing.T) { 34 | size := 100 35 | q := newWorkerLoopQueue(size) 36 | require.EqualValues(t, 0, q.len(), "Len error") 37 | require.Equal(t, true, q.isEmpty(), "IsEmpty error") 38 | require.Nil(t, q.detach(), "Dequeue error") 39 | 40 | require.Nil(t, newWorkerLoopQueue(0)) 41 | } 42 | 43 | func TestLoopQueue(t *testing.T) { 44 | size := 10 45 | q := newWorkerLoopQueue(size) 46 | 47 | for i := 0; i < 5; i++ { 48 | err := q.insert(&goWorker{lastUsed: time.Now()}) 49 | if err != nil { 50 | break 51 | } 52 | } 53 | require.EqualValues(t, 5, q.len(), "Len error") 54 | _ = q.detach() 55 | require.EqualValues(t, 4, q.len(), "Len error") 56 | 57 | time.Sleep(time.Second) 58 | 59 | for i := 0; i < 6; i++ { 60 | err := q.insert(&goWorker{lastUsed: time.Now()}) 61 | if err != nil { 62 | break 63 | } 64 | } 65 | require.EqualValues(t, 10, q.len(), "Len error") 66 | 67 | err := q.insert(&goWorker{lastUsed: time.Now()}) 68 | require.Error(t, err, "Enqueue, error") 69 | 70 | q.refresh(time.Second) 71 | require.EqualValuesf(t, 6, q.len(), "Len error: %d", q.len()) 72 | } 73 | 74 | func TestRotatedQueueSearch(t *testing.T) { 75 | if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows 76 | t.Skip("Skip this test on Windows platform") 77 | } 78 | 79 | size := 10 80 | q := newWorkerLoopQueue(size) 81 | 82 | // 1 83 | expiry1 := time.Now() 84 | 85 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 86 | 87 | require.EqualValues(t, 0, q.binarySearch(time.Now()), "index should be 0") 88 | require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1") 89 | 90 | // 2 91 | expiry2 := time.Now() 92 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 93 | 94 | require.EqualValues(t, -1, q.binarySearch(expiry1), "index should be -1") 95 | 96 | require.EqualValues(t, 0, q.binarySearch(expiry2), "index should be 0") 97 | 98 | require.EqualValues(t, 1, q.binarySearch(time.Now()), "index should be 1") 99 | 100 | // more 101 | for i := 0; i < 5; i++ { 102 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 103 | } 104 | 105 | expiry3 := time.Now() 106 | _ = q.insert(&goWorker{lastUsed: expiry3}) 107 | 108 | var err error 109 | for err != errQueueIsFull { 110 | err = q.insert(&goWorker{lastUsed: time.Now()}) 111 | } 112 | 113 | require.EqualValues(t, 7, q.binarySearch(expiry3), "index should be 7") 114 | 115 | // rotate 116 | for i := 0; i < 6; i++ { 117 | _ = q.detach() 118 | } 119 | 120 | expiry4 := time.Now() 121 | _ = q.insert(&goWorker{lastUsed: expiry4}) 122 | 123 | for i := 0; i < 4; i++ { 124 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 125 | } 126 | // head = 6, tail = 5, insert direction -> 127 | // [expiry4, time, time, time, time, nil/tail, time/head, time, time, time] 128 | require.EqualValues(t, 0, q.binarySearch(expiry4), "index should be 0") 129 | 130 | for i := 0; i < 3; i++ { 131 | _ = q.detach() 132 | } 133 | expiry5 := time.Now() 134 | _ = q.insert(&goWorker{lastUsed: expiry5}) 135 | 136 | // head = 6, tail = 5, insert direction -> 137 | // [expiry4, time, time, time, time, expiry5, nil/tail, nil, nil, time/head] 138 | require.EqualValues(t, 5, q.binarySearch(expiry5), "index should be 5") 139 | 140 | for i := 0; i < 3; i++ { 141 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 142 | } 143 | // head = 9, tail = 9, insert direction -> 144 | // [expiry4, time, time, time, time, expiry5, time, time, time, time/head/tail] 145 | require.EqualValues(t, -1, q.binarySearch(expiry2), "index should be -1") 146 | 147 | require.EqualValues(t, 9, q.binarySearch(q.items[9].lastUsedTime()), "index should be 9") 148 | require.EqualValues(t, 8, q.binarySearch(time.Now()), "index should be 8") 149 | } 150 | 151 | func TestRetrieveExpiry(t *testing.T) { 152 | size := 10 153 | q := newWorkerLoopQueue(size) 154 | expirew := make([]worker, 0) 155 | u, _ := time.ParseDuration("1s") 156 | 157 | // test [ time+1s, time+1s, time+1s, time+1s, time+1s, time, time, time, time, time] 158 | for i := 0; i < size/2; i++ { 159 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 160 | } 161 | expirew = append(expirew, q.items[:size/2]...) 162 | time.Sleep(u) 163 | 164 | for i := 0; i < size/2; i++ { 165 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 166 | } 167 | workers := q.refresh(u) 168 | 169 | require.EqualValues(t, expirew, workers, "expired workers aren't right") 170 | 171 | // test [ time, time, time, time, time, time+1s, time+1s, time+1s, time+1s, time+1s] 172 | time.Sleep(u) 173 | 174 | for i := 0; i < size/2; i++ { 175 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 176 | } 177 | expirew = expirew[:0] 178 | expirew = append(expirew, q.items[size/2:]...) 179 | 180 | workers2 := q.refresh(u) 181 | 182 | require.EqualValues(t, expirew, workers2, "expired workers aren't right") 183 | 184 | // test [ time+1s, time+1s, time+1s, nil, nil, time+1s, time+1s, time+1s, time+1s, time+1s] 185 | for i := 0; i < size/2; i++ { 186 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 187 | } 188 | for i := 0; i < size/2; i++ { 189 | _ = q.detach() 190 | } 191 | for i := 0; i < 3; i++ { 192 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 193 | } 194 | time.Sleep(u) 195 | 196 | expirew = expirew[:0] 197 | expirew = append(expirew, q.items[0:3]...) 198 | expirew = append(expirew, q.items[size/2:]...) 199 | 200 | workers3 := q.refresh(u) 201 | 202 | require.EqualValues(t, expirew, workers3, "expired workers aren't right") 203 | } 204 | -------------------------------------------------------------------------------- /worker_queue.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. Ants Authors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import ( 26 | "errors" 27 | "time" 28 | ) 29 | 30 | // errQueueIsFull will be returned when the worker queue is full. 31 | var errQueueIsFull = errors.New("the queue is full") 32 | 33 | type worker interface { 34 | run() 35 | finish() 36 | lastUsedTime() time.Time 37 | setLastUsedTime(t time.Time) 38 | inputFunc(func()) 39 | inputArg(any) 40 | } 41 | 42 | type workerQueue interface { 43 | len() int 44 | isEmpty() bool 45 | insert(worker) error 46 | detach() worker 47 | refresh(duration time.Duration) []worker // clean up the stale workers and return them 48 | reset() 49 | } 50 | 51 | type queueType int 52 | 53 | const ( 54 | queueTypeStack queueType = 1 << iota 55 | queueTypeLoopQueue 56 | ) 57 | 58 | func newWorkerQueue(qType queueType, size int) workerQueue { 59 | switch qType { 60 | case queueTypeStack: 61 | return newWorkerStack(size) 62 | case queueTypeLoopQueue: 63 | return newWorkerLoopQueue(size) 64 | default: 65 | return newWorkerStack(size) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /worker_stack.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. Ants Authors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import "time" 26 | 27 | type workerStack struct { 28 | items []worker 29 | expiry []worker 30 | } 31 | 32 | func newWorkerStack(size int) *workerStack { 33 | return &workerStack{ 34 | items: make([]worker, 0, size), 35 | } 36 | } 37 | 38 | func (ws *workerStack) len() int { 39 | return len(ws.items) 40 | } 41 | 42 | func (ws *workerStack) isEmpty() bool { 43 | return len(ws.items) == 0 44 | } 45 | 46 | func (ws *workerStack) insert(w worker) error { 47 | ws.items = append(ws.items, w) 48 | return nil 49 | } 50 | 51 | func (ws *workerStack) detach() worker { 52 | l := ws.len() 53 | if l == 0 { 54 | return nil 55 | } 56 | 57 | w := ws.items[l-1] 58 | ws.items[l-1] = nil // avoid memory leaks 59 | ws.items = ws.items[:l-1] 60 | 61 | return w 62 | } 63 | 64 | func (ws *workerStack) refresh(duration time.Duration) []worker { 65 | n := ws.len() 66 | if n == 0 { 67 | return nil 68 | } 69 | 70 | expiryTime := time.Now().Add(-duration) 71 | index := ws.binarySearch(0, n-1, expiryTime) 72 | 73 | ws.expiry = ws.expiry[:0] 74 | if index != -1 { 75 | ws.expiry = append(ws.expiry, ws.items[:index+1]...) 76 | m := copy(ws.items, ws.items[index+1:]) 77 | for i := m; i < n; i++ { 78 | ws.items[i] = nil 79 | } 80 | ws.items = ws.items[:m] 81 | } 82 | return ws.expiry 83 | } 84 | 85 | func (ws *workerStack) binarySearch(l, r int, expiryTime time.Time) int { 86 | for l <= r { 87 | mid := l + ((r - l) >> 1) // avoid overflow when computing mid 88 | if expiryTime.Before(ws.items[mid].lastUsedTime()) { 89 | r = mid - 1 90 | } else { 91 | l = mid + 1 92 | } 93 | } 94 | return r 95 | } 96 | 97 | func (ws *workerStack) reset() { 98 | for i := 0; i < ws.len(); i++ { 99 | ws.items[i].finish() 100 | ws.items[i] = nil 101 | } 102 | ws.items = ws.items[:0] 103 | } 104 | -------------------------------------------------------------------------------- /worker_stack_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. Ants Authors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | package ants 24 | 25 | import ( 26 | "runtime" 27 | "testing" 28 | "time" 29 | 30 | "github.com/stretchr/testify/require" 31 | ) 32 | 33 | func TestNewWorkerStack(t *testing.T) { 34 | size := 100 35 | q := newWorkerStack(size) 36 | require.EqualValues(t, 0, q.len(), "Len error") 37 | require.Equal(t, true, q.isEmpty(), "IsEmpty error") 38 | require.Nil(t, q.detach(), "Dequeue error") 39 | } 40 | 41 | func TestWorkerStack(t *testing.T) { 42 | q := newWorkerQueue(queueType(-1), 0) 43 | 44 | for i := 0; i < 5; i++ { 45 | err := q.insert(&goWorker{lastUsed: time.Now()}) 46 | if err != nil { 47 | break 48 | } 49 | } 50 | require.EqualValues(t, 5, q.len(), "Len error") 51 | 52 | expired := time.Now() 53 | 54 | err := q.insert(&goWorker{lastUsed: expired}) 55 | if err != nil { 56 | t.Fatal("Enqueue error") 57 | } 58 | 59 | time.Sleep(time.Second) 60 | 61 | for i := 0; i < 6; i++ { 62 | err := q.insert(&goWorker{lastUsed: time.Now()}) 63 | if err != nil { 64 | t.Fatal("Enqueue error") 65 | } 66 | } 67 | require.EqualValues(t, 12, q.len(), "Len error") 68 | q.refresh(time.Second) 69 | require.EqualValues(t, 6, q.len(), "Len error") 70 | } 71 | 72 | // It seems that something wrong with time.Now() on Windows, not sure whether it is a bug on Windows, 73 | // so exclude this test from Windows platform temporarily. 74 | func TestSearch(t *testing.T) { 75 | if runtime.GOOS == "windows" { // time.Now() doesn't seem to be precise on Windows 76 | t.Skip("Skip this test on Windows platform") 77 | } 78 | 79 | q := newWorkerStack(0) 80 | 81 | // 1 82 | expiry1 := time.Now() 83 | 84 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 85 | 86 | require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, time.Now()), "index should be 0") 87 | require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1") 88 | 89 | // 2 90 | expiry2 := time.Now() 91 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 92 | 93 | require.EqualValues(t, -1, q.binarySearch(0, q.len()-1, expiry1), "index should be -1") 94 | 95 | require.EqualValues(t, 0, q.binarySearch(0, q.len()-1, expiry2), "index should be 0") 96 | 97 | require.EqualValues(t, 1, q.binarySearch(0, q.len()-1, time.Now()), "index should be 1") 98 | 99 | // more 100 | for i := 0; i < 5; i++ { 101 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 102 | } 103 | 104 | expiry3 := time.Now() 105 | 106 | _ = q.insert(&goWorker{lastUsed: expiry3}) 107 | 108 | for i := 0; i < 10; i++ { 109 | _ = q.insert(&goWorker{lastUsed: time.Now()}) 110 | } 111 | 112 | require.EqualValues(t, 7, q.binarySearch(0, q.len()-1, expiry3), "index should be 7") 113 | } 114 | --------------------------------------------------------------------------------