├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE.txt ├── README.adoc ├── SECURITY.md ├── build-release-17 ├── build-test-java17 ├── build-test-java21 ├── pom.xml └── src ├── .DS_Store ├── main ├── java │ ├── module-info.java │ └── org │ │ └── jboss │ │ └── threads │ │ ├── AsyncCancellable.java │ │ ├── AsyncFuture.java │ │ ├── AsyncFutureTask.java │ │ ├── ContextClassLoaderSavingRunnable.java │ │ ├── ContextHandler.java │ │ ├── DeclaredFieldAction.java │ │ ├── DelegatingExecutor.java │ │ ├── DelegatingExecutorService.java │ │ ├── DelegatingRunnable.java │ │ ├── DelegatingScheduledExecutorService.java │ │ ├── DiscardingExecutor.java │ │ ├── EnhancedQueueExecutor.java │ │ ├── EnhancedViewExecutor.java │ │ ├── HandoffRejectedExecutionHandler.java │ │ ├── InterruptHandler.java │ │ ├── JBossExecutors.java │ │ ├── JBossScheduledThreadPoolExecutor.java │ │ ├── JBossThread.java │ │ ├── JBossThreadFactory.java │ │ ├── JDKSpecific.java │ │ ├── LoggingUncaughtExceptionHandler.java │ │ ├── ManagedThreadPoolExecutor.java │ │ ├── Messages.java │ │ ├── NullRunnable.java │ │ ├── RejectingExecutor.java │ │ ├── SimpleDirectExecutor.java │ │ ├── StoppedExecutorException.java │ │ ├── Substitutions.java │ │ ├── ThreadLocalResettingRunnable.java │ │ ├── ThreadNameInfo.java │ │ ├── TimeUtil.java │ │ ├── Version.java │ │ ├── Version.properties │ │ ├── VersionLogging.java │ │ ├── ViewExecutor.java │ │ ├── Waiter.java │ │ └── management │ │ ├── ManageableThreadPoolExecutorService.java │ │ └── StandardThreadPoolMXBean.java ├── java24 │ └── org │ │ └── jboss │ │ └── threads │ │ └── JDKSpecific.java └── resources │ └── META-INF │ └── native-image │ └── org.jboss.threads │ └── jboss-threads │ └── native-image.properties └── test ├── java └── org │ └── jboss │ └── threads │ ├── ArrayQueueTests.java │ ├── DeferredInterruptTestCase.java │ ├── EnhancedQueueExecutorTest.java │ ├── EnhancedThreadQueueExecutorTestCase.java │ ├── QueuelessViewExecutorTest.java │ ├── ScheduledEnhancedQueueExecutorTest.java │ ├── ThreadFactoryTestCase.java │ ├── ThreadLocalResetterTests.java │ └── ViewExecutorTest.java └── resources └── logging.properties /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "maven" 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ ubuntu-latest, macos-latest, windows-latest ] 18 | runs-on: ${{ matrix.os }} 19 | name: Build on ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Check out project 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up JDKs 26 | uses: actions/setup-java@v4 27 | with: 28 | distribution: temurin 29 | java-version: | 30 | 17 31 | 21 32 | 24 33 | 34 | - name: Build with Maven 35 | run: mvn verify -ntp -B "-Djava17.home=${{env.JAVA_HOME_17_X64}}${{env.JAVA_HOME_17_ARM64}}" "-Djava21.home=${{env.JAVA_HOME_21_X64}}${{env.JAVA_HOME_21_ARM64}}" 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .classpath 3 | .project 4 | .settings 5 | .idea 6 | *.iml 7 | *.ipr 8 | *.iws 9 | .DS_Store -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | david.lloyd@redhat.com. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | # JBoss Threads 2 | 3 | jboss-threads is a library to manage and execute Java threads. 4 | 5 | ## Legal 6 | 7 | All original contributions to JBoss Threads are licensed under the ASL - Apache License, version 2.0 or later, or, if another license is specified as governing the file or directory being modified, such other license. 8 | 9 | ## Reporting an issue 10 | 11 | This project uses GitHub issues to manage the issues. Open an issue directly in GitHub. 12 | 13 | If you believe you found a bug, and it's likely possible, please indicate a way to reproduce it, what you are seeing and what you would expect to see. Don't forget to indicate your Java version. 14 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security Contacts and Procedures 4 | 5 | The JBoss community takes security very seriously, and we aim to take immediate action to address serious security-related problems that involve our products or services. 6 | 7 | Please report any suspected security vulnerability in this project to Red Hat Product Security at secalert@redhat.com. You can use our GPG key to communicate with us securely. 8 | 9 | To report an issue in any Red Hat branded website or online service, please contact Red Hat Information Security at site-security@redhat.com. 10 | 11 | https://access.redhat.com/security/team/contact 12 | -------------------------------------------------------------------------------- /build-release-17: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jboss/jboss-threads/e1debefdb706832b6d7d6d22245d3412c1482fd9/build-release-17 -------------------------------------------------------------------------------- /build-test-java17: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jboss/jboss-threads/e1debefdb706832b6d7d6d22245d3412c1482fd9/build-test-java17 -------------------------------------------------------------------------------- /build-test-java21: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jboss/jboss-threads/e1debefdb706832b6d7d6d22245d3412c1482fd9/build-test-java21 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | org.jboss.threads 28 | jboss-threads 29 | jar 30 | 999-SNAPSHOT 31 | 32 | JBoss Threads 33 | Thread pool implementations and threading utilities 34 | https://github.com/jbossas/jboss-threads 35 | 36 | 37 | org.jboss 38 | jboss-parent 39 | 49 40 | 41 | 42 | 43 | Jira 44 | https://issues.jboss.org/browse/JBTHR 45 | 46 | 47 | 48 | 49 | Apache License 2.0 50 | http://www.apache.org/licenses/LICENSE-2.0.txt 51 | 52 | 53 | 54 | 55 | https://github.com/jbossas/jboss-threads 56 | scm:git:git@github.com:jbossas/jboss-threads.git 57 | scm:git:git@github.com:jbossas/jboss-threads.git 58 | HEAD 59 | 60 | 61 | 62 | INFO 63 | 64 | true 65 | false 66 | false 67 | 68 | 24 69 | 70 | 3.0.4.Final 71 | 72 | https://repository.jboss.org/nexus3 73 | jboss-common 74 | 75 | 76 | 77 | 78 | 79 | io.smallrye.common 80 | smallrye-common-bom 81 | 2.12.0 82 | pom 83 | import 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.graalvm.sdk 91 | nativeimage 92 | 24.2.1 93 | provided 94 | 95 | 96 | org.jboss.logging 97 | jboss-logging-annotations 98 | ${version.jboss.logging.tools} 99 | provided 100 | 101 | 102 | org.jboss.logging 103 | jboss-logging 104 | 3.6.1.Final 105 | 106 | 107 | org.wildfly.common 108 | wildfly-common 109 | 2.0.1 110 | 111 | 112 | io.smallrye.common 113 | smallrye-common-annotation 114 | 115 | 116 | io.smallrye.common 117 | smallrye-common-constraint 118 | 119 | 120 | io.smallrye.common 121 | smallrye-common-cpu 122 | 123 | 124 | io.smallrye.common 125 | smallrye-common-function 126 | 127 | 128 | org.junit.jupiter 129 | junit-jupiter 130 | 5.13.0 131 | test 132 | 133 | 134 | org.assertj 135 | assertj-core 136 | 3.27.3 137 | test 138 | 139 | 140 | org.awaitility 141 | awaitility 142 | 4.3.0 143 | test 144 | 145 | 146 | org.jboss.logmanager 147 | jboss-logmanager 148 | 3.1.2.Final 149 | test 150 | 151 | 152 | 153 | 154 | 155 | 156 | ${project.basedir} 157 | 158 | LICENSE.txt 159 | 160 | META-INF 161 | false 162 | 163 | 164 | src/main/java 165 | true 166 | 167 | **/*.properties 168 | 169 | 170 | 171 | 172 | 173 | maven-resources-plugin 174 | 175 | UTF-8 176 | 177 | 178 | 179 | maven-javadoc-plugin 180 | 181 | 17 182 | false 183 | false 184 | 185 | 186 | 187 | maven-compiler-plugin 188 | 189 | 190 | 191 | org.jboss.logging 192 | jboss-logging-processor 193 | ${version.jboss.logging.tools} 194 | 195 | 196 | 197 | 198 | --add-reads=org.jboss.threads=ALL-UNNAMED 199 | 200 | 201 | 202 | 203 | maven-source-plugin 204 | 205 | 206 | maven-surefire-plugin 207 | 208 | 209 | ${test.level} 210 | org.jboss.logmanager.LogManager 211 | ${jboss.threads.eqe.statistics} 212 | ${jboss.threads.eqe.unlimited-queue} 213 | ${jboss.threads.eqe.register-mbean} 214 | 215 | true 216 | 217 | 218 | 219 | 220 | 221 | 222 | fix-java24 223 | 224 | [24,) 225 | 226 | 227 | 228 | 229 | org.apache.maven.plugins 230 | maven-surefire-plugin 231 | 232 | 233 | default-test 234 | test 235 | 236 | test 237 | 238 | 239 | --add-opens=java.base/java.lang=ALL-UNNAMED 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | release 249 | 250 | 251 | release 252 | 253 | 254 | 255 | 256 | 257 | org.apache.maven.plugins 258 | maven-deploy-plugin 259 | 260 | 261 | default-deploy 262 | none 263 | 264 | 265 | 266 | 267 | org.sonatype.plugins 268 | nxrm3-maven-plugin 269 | 1.0.7 270 | true 271 | 272 | jboss-common 273 | ${nexus.url} 274 | ${nexus.repo} 275 | 276 | 277 | 278 | nexus-deploy 279 | deploy 280 | 281 | deploy 282 | 283 | 284 | 285 | 286 | 287 | org.apache.maven.plugins 288 | maven-gpg-plugin 289 | 290 | 291 | sign-artifacts 292 | verify 293 | 294 | sign 295 | 296 | 297 | 298 | --pinentry-mode 299 | loopback 300 | 301 | 302 | 303 | 304 | 305 | 306 | org.apache.maven.plugins 307 | maven-javadoc-plugin 308 | 309 | 310 | attach-javadocs 311 | 312 | jar 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jboss/jboss-threads/e1debefdb706832b6d7d6d22245d3412c1482fd9/src/.DS_Store -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.jboss.threads { 2 | requires java.management; 3 | requires jdk.unsupported; 4 | requires org.jboss.logging; 5 | requires static org.jboss.logging.annotations; 6 | requires static org.graalvm.nativeimage; 7 | requires org.wildfly.common; 8 | requires io.smallrye.common.annotation; 9 | requires io.smallrye.common.constraint; 10 | requires io.smallrye.common.cpu; 11 | requires io.smallrye.common.function; 12 | 13 | exports org.jboss.threads; 14 | exports org.jboss.threads.management; 15 | } -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/AsyncCancellable.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | /** 4 | * An interface which supports asynchronous cancellation. 5 | * 6 | * @author David M. Lloyd 7 | */ 8 | public interface AsyncCancellable { 9 | 10 | /** 11 | * Handle an asynchronous cancellation. Does not block, and may be called more than once; 12 | * in particular, this method may be re-invoked to set the {@code interruptionDesired} flag 13 | * even if it has already been called without that flag set before. Otherwise, this method is 14 | * idempotent, and thus may be called more than once without additional effect. 15 | * 16 | * @param interruptionDesired {@code true} if interruption of threads is desired 17 | */ 18 | void asyncCancel(boolean interruptionDesired); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/AsyncFuture.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.CancellationException; 4 | import java.util.concurrent.ExecutionException; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | /** 10 | * This interface represents the result of an asynchronous future task, which provides all the features 11 | * of {@link Future} while also adding several additional convenience methods and the ability to add asynchronous 12 | * callbacks. 13 | * 14 | * @author David M. Lloyd 15 | */ 16 | public interface AsyncFuture extends Future, AsyncCancellable { 17 | 18 | /** 19 | * Wait if necessary for this operation to complete, returning the outcome. The outcome will be one of 20 | * {@link Status#COMPLETE}, {@link Status#CANCELLED}, or {@link Status#FAILED}. 21 | * 22 | * @return the outcome 23 | * @throws InterruptedException if execution was interrupted while waiting 24 | */ 25 | Status await() throws InterruptedException; 26 | 27 | /** 28 | * Wait if necessary for this operation to complete, returning the outcome, which may include {@link Status#WAITING} if 29 | * the timeout expires before the operation completes. 30 | * 31 | * @param timeout the maximum time to wait 32 | * @param unit the time unit of the timeout argument 33 | * @return the outcome 34 | * @throws InterruptedException if execution was interrupted while waiting 35 | */ 36 | Status await(long timeout, TimeUnit unit) throws InterruptedException; 37 | 38 | /** 39 | * Waits (uninterruptibly) if necessary for the computation to complete, and then retrieves the result. 40 | * 41 | * @return the computed result 42 | * @throws CancellationException if the computation was cancelled 43 | * @throws ExecutionException if the computation threw an exception 44 | */ 45 | T getUninterruptibly() throws CancellationException, ExecutionException; 46 | 47 | /** 48 | * Waits (uninterruptibly) if necessary for at most the given time for the computation to complete, and then 49 | * retrieves the result, if available. 50 | * 51 | * @param timeout the maximum time to wait 52 | * @param unit the time unit of the timeout argument 53 | * @return the computed result 54 | * @throws CancellationException if the computation was cancelled 55 | * @throws ExecutionException if the computation threw an exception 56 | * @throws TimeoutException if the wait timed out 57 | */ 58 | T getUninterruptibly(long timeout, TimeUnit unit) throws CancellationException, ExecutionException, TimeoutException; 59 | 60 | /** 61 | * Wait (uninterruptibly) if necessary for this operation to complete, returning the outcome. The outcome will be one of 62 | * {@link Status#COMPLETE}, {@link Status#CANCELLED}, or {@link Status#FAILED}. 63 | * 64 | * @return the outcome 65 | */ 66 | Status awaitUninterruptibly(); 67 | 68 | /** 69 | * Wait if necessary for this operation to complete, returning the outcome, which may include {@link Status#WAITING} if 70 | * the timeout expires before the operation completes. 71 | * 72 | * @param timeout the maximum time to wait 73 | * @param unit the time unit of the timeout argument 74 | * @return the outcome 75 | */ 76 | Status awaitUninterruptibly(long timeout, TimeUnit unit); 77 | 78 | /** 79 | * Get (poll) the current status of the asynchronous operation. 80 | * 81 | * @return the current status 82 | */ 83 | Status getStatus(); 84 | 85 | /** 86 | * Add an asynchronous listener to be called when this operation completes. 87 | * 88 | * @param listener the listener to add 89 | * @param attachment the attachment to pass in 90 | * @param the attachment type 91 | */ 92 | void addListener(Listener listener, A attachment); 93 | 94 | /** 95 | * Synchronously cancel a task, blocking uninterruptibly until it is known whether such cancellation was 96 | * successful. Note that the {@link Future#cancel(boolean)} is somewhat unclear about blocking semantics. 97 | * It is recommended to use {@link #asyncCancel(boolean)} instead. 98 | * 99 | * @param interruptionDesired if interruption is desired (if available) 100 | * @return {@code true} if cancel succeeded, {@code false} otherwise 101 | */ 102 | boolean cancel(boolean interruptionDesired); 103 | 104 | /** {@inheritDoc} */ 105 | void asyncCancel(boolean interruptionDesired); 106 | 107 | /** 108 | * The possible statuses of an {@link AsyncFuture}. 109 | */ 110 | enum Status { 111 | 112 | /** 113 | * The operation is still in progress. 114 | */ 115 | WAITING, 116 | /** 117 | * The operation has completed successfully. 118 | */ 119 | COMPLETE, 120 | /** 121 | * The operation was cancelled before it completed. 122 | */ 123 | CANCELLED, 124 | /** 125 | * The operation has failed with some exception. 126 | */ 127 | FAILED, 128 | ; 129 | } 130 | 131 | /** 132 | * A listener for an asynchronous future computation result. Each listener method is passed the 133 | * {@link AsyncFuture} which it was added to, as well as the {@code attachment} which was passed in to 134 | * {@link AsyncFuture#addListener(Listener, Object)}. 135 | * 136 | * @param the future type 137 | * @param the attachment type 138 | */ 139 | interface Listener extends java.util.EventListener { 140 | 141 | /** 142 | * Handle a successful computation result. 143 | * 144 | * @param future the future 145 | * @param attachment the attachment 146 | */ 147 | void handleComplete(AsyncFuture future, A attachment); 148 | 149 | /** 150 | * Handle a failure result. 151 | * 152 | * @param future the future 153 | * @param cause the reason for the failure 154 | * @param attachment the attachment 155 | */ 156 | void handleFailed(AsyncFuture future, Throwable cause, A attachment); 157 | 158 | /** 159 | * Handle a cancellation result. 160 | * 161 | * @param future the future 162 | * @param attachment the attachment 163 | */ 164 | void handleCancelled(AsyncFuture future, A attachment); 165 | } 166 | 167 | /** 168 | * An abstract base class for an implementation of the {@code Listener} interface. The implementation 169 | * methods do nothing unless overridden. 170 | * 171 | * @param the future type 172 | * @param the attachment type 173 | */ 174 | abstract class AbstractListener implements Listener { 175 | 176 | /** {@inheritDoc} */ 177 | public void handleComplete(final AsyncFuture future, final A attachment) { 178 | } 179 | 180 | /** {@inheritDoc} */ 181 | public void handleFailed(final AsyncFuture future, final Throwable cause, final A attachment) { 182 | } 183 | 184 | /** {@inheritDoc} */ 185 | public void handleCancelled(final AsyncFuture future, final A attachment) { 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/AsyncFutureTask.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.CancellationException; 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.concurrent.Executor; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.TimeoutException; 10 | 11 | import org.wildfly.common.Assert; 12 | 13 | /** 14 | * A base class for implementing asynchronous tasks. This class implements 15 | * {@link java.util.concurrent.Future Future} as well as {@link AsyncFuture}, and 16 | * is approximately equivalent to {@link java.util.concurrent.FutureTask}, however it 17 | * does not implement {@link Runnable} and is somewhat more flexible. 18 | * 19 | * @author David M. Lloyd 20 | * 21 | * @deprecated This class is not friendly towards virtual threads. 22 | */ 23 | @Deprecated 24 | public abstract class AsyncFutureTask implements AsyncFuture { 25 | private final Executor executor; 26 | private AsyncFuture.Status status; 27 | private Object result; 28 | private List> listeners; 29 | 30 | private final class Reg implements Runnable { 31 | private final AsyncFuture.Listener listener; 32 | private final A attachment; 33 | 34 | private Reg(final AsyncFuture.Listener listener, final A attachment) { 35 | this.listener = listener; 36 | this.attachment = attachment; 37 | } 38 | 39 | public void run() { 40 | switch (getStatus()) { 41 | case CANCELLED: listener.handleCancelled(AsyncFutureTask.this, attachment); break; 42 | case COMPLETE: listener.handleComplete(AsyncFutureTask.this, attachment); break; 43 | case FAILED: listener.handleFailed(AsyncFutureTask.this, (Throwable) result, attachment); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Construct a new instance. 50 | * 51 | * @param executor the executor to use for asynchronous notifications 52 | */ 53 | protected AsyncFutureTask(final Executor executor) { 54 | this.executor = executor; 55 | status = AsyncFuture.Status.WAITING; 56 | } 57 | 58 | /** 59 | * Set the successful result of this operation. Once a result is set, calls to this 60 | * or the other {@code set*()} methods are ignored. 61 | * 62 | * @param result the result 63 | * @return {@code true} if the result was successfully set, or {@code false} if a result was already set 64 | */ 65 | protected final boolean setResult(final T result) { 66 | List> list; 67 | synchronized (this) { 68 | if (status == AsyncFuture.Status.WAITING) { 69 | this.result = result; 70 | status = AsyncFuture.Status.COMPLETE; 71 | notifyAll(); 72 | list = listeners; 73 | listeners = null; 74 | } else { 75 | return false; 76 | } 77 | } 78 | if (list != null) for (Reg reg : list) { 79 | safeExecute(reg); 80 | } 81 | return true; 82 | } 83 | 84 | /** 85 | * Set the cancelled result of this operation. Once a result is set, calls to this 86 | * or the other {@code set*()} methods are ignored. 87 | * 88 | * @return {@code true} if the result was successfully set, or {@code false} if a result was already set 89 | */ 90 | protected final boolean setCancelled() { 91 | List> list; 92 | synchronized (this) { 93 | if (status == AsyncFuture.Status.WAITING) { 94 | status = AsyncFuture.Status.CANCELLED; 95 | notifyAll(); 96 | list = listeners; 97 | listeners = null; 98 | } else { 99 | return false; 100 | } 101 | } 102 | if (list != null) for (Reg reg : list) { 103 | safeExecute(reg); 104 | } 105 | return true; 106 | } 107 | 108 | /** 109 | * Set the failure result of this operation. Once a result is set, calls to this 110 | * or the other {@code set*()} methods are ignored. 111 | * 112 | * @param cause the cause of failure 113 | * @return {@code true} if the result was successfully set, or {@code false} if a result was already set 114 | */ 115 | protected final boolean setFailed(final Throwable cause) { 116 | List> list; 117 | synchronized (this) { 118 | if (status == AsyncFuture.Status.WAITING) { 119 | status = AsyncFuture.Status.FAILED; 120 | result = cause; 121 | notifyAll(); 122 | list = listeners; 123 | listeners = null; 124 | } else { 125 | return false; 126 | } 127 | } 128 | if (list != null) for (Reg reg : list) { 129 | safeExecute(reg); 130 | } 131 | return true; 132 | } 133 | 134 | private void safeExecute(final Reg reg) { 135 | try { 136 | executor.execute(reg); 137 | } catch (Throwable t) { 138 | // todo log it 139 | } 140 | } 141 | 142 | /** 143 | * Cancel this task. The default implementation of this method does nothing; if the 144 | * task support asynchronous cancel, this method may be overridden to implement it. The 145 | * implementation may choose to support or disregard the {@code interruptionDesired} flag. 146 | * Implementations are allowed to interrupt threads associated with tasks even if the flag is 147 | * {@code false}; likewise, implementations may choose not to interrupt threads even if the 148 | * flag is {@code true}. 149 | * 150 | * @param interruptionDesired {@code true} if interruption of threads is desired 151 | */ 152 | public void asyncCancel(final boolean interruptionDesired) { 153 | } 154 | 155 | /** {@inheritDoc} */ 156 | public final Status await() throws InterruptedException { 157 | synchronized (this) { 158 | while (status == Status.WAITING) { 159 | wait(); 160 | } 161 | return status; 162 | } 163 | } 164 | 165 | /** {@inheritDoc} */ 166 | public final Status await(final long timeout, final TimeUnit unit) throws InterruptedException { 167 | long remaining = unit.toNanos(timeout); 168 | long now = System.nanoTime(); 169 | Status status; 170 | synchronized (this) { 171 | for (;;) { 172 | status = this.status; 173 | if (remaining <= 0L || status != Status.WAITING) { 174 | return status; 175 | } 176 | wait(remaining / 1_000_000L, (int) (remaining % 1_000_000)); 177 | remaining -= -now + (now = System.nanoTime()); 178 | } 179 | } 180 | } 181 | 182 | /** {@inheritDoc} */ 183 | public final Status awaitUninterruptibly() { 184 | synchronized (this) { 185 | boolean intr = Thread.interrupted(); 186 | try { 187 | while (status == Status.WAITING) try { 188 | wait(); 189 | } catch (InterruptedException e) { 190 | intr = true; 191 | } 192 | } finally { 193 | if (intr) { 194 | Thread.currentThread().interrupt(); 195 | } 196 | } 197 | return status; 198 | } 199 | } 200 | 201 | /** {@inheritDoc} */ 202 | public final Status awaitUninterruptibly(final long timeout, final TimeUnit unit) { 203 | long remaining = unit.toNanos(timeout); 204 | long now = System.nanoTime(); 205 | Status status; 206 | boolean intr = Thread.interrupted(); 207 | try { 208 | synchronized (this) { 209 | for (;;) { 210 | status = this.status; 211 | if (remaining <= 0L || status != Status.WAITING) { 212 | return status; 213 | } 214 | try { 215 | wait(remaining / 1_000_000L, (int) (remaining % 1_000_000)); 216 | } catch (InterruptedException e) { 217 | intr = true; 218 | } 219 | remaining -= -now + (now = System.nanoTime()); 220 | } 221 | } 222 | } finally { 223 | if (intr) { 224 | Thread.currentThread().interrupt(); 225 | } 226 | } 227 | } 228 | 229 | /** {@inheritDoc} */ 230 | @SuppressWarnings({ "unchecked" }) 231 | public final T get() throws InterruptedException, ExecutionException { 232 | synchronized (AsyncFutureTask.this) { 233 | final Status status = await(); 234 | switch (status) { 235 | case CANCELLED: 236 | throw Messages.msg.operationCancelled(); 237 | case FAILED: 238 | throw Messages.msg.operationFailed((Throwable) result); 239 | case COMPLETE: return (T) result; 240 | default: throw Assert.impossibleSwitchCase(status); 241 | } 242 | } 243 | } 244 | 245 | /** {@inheritDoc} */ 246 | @SuppressWarnings({ "unchecked" }) 247 | public final T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { 248 | synchronized (AsyncFutureTask.this) { 249 | final Status status = await(timeout, unit); 250 | switch (status) { 251 | case CANCELLED: 252 | throw Messages.msg.operationCancelled(); 253 | case FAILED: 254 | throw Messages.msg.operationFailed((Throwable) result); 255 | case COMPLETE: return (T) result; 256 | case WAITING: 257 | throw Messages.msg.operationTimedOut(); 258 | default: throw Assert.impossibleSwitchCase(status); 259 | } 260 | } 261 | } 262 | 263 | /** {@inheritDoc} */ 264 | @SuppressWarnings({ "unchecked" }) 265 | public final T getUninterruptibly() throws CancellationException, ExecutionException { 266 | synchronized (AsyncFutureTask.this) { 267 | final Status status = awaitUninterruptibly(); 268 | switch (status) { 269 | case CANCELLED: 270 | throw Messages.msg.operationCancelled(); 271 | case FAILED: 272 | throw Messages.msg.operationFailed((Throwable) result); 273 | case COMPLETE: return (T) result; 274 | default: throw Assert.impossibleSwitchCase(status); 275 | } 276 | } 277 | } 278 | 279 | /** {@inheritDoc} */ 280 | @SuppressWarnings({ "unchecked" }) 281 | public final T getUninterruptibly(final long timeout, final TimeUnit unit) throws CancellationException, ExecutionException, TimeoutException { 282 | synchronized (AsyncFutureTask.this) { 283 | final Status status = awaitUninterruptibly(timeout, unit); 284 | switch (status) { 285 | case CANCELLED: 286 | throw Messages.msg.operationCancelled(); 287 | case FAILED: 288 | throw Messages.msg.operationFailed((Throwable) result); 289 | case COMPLETE: return (T) result; 290 | case WAITING: 291 | throw Messages.msg.operationTimedOut(); 292 | default: throw Assert.impossibleSwitchCase(status); 293 | } 294 | } 295 | } 296 | 297 | /** {@inheritDoc} */ 298 | public final Status getStatus() { 299 | synchronized (AsyncFutureTask.this) { 300 | return status; 301 | } 302 | } 303 | 304 | /** {@inheritDoc} */ 305 | public final void addListener(final Listener listener, final A attachment) { 306 | synchronized (AsyncFutureTask.this) { 307 | final Reg reg = new Reg(listener, attachment); 308 | if (status == Status.WAITING) { 309 | if (listeners == null) { 310 | listeners = new ArrayList>(); 311 | } 312 | listeners.add(reg); 313 | } else { 314 | safeExecute(reg); 315 | } 316 | } 317 | } 318 | 319 | /** {@inheritDoc} */ 320 | public final boolean cancel(final boolean interruptionDesired) { 321 | asyncCancel(interruptionDesired); 322 | return awaitUninterruptibly() == Status.CANCELLED; 323 | } 324 | 325 | /** {@inheritDoc} */ 326 | public final boolean isCancelled() { 327 | return getStatus() == Status.CANCELLED; 328 | } 329 | 330 | /** {@inheritDoc} */ 331 | public final boolean isDone() { 332 | return getStatus() != Status.WAITING; 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ContextClassLoaderSavingRunnable.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | class ContextClassLoaderSavingRunnable implements Runnable { 4 | 5 | private final ClassLoader loader; 6 | private final Runnable delegate; 7 | 8 | ContextClassLoaderSavingRunnable(final ClassLoader loader, final Runnable delegate) { 9 | this.loader = loader; 10 | this.delegate = delegate; 11 | } 12 | 13 | public void run() { 14 | final Thread currentThread = Thread.currentThread(); 15 | final ClassLoader old = JBossExecutors.getAndSetContextClassLoader(currentThread, loader); 16 | try { 17 | delegate.run(); 18 | } finally { 19 | JBossExecutors.setContextClassLoader(currentThread, old); 20 | } 21 | } 22 | 23 | public String toString() { 24 | return "Context class loader saving " + delegate.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ContextHandler.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | /** 4 | * A handler for propagating context from a task submitter to a task execution. 5 | * 6 | * @param the class of the context to capture 7 | */ 8 | public interface ContextHandler { 9 | /** 10 | * The context handler which captures no context. 11 | */ 12 | ContextHandler NONE = new ContextHandler() { 13 | public Object captureContext() { 14 | return null; 15 | } 16 | 17 | public void runWith(final Runnable task, final Object context) { 18 | task.run(); 19 | } 20 | }; 21 | 22 | /** 23 | * Capture the current context from the submitting thread. 24 | * 25 | * @return the captured context 26 | */ 27 | T captureContext(); 28 | 29 | /** 30 | * Run the given task with the given captured context. The context should be cleared 31 | * when this method returns. 32 | * 33 | * @param task the task to run (not {@code null}) 34 | * @param context the context returned from {@link #captureContext()} 35 | */ 36 | void runWith(Runnable task, T context); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DeclaredFieldAction.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.lang.reflect.Field; 4 | import java.security.PrivilegedAction; 5 | 6 | /** 7 | */ 8 | final class DeclaredFieldAction implements PrivilegedAction { 9 | private final Class clazz; 10 | private final String fieldName; 11 | 12 | DeclaredFieldAction(final Class clazz, final String fieldName) { 13 | this.clazz = clazz; 14 | this.fieldName = fieldName; 15 | } 16 | 17 | public Field run() { 18 | try { 19 | return clazz.getDeclaredField(fieldName); 20 | } catch (NoSuchFieldException e) { 21 | return null; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DelegatingExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | /** 6 | * An executor that simply delegates to another executor. Use instances of this class to hide extra methods on 7 | * another executor. 8 | */ 9 | class DelegatingExecutor implements Executor { 10 | private final Executor delegate; 11 | 12 | DelegatingExecutor(final Executor delegate) { 13 | this.delegate = delegate; 14 | } 15 | 16 | /** 17 | * Execute a task by passing it to the delegate executor. 18 | * 19 | * @param command the task 20 | */ 21 | public void execute(final Runnable command) { 22 | delegate.execute(command); 23 | } 24 | 25 | public String toString() { 26 | return String.format("%s -> %s", super.toString(), delegate); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DelegatingExecutorService.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.TimeUnit; 5 | import java.util.concurrent.AbstractExecutorService; 6 | import java.util.concurrent.Executor; 7 | import java.util.List; 8 | 9 | /** 10 | * An implementation of {@code ExecutorService} that delegates to the real executor, while disallowing termination. 11 | */ 12 | class DelegatingExecutorService extends AbstractExecutorService implements ExecutorService { 13 | private final Executor delegate; 14 | 15 | DelegatingExecutorService(final Executor delegate) { 16 | this.delegate = delegate; 17 | } 18 | 19 | public void execute(final Runnable command) { 20 | delegate.execute(command); 21 | } 22 | 23 | public boolean isShutdown() { 24 | // container managed executors are never shut down from the application's perspective 25 | return false; 26 | } 27 | 28 | public boolean isTerminated() { 29 | // container managed executors are never shut down from the application's perspective 30 | return false; 31 | } 32 | 33 | public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { 34 | return false; 35 | } 36 | 37 | public void shutdown() { 38 | throw Messages.msg.notAllowedContainerManaged("shutdown"); 39 | } 40 | 41 | public List shutdownNow() { 42 | throw Messages.msg.notAllowedContainerManaged("shutdownNow"); 43 | } 44 | 45 | public String toString() { 46 | return String.format("%s -> %s", super.toString(), delegate); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DelegatingRunnable.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | class DelegatingRunnable implements Runnable { 4 | private final Runnable delegate; 5 | 6 | DelegatingRunnable(final Runnable delegate) { 7 | this.delegate = delegate; 8 | } 9 | 10 | public void run() { 11 | delegate.run(); 12 | } 13 | 14 | public String toString() { 15 | return String.format("%s -> %s", super.toString(), delegate); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DelegatingScheduledExecutorService.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.ScheduledFuture; 6 | import java.util.concurrent.Callable; 7 | 8 | /** 9 | * An implementation of {@code ScheduledExecutorService} that delegates to the real executor, while disallowing termination. 10 | */ 11 | class DelegatingScheduledExecutorService extends DelegatingExecutorService implements ScheduledExecutorService { 12 | private final ScheduledExecutorService delegate; 13 | 14 | DelegatingScheduledExecutorService(final ScheduledExecutorService delegate) { 15 | super(delegate); 16 | this.delegate = delegate; 17 | } 18 | 19 | public ScheduledFuture schedule(final Runnable command, final long delay, final TimeUnit unit) { 20 | return delegate.schedule(command, delay, unit); 21 | } 22 | 23 | public ScheduledFuture schedule(final Callable callable, final long delay, final TimeUnit unit) { 24 | return delegate.schedule(callable, delay, unit); 25 | } 26 | 27 | public ScheduledFuture scheduleAtFixedRate(final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { 28 | return delegate.scheduleAtFixedRate(command, initialDelay, period, unit); 29 | } 30 | 31 | public ScheduledFuture scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) { 32 | return delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/DiscardingExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | class DiscardingExecutor implements Executor { 6 | static final DiscardingExecutor INSTANCE = new DiscardingExecutor(); 7 | 8 | private DiscardingExecutor() { 9 | } 10 | 11 | public void execute(final Runnable command) { 12 | // nothing 13 | } 14 | 15 | public String toString() { 16 | return "Discarding executor"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/HandoffRejectedExecutionHandler.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.RejectedExecutionHandler; 4 | import java.util.concurrent.Executor; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | 7 | class HandoffRejectedExecutionHandler implements RejectedExecutionHandler { 8 | 9 | private final Executor target; 10 | 11 | HandoffRejectedExecutionHandler(final Executor target) { 12 | this.target = target; 13 | } 14 | 15 | public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { 16 | target.execute(r); 17 | } 18 | 19 | public String toString() { 20 | return String.format("%s -> %s", super.toString(), target); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/InterruptHandler.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | /** 4 | * A thread interrupt handler. Called when a thread's {@code interrupt()} method is invoked. The handler should 5 | * not throw an exception; otherwise user code might end up in an unexpected state. 6 | */ 7 | public interface InterruptHandler { 8 | 9 | /** 10 | * Handle an interrupt condition on the given thread. This method should not throw an exception. 11 | * 12 | * @param thread the thread which was interrupted 13 | */ 14 | void handleInterrupt(Thread thread); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/JBossExecutors.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.RejectedExecutionHandler; 7 | import java.util.concurrent.ThreadFactory; 8 | import java.util.concurrent.ScheduledExecutorService; 9 | 10 | import org.jboss.logging.Logger; 11 | import io.smallrye.common.constraint.Assert; 12 | 13 | /** 14 | * JBoss thread- and executor-related utility and factory methods. 15 | */ 16 | public final class JBossExecutors { 17 | 18 | private static final Logger THREAD_ERROR_LOGGER = Logger.getLogger("org.jboss.threads.errors"); 19 | 20 | private JBossExecutors() {} 21 | 22 | private static final RuntimePermission COPY_CONTEXT_CLASSLOADER_PERMISSION = new RuntimePermission("copyClassLoader"); 23 | 24 | private static final ExecutorService REJECTING_EXECUTOR_SERVICE = new DelegatingExecutorService(RejectingExecutor.INSTANCE); 25 | private static final ExecutorService DISCARDING_EXECUTOR_SERVICE = new DelegatingExecutorService(DiscardingExecutor.INSTANCE); 26 | 27 | // ================================================== 28 | // DIRECT EXECUTORS 29 | // ================================================== 30 | 31 | /** 32 | * Get the direct executor. This executor will immediately run any task it is given, and propagate back any 33 | * run-time exceptions thrown. 34 | * 35 | * @return the direct executor instance 36 | */ 37 | public static Executor directExecutor() { 38 | return SimpleDirectExecutor.INSTANCE; 39 | } 40 | 41 | /** 42 | * Get the rejecting executor. This executor will reject any task submitted to it. 43 | * 44 | * @return the rejecting executor instance 45 | */ 46 | public static Executor rejectingExecutor() { 47 | return RejectingExecutor.INSTANCE; 48 | } 49 | 50 | /** 51 | * Get a rejecting executor. This executor will reject any task submitted to it with the given message. 52 | * 53 | * @param message the reject message 54 | * @return the rejecting executor instance 55 | */ 56 | public static Executor rejectingExecutor(final String message) { 57 | return new RejectingExecutor(message); 58 | } 59 | 60 | /** 61 | * Get the rejecting executor service. This executor will reject any task submitted to it. It cannot be shut down. 62 | * 63 | * @return the rejecting executor service instance 64 | */ 65 | public static ExecutorService rejectingExecutorService() { 66 | return REJECTING_EXECUTOR_SERVICE; 67 | } 68 | 69 | /** 70 | * Get the rejecting executor service. This executor will reject any task submitted to it with the given message. 71 | * It cannot be shut down. 72 | * 73 | * @param message the reject message 74 | * @return the rejecting executor service instance 75 | */ 76 | public static ExecutorService rejectingExecutorService(final String message) { 77 | return protectedExecutorService(rejectingExecutor(message)); 78 | } 79 | 80 | /** 81 | * Get the discarding executor. This executor will silently discard any task submitted to it. 82 | * 83 | * @return the discarding executor instance 84 | */ 85 | public static Executor discardingExecutor() { 86 | return DiscardingExecutor.INSTANCE; 87 | } 88 | 89 | /** 90 | * Get the discarding executor service. This executor will silently discard any task submitted to it. It cannot 91 | * be shut down. 92 | * 93 | * @return the discarding executor service instance 94 | */ 95 | public static ExecutorService discardingExecutorService() { 96 | return DISCARDING_EXECUTOR_SERVICE; 97 | } 98 | 99 | /** 100 | * Create an executor which runs tasks with the given context class loader. 101 | * 102 | * @param delegate the executor to delegate to 103 | * @param taskClassLoader the context class loader to use 104 | * @return the new direct executor 105 | */ 106 | public static Executor contextClassLoaderExecutor(final Executor delegate, final ClassLoader taskClassLoader) { 107 | return new DelegatingExecutor(delegate) { 108 | public void execute(final Runnable command) { 109 | super.execute(new ContextClassLoaderSavingRunnable(taskClassLoader, command)); 110 | } 111 | }; 112 | } 113 | 114 | // ================================================== 115 | // REJECTED EXECUTION HANDLERS 116 | // ================================================== 117 | 118 | private static final RejectedExecutionHandler ABORT_POLICY = new ThreadPoolExecutor.AbortPolicy(); 119 | private static final RejectedExecutionHandler CALLER_RUNS_POLICY = new ThreadPoolExecutor.CallerRunsPolicy(); 120 | private static final RejectedExecutionHandler DISCARD_OLDEST_POLICY = new ThreadPoolExecutor.DiscardOldestPolicy(); 121 | private static final RejectedExecutionHandler DISCARD_POLICY = new ThreadPoolExecutor.DiscardPolicy(); 122 | 123 | /** 124 | * Get the abort policy for a {@link java.util.concurrent.ThreadPoolExecutor}. 125 | * 126 | * @return the abort policy 127 | * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy 128 | */ 129 | public static RejectedExecutionHandler abortPolicy() { 130 | return ABORT_POLICY; 131 | } 132 | 133 | /** 134 | * Get the caller-runs policy for a {@link java.util.concurrent.ThreadPoolExecutor}. 135 | * 136 | * @return the caller-runs policy 137 | * @see java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy 138 | */ 139 | public static RejectedExecutionHandler callerRunsPolicy() { 140 | return CALLER_RUNS_POLICY; 141 | } 142 | 143 | /** 144 | * Get the discard-oldest policy for a {@link java.util.concurrent.ThreadPoolExecutor}. 145 | * 146 | * @return the discard-oldest policy 147 | * @see java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy 148 | */ 149 | public static RejectedExecutionHandler discardOldestPolicy() { 150 | return DISCARD_OLDEST_POLICY; 151 | } 152 | 153 | /** 154 | * Get the discard policy for a {@link java.util.concurrent.ThreadPoolExecutor}. 155 | * 156 | * @return the discard policy 157 | * @see java.util.concurrent.ThreadPoolExecutor.DiscardPolicy 158 | */ 159 | public static RejectedExecutionHandler discardPolicy() { 160 | return DISCARD_POLICY; 161 | } 162 | 163 | /** 164 | * Get a handoff policy for a {@link java.util.concurrent.ThreadPoolExecutor}. The returned instance will 165 | * delegate to another executor in the event that the task is rejected. 166 | * 167 | * @param target the target executor 168 | * @return the new handoff policy implementation 169 | */ 170 | public static RejectedExecutionHandler handoffPolicy(final Executor target) { 171 | return new HandoffRejectedExecutionHandler(target); 172 | } 173 | 174 | // ================================================== 175 | // PROTECTED EXECUTOR SERVICE WRAPPERS 176 | // ================================================== 177 | 178 | /** 179 | * Wrap an executor with an {@code ExecutorService} instance which supports all the features of {@code ExecutorService} 180 | * except for shutting down the executor. 181 | * 182 | * @param target the target executor 183 | * @return the executor service 184 | */ 185 | public static ExecutorService protectedExecutorService(final Executor target) { 186 | return new DelegatingExecutorService(target); 187 | } 188 | 189 | /** 190 | * Wrap a scheduled executor with a {@code ScheduledExecutorService} instance which supports all the features of 191 | * {@code ScheduledExecutorService} except for shutting down the executor. 192 | * 193 | * @param target the target executor 194 | * @return the executor service 195 | */ 196 | public static ScheduledExecutorService protectedScheduledExecutorService(final ScheduledExecutorService target) { 197 | return new DelegatingScheduledExecutorService(target); 198 | } 199 | 200 | // ================================================== 201 | // THREAD FACTORIES 202 | // ================================================== 203 | 204 | /** 205 | * Create a thread factory which resets all thread-local storage and delegates to the given thread factory. 206 | * You must have the {@link RuntimePermission}{@code ("modifyThread")} permission to use this method. 207 | * 208 | * @param delegate the delegate thread factory 209 | * @return the resetting thread factory 210 | * @throws SecurityException if the caller does not have the {@link RuntimePermission}{@code ("modifyThread")} 211 | * permission 212 | */ 213 | public static ThreadFactory resettingThreadFactory(final ThreadFactory delegate) throws SecurityException { 214 | return new ThreadFactory() { 215 | public Thread newThread(final Runnable r) { 216 | return delegate.newThread(new ThreadLocalResettingRunnable(r)); 217 | } 218 | }; 219 | } 220 | 221 | private static final Runnable TCCL_RESETTER = new Runnable() { 222 | public void run() { 223 | JDKSpecific.setThreadContextClassLoader(Thread.currentThread(), null); 224 | } 225 | 226 | public String toString() { 227 | return "ContextClassLoader-resetting Runnable"; 228 | } 229 | }; 230 | 231 | // ================================================== 232 | // RUNNABLES 233 | // ================================================== 234 | 235 | private static final Runnable NULL_RUNNABLE = NullRunnable.getInstance(); 236 | 237 | /** 238 | * Get the null runnable which does nothing. 239 | * 240 | * @return the null runnable 241 | */ 242 | public static Runnable nullRunnable() { 243 | return NULL_RUNNABLE; 244 | } 245 | 246 | /** 247 | * Get a {@code Runnable} which, when executed, clears the thread context class loader (if the caller has sufficient 248 | * privileges). 249 | * 250 | * @return the runnable 251 | */ 252 | public static Runnable contextClassLoaderResetter() { 253 | return TCCL_RESETTER; 254 | } 255 | 256 | /** 257 | * Create a task that delegates to the given task, preserving the context classloader which was in effect when 258 | * this method was invoked. 259 | * 260 | * @param delegate the delegate runnable 261 | * @return the wrapping runnable 262 | * @throws SecurityException if a security manager exists and the caller does not have the {@code "copyClassLoader"} 263 | * {@link RuntimePermission}. 264 | */ 265 | public static Runnable classLoaderPreservingTask(final Runnable delegate) throws SecurityException { 266 | final SecurityManager sm = System.getSecurityManager(); 267 | if (sm != null) { 268 | sm.checkPermission(COPY_CONTEXT_CLASSLOADER_PERMISSION); 269 | } 270 | return classLoaderPreservingTaskUnchecked(delegate); 271 | } 272 | 273 | static final ClassLoader SAFE_CL; 274 | 275 | static { 276 | ClassLoader safeClassLoader = JBossExecutors.class.getClassLoader(); 277 | if (safeClassLoader == null) { 278 | safeClassLoader = ClassLoader.getSystemClassLoader(); 279 | } 280 | if (safeClassLoader == null) { 281 | safeClassLoader = new ClassLoader() { 282 | }; 283 | } 284 | SAFE_CL = safeClassLoader; 285 | } 286 | 287 | static Runnable classLoaderPreservingTaskUnchecked(final Runnable delegate) { 288 | Assert.checkNotNullParam("delegate", delegate); 289 | return new ContextClassLoaderSavingRunnable(getContextClassLoader(Thread.currentThread()), delegate); 290 | } 291 | 292 | /** 293 | * Privileged method to get the context class loader of the given thread. 294 | * 295 | * @param thread the thread to introspect 296 | * @return the context class loader 297 | */ 298 | static ClassLoader getContextClassLoader(final Thread thread) { 299 | return JDKSpecific.getThreadContextClassLoader(thread); 300 | } 301 | 302 | /** 303 | * Privileged method to get and set the context class loader of the given thread. 304 | * 305 | * @param thread the thread to introspect 306 | * @param newClassLoader the new context class loader 307 | * @return the old context class loader 308 | */ 309 | static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassLoader newClassLoader) { 310 | ClassLoader old = JDKSpecific.getThreadContextClassLoader(thread); 311 | if (old != newClassLoader) { 312 | JDKSpecific.setThreadContextClassLoader(thread, newClassLoader); 313 | } 314 | return old; 315 | } 316 | 317 | /** 318 | * Privileged method to set the context class loader of the given thread. 319 | * 320 | * @param thread the thread to introspect 321 | * @param classLoader the new context class loader 322 | */ 323 | static void setContextClassLoader(final Thread thread, final ClassLoader classLoader) { 324 | JDKSpecific.setThreadContextClassLoader(thread, classLoader); 325 | } 326 | 327 | /** 328 | * Privileged method to clear the context class loader of the given thread to a safe non-{@code null} value. 329 | * 330 | * @param thread the thread to introspect 331 | */ 332 | static void clearContextClassLoader(final Thread thread) { 333 | JDKSpecific.setThreadContextClassLoader(thread, SAFE_CL); 334 | } 335 | 336 | // ================================================== 337 | // UNCAUGHT EXCEPTION HANDLERS 338 | // ================================================== 339 | 340 | /** 341 | * Get an uncaught exception handler which logs to the given logger. 342 | * 343 | * @param log the logger 344 | * @return the handler 345 | */ 346 | public static Thread.UncaughtExceptionHandler loggingExceptionHandler(final Logger log) { 347 | return new LoggingUncaughtExceptionHandler(log); 348 | } 349 | 350 | /** 351 | * Get an uncaught exception handler which logs to the given logger. 352 | * 353 | * @param categoryName the name of the logger category to log to 354 | * @return the handler 355 | */ 356 | public static Thread.UncaughtExceptionHandler loggingExceptionHandler(final String categoryName) { 357 | return new LoggingUncaughtExceptionHandler(Logger.getLogger(categoryName)); 358 | } 359 | 360 | private static final Thread.UncaughtExceptionHandler LOGGING_HANDLER = loggingExceptionHandler(THREAD_ERROR_LOGGER); 361 | 362 | /** 363 | * Get an uncaught exception handler which logs to the default error logger. 364 | * 365 | * @return the handler 366 | */ 367 | public static Thread.UncaughtExceptionHandler loggingExceptionHandler() { 368 | return LOGGING_HANDLER; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/JBossScheduledThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.RejectedExecutionHandler; 4 | import java.util.concurrent.ScheduledThreadPoolExecutor; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | public final class JBossScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { 11 | 12 | private final AtomicInteger rejectCount = new AtomicInteger(); 13 | private final Runnable terminationTask; 14 | 15 | public JBossScheduledThreadPoolExecutor(int corePoolSize, final Runnable terminationTask) { 16 | super(corePoolSize); 17 | this.terminationTask = terminationTask; 18 | setRejectedExecutionHandler(super.getRejectedExecutionHandler()); 19 | } 20 | 21 | public JBossScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, final Runnable terminationTask) { 22 | super(corePoolSize, threadFactory); 23 | this.terminationTask = terminationTask; 24 | setRejectedExecutionHandler(super.getRejectedExecutionHandler()); 25 | } 26 | 27 | public JBossScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler, final Runnable terminationTask) { 28 | super(corePoolSize); 29 | this.terminationTask = terminationTask; 30 | setRejectedExecutionHandler(handler); 31 | } 32 | 33 | public JBossScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler, final Runnable terminationTask) { 34 | super(corePoolSize, threadFactory); 35 | this.terminationTask = terminationTask; 36 | setRejectedExecutionHandler(handler); 37 | } 38 | 39 | public long getKeepAliveTime() { 40 | return getKeepAliveTime(TimeUnit.MILLISECONDS); 41 | } 42 | 43 | public void setKeepAliveTime(final long milliseconds) { 44 | super.setKeepAliveTime(milliseconds, TimeUnit.MILLISECONDS); 45 | super.allowCoreThreadTimeOut(milliseconds < Long.MAX_VALUE); 46 | } 47 | 48 | public void setKeepAliveTime(final long time, final TimeUnit unit) { 49 | super.setKeepAliveTime(time, unit); 50 | super.allowCoreThreadTimeOut(time < Long.MAX_VALUE); 51 | } 52 | 53 | public int getRejectedCount() { 54 | return rejectCount.get(); 55 | } 56 | 57 | public int getCurrentThreadCount() { 58 | return getActiveCount(); 59 | } 60 | 61 | public int getLargestThreadCount() { 62 | return getLargestPoolSize(); 63 | } 64 | 65 | public int getMaxThreads() { 66 | return getCorePoolSize(); 67 | } 68 | 69 | public void setMaxThreads(final int newSize) { 70 | setCorePoolSize(newSize); 71 | } 72 | 73 | public RejectedExecutionHandler getRejectedExecutionHandler() { 74 | return ((CountingRejectHandler)super.getRejectedExecutionHandler()).getDelegate(); 75 | } 76 | 77 | public void setRejectedExecutionHandler(final RejectedExecutionHandler handler) { 78 | super.setRejectedExecutionHandler(new CountingRejectHandler(handler)); 79 | } 80 | 81 | /** {@inheritDoc} */ 82 | public int getQueueSize() { 83 | return this.getQueue().size(); 84 | } 85 | 86 | protected void terminated() { 87 | terminationTask.run(); 88 | } 89 | 90 | private final class CountingRejectHandler implements RejectedExecutionHandler { 91 | private final RejectedExecutionHandler delegate; 92 | 93 | public CountingRejectHandler(final RejectedExecutionHandler delegate) { 94 | this.delegate = delegate; 95 | } 96 | 97 | public RejectedExecutionHandler getDelegate() { 98 | return delegate; 99 | } 100 | 101 | public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { 102 | rejectCount.incrementAndGet(); 103 | if (isShutdown()) { 104 | throw Messages.msg.shutDownInitiated(); 105 | } 106 | delegate.rejectedExecution(r, executor); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/JBossThreadFactory.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.security.AccessController; 4 | import java.security.PrivilegedAction; 5 | import java.security.AccessControlContext; 6 | import java.util.concurrent.ThreadFactory; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * A factory for {@link JBossThread} instances. 11 | */ 12 | public final class JBossThreadFactory implements ThreadFactory { 13 | private final ThreadGroup threadGroup; 14 | private final Boolean daemon; 15 | private final Integer initialPriority; 16 | private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler; 17 | private final Long stackSize; 18 | private final String namePattern; 19 | 20 | private final AtomicLong factoryThreadIndexSequence = new AtomicLong(1L); 21 | 22 | private final long factoryIndex; 23 | 24 | private final AccessControlContext creatingContext; 25 | 26 | private static final AtomicLong globalThreadIndexSequence = new AtomicLong(1L); 27 | private static final AtomicLong factoryIndexSequence = new AtomicLong(1L); 28 | 29 | /** 30 | * Construct a new instance. The access control context of the calling thread will be the one used to create 31 | * new threads if a security manager is installed. 32 | * 33 | * @param threadGroup the thread group to assign threads to by default (may be {@code null}) 34 | * @param daemon whether the created threads should be daemon threads, or {@code null} to use the thread group's setting 35 | * @param initialPriority the initial thread priority, or {@code null} to use the thread group's setting 36 | * @param namePattern the name pattern string 37 | * @param uncaughtExceptionHandler the uncaught exception handler, if any 38 | * @param stackSize the JVM-specific stack size, or {@code null} to leave it unspecified 39 | */ 40 | public JBossThreadFactory(ThreadGroup threadGroup, final Boolean daemon, final Integer initialPriority, String namePattern, final Thread.UncaughtExceptionHandler uncaughtExceptionHandler, final Long stackSize) { 41 | if (threadGroup == null) { 42 | final SecurityManager sm = System.getSecurityManager(); 43 | threadGroup = sm != null ? sm.getThreadGroup() : Thread.currentThread().getThreadGroup(); 44 | } 45 | this.threadGroup = threadGroup; 46 | this.daemon = daemon; 47 | this.initialPriority = initialPriority; 48 | this.uncaughtExceptionHandler = uncaughtExceptionHandler; 49 | this.stackSize = stackSize; 50 | factoryIndex = factoryIndexSequence.getAndIncrement(); 51 | if (namePattern == null) { 52 | namePattern = "pool-%f-thread-%t"; 53 | } 54 | this.namePattern = namePattern; 55 | this.creatingContext = AccessController.getContext(); 56 | } 57 | 58 | /** 59 | * @deprecated Use {@link #JBossThreadFactory(ThreadGroup, Boolean, Integer, String, Thread.UncaughtExceptionHandler, Long)} instead. 60 | */ 61 | @Deprecated 62 | public JBossThreadFactory(ThreadGroup threadGroup, final Boolean daemon, final Integer initialPriority, String namePattern, final Thread.UncaughtExceptionHandler uncaughtExceptionHandler, final Long stackSize, final AccessControlContext ignored) { 63 | this(threadGroup, daemon, initialPriority, namePattern, uncaughtExceptionHandler, stackSize); 64 | } 65 | 66 | public Thread newThread(final Runnable target) { 67 | final AccessControlContext context; 68 | if ((context = creatingContext) != null) { 69 | return AccessController.doPrivileged(new ThreadCreateAction(target), context); 70 | } else { 71 | return createThread(target); 72 | } 73 | } 74 | 75 | private final class ThreadCreateAction implements PrivilegedAction { 76 | private final Runnable target; 77 | 78 | private ThreadCreateAction(final Runnable target) { 79 | this.target = target; 80 | } 81 | 82 | public Thread run() { 83 | return createThread(target); 84 | } 85 | } 86 | 87 | private Thread createThread(final Runnable target) { 88 | final ThreadNameInfo nameInfo = new ThreadNameInfo(globalThreadIndexSequence.getAndIncrement(), factoryThreadIndexSequence.getAndIncrement(), factoryIndex); 89 | final JBossThread thread; 90 | if (stackSize != null) { 91 | thread = new JBossThread(threadGroup, target, "", stackSize.longValue()); 92 | } else { 93 | thread = new JBossThread(threadGroup, target); 94 | } 95 | thread.setThreadNameInfo(nameInfo); 96 | thread.setName(nameInfo.format(thread, namePattern)); 97 | if (initialPriority != null) thread.setPriority(initialPriority.intValue()); 98 | if (daemon != null) thread.setDaemon(daemon.booleanValue()); 99 | if (uncaughtExceptionHandler != null) thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); 100 | JBossExecutors.clearContextClassLoader(thread); 101 | return thread; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/JDKSpecific.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.lang.reflect.Field; 4 | import java.security.AccessController; 5 | import java.security.PrivilegedAction; 6 | 7 | import sun.misc.Unsafe; 8 | 9 | final class JDKSpecific { 10 | private JDKSpecific() {} 11 | 12 | private static final Unsafe unsafe; 13 | private static final long contextClassLoaderOffs; 14 | 15 | static { 16 | unsafe = AccessController.doPrivileged(new PrivilegedAction() { 17 | public Unsafe run() { 18 | try { 19 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 20 | field.setAccessible(true); 21 | return (Unsafe) field.get(null); 22 | } catch (IllegalAccessException e) { 23 | IllegalAccessError error = new IllegalAccessError(e.getMessage()); 24 | error.setStackTrace(e.getStackTrace()); 25 | throw error; 26 | } catch (NoSuchFieldException e) { 27 | NoSuchFieldError error = new NoSuchFieldError(e.getMessage()); 28 | error.setStackTrace(e.getStackTrace()); 29 | throw error; 30 | } 31 | } 32 | }); 33 | try { 34 | contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader")); 35 | } catch (NoSuchFieldException e) { 36 | NoSuchFieldError error = new NoSuchFieldError(e.getMessage()); 37 | error.setStackTrace(e.getStackTrace()); 38 | throw error; 39 | } 40 | } 41 | 42 | static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) { 43 | unsafe.putObject(thread, contextClassLoaderOffs, classLoader); 44 | } 45 | 46 | static ClassLoader getThreadContextClassLoader(Thread thread) { 47 | return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs); 48 | } 49 | 50 | static final class ThreadAccess { 51 | private static final long threadLocalMapOffs; 52 | private static final long inheritableThreadLocalMapOffs; 53 | 54 | static { 55 | try { 56 | threadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals")); 57 | inheritableThreadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("inheritableThreadLocals")); 58 | } catch (NoSuchFieldException e) { 59 | NoSuchFieldError error = new NoSuchFieldError(e.getMessage()); 60 | error.setStackTrace(e.getStackTrace()); 61 | throw error; 62 | } 63 | } 64 | 65 | static void clearThreadLocals() { 66 | Thread thread = Thread.currentThread(); 67 | unsafe.putObject(thread, threadLocalMapOffs, null); 68 | unsafe.putObject(thread, inheritableThreadLocalMapOffs, null); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/LoggingUncaughtExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import org.jboss.logging.Logger; 4 | 5 | class LoggingUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 6 | 7 | private final Logger log; 8 | 9 | LoggingUncaughtExceptionHandler(final Logger log) { 10 | this.log = log; 11 | } 12 | 13 | public void uncaughtException(final Thread thread, final Throwable throwable) { 14 | log.errorf(throwable, "Thread %s threw an uncaught exception", thread); 15 | } 16 | 17 | public String toString() { 18 | return String.format("%s to \"%s\"", super.toString(), log.getName()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ManagedThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.Executor; 5 | import java.util.concurrent.RejectedExecutionHandler; 6 | import java.util.concurrent.ThreadFactory; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import org.jboss.threads.management.ManageableThreadPoolExecutorService; 11 | import org.jboss.threads.management.StandardThreadPoolMXBean; 12 | import io.smallrye.common.constraint.Assert; 13 | 14 | /** 15 | * A version of {@link ThreadPoolExecutor} which implements {@link ManageableThreadPoolExecutorService} in order to allow 16 | * opting out of using {@link EnhancedQueueExecutor}. 17 | */ 18 | public final class ManagedThreadPoolExecutor extends ThreadPoolExecutor implements ManageableThreadPoolExecutorService { 19 | private final Runnable terminationTask; 20 | private final StandardThreadPoolMXBean mxBean = new MXBeanImpl(); 21 | private volatile Executor handoffExecutor = JBossExecutors.rejectingExecutor(); 22 | private static final RejectedExecutionHandler HANDLER = new RejectedExecutionHandler() { 23 | public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { 24 | ((ManagedThreadPoolExecutor) executor).reject(r); 25 | } 26 | }; 27 | 28 | public ManagedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final Runnable terminationTask) { 29 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, HANDLER); 30 | this.terminationTask = terminationTask; 31 | } 32 | 33 | public ManagedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final ThreadFactory threadFactory, final Runnable terminationTask) { 34 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, HANDLER); 35 | this.terminationTask = terminationTask; 36 | } 37 | 38 | public ManagedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final Executor handoffExecutor, final Runnable terminationTask) { 39 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, HANDLER); 40 | this.terminationTask = terminationTask; 41 | this.handoffExecutor = handoffExecutor; 42 | } 43 | 44 | public ManagedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final TimeUnit unit, final BlockingQueue workQueue, final ThreadFactory threadFactory, final Executor handoffExecutor, final Runnable terminationTask) { 45 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, HANDLER); 46 | this.terminationTask = terminationTask; 47 | this.handoffExecutor = handoffExecutor; 48 | } 49 | 50 | public StandardThreadPoolMXBean getThreadPoolMXBean() { 51 | return mxBean; 52 | } 53 | 54 | public Executor getHandoffExecutor() { 55 | return handoffExecutor; 56 | } 57 | 58 | public void setHandoffExecutor(final Executor handoffExecutor) { 59 | Assert.checkNotNullParam("handoffExecutor", handoffExecutor); 60 | this.handoffExecutor = handoffExecutor; 61 | super.setRejectedExecutionHandler(HANDLER); 62 | } 63 | 64 | void reject(Runnable r) { 65 | handoffExecutor.execute(r); 66 | } 67 | 68 | protected void terminated() { 69 | terminationTask.run(); 70 | } 71 | 72 | class MXBeanImpl implements StandardThreadPoolMXBean { 73 | public float getGrowthResistance() { 74 | return 1.0f; 75 | } 76 | 77 | public void setGrowthResistance(final float value) { 78 | // ignored 79 | } 80 | 81 | public boolean isGrowthResistanceSupported() { 82 | return false; 83 | } 84 | 85 | public int getCorePoolSize() { 86 | return ManagedThreadPoolExecutor.this.getCorePoolSize(); 87 | } 88 | 89 | public void setCorePoolSize(final int corePoolSize) { 90 | ManagedThreadPoolExecutor.this.setCorePoolSize(corePoolSize); 91 | } 92 | 93 | public boolean isCorePoolSizeSupported() { 94 | return true; 95 | } 96 | 97 | public boolean prestartCoreThread() { 98 | return ManagedThreadPoolExecutor.this.prestartCoreThread(); 99 | } 100 | 101 | public int prestartAllCoreThreads() { 102 | return ManagedThreadPoolExecutor.this.prestartAllCoreThreads(); 103 | } 104 | 105 | public boolean isCoreThreadPrestartSupported() { 106 | return true; 107 | } 108 | 109 | public int getMaximumPoolSize() { 110 | return ManagedThreadPoolExecutor.this.getMaximumPoolSize(); 111 | } 112 | 113 | public void setMaximumPoolSize(final int maxPoolSize) { 114 | ManagedThreadPoolExecutor.this.setMaximumPoolSize(maxPoolSize); 115 | } 116 | 117 | public int getPoolSize() { 118 | return ManagedThreadPoolExecutor.this.getPoolSize(); 119 | } 120 | 121 | public int getLargestPoolSize() { 122 | return ManagedThreadPoolExecutor.this.getLargestPoolSize(); 123 | } 124 | 125 | public int getActiveCount() { 126 | return ManagedThreadPoolExecutor.this.getActiveCount(); 127 | } 128 | 129 | public boolean isAllowCoreThreadTimeOut() { 130 | return ManagedThreadPoolExecutor.this.allowsCoreThreadTimeOut(); 131 | } 132 | 133 | public void setAllowCoreThreadTimeOut(final boolean value) { 134 | ManagedThreadPoolExecutor.this.allowCoreThreadTimeOut(value); 135 | } 136 | 137 | public long getKeepAliveTimeSeconds() { 138 | return ManagedThreadPoolExecutor.this.getKeepAliveTime(TimeUnit.SECONDS); 139 | } 140 | 141 | public void setKeepAliveTimeSeconds(final long seconds) { 142 | ManagedThreadPoolExecutor.this.setKeepAliveTime(seconds, TimeUnit.SECONDS); 143 | } 144 | 145 | public int getMaximumQueueSize() { 146 | return 0; 147 | } 148 | 149 | public void setMaximumQueueSize(final int size) { 150 | } 151 | 152 | public int getQueueSize() { 153 | return ManagedThreadPoolExecutor.this.getQueue().size(); 154 | } 155 | 156 | public int getLargestQueueSize() { 157 | return 0; 158 | } 159 | 160 | public boolean isQueueBounded() { 161 | return false; 162 | } 163 | 164 | public boolean isQueueSizeModifiable() { 165 | return false; 166 | } 167 | 168 | public boolean isShutdown() { 169 | return ManagedThreadPoolExecutor.this.isShutdown(); 170 | } 171 | 172 | public boolean isTerminating() { 173 | return ManagedThreadPoolExecutor.this.isTerminating(); 174 | } 175 | 176 | public boolean isTerminated() { 177 | return ManagedThreadPoolExecutor.this.isTerminated(); 178 | } 179 | 180 | public long getSubmittedTaskCount() { 181 | return ManagedThreadPoolExecutor.this.getTaskCount(); 182 | } 183 | 184 | public long getRejectedTaskCount() { 185 | return 0; 186 | } 187 | 188 | public long getCompletedTaskCount() { 189 | return ManagedThreadPoolExecutor.this.getCompletedTaskCount(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/Messages.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import static java.lang.invoke.MethodHandles.*; 4 | 5 | import java.time.Duration; 6 | import java.util.concurrent.CancellationException; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.TimeoutException; 9 | 10 | import org.jboss.logging.BasicLogger; 11 | import org.jboss.logging.Logger; 12 | import org.jboss.logging.annotations.Cause; 13 | import org.jboss.logging.annotations.LogMessage; 14 | import org.jboss.logging.annotations.Message; 15 | import org.jboss.logging.annotations.MessageLogger; 16 | 17 | /** 18 | * @author David M. Lloyd 19 | */ 20 | @MessageLogger(projectCode = "JBTHR", length = 5) 21 | interface Messages extends BasicLogger { 22 | Messages msg = Logger.getMessageLogger(lookup(), Messages.class, "org.jboss.threads"); 23 | Messages intMsg = Logger.getMessageLogger(lookup(), Messages.class, "org.jboss.threads.interrupt-handler"); 24 | 25 | // version 26 | @Message(value = "JBoss Threads version %s") 27 | @LogMessage(level = Logger.Level.INFO) 28 | void version(String version); 29 | 30 | // execution 31 | 32 | // @Message(id = 1, value = "Thread factory did not produce a thread") 33 | 34 | // @Message(id = 2, value = "Task limit reached") 35 | 36 | @Message(id = 3, value = "Operation timed out") 37 | TimeoutException operationTimedOut(); 38 | 39 | @Message(id = 4, value = "Operation was cancelled") 40 | CancellationException operationCancelled(); 41 | 42 | @Message(id = 5, value = "Operation failed") 43 | ExecutionException operationFailed(@Cause Throwable cause); 44 | 45 | // @Message(id = 6, value = "Unable to add new thread to the running set") 46 | 47 | // @Message(id = 7, value = "Task execution interrupted") 48 | 49 | // @Message(id = 8, value = "Task rejected") 50 | 51 | @Message(id = 9, value = "Executor has been shut down") 52 | StoppedExecutorException shutDownInitiated(); 53 | 54 | // @Message(id = 10, value = "Task execution timed out") 55 | 56 | // @Message(id = 11, value = "Task execution failed for task %s") 57 | 58 | @Message(id = 12, value = "Cannot await termination of a thread pool from one of its own threads") 59 | IllegalStateException cannotAwaitWithin(); 60 | 61 | // @Message(id = 13, value = "No executors available to run task") 62 | 63 | // @Message(id = 14, value = "Error submitting task %s to executor") 64 | 65 | // validation 66 | 67 | // @Message(id = 100, value = "Keep-alive may only be set to 0 for this executor type") 68 | 69 | // @Message(id = 101, value = "Cannot reduce maximum threads below current thread number of running threads") 70 | 71 | // @Message(id = 102, value = "Empty array parameter is not empty") 72 | 73 | @Message(id = 103, value = "The current thread does not support interrupt handlers") 74 | IllegalStateException noInterruptHandlers(); 75 | 76 | // @Message(id = 104, value = "Executor is not shut down") 77 | 78 | // @Message(id = 105, value = "Concurrent modification of collection detected") 79 | 80 | // @Message(id = 106, value = "No such element (iteration past end)") 81 | 82 | // @Message(id = 107, value = "Unknown throwable received") 83 | 84 | @Message(id = 108, value = "Interrupt handler %s threw an exception") 85 | @LogMessage(level = Logger.Level.ERROR) 86 | void interruptHandlerThrew(@Cause Throwable cause, InterruptHandler interruptHandler); 87 | 88 | @Message(id = 109, value = "Keep-alive time must be positive but was %s") 89 | IllegalArgumentException nonPositiveKeepAlive(Duration actual); 90 | 91 | // security 92 | 93 | @Message(id = 200, value = "%s() not allowed on container-managed executor") 94 | SecurityException notAllowedContainerManaged(String methodName); 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/NullRunnable.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | final class NullRunnable implements Runnable { 4 | 5 | private static final NullRunnable INSTANCE = new NullRunnable(); 6 | 7 | static NullRunnable getInstance() { 8 | return INSTANCE; 9 | } 10 | 11 | NullRunnable() { 12 | } 13 | 14 | public void run() { 15 | // do nothing 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/RejectingExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.RejectedExecutionException; 5 | 6 | class RejectingExecutor implements Executor { 7 | static final RejectingExecutor INSTANCE = new RejectingExecutor(); 8 | 9 | private final String message; 10 | 11 | private RejectingExecutor() { 12 | message = null; 13 | } 14 | 15 | RejectingExecutor(final String message) { 16 | this.message = message; 17 | } 18 | 19 | public void execute(final Runnable command) { 20 | throw new RejectedExecutionException(message); 21 | } 22 | 23 | public String toString() { 24 | return "Rejecting executor"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/SimpleDirectExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | class SimpleDirectExecutor implements Executor { 6 | 7 | static final SimpleDirectExecutor INSTANCE = new SimpleDirectExecutor(); 8 | 9 | private SimpleDirectExecutor() { 10 | } 11 | 12 | public void execute(final Runnable command) { 13 | command.run(); 14 | } 15 | 16 | public String toString() { 17 | return "Direct executor"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/StoppedExecutorException.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.RejectedExecutionException; 4 | 5 | /** 6 | * Thrown when a task is submitted to an executor which is in the process of, or has completed shutting down. 7 | */ 8 | public class StoppedExecutorException extends RejectedExecutionException { 9 | 10 | private static final long serialVersionUID = 4815103522815471074L; 11 | 12 | /** 13 | * Constructs a {@code StoppedExecutorException} with no detail message. The cause is not initialized, and may 14 | * subsequently be initialized by a call to {@link #initCause(Throwable) initCause}. 15 | */ 16 | public StoppedExecutorException() { 17 | } 18 | 19 | /** 20 | * Constructs a {@code StoppedExecutorException} with the specified detail message. The cause is not initialized, and 21 | * may subsequently be initialized by a call to {@link #initCause(Throwable) initCause}. 22 | * 23 | * @param msg the detail message 24 | */ 25 | public StoppedExecutorException(final String msg) { 26 | super(msg); 27 | } 28 | 29 | /** 30 | * Constructs a {@code StoppedExecutorException} with the specified cause. The detail message is set to: 31 | *
(cause == null ? null : cause.toString())
32 | * (which typically contains the class and detail message of {@code cause}). 33 | * 34 | * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method) 35 | */ 36 | public StoppedExecutorException(final Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | /** 41 | * Constructs a {@code StoppedExecutorException} with the specified detail message and cause. 42 | * 43 | * @param msg the detail message 44 | * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method) 45 | */ 46 | public StoppedExecutorException(final String msg, final Throwable cause) { 47 | super(msg, cause); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/Substitutions.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import javax.management.ObjectInstance; 4 | 5 | import com.oracle.svm.core.annotate.Substitute; 6 | import com.oracle.svm.core.annotate.TargetClass; 7 | 8 | final class Substitutions { 9 | @TargetClass(EnhancedQueueExecutor.MBeanRegisterAction.class) 10 | static final class Target_EnhancedQueueExecutor_MBeanRegisterAction { 11 | 12 | @Substitute 13 | public ObjectInstance run() { 14 | return null; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | final class ThreadLocalResettingRunnable extends DelegatingRunnable { 4 | 5 | ThreadLocalResettingRunnable(final Runnable delegate) { 6 | super(delegate); 7 | } 8 | 9 | public void run() { 10 | try { 11 | super.run(); 12 | } finally { 13 | JDKSpecific.ThreadAccess.clearThreadLocals(); 14 | } 15 | } 16 | 17 | public String toString() { 18 | return "Thread-local resetting Runnable"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ThreadNameInfo.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.regex.Pattern; 4 | import java.util.regex.Matcher; 5 | 6 | /** 7 | * Thread name information. 8 | */ 9 | final class ThreadNameInfo { 10 | private final long globalThreadSequenceNum; 11 | private final long perFactoryThreadSequenceNum; 12 | private final long factorySequenceNum; 13 | 14 | ThreadNameInfo(final long globalThreadSequenceNum, final long perFactoryThreadSequenceNum, final long factorySequenceNum) { 15 | this.globalThreadSequenceNum = globalThreadSequenceNum; 16 | this.perFactoryThreadSequenceNum = perFactoryThreadSequenceNum; 17 | this.factorySequenceNum = factorySequenceNum; 18 | } 19 | 20 | public long getGlobalThreadSequenceNum() { 21 | return globalThreadSequenceNum; 22 | } 23 | 24 | public long getPerFactoryThreadSequenceNum() { 25 | return perFactoryThreadSequenceNum; 26 | } 27 | 28 | public long getFactorySequenceNum() { 29 | return factorySequenceNum; 30 | } 31 | 32 | private static final Pattern searchPattern = Pattern.compile("([^%]+)|%."); 33 | 34 | /** 35 | * Format the thread name string. 36 | *
    37 | *
  • {@code %%} - emit a percent sign
  • 38 | *
  • {@code %t} - emit the per-factory thread sequence number
  • 39 | *
  • {@code %g} - emit the global thread sequence number
  • 40 | *
  • {@code %f} - emit the factory sequence number
  • 41 | *
  • {@code %p} - emit the {@code ":"}-separated thread group path
  • 42 | *
  • {@code %i} - emit the thread ID
  • 43 | *
  • {@code %G} - emit the thread group name
  • 44 | *
45 | * 46 | * @param thread the thread 47 | * @param formatString the format string 48 | * @return the thread name string 49 | */ 50 | public String format(Thread thread, String formatString) { 51 | final StringBuilder builder = new StringBuilder(formatString.length() * 5); 52 | final ThreadGroup group = thread.getThreadGroup(); 53 | final Matcher matcher = searchPattern.matcher(formatString); 54 | while (matcher.find()) { 55 | if (matcher.group(1) != null) { 56 | builder.append(matcher.group()); 57 | } else { 58 | switch (matcher.group().charAt(1)) { 59 | case '%': builder.append('%'); break; 60 | case 't': builder.append(perFactoryThreadSequenceNum); break; 61 | case 'g': builder.append(globalThreadSequenceNum); break; 62 | case 'f': builder.append(factorySequenceNum); break; 63 | case 'p': if (group != null) appendGroupPath(group, builder); break; 64 | case 'i': builder.append(thread.getId()); break; 65 | case 'G': if (group != null) builder.append(group.getName()); break; 66 | } 67 | } 68 | } 69 | return builder.toString(); 70 | } 71 | 72 | private static void appendGroupPath(ThreadGroup group, StringBuilder builder) { 73 | final ThreadGroup parent = group.getParent(); 74 | if (parent != null) { 75 | appendGroupPath(parent, builder); 76 | builder.append(':'); 77 | } 78 | builder.append(group.getName()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/TimeUtil.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import static java.lang.Math.max; 4 | 5 | import java.time.Duration; 6 | 7 | /** 8 | */ 9 | final class TimeUtil { 10 | private TimeUtil() {} 11 | 12 | private static final long LARGEST_SECONDS = 9_223_372_035L; // Long.MAX_VALUE / 1_000_000_000L - 1 13 | 14 | static long clampedPositiveNanos(Duration duration) { 15 | final long seconds = max(0L, duration.getSeconds()); 16 | return seconds > LARGEST_SECONDS ? Long.MAX_VALUE : max(1, seconds * 1_000_000_000L + duration.getNano()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/Version.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.nio.charset.StandardCharsets; 7 | import java.security.AccessController; 8 | import java.security.PrivilegedAction; 9 | import java.util.Properties; 10 | 11 | /** 12 | * 13 | */ 14 | public final class Version { 15 | private Version() { 16 | } 17 | 18 | private static final String JAR_NAME; 19 | private static final String VERSION_STRING; 20 | 21 | static { 22 | Properties versionProps = new Properties(); 23 | String jarName = "(unknown)"; 24 | String versionString = "(unknown)"; 25 | try (InputStream stream = Version.class.getResourceAsStream("Version.properties")) { 26 | if (stream != null) try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { 27 | versionProps.load(reader); 28 | jarName = versionProps.getProperty("jarName", jarName); 29 | versionString = versionProps.getProperty("version", versionString); 30 | } 31 | } catch (IOException ignored) { 32 | } 33 | JAR_NAME = jarName; 34 | VERSION_STRING = versionString; 35 | boolean logVersion = AccessController.doPrivileged((PrivilegedAction) VersionLogging::shouldLogVersion).booleanValue(); 36 | if (logVersion) try { 37 | Messages.msg.version(versionString); 38 | } catch (Throwable ignored) {} 39 | } 40 | 41 | /** 42 | * Get the name of the JBoss Modules JAR. 43 | * 44 | * @return the name 45 | */ 46 | public static String getJarName() { 47 | return JAR_NAME; 48 | } 49 | 50 | /** 51 | * Get the version string of JBoss Modules. 52 | * 53 | * @return the version string 54 | */ 55 | public static String getVersionString() { 56 | return VERSION_STRING; 57 | } 58 | 59 | /** 60 | * Print out the current version on {@code System.out}. 61 | * 62 | * @param args ignored 63 | */ 64 | public static void main(String[] args) { 65 | System.out.printf("JBoss Threads version %s\n", VERSION_STRING); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/Version.properties: -------------------------------------------------------------------------------- 1 | # 2 | # JBoss, Home of Professional Open Source. 3 | # Copyright 2017 Red Hat, Inc., and individual contributors 4 | # as indicated by the @author tags. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | version=${project.version} 20 | jarName=${project.artifactId} 21 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/VersionLogging.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | /** 4 | * Not part of {@link Version} in order to not force class initialization of the latter 5 | */ 6 | class VersionLogging { 7 | 8 | static Boolean shouldLogVersion() { 9 | try { 10 | return Boolean.valueOf(System.getProperty("jboss.log-version", "true")); 11 | } catch (Throwable ignored) { 12 | return Boolean.FALSE; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/ViewExecutor.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import io.smallrye.common.constraint.Assert; 4 | 5 | import java.security.PrivilegedAction; 6 | import java.util.concurrent.AbstractExecutorService; 7 | import java.util.concurrent.Executor; 8 | 9 | import static java.security.AccessController.doPrivileged; 10 | 11 | /** 12 | * An executor service that is actually a "view" over another executor service. 13 | */ 14 | public abstract class ViewExecutor extends AbstractExecutorService { 15 | 16 | private volatile Thread.UncaughtExceptionHandler handler; 17 | private volatile Runnable terminationTask; 18 | 19 | // Intentionally package private to effectively seal the type. 20 | ViewExecutor() {} 21 | 22 | @Override 23 | public final void shutdown() { 24 | shutdown(false); 25 | } 26 | 27 | public abstract void shutdown(boolean interrupt); 28 | 29 | public final Thread.UncaughtExceptionHandler getExceptionHandler() { 30 | return handler; 31 | } 32 | 33 | public final void setExceptionHandler(final Thread.UncaughtExceptionHandler handler) { 34 | Assert.checkNotNullParam("handler", handler); 35 | this.handler = handler; 36 | } 37 | 38 | public final Runnable getTerminationTask() { 39 | return terminationTask; 40 | } 41 | 42 | public final void setTerminationTask(final Runnable terminationTask) { 43 | this.terminationTask = terminationTask; 44 | } 45 | 46 | public static Builder builder(Executor delegate) { 47 | Assert.checkNotNullParam("delegate", delegate); 48 | return new Builder(delegate); 49 | } 50 | 51 | public static final class Builder { 52 | private final Executor delegate; 53 | private int maxSize = 1; 54 | private int queueLimit = Integer.MAX_VALUE; 55 | private Thread.UncaughtExceptionHandler handler = JBossExecutors.loggingExceptionHandler(); 56 | 57 | Builder(final Executor delegate) { 58 | this.delegate = delegate; 59 | } 60 | 61 | public int getMaxSize() { 62 | return maxSize; 63 | } 64 | 65 | public Builder setMaxSize(final int maxSize) { 66 | Assert.checkMinimumParameter("maxSize", 1, maxSize); 67 | this.maxSize = maxSize; 68 | return this; 69 | } 70 | 71 | public int getQueueLimit() { 72 | return queueLimit; 73 | } 74 | 75 | public Builder setQueueLimit(final int queueLimit) { 76 | Assert.checkMinimumParameter("queueLimit", 0, queueLimit); 77 | this.queueLimit = queueLimit; 78 | return this; 79 | } 80 | 81 | public Executor getDelegate() { 82 | return delegate; 83 | } 84 | 85 | public Thread.UncaughtExceptionHandler getUncaughtHandler() { 86 | return handler; 87 | } 88 | 89 | public Builder setUncaughtHandler(final Thread.UncaughtExceptionHandler handler) { 90 | this.handler = handler; 91 | return this; 92 | } 93 | 94 | /** 95 | * @deprecated This value no longer has any impact. 96 | */ 97 | @Deprecated 98 | public int getQueueInitialSize() { 99 | return 0; 100 | } 101 | 102 | /** 103 | * @deprecated This option no longer has any impact. 104 | */ 105 | @Deprecated 106 | public Builder setQueueInitialSize(@SuppressWarnings("unused") final int queueInitialSize) { 107 | return this; 108 | } 109 | 110 | public ViewExecutor build() { 111 | return new EnhancedViewExecutor( 112 | Assert.checkNotNullParam("delegate", delegate), 113 | maxSize, 114 | queueLimit, 115 | handler); 116 | } 117 | } 118 | 119 | protected void runTermination() { 120 | final Runnable task = ViewExecutor.this.terminationTask; 121 | ViewExecutor.this.terminationTask = null; 122 | if (task != null) try { 123 | task.run(); 124 | } catch (Throwable t) { 125 | Thread.UncaughtExceptionHandler configuredHandler = handler; 126 | if (configuredHandler != null) { 127 | try { 128 | handler.uncaughtException(Thread.currentThread(), t); 129 | } catch (Throwable ignored) { 130 | } 131 | } 132 | } 133 | } 134 | 135 | static int readIntPropertyPrefixed(String name, int defVal) { 136 | try { 137 | return Integer.parseInt(readPropertyPrefixed(name, Integer.toString(defVal))); 138 | } catch (NumberFormatException ignored) { 139 | return defVal; 140 | } 141 | } 142 | 143 | static String readPropertyPrefixed(String name, String defVal) { 144 | return readProperty("org.jboss.threads.view-executor." + name, defVal); 145 | } 146 | 147 | static String readProperty(String name, String defVal) { 148 | final SecurityManager sm = System.getSecurityManager(); 149 | if (sm != null) { 150 | return doPrivileged((PrivilegedAction) () -> readPropertyRaw(name, defVal)); 151 | } else { 152 | return readPropertyRaw(name, defVal); 153 | } 154 | } 155 | 156 | static String readPropertyRaw(final String name, final String defVal) { 157 | return System.getProperty(name, defVal); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/Waiter.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | /** 4 | */ 5 | final class Waiter { 6 | private volatile Thread thread; 7 | private Waiter next; 8 | 9 | Waiter(final Waiter next) { 10 | this.next = next; 11 | } 12 | 13 | Thread getThread() { 14 | return thread; 15 | } 16 | 17 | Waiter setThread(final Thread thread) { 18 | this.thread = thread; 19 | return this; 20 | } 21 | 22 | Waiter getNext() { 23 | return next; 24 | } 25 | 26 | Waiter setNext(final Waiter next) { 27 | this.next = next; 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/management/ManageableThreadPoolExecutorService.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads.management; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | 5 | import io.smallrye.common.constraint.NotNull; 6 | 7 | /** 8 | * A thread pool for which an MBean can be obtained. 9 | */ 10 | public interface ManageableThreadPoolExecutorService extends ExecutorService { 11 | /** 12 | * Create or acquire an MXBean instance for this thread pool. Note that the thread pool itself will not 13 | * do anything in particular to register (or unregister) the MXBean with a JMX server; that is the caller's 14 | * responsibility. 15 | * 16 | * @return the MXBean instance (must not be {@code null}) 17 | */ 18 | @NotNull 19 | StandardThreadPoolMXBean getThreadPoolMXBean(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/jboss/threads/management/StandardThreadPoolMXBean.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads.management; 2 | 3 | /** 4 | * An MXBean which contains the attributes and operations found on all standard thread pools. 5 | */ 6 | public interface StandardThreadPoolMXBean { 7 | /** 8 | * Get the pool size growth resistance factor. If the thread pool does not support growth resistance, 9 | * then {@code 0.0} (no resistance) is returned. 10 | * 11 | * @return the growth resistance factor ({@code 0.0 ≤ n ≤ 1.0}) 12 | */ 13 | float getGrowthResistance(); 14 | 15 | /** 16 | * Set the pool size growth resistance factor, if supported. 17 | * 18 | * @param value the growth resistance factor ({@code 0.0 ≤ n ≤ 1.0}) 19 | */ 20 | void setGrowthResistance(float value); 21 | 22 | /** 23 | * Determine whether the thread pool supports a growth resistance factor. 24 | * 25 | * @return {@code true} if the growth resistance factor is supported, {@code false} otherwise 26 | */ 27 | boolean isGrowthResistanceSupported(); 28 | 29 | /** 30 | * Get the core pool size. This is the size below which new threads will always be created if no idle threads 31 | * are available. If the thread pool does not support a separate core pool size, this size will match the 32 | * maximum size. 33 | * 34 | * @return the core pool size 35 | */ 36 | int getCorePoolSize(); 37 | 38 | /** 39 | * Set the core pool size. If the configured maximum pool size is less than the configured core size, the 40 | * core size will be reduced to match the maximum size when the thread pool is constructed. If the thread pool 41 | * does not support a separate core pool size, this setting will be ignored. 42 | * 43 | * @param corePoolSize the core pool size (must be greater than or equal to 0) 44 | */ 45 | void setCorePoolSize(int corePoolSize); 46 | 47 | /** 48 | * Determine whether this implementation supports a separate core pool size. 49 | * 50 | * @return {@code true} if a separate core size is supported; {@code false} otherwise 51 | */ 52 | boolean isCorePoolSizeSupported(); 53 | 54 | /** 55 | * Attempt to start a core thread without submitting work to it. 56 | * 57 | * @return {@code true} if the thread was started, or {@code false} if the pool is filled to the core size, the 58 | * thread could not be created, or prestart of core threads is not supported. 59 | */ 60 | boolean prestartCoreThread(); 61 | 62 | /** 63 | * Attempt to start all core threads. This is usually equivalent to calling {@link #prestartCoreThread()} in a loop 64 | * until it returns {@code false} and counting the {@code true} results. 65 | * 66 | * @return the number of started core threads (may be 0) 67 | */ 68 | int prestartAllCoreThreads(); 69 | 70 | /** 71 | * Determine whether this thread pool allows manual pre-start of core threads. 72 | * 73 | * @return {@code true} if pre-starting core threads is supported, {@code false} otherwise 74 | */ 75 | boolean isCoreThreadPrestartSupported(); 76 | 77 | /** 78 | * Get the maximum pool size. This is the absolute upper limit to the size of the thread pool. 79 | * 80 | * @return the maximum pool size 81 | */ 82 | int getMaximumPoolSize(); 83 | 84 | /** 85 | * Set the maximum pool size. If the configured maximum pool size is less than the configured core size, the 86 | * core size will be reduced to match the maximum size when the thread pool is constructed. 87 | * 88 | * @param maxPoolSize the maximum pool size (must be greater than or equal to 0) 89 | */ 90 | void setMaximumPoolSize(final int maxPoolSize); 91 | 92 | /** 93 | * Get an estimate of the current number of active threads in the pool. 94 | * 95 | * @return an estimate of the current number of active threads in the pool 96 | */ 97 | int getPoolSize(); 98 | 99 | /** 100 | * Get an estimate of the peak number of threads that the pool has ever held. 101 | * 102 | * @return an estimate of the peak number of threads that the pool has ever held 103 | */ 104 | int getLargestPoolSize(); 105 | 106 | /** 107 | * Get an estimate of the current number of active (busy) threads. 108 | * 109 | * @return an estimate of the active count 110 | */ 111 | int getActiveCount(); 112 | 113 | /** 114 | * Determine whether core threads are allowed to time out. A "core thread" is defined as any thread in the pool 115 | * when the pool size is below the pool's {@linkplain #getCorePoolSize() core pool size}. If the thread pool 116 | * does not support a separate core pool size, this method should return {@code false}. 117 | *

118 | * This method is named differently from the typical {@code allowsCoreThreadTimeOut()} in order to accommodate 119 | * the requirements of MXBean attribute methods. 120 | * 121 | * @return {@code true} if core threads are allowed to time out, {@code false} otherwise 122 | */ 123 | boolean isAllowCoreThreadTimeOut(); 124 | 125 | /** 126 | * Establish whether core threads are allowed to time out. A "core thread" is defined as any thread in the pool 127 | * when the pool size is below the pool's {@linkplain #getCorePoolSize() core pool size}. If the thread pool 128 | * does not support a separate core pool size, the value is ignored. 129 | *

130 | * This method is named differently from the typical {@code allowCoreThreadTimeOut(boolean)} in order to accommodate 131 | * the requirements of MXBean attribute methods. 132 | * 133 | * @param value {@code true} if core threads are allowed to time out, {@code false} otherwise 134 | */ 135 | void setAllowCoreThreadTimeOut(boolean value); 136 | 137 | /** 138 | * Get the thread keep-alive time, in seconds. 139 | *

140 | * This method differs from the typical {@code getKeepAliveTime(TimeUnit)} due to the inability to send in a 141 | * time units parameter on an MXBean attribute. As such, the unit is hard-coded to seconds. 142 | * 143 | * @return the thread keep-alive time, in seconds 144 | */ 145 | long getKeepAliveTimeSeconds(); 146 | 147 | /** 148 | * Set the thread keep-alive time, in seconds. 149 | *

150 | * This method differs from the typical {@code getKeepAliveTime(TimeUnit)} due to the inability to send in a 151 | * time units parameter on an MXBean attribute. As such, the unit is hard-coded to seconds. 152 | * 153 | * @param seconds the thread keep-alive time, in seconds (must be greater than or equal to 0) 154 | */ 155 | void setKeepAliveTimeSeconds(long seconds); 156 | 157 | /** 158 | * Get the maximum queue size for this thread pool. If there is no queue or it is not bounded, {@link Integer#MAX_VALUE} is 159 | * returned. 160 | * 161 | * @return the maximum queue size 162 | */ 163 | int getMaximumQueueSize(); 164 | 165 | /** 166 | * Set the maximum queue size for this thread pool. If the new maximum queue size is smaller than the current queue 167 | * size, there is no effect other than preventing tasks from being enqueued until the size decreases below the 168 | * maximum again. If changing the maximum queue size is not supported, or there is no bounded backing queue, 169 | * then the value is ignored. 170 | * 171 | * @param size the maximum queue size for this thread pool 172 | */ 173 | void setMaximumQueueSize(int size); 174 | 175 | /** 176 | * Get an estimate of the current queue size, if any. If no backing queue exists, or its size cannot be determined, 177 | * this method will return 0. 178 | * 179 | * @return an estimate of the current queue size 180 | */ 181 | int getQueueSize(); 182 | 183 | /** 184 | * Get an estimate of the peak size of the queue, if any. If no backing queue exists, or its size cannot be determined, 185 | * this method will return 0. 186 | * 187 | * @return an estimate of the peak size of the queue 188 | */ 189 | int getLargestQueueSize(); 190 | 191 | /** 192 | * Determine whether there is a bounded queue backing this thread pool. 193 | * 194 | * @return {@code true} if there is a bounded backing queue, {@code false} otherwise 195 | */ 196 | boolean isQueueBounded(); 197 | 198 | /** 199 | * Determine whether the maximum queue size is modifiable. 200 | * 201 | * @return {@code true} if the queue size is modifiable, false otherwise 202 | */ 203 | boolean isQueueSizeModifiable(); 204 | 205 | /** 206 | * Determine whether shutdown was requested. 207 | * 208 | * @return {@code true} if shutdown was requested, {@code false} otherwise 209 | */ 210 | boolean isShutdown(); 211 | 212 | /** 213 | * Determine whether shutdown is in progress. 214 | * 215 | * @return {@code true} if shutdown is in progress, {@code false} otherwise 216 | */ 217 | boolean isTerminating(); 218 | 219 | /** 220 | * Determine whether shutdown is complete. 221 | * 222 | * @return {@code true} if shutdown is complete, {@code false} otherwise 223 | */ 224 | boolean isTerminated(); 225 | 226 | /** 227 | * Get an estimate of the total number of tasks ever submitted to this thread pool. This number may be zero 228 | * if the underlying thread pool does not support this metric. 229 | * 230 | * @return an estimate of the total number of tasks ever submitted to this thread pool 231 | */ 232 | long getSubmittedTaskCount(); 233 | 234 | /** 235 | * Get an estimate of the total number of tasks ever rejected by this thread pool for any reason. This number may be zero 236 | * if the underlying thread pool does not support this metric. 237 | * 238 | * @return an estimate of the total number of tasks ever rejected by this thread pool 239 | */ 240 | long getRejectedTaskCount(); 241 | 242 | /** 243 | * Get an estimate of the number of tasks completed by this thread pool. This number may be zero 244 | * if the underlying thread pool does not support this metric. 245 | * 246 | * @return an estimate of the number of tasks completed by this thread pool 247 | */ 248 | long getCompletedTaskCount(); 249 | 250 | /** 251 | * Get the number of spin misses that have occurred. Spin misses indicate that contention is not being properly 252 | * handled by the thread pool. 253 | * 254 | * @return an estimate of the number of spin misses 255 | */ 256 | default long getSpinMissCount() { 257 | return 0; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/main/java24/org/jboss/threads/JDKSpecific.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.lang.invoke.MethodHandles; 5 | import java.lang.invoke.MethodType; 6 | import java.lang.invoke.VarHandle; 7 | import java.lang.reflect.UndeclaredThrowableException; 8 | 9 | final class JDKSpecific { 10 | private JDKSpecific() {} 11 | 12 | static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) { 13 | thread.setContextClassLoader(classLoader); 14 | } 15 | 16 | static ClassLoader getThreadContextClassLoader(Thread thread) { 17 | return thread.getContextClassLoader(); 18 | } 19 | 20 | static final class ThreadAccess { 21 | private static final MethodHandle setThreadLocalsHandle; 22 | private static final MethodHandle setInheritableThreadLocalsHandle; 23 | 24 | static { 25 | try { 26 | MethodHandles.Lookup threadLookup = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup()); 27 | setThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("threadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Thread.class, Object.class)); 28 | setInheritableThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("inheritableThreadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Thread.class, Object.class)); 29 | } catch (IllegalAccessException e) { 30 | Module myModule = ThreadAccess.class.getModule(); 31 | String myName = myModule.isNamed() ? myModule.getName() : "ALL-UNNAMED"; 32 | throw new IllegalAccessError(e.getMessage() + 33 | "; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=" + myName); 34 | } catch (NoSuchFieldException e) { 35 | throw new NoSuchFieldError(e.getMessage()); 36 | } 37 | } 38 | 39 | static void clearThreadLocals() { 40 | final Thread thread = Thread.currentThread(); 41 | try { 42 | setThreadLocalsHandle.invokeExact(thread, (Object) null); 43 | setInheritableThreadLocalsHandle.invokeExact(thread, (Object) null); 44 | } catch (RuntimeException | Error e) { 45 | throw e; 46 | } catch (Throwable t) { 47 | throw new UndeclaredThrowableException(t); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/native-image/org.jboss.threads/jboss-threads/native-image.properties: -------------------------------------------------------------------------------- 1 | Args = --initialize-at-run-time=org.jboss.threads.EnhancedQueueExecutor$RuntimeFields 2 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/ArrayQueueTests.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertNull; 6 | import static org.junit.jupiter.api.Assertions.assertSame; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import org.junit.jupiter.api.AfterAll; 12 | import org.junit.jupiter.api.BeforeAll; 13 | import org.junit.jupiter.api.Test; 14 | 15 | public class ArrayQueueTests { 16 | 17 | static EnhancedQueueExecutor eqe; 18 | 19 | static EnhancedQueueExecutor.AbstractScheduledFuture[] ITEMS; 20 | 21 | @BeforeAll 22 | public static void beforeAll() { 23 | eqe = new EnhancedQueueExecutor.Builder().build(); 24 | ITEMS = new EnhancedQueueExecutor.AbstractScheduledFuture[32]; 25 | for (int i = 0; i < 32; i ++) { 26 | final String toString = "[" + i + "]"; 27 | ITEMS[i] = eqe.new RunnableScheduledFuture(new Runnable() { 28 | public void run() { 29 | // nothing 30 | } 31 | 32 | public String toString() { 33 | return toString; 34 | } 35 | }, 0, TimeUnit.DAYS); 36 | } 37 | } 38 | 39 | @Test 40 | public void testMoveForward() { 41 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16); 42 | 43 | int head = 5; 44 | aq.testPoint_setHead(head); 45 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]); 46 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]); 47 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]); 48 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]); 49 | aq.testPoint_setSize(4); 50 | 51 | aq.moveForward(2, ITEMS[4]); 52 | 53 | assertEquals(head, aq.testPoint_head()); 54 | 55 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0)); 56 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1)); 57 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2)); 58 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3)); 59 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4)); 60 | } 61 | 62 | @Test 63 | public void testMoveForwardWrap() { 64 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16); 65 | 66 | int head = 14; 67 | aq.testPoint_setHead(head); 68 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]); 69 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]); 70 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]); 71 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]); 72 | aq.testPoint_setSize(4); 73 | 74 | aq.moveForward(2, ITEMS[4]); 75 | 76 | assertEquals(head, aq.testPoint_head()); 77 | 78 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0)); 79 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1)); 80 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2)); 81 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3)); 82 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4)); 83 | } 84 | 85 | @Test 86 | public void testMoveBackward() { 87 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16); 88 | 89 | int head = 5; 90 | aq.testPoint_setHead(head); 91 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]); 92 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]); 93 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]); 94 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]); 95 | aq.testPoint_setSize(4); 96 | 97 | aq.moveBackward(2, ITEMS[4]); 98 | 99 | assertEquals(head - 1, aq.testPoint_head()); 100 | head--; 101 | 102 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0)); 103 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1)); 104 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2)); 105 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3)); 106 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4)); 107 | } 108 | 109 | @Test 110 | public void testMoveBackwardWrap() { 111 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16); 112 | 113 | int head = 14; 114 | aq.testPoint_setHead(head); 115 | aq.testPoint_setArrayItem(head + 0, ITEMS[0]); 116 | aq.testPoint_setArrayItem(head + 1, ITEMS[1]); 117 | aq.testPoint_setArrayItem(head + 2, ITEMS[2]); 118 | aq.testPoint_setArrayItem(head + 3, ITEMS[3]); 119 | aq.testPoint_setSize(4); 120 | 121 | aq.moveBackward(2, ITEMS[4]); 122 | 123 | assertEquals(head - 1, aq.testPoint_head()); 124 | head--; 125 | 126 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(head + 0)); 127 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(head + 1)); 128 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(head + 2)); 129 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(head + 3)); 130 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(head + 4)); 131 | } 132 | 133 | @Test 134 | public void testQueueBehavior() { 135 | EnhancedQueueExecutor.ArrayQueue aq = new EnhancedQueueExecutor.ArrayQueue(16); 136 | 137 | // tc 0 (n/a) 138 | assertTrue(aq.isEmpty()); 139 | assertEquals(0, aq.size()); 140 | assertEquals(0, aq.testPoint_head()); 141 | assertEquals(16, aq.testPoint_arrayLength()); 142 | 143 | // tc 1 144 | aq.insertAt(0, ITEMS[0]); 145 | assertFalse(aq.isEmpty()); 146 | assertEquals(1, aq.size()); 147 | assertEquals(0, aq.testPoint_head()); 148 | assertSame(ITEMS[0], aq.testPoint_getArrayItem(0)); 149 | assertNull(aq.testPoint_getArrayItem(1)); 150 | assertNull(aq.testPoint_getArrayItem(15)); 151 | 152 | // removal 153 | assertSame(ITEMS[0], aq.pollFirst()); 154 | assertTrue(aq.isEmpty()); 155 | assertEquals(0, aq.size()); 156 | assertEquals(1, aq.testPoint_head()); 157 | assertNull(aq.testPoint_getArrayItem(0)); 158 | assertNull(aq.testPoint_getArrayItem(1)); 159 | 160 | // tc 1 (but this time with head == 1) 161 | aq.insertAt(0, ITEMS[1]); 162 | assertFalse(aq.isEmpty()); 163 | assertEquals(1, aq.size()); 164 | assertEquals(1, aq.testPoint_head()); 165 | assertNull(aq.testPoint_getArrayItem(0)); 166 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 167 | assertNull(aq.testPoint_getArrayItem(2)); 168 | 169 | // tc 1 (but with head == 1 and size == 1) 170 | aq.insertAt(1, ITEMS[2]); 171 | assertFalse(aq.isEmpty()); 172 | assertEquals(2, aq.size()); 173 | assertEquals(1, aq.testPoint_head()); 174 | assertNull(aq.testPoint_getArrayItem(0)); 175 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 176 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 177 | assertNull(aq.testPoint_getArrayItem(3)); 178 | 179 | // tc 2 (but with head == 1 and size == 2) 180 | aq.insertAt(0, ITEMS[3]); 181 | assertFalse(aq.isEmpty()); 182 | assertEquals(3, aq.size()); // halfSize == 2 183 | // head moves back to 0 184 | assertEquals(0, aq.testPoint_head()); 185 | assertNull(aq.testPoint_getArrayItem(15)); 186 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0)); 187 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 188 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 189 | assertNull(aq.testPoint_getArrayItem(3)); 190 | 191 | // tc 2 (but with head == 0 and size == 3) 192 | aq.insertAt(0, ITEMS[4]); 193 | assertFalse(aq.isEmpty()); 194 | assertEquals(4, aq.size()); 195 | // head wraps around to 15 196 | assertEquals(15, aq.testPoint_head()); 197 | assertNull(aq.testPoint_getArrayItem(14)); 198 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15)); 199 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0)); 200 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 201 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 202 | assertNull(aq.testPoint_getArrayItem(3)); 203 | 204 | // tc 2 (but with head == 15 and size == 4) 205 | aq.insertAt(0, ITEMS[5]); 206 | assertFalse(aq.isEmpty()); 207 | assertEquals(5, aq.size()); 208 | assertEquals(14, aq.testPoint_head()); 209 | assertNull(aq.testPoint_getArrayItem(13)); 210 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(14)); 211 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15)); 212 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0)); 213 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 214 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 215 | assertNull(aq.testPoint_getArrayItem(3)); 216 | 217 | // tc 218 | aq.insertAt(1, ITEMS[6]); 219 | 220 | assertNull(aq.testPoint_getArrayItem(12)); 221 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(13)); 222 | assertSame(ITEMS[6], aq.testPoint_getArrayItem(14)); 223 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15)); 224 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0)); 225 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 226 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 227 | assertNull(aq.testPoint_getArrayItem(3)); 228 | 229 | aq.insertAt(0, ITEMS[7]); 230 | 231 | assertNull(aq.testPoint_getArrayItem(11)); 232 | assertSame(ITEMS[7], aq.testPoint_getArrayItem(12)); 233 | assertSame(ITEMS[5], aq.testPoint_getArrayItem(13)); 234 | assertSame(ITEMS[6], aq.testPoint_getArrayItem(14)); 235 | assertSame(ITEMS[4], aq.testPoint_getArrayItem(15)); 236 | assertSame(ITEMS[3], aq.testPoint_getArrayItem(0)); 237 | assertSame(ITEMS[1], aq.testPoint_getArrayItem(1)); 238 | assertSame(ITEMS[2], aq.testPoint_getArrayItem(2)); 239 | assertNull(aq.testPoint_getArrayItem(3)); 240 | } 241 | 242 | @AfterAll 243 | public static void afterAll() throws InterruptedException { 244 | try { 245 | eqe.shutdown(); 246 | eqe.awaitTermination(30, TimeUnit.SECONDS); 247 | } finally { 248 | eqe = null; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/DeferredInterruptTestCase.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.atomic.AtomicBoolean; 5 | import java.util.concurrent.locks.LockSupport; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | /** 12 | * @author David M. Lloyd 13 | */ 14 | public class DeferredInterruptTestCase { 15 | 16 | @Test 17 | public void testDeferral() throws Exception { 18 | final AtomicBoolean delivered0 = new AtomicBoolean(); 19 | final AtomicBoolean deferred = new AtomicBoolean(); 20 | final AtomicBoolean delivered = new AtomicBoolean(); 21 | final CountDownLatch latch1 = new CountDownLatch(1); 22 | final CountDownLatch latch2 = new CountDownLatch(1); 23 | final JBossThread thread = new JBossThread(new Runnable() { 24 | public void run() { 25 | Thread.interrupted(); 26 | latch1.countDown(); 27 | // now wait 28 | LockSupport.parkNanos(3000000000L); 29 | if (! Thread.currentThread().isInterrupted()) { 30 | // spurious unpark, perhaps 31 | LockSupport.parkNanos(3000000000L); 32 | } 33 | delivered0.set(Thread.interrupted()); 34 | JBossThread.executeWithInterruptDeferred(new Runnable() { 35 | public void run() { 36 | latch2.countDown(); 37 | LockSupport.parkNanos(500000000L); 38 | deferred.set(! Thread.interrupted()); 39 | } 40 | }); 41 | delivered.set(Thread.interrupted()); 42 | } 43 | }); 44 | thread.start(); 45 | latch1.await(); 46 | thread.interrupt(); 47 | latch2.await(); 48 | thread.interrupt(); 49 | thread.join(); 50 | assertTrue(delivered0.get()); 51 | assertTrue(deferred.get()); 52 | assertTrue(delivered.get()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/EnhancedQueueExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import java.time.Duration; 4 | import java.util.concurrent.CountDownLatch; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.RejectedExecutionException; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.TimeoutException; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | import org.junit.jupiter.api.Disabled; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertFalse; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | import static org.junit.jupiter.api.Assertions.fail; 18 | 19 | public class EnhancedQueueExecutorTest { 20 | private int coreSize = 3; 21 | private int maxSize = coreSize * 2; 22 | private long keepaliveTimeMillis = 1000; 23 | 24 | class TestTask implements Runnable { 25 | private long sleepTime = 0; 26 | 27 | public TestTask withSleepTime(long sleepTime) { 28 | if (sleepTime > 0) { 29 | this.sleepTime = sleepTime; 30 | } 31 | return this; 32 | } 33 | 34 | @Override 35 | public void run() { 36 | try { 37 | if (sleepTime > 0) { 38 | Thread.sleep(sleepTime); 39 | } 40 | } catch (InterruptedException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | 46 | @Test 47 | public void testMaximumQueueSize() throws InterruptedException { 48 | var builder = (new EnhancedQueueExecutor.Builder()) 49 | .setMaximumQueueSize(1) 50 | .setCorePoolSize(1) 51 | .setMaximumPoolSize(1); 52 | assertTrue(builder.getQueueLimited()); 53 | var executor = builder.build(); 54 | CountDownLatch executeEnqueuedTask = new CountDownLatch(1); 55 | AtomicInteger count = new AtomicInteger(); 56 | CountDownLatch enqueuedTask = new CountDownLatch(1); 57 | CountDownLatch executedEnqueuedTask = new CountDownLatch(1); 58 | executor.execute(() -> { 59 | count.incrementAndGet(); 60 | try { 61 | enqueuedTask.countDown(); 62 | executeEnqueuedTask.await(); 63 | } catch (InterruptedException ignored) { 64 | } 65 | }); 66 | enqueuedTask.await(); 67 | assertEquals(1, count.get()); 68 | assertEquals(0, executor.getQueueSize()); 69 | // this is going to cause the queue size to be == 1 70 | executor.execute(() -> { 71 | count.incrementAndGet(); 72 | executedEnqueuedTask.countDown(); 73 | }); 74 | assertEquals(1, count.get()); 75 | assertEquals(1, executor.getQueueSize()); 76 | try { 77 | executor.execute(count::incrementAndGet); 78 | fail("Expected RejectedExecutionException"); 79 | } catch (RejectedExecutionException e) { 80 | // expected 81 | } 82 | assertEquals(1, count.get()); 83 | assertEquals(1, executor.getQueueSize()); 84 | executeEnqueuedTask.countDown(); 85 | executedEnqueuedTask.await(); 86 | assertEquals(2, count.get()); 87 | assertEquals(0, executor.getQueueSize()); 88 | executor.shutdown(); 89 | } 90 | 91 | @Test 92 | public void testNoQueueLimit() throws InterruptedException { 93 | var builder = (new EnhancedQueueExecutor.Builder()) 94 | .setQueueLimited(false) 95 | .setMaximumQueueSize(1) 96 | .setCorePoolSize(1) 97 | .setMaximumPoolSize(1); 98 | assertFalse(builder.getQueueLimited()); 99 | var executor = builder.build(); 100 | assertEquals(Integer.MAX_VALUE, executor.getMaximumQueueSize()); 101 | CountDownLatch executeEnqueuedTasks = new CountDownLatch(1); 102 | AtomicInteger count = new AtomicInteger(); 103 | CountDownLatch enqueuedTask = new CountDownLatch(1); 104 | CountDownLatch executedEnqueuedTasks = new CountDownLatch(2); 105 | executor.execute(() -> { 106 | count.incrementAndGet(); 107 | try { 108 | enqueuedTask.countDown(); 109 | executeEnqueuedTasks.await(); 110 | } catch (InterruptedException ignored) { 111 | } 112 | }); 113 | enqueuedTask.await(); 114 | executor.execute(() -> { 115 | count.incrementAndGet(); 116 | executedEnqueuedTasks.countDown(); 117 | }); 118 | assertEquals(1, count.get()); 119 | assertEquals(-1, executor.getQueueSize()); 120 | executor.execute(() -> { 121 | count.incrementAndGet(); 122 | executedEnqueuedTasks.countDown(); 123 | }); 124 | assertEquals(1, count.get()); 125 | assertEquals(-1, executor.getQueueSize()); 126 | executeEnqueuedTasks.countDown(); 127 | executedEnqueuedTasks.await(); 128 | assertEquals(3, count.get()); 129 | assertEquals(-1, executor.getQueueSize()); 130 | executor.shutdown(); 131 | } 132 | 133 | /** 134 | * Test that unused threads are being reused. Scenario: 135 | *

    136 | *
  • max threads = 2x, core threads = x
  • 137 | *
  • schedule x tasks, wait for tasks to finish
  • 138 | *
  • schedule x tasks, expect pool size = x immediately after
  • 139 | *
140 | */ 141 | @Test 142 | @Disabled("https://issues.jboss.org/browse/JBTHR-67") 143 | public void testThreadReuse() throws TimeoutException, InterruptedException { 144 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 145 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 146 | .setCorePoolSize(coreSize) 147 | .setMaximumPoolSize(maxSize) 148 | .build(); 149 | 150 | for (int i = 0; i < coreSize; i++) { 151 | executor.execute(new TestTask().withSleepTime(100)); 152 | } 153 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize()); 154 | waitForActiveCount(executor, 0, 1000); 155 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize()); 156 | for (int i = 0; i < coreSize; i++) { 157 | executor.execute(new TestTask().withSleepTime(1000)); 158 | } 159 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize()); 160 | executor.shutdown(); 161 | } 162 | 163 | /** 164 | * Test that keepalive time is honored and threads above the core count are being removed when no tasks are 165 | * available. 166 | * 167 | * @throws InterruptedException 168 | * @throws TimeoutException 169 | */ 170 | @Test 171 | @Disabled("https://issues.jboss.org/browse/JBTHR-67") 172 | public void testKeepaliveTime() throws TimeoutException, InterruptedException { 173 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 174 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 175 | .setCorePoolSize(coreSize) 176 | .setMaximumPoolSize(maxSize) 177 | .build(); 178 | 179 | assertTrue(executor.getPoolSize() <= coreSize, "expected: <=" + coreSize + ", actual: " + executor.getPoolSize()); 180 | for (int i = 0; i < maxSize; i++) { 181 | executor.execute(new TestTask().withSleepTime(1000)); 182 | } 183 | assertEquals(executor.getPoolSize(), maxSize, "expected: ==" + maxSize + ", actual: " + executor.getPoolSize()); 184 | waitForActiveCount(executor, 0, 5000); 185 | waitForPoolSize(executor, coreSize, keepaliveTimeMillis * 2); 186 | executor.shutdown(); 187 | } 188 | 189 | /** 190 | * Test that max size setting is honored. Test that keepalive time is ignored when core threads are the same as max 191 | * threads and core thread time out is disabled. 192 | */ 193 | @Test 194 | @Disabled("https://issues.jboss.org/browse/JBTHR-67") 195 | public void testKeepaliveTime2() throws TimeoutException, InterruptedException { 196 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 197 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 198 | .setCorePoolSize(coreSize) 199 | .setMaximumPoolSize(coreSize) 200 | .build(); 201 | 202 | for (int i = 0; i < 2*coreSize; i++) { 203 | executor.execute(new TestTask().withSleepTime(100)); 204 | } 205 | int currentThreads = executor.getPoolSize(); 206 | assertEquals(currentThreads, coreSize, "expected: == " + coreSize + ", actual: " + currentThreads); 207 | waitForActiveCount(executor, 0, 5000); 208 | assertEquals(executor.getPoolSize(), currentThreads, "expected: == " + currentThreads + ", actual: " + executor.getPoolSize()); 209 | executor.shutdown(); 210 | } 211 | 212 | /** 213 | * Test the keepalive setting with core thread time out enabled. 214 | */ 215 | @Test 216 | @Disabled("https://issues.jboss.org/browse/JBTHR-67") 217 | public void testKeepaliveTime3() throws TimeoutException, InterruptedException { 218 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 219 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 220 | .allowCoreThreadTimeOut(true) 221 | .setCorePoolSize(coreSize) 222 | .setMaximumPoolSize(maxSize) 223 | .build(); 224 | 225 | for (int i = 0; i < maxSize; i++) { 226 | executor.execute(new TestTask().withSleepTime(0)); 227 | } 228 | waitForActiveCount(executor, 0, 5000); 229 | waitForPoolSize(executor, 0, keepaliveTimeMillis * 2); 230 | executor.shutdown(); 231 | } 232 | 233 | /** 234 | * Tests that prestarting core threads starts exactly the core threads amount specified. 235 | */ 236 | @Test 237 | public void testPrestartCoreThreads() { 238 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 239 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 240 | .setCorePoolSize(coreSize) 241 | .setMaximumPoolSize(maxSize) 242 | .build(); 243 | int prestarted = executor.prestartAllCoreThreads(); 244 | assertEquals(prestarted, coreSize, "expected: == " + coreSize + ", actual: " + prestarted); 245 | assertEquals(executor.getPoolSize(), coreSize, "expected: == " + coreSize + ", actual: " + executor.getPoolSize()); 246 | executor.shutdown(); 247 | } 248 | 249 | public void assertStackDepth(ExecutorService executor, int expectedStackFrames) throws InterruptedException { 250 | CountDownLatch initialTaskCompletionBlockingLatch = new CountDownLatch(1); 251 | AtomicInteger initialTaskStackFrames = new AtomicInteger(); 252 | Runnable initialTask = new Runnable() { 253 | @Override 254 | public void run() { 255 | initialTaskStackFrames.set(new RuntimeException().getStackTrace().length); 256 | try { 257 | initialTaskCompletionBlockingLatch.await(); 258 | } catch (InterruptedException e) { 259 | throw new AssertionError(e); 260 | } 261 | } 262 | }; 263 | CountDownLatch queuedTaskCompletionLatch = new CountDownLatch(1); 264 | AtomicInteger queuedTaskStackFrames = new AtomicInteger(); 265 | Runnable queuedTask = new Runnable() { 266 | @Override 267 | public void run() { 268 | queuedTaskStackFrames.set(new RuntimeException().getStackTrace().length); 269 | queuedTaskCompletionLatch.countDown(); 270 | } 271 | }; 272 | try { 273 | executor.submit(initialTask); 274 | executor.submit(queuedTask); 275 | initialTaskCompletionBlockingLatch.countDown(); 276 | queuedTaskCompletionLatch.await(); 277 | assertEquals(expectedStackFrames, initialTaskStackFrames.get()); 278 | assertEquals(expectedStackFrames, queuedTaskStackFrames.get()); 279 | } finally { 280 | executor.shutdown(); 281 | assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS), "Executor failed to terminate"); 282 | } 283 | } 284 | 285 | private void waitForPoolSize(EnhancedQueueExecutor executor, int expectedPoolSize, long waitMillis) throws TimeoutException, InterruptedException { 286 | long deadline = System.currentTimeMillis() + waitMillis; 287 | long delayMillis = 100; 288 | 289 | do { 290 | if (executor.getPoolSize() == expectedPoolSize) { 291 | break; 292 | } 293 | Thread.sleep(delayMillis); 294 | } while (System.currentTimeMillis() + delayMillis < deadline); 295 | if (executor.getPoolSize() != expectedPoolSize) { 296 | throw new TimeoutException("Timed out waiting for pool size to become " + expectedPoolSize 297 | + ", current pool size is " + executor.getPoolSize()); 298 | } 299 | } 300 | 301 | private void waitForActiveCount(EnhancedQueueExecutor executor, int expectedActiveCount, long waitMillis) throws TimeoutException, InterruptedException { 302 | long deadline = System.currentTimeMillis() + waitMillis; 303 | long delayMillis = 100; 304 | 305 | do { 306 | if (executor.getActiveCount() == expectedActiveCount) { 307 | break; 308 | } 309 | Thread.sleep(delayMillis); 310 | } while (System.currentTimeMillis() + delayMillis < deadline); 311 | if (executor.getActiveCount() != expectedActiveCount) { 312 | throw new TimeoutException("Timed out waiting for active count to become " + expectedActiveCount 313 | + ", current active count is " + executor.getActiveCount()); 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/EnhancedThreadQueueExecutorTestCase.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.time.Duration; 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.TimeoutException; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | /** 16 | * Tests for checking EnhancedTreadPoolExecutor 17 | *

18 | */ 19 | public class EnhancedThreadQueueExecutorTestCase { 20 | 21 | private int coreSize = 4; 22 | private int maxSize = 7; 23 | private long keepaliveTimeMillis = 1000; 24 | private long defaultWaitTimeout = 5000; 25 | 26 | class TestTask implements Runnable { 27 | CountDownLatch exitLatch; 28 | CountDownLatch allThreadsRunningLatch; 29 | 30 | private TestTask(CountDownLatch exitLatch, CountDownLatch allThreadsRunningLatch) { 31 | this.exitLatch = exitLatch; 32 | this.allThreadsRunningLatch = allThreadsRunningLatch; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | try { 38 | allThreadsRunningLatch.countDown(); 39 | exitLatch.await(); 40 | } catch (InterruptedException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Test invalid values: 48 | * * Negative keepAlive, coreSize, maxSize 49 | * * maxSize > coreSize 50 | */ 51 | @Test 52 | public void testInvalidValuesKeepAliveZero() { 53 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class) 54 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder() 55 | .setKeepAliveTime(Duration.ZERO) 56 | .setCorePoolSize(coreSize) 57 | .setMaximumPoolSize(maxSize) 58 | .build()); 59 | } 60 | 61 | @Test 62 | public void testInvalidValuesKeepAliveNegative() { 63 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class) 64 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder() 65 | .setKeepAliveTime(Duration.ofMillis(-3456)) 66 | .setCorePoolSize(coreSize) 67 | .setMaximumPoolSize(maxSize) 68 | .build()); 69 | } 70 | 71 | @Test 72 | public void testInvalidValuesCoreSizeNegative() { 73 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class) 74 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder() 75 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 76 | .setCorePoolSize(-5) 77 | .setMaximumPoolSize(maxSize) 78 | .build()); 79 | } 80 | 81 | @Test 82 | public void testInvalidValuesMaxSizeNegative() { 83 | Assertions.assertThatExceptionOfType(IllegalArgumentException.class) 84 | .isThrownBy(() -> new EnhancedQueueExecutor.Builder() 85 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 86 | .setCorePoolSize(coreSize) 87 | .setMaximumPoolSize(-3) 88 | .build()); 89 | } 90 | 91 | @Test 92 | public void testCoreSizeBiggerThanMaxSize() { 93 | int expectedCorePoolSize = 5; 94 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 95 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 96 | .setCorePoolSize(2 * expectedCorePoolSize) 97 | .setMaximumPoolSize(expectedCorePoolSize) 98 | .build(); 99 | assertEquals(expectedCorePoolSize, executor.getCorePoolSize(), "Core size should be automatically adjusted to be equal to max size in case it's bigger."); 100 | } 101 | 102 | /** 103 | * Test that unused threads are being reused. Scenario: 104 | *

    105 | *
  • max threads = 2x, core threads = x
  • 106 | *
  • schedule x tasks, wait for tasks to finish
  • 107 | *
  • schedule x tasks, expect pool size = x immediately after
  • 108 | *
109 | */ 110 | @Test 111 | public void testThreadReuse() throws TimeoutException, InterruptedException { 112 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 113 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 114 | .setCorePoolSize(coreSize) 115 | .setMaximumPoolSize(maxSize) 116 | .build(); 117 | 118 | CountDownLatch exitLatch = new CountDownLatch(1); 119 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize); 120 | 121 | for (int i = 0; i < coreSize; i++) { 122 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 123 | } 124 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 125 | "Not all threads were running. They were most likely not scheduled for execution."); 126 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 127 | exitLatch.countDown(); 128 | waitForActiveCount(executor, 0, defaultWaitTimeout); 129 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 130 | exitLatch = new CountDownLatch(1); 131 | allThreadsRunningLatch = new CountDownLatch(coreSize); 132 | for (int i = 0; i < coreSize; i++) { 133 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 134 | } 135 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 136 | "Not all threads were running. They were most likely not scheduled for execution."); 137 | exitLatch.countDown(); 138 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 139 | executor.shutdown(); 140 | } 141 | 142 | /** 143 | * Test thread reuse above core size 144 | * Scenario: 145 | *
    146 | *
  • setKeepAlive=60 sec
  • 147 | *
  • max threads = 2x, core threads = x
  • 148 | *
  • schedule x tasks and wait to occupy all core threads
  • 149 | *
  • schedule one more task and let it finish
  • 150 | *
  • schedule one task and check that pool size is still x+1
  • 151 | *
152 | */ 153 | @Test 154 | @Disabled("This test consistently fails, see JBTHR-67") 155 | public void testThreadReuseAboveCoreSize() throws TimeoutException, InterruptedException { 156 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 157 | .setKeepAliveTime(Duration.ofSeconds(60)) 158 | .setCorePoolSize(coreSize) 159 | .setMaximumPoolSize(maxSize) 160 | .build(); 161 | 162 | // submit 3 tasks to fill core size 163 | CountDownLatch exitLatch = new CountDownLatch(1); 164 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize); 165 | for (int i = 0; i < coreSize; i++) { 166 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 167 | } 168 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 169 | "Not all threads were running. They were most likely not scheduled for execution."); 170 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 171 | 172 | // submit one more task and allow it to finish 173 | CountDownLatch singleExitLatch = new CountDownLatch(1); 174 | CountDownLatch threadRunningLatch = new CountDownLatch(1); 175 | executor.execute(new TestTask(singleExitLatch, threadRunningLatch)); 176 | threadRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS); 177 | waitForPoolSize(executor, coreSize + 1, defaultWaitTimeout); 178 | singleExitLatch.countDown(); 179 | waitForActiveCount(executor, coreSize, defaultWaitTimeout); 180 | 181 | // now there are just core threads and one free thread, submit another task and check it's reused 182 | singleExitLatch = new CountDownLatch(1); 183 | threadRunningLatch = new CountDownLatch(1); 184 | executor.execute(new TestTask(singleExitLatch, threadRunningLatch)); 185 | threadRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS); 186 | waitForPoolSize(executor, coreSize + 1, defaultWaitTimeout); 187 | singleExitLatch.countDown(); 188 | 189 | // finish all 190 | exitLatch.countDown(); 191 | executor.shutdown(); 192 | } 193 | 194 | /** 195 | * Test that keepalive time is honored and threads above the core count are being removed when no tasks are 196 | * available. 197 | * 198 | * @throws InterruptedException 199 | * @throws TimeoutException 200 | */ 201 | @Test 202 | @Disabled("This test consistently fails, see JBTHR-67") 203 | public void testKeepaliveTime() throws TimeoutException, InterruptedException { 204 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 205 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 206 | .setCorePoolSize(coreSize) 207 | .setMaximumPoolSize(maxSize) 208 | .allowCoreThreadTimeOut(false) 209 | .build(); 210 | 211 | CountDownLatch exitLatch = new CountDownLatch(1); 212 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize); 213 | for (int i = 0; i < coreSize; i++) { 214 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 215 | } 216 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 217 | "Not all threads were running. They were most likely not scheduled for execution."); 218 | CountDownLatch exitLatch2 = new CountDownLatch(1); 219 | CountDownLatch allThreadsRunningLatch2 = new CountDownLatch(maxSize - coreSize); 220 | for (int i = 0; i < (maxSize - coreSize); i++) { 221 | executor.execute(new TestTask(exitLatch2, allThreadsRunningLatch2)); 222 | } 223 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 224 | "Not all threads were running. They were most likely not scheduled for execution."); 225 | waitForPoolSize(executor, maxSize, defaultWaitTimeout); 226 | 227 | // finish core tasks and let timeout "core" threads 228 | exitLatch.countDown(); 229 | waitForActiveCount(executor, maxSize - coreSize, defaultWaitTimeout); 230 | waitForPoolSize(executor, Math.max(coreSize, (maxSize - coreSize)), defaultWaitTimeout); 231 | exitLatch2.countDown(); 232 | waitForActiveCount(executor, 0, defaultWaitTimeout); 233 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 234 | executor.shutdown(); 235 | } 236 | 237 | /** 238 | * Test that keepalive time is ignored when core threads are the same as max 239 | * threads and core thread time out is disabled. 240 | */ 241 | @Test 242 | public void testKeepaliveTime2() throws TimeoutException, InterruptedException { 243 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 244 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 245 | .setCorePoolSize(coreSize) 246 | .setMaximumPoolSize(coreSize) 247 | .build(); 248 | 249 | CountDownLatch exitLatch = new CountDownLatch(1); 250 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(coreSize); 251 | 252 | for (int i = 0; i < coreSize; i++) { 253 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 254 | } 255 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 256 | "Not all threads were running. They were most likely not scheduled for execution."); 257 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 258 | exitLatch.countDown(); 259 | waitForActiveCount(executor, 0, defaultWaitTimeout); 260 | waitForPoolSize(executor, coreSize, defaultWaitTimeout); 261 | executor.shutdown(); 262 | } 263 | 264 | /** 265 | * Test the keepalive setting with core thread time out enabled. 266 | */ 267 | @Test 268 | public void testKeepaliveTimeWithCoreThreadTimeoutEnabled() throws TimeoutException, InterruptedException { 269 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 270 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 271 | .allowCoreThreadTimeOut(true) 272 | .setCorePoolSize(coreSize) 273 | .setMaximumPoolSize(maxSize) 274 | .build(); 275 | 276 | CountDownLatch exitLatch = new CountDownLatch(1); 277 | CountDownLatch allThreadsRunningLatch = new CountDownLatch(maxSize); 278 | 279 | for (int i = 0; i < maxSize; i++) { 280 | executor.execute(new TestTask(exitLatch, allThreadsRunningLatch)); 281 | } 282 | // this will make sure that all thread are running at the same time 283 | assertTrue(allThreadsRunningLatch.await(defaultWaitTimeout, TimeUnit.MILLISECONDS), 284 | "Not all threads were running. They were most likely not scheduled for execution."); 285 | exitLatch.countDown(); 286 | waitForActiveCount(executor, 0, defaultWaitTimeout); 287 | waitForPoolSize(executor, 0, defaultWaitTimeout); 288 | executor.shutdown(); 289 | } 290 | 291 | /** 292 | * Tests that prestarting core threads starts exactly the core threads amount specified. 293 | */ 294 | @Test 295 | public void testPrestartCoreThreads() { 296 | EnhancedQueueExecutor executor = (new EnhancedQueueExecutor.Builder()) 297 | .setKeepAliveTime(Duration.ofMillis(keepaliveTimeMillis)) 298 | .setCorePoolSize(coreSize) 299 | .setMaximumPoolSize(maxSize) 300 | .build(); 301 | int prestarted = executor.prestartAllCoreThreads(); 302 | assertEquals(coreSize, prestarted, "expected: == " + coreSize + ", actual: " + prestarted); 303 | assertEquals(coreSize, executor.getPoolSize(), "expected: == " + coreSize + ", actual: " + executor.getPoolSize()); 304 | executor.shutdown(); 305 | } 306 | 307 | private void waitForPoolSize(EnhancedQueueExecutor executor, int expectedPoolSize, long waitMillis) throws TimeoutException, InterruptedException { 308 | long deadline = System.currentTimeMillis() + waitMillis; 309 | long delayMillis = 100; 310 | 311 | do { 312 | if (executor.getPoolSize() == expectedPoolSize) { 313 | break; 314 | } 315 | Thread.sleep(delayMillis); 316 | } while (System.currentTimeMillis() + delayMillis < deadline); 317 | if (executor.getPoolSize() != expectedPoolSize) { 318 | throw new TimeoutException("Timed out waiting for pool size to become " + expectedPoolSize 319 | + ", current pool size is " + executor.getPoolSize()); 320 | } 321 | } 322 | 323 | private void waitForActiveCount(EnhancedQueueExecutor executor, int expectedActiveCount, long waitMillis) throws TimeoutException, InterruptedException { 324 | long deadline = System.currentTimeMillis() + waitMillis; 325 | long delayMillis = 100; 326 | 327 | do { 328 | if (executor.getActiveCount() == expectedActiveCount) { 329 | break; 330 | } 331 | Thread.sleep(delayMillis); 332 | } while (System.currentTimeMillis() + delayMillis < deadline); 333 | if (executor.getActiveCount() != expectedActiveCount) { 334 | throw new TimeoutException("Timed out waiting for active count to become " + expectedActiveCount 335 | + ", current active count is " + executor.getActiveCount()); 336 | } 337 | } 338 | 339 | @Test 340 | public void testEnhancedExecutorShutdownNoTasks() throws Exception { 341 | final CountDownLatch terminateLatch = new CountDownLatch(1); 342 | EnhancedQueueExecutor executor = new EnhancedQueueExecutor.Builder() 343 | .setCorePoolSize(10) 344 | .setKeepAliveTime(Duration.ofNanos(1)) 345 | .setTerminationTask(new Runnable() { 346 | @Override 347 | public void run() { 348 | terminateLatch.countDown(); 349 | } 350 | }) 351 | .build(); 352 | 353 | executor.shutdown(); 354 | assertTrue(terminateLatch.await(10, TimeUnit.SECONDS)); 355 | } 356 | 357 | @Test //JBTHR-50 358 | public void testEnhancedExecutorShutdown() throws Exception { 359 | final CountDownLatch terminateLatch = new CountDownLatch(1); 360 | EnhancedQueueExecutor executor = new EnhancedQueueExecutor.Builder() 361 | .setCorePoolSize(10) 362 | .setKeepAliveTime(Duration.ofNanos(1)) 363 | .setTerminationTask(new Runnable() { 364 | @Override 365 | public void run() { 366 | terminateLatch.countDown(); 367 | } 368 | }) 369 | .build(); 370 | 371 | for (int i = 0; i < 10000; ++i) { 372 | executor.submit(new Runnable() { 373 | @Override 374 | public void run() { 375 | try { 376 | Thread.sleep(1); 377 | } catch (InterruptedException e) { 378 | //ignore 379 | } 380 | } 381 | }); 382 | } 383 | executor.shutdown(); 384 | assertTrue(terminateLatch.await(10, TimeUnit.SECONDS)); 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/QueuelessViewExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import org.awaitility.Awaitility; 4 | import org.junit.jupiter.api.Timeout; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.EnumSource; 7 | 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.Executor; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.RejectedExecutionException; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.concurrent.atomic.AtomicBoolean; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 19 | 20 | 21 | public class QueuelessViewExecutorTest { 22 | 23 | private static final String THREAD_BASE_NAME = "CachedExecutorViewTest-"; 24 | 25 | /* 26 | * Both implementations have enough permits that queues shouldn't 27 | * be used so the implementations should be equivalent. 28 | */ 29 | public enum ExecutorType { 30 | QUEUELESS_VIEW() { 31 | @Override 32 | ExecutorService wrap(Executor delegate) { 33 | return ViewExecutor.builder(delegate) 34 | .setQueueLimit(0) 35 | .setMaxSize(Short.MAX_VALUE) 36 | .build(); 37 | } 38 | }, 39 | QUEUED() { 40 | @Override 41 | ExecutorService wrap(Executor delegate) { 42 | return ViewExecutor.builder(delegate) 43 | .setQueueLimit(Integer.MAX_VALUE) 44 | .setMaxSize(Short.MAX_VALUE) 45 | .build(); 46 | } 47 | }; 48 | 49 | abstract ExecutorService wrap(Executor delegate); 50 | } 51 | 52 | @ParameterizedTest 53 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class) 54 | public void testShutdownNow(ExecutorType executorType) throws InterruptedException { 55 | AtomicBoolean interrupted = new AtomicBoolean(); 56 | ExecutorService cached = cachedExecutor(); 57 | ExecutorService view = executorType.wrap(cached); 58 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse(); 59 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse(); 60 | CountDownLatch executionStartedLatch = new CountDownLatch(1); 61 | CountDownLatch interruptedLatch = new CountDownLatch(1); 62 | view.execute(() -> { 63 | try { 64 | executionStartedLatch.countDown(); 65 | Thread.sleep(10_000); 66 | } catch (InterruptedException e) { 67 | interrupted.set(true); 68 | // Wait so we can validate the time between shutdown and terminated 69 | try { 70 | interruptedLatch.await(); 71 | } catch (InterruptedException ee) { 72 | throw new AssertionError(ee); 73 | } 74 | } 75 | }); 76 | executionStartedLatch.await(); 77 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty(); 78 | assertThat(view.isShutdown()).isTrue(); 79 | assertThatThrownBy(() -> view.execute(NullRunnable.getInstance())) 80 | .as("Submitting work after invoking shutdown or shutdownNow should fail") 81 | .isInstanceOf(RejectedExecutionException.class); 82 | Awaitility.waitAtMost(3, TimeUnit.SECONDS).untilAsserted(() -> { 83 | assertThat(interrupted).isTrue(); 84 | assertThat(view.isTerminated()).isFalse(); 85 | }); 86 | interruptedLatch.countDown(); 87 | Awaitility.waitAtMost(3, TimeUnit.SECONDS) 88 | .untilAsserted(() -> assertThat(view.isTerminated()).as("%s", view).isTrue()); 89 | 90 | assertCleanShutdown(cached); 91 | } 92 | 93 | @ParameterizedTest 94 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class) 95 | public void testShutdownNow_immediatelyAfterTaskIsSubmitted(ExecutorType executorType) throws InterruptedException { 96 | AtomicBoolean interrupted = new AtomicBoolean(); 97 | ExecutorService cached = cachedExecutor(); 98 | ExecutorService view = executorType.wrap(runnable -> { 99 | cached.execute(() -> { 100 | // Emphasize the jitter between when a task is submitted, and when it begins to execute 101 | try { 102 | Thread.sleep(100); 103 | } catch (InterruptedException e) { 104 | throw new AssertionError(e); 105 | } 106 | runnable.run(); 107 | }); 108 | }); 109 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse(); 110 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse(); 111 | view.execute(() -> { 112 | try { 113 | Thread.sleep(10_000); 114 | } catch (InterruptedException e) { 115 | interrupted.set(true); 116 | } 117 | }); 118 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty(); 119 | assertThat(view.awaitTermination(3, TimeUnit.SECONDS)) 120 | .as("View failed to terminate within 3 seconds: %s", view) 121 | .isTrue(); 122 | assertThat(interrupted).as("Task should have been interrupted").isTrue(); 123 | 124 | assertCleanShutdown(cached); 125 | } 126 | 127 | @Timeout(5_000) // Failing awaitTermination should return quickly 128 | @ParameterizedTest 129 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class) 130 | public void testAwaitTermination(ExecutorType executorType) throws InterruptedException { 131 | AtomicBoolean interrupted = new AtomicBoolean(); 132 | ExecutorService cached = cachedExecutor(); 133 | ExecutorService view = executorType.wrap(cached); 134 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse(); 135 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse(); 136 | view.execute(() -> { 137 | try { 138 | Thread.sleep(30_000); 139 | } catch (InterruptedException e) { 140 | interrupted.set(true); 141 | } 142 | }); 143 | 144 | view.shutdown(); 145 | assertThat(view.awaitTermination(10, TimeUnit.MILLISECONDS)) 146 | .as("Task should not have been interrupted, and is still sleeping") 147 | .isFalse(); 148 | 149 | assertThat(interrupted).as("Task should not be interrupted by 'shutdown'").isFalse(); 150 | 151 | assertThat(view.shutdownNow()).as("Cached executors have no queue").isEmpty(); 152 | assertThat(view.awaitTermination(3, TimeUnit.SECONDS)) 153 | .as("Task should have been interrupted: %s", view) 154 | .isTrue(); 155 | 156 | assertThat(interrupted).as("Task should be interrupted by 'shutdownNow'").isTrue(); 157 | 158 | assertCleanShutdown(cached); 159 | } 160 | 161 | @ParameterizedTest 162 | @EnumSource(QueuelessViewExecutorTest.ExecutorType.class) 163 | public void testShutdown(ExecutorType executorType) throws InterruptedException { 164 | AtomicBoolean interrupted = new AtomicBoolean(); 165 | ExecutorService cached = cachedExecutor(); 166 | ExecutorService view = executorType.wrap(cached); 167 | assertThat(view.isShutdown()).isEqualTo(cached.isShutdown()).isFalse(); 168 | assertThat(view.isTerminated()).isEqualTo(cached.isTerminated()).isFalse(); 169 | CountDownLatch executionStartedLatch = new CountDownLatch(1); 170 | view.execute(() -> { 171 | try { 172 | executionStartedLatch.countDown(); 173 | Thread.sleep(500); 174 | } catch (InterruptedException e) { 175 | interrupted.set(true); 176 | } 177 | }); 178 | executionStartedLatch.await(); 179 | view.shutdown(); 180 | assertThat(view.isShutdown()).isTrue(); 181 | assertThatThrownBy(() -> view.execute(NullRunnable.getInstance())) 182 | .as("Submitting work after invoking shutdown or shutdown should fail") 183 | .isInstanceOf(RejectedExecutionException.class); 184 | assertThat(view.isTerminated()).isFalse(); 185 | Awaitility.waitAtMost(3, TimeUnit.SECONDS) 186 | .untilAsserted(() -> assertThat(view.isTerminated()).as("%s", view).isTrue()); 187 | assertThat(interrupted).isFalse(); 188 | 189 | assertCleanShutdown(cached); 190 | } 191 | 192 | private static ExecutorService cachedExecutor() { 193 | AtomicInteger index = new AtomicInteger(); 194 | return Executors.newCachedThreadPool( 195 | task -> { 196 | Thread thread = new Thread(task); 197 | thread.setDaemon(true); 198 | thread.setName(THREAD_BASE_NAME + index.getAndIncrement()); 199 | return thread; 200 | }); 201 | } 202 | 203 | private static void assertCleanShutdown(ExecutorService executor) { 204 | assertThat(executor.isShutdown()).isFalse(); 205 | assertThat(executor.isTerminated()).isFalse(); 206 | executor.shutdown(); 207 | try { 208 | assertThat(executor.awaitTermination(1, TimeUnit.SECONDS)) 209 | .as("Failed to clean up the executor") 210 | .isTrue(); 211 | } catch (InterruptedException e) { 212 | throw new AssertionError(e); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/ScheduledEnhancedQueueExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.ArrayList; 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.CancellationException; 9 | import java.util.concurrent.CountDownLatch; 10 | import java.util.concurrent.ExecutionException; 11 | import java.util.concurrent.ScheduledFuture; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | import org.junit.jupiter.api.Test; 16 | 17 | public class ScheduledEnhancedQueueExecutorTest { 18 | 19 | @Test 20 | public void testCancel() throws Exception { 21 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 22 | try { 23 | ScheduledFuture future = eqe.schedule(() -> fail("Should never run"), 1000, TimeUnit.DAYS); 24 | Thread.sleep(400); // a few ms to let things percolate 25 | assertFalse(future.isCancelled()); 26 | // this should succeed since the task isn't submitted yet 27 | assertTrue(future.cancel(false)); 28 | assertTrue(future.isCancelled()); 29 | eqe.shutdown(); 30 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 31 | } finally { 32 | eqe.shutdownNow(); 33 | } 34 | } 35 | 36 | @Test 37 | public void testCancelWhileRunning() throws Exception { 38 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 39 | try { 40 | CountDownLatch latch = new CountDownLatch(1); 41 | ScheduledFuture future = eqe.schedule(() -> { latch.countDown(); Thread.sleep(1_000_000_000L); return Boolean.TRUE; }, 1, TimeUnit.NANOSECONDS); 42 | assertTrue(latch.await(5, TimeUnit.SECONDS), "Timely task execution"); 43 | assertFalse(future.isCancelled()); 44 | // task is running 45 | assertTrue(future.cancel(false)); 46 | assertFalse(future.isCancelled()); 47 | assertFalse(future.isDone()); 48 | // now try to interrupt it 49 | assertTrue(future.cancel(true)); 50 | assertFalse(future.isCancelled()); 51 | // now get it 52 | Throwable cause = assertThrows(ExecutionException.class, () -> future.get(100L, TimeUnit.MILLISECONDS)).getCause(); 53 | assertInstanceOf(InterruptedException.class, cause); 54 | assertTrue(future.isDone()); 55 | eqe.shutdown(); 56 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 57 | } finally { 58 | eqe.shutdownNow(); 59 | } 60 | } 61 | 62 | @Test 63 | public void testReasonableExecutionDelay() throws Exception { 64 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 65 | try { 66 | Callable task = () -> Boolean.TRUE; 67 | long start = System.nanoTime(); 68 | ScheduledFuture future = eqe.schedule(task, 1, TimeUnit.MILLISECONDS); 69 | Boolean result = future.get(); 70 | long execTime = System.nanoTime() - start; 71 | long expected = 1_000_000L; 72 | assertTrue(execTime >= expected, "Execution too short (expected at least " + expected + ", got " + execTime + ")"); 73 | assertNotNull(result); 74 | assertTrue(result.booleanValue()); 75 | start = System.nanoTime(); 76 | future = eqe.schedule(task, 500, TimeUnit.MILLISECONDS); 77 | result = future.get(); 78 | execTime = System.nanoTime() - start; 79 | expected = 500_000_000L; 80 | assertTrue(execTime >= expected, "Execution too short (expected at least " + expected + ", got " + execTime + ")"); 81 | assertNotNull(result); 82 | assertTrue(result.booleanValue()); 83 | eqe.shutdown(); 84 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 85 | } finally { 86 | eqe.shutdownNow(); 87 | } 88 | } 89 | 90 | @Test 91 | public void testFixedRateExecution() throws Exception { 92 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 93 | try { 94 | AtomicInteger ai = new AtomicInteger(); 95 | CountDownLatch completeLatch = new CountDownLatch(1); 96 | ScheduledFuture future = eqe.scheduleAtFixedRate(() -> { 97 | if (ai.incrementAndGet() == 5) { 98 | completeLatch.countDown(); 99 | } 100 | }, 20, 50, TimeUnit.MILLISECONDS); 101 | assertTrue(completeLatch.await(5, TimeUnit.SECONDS), "Completion of enough iterations"); 102 | assertFalse(future.isDone()); // they're never done 103 | // don't assert, because there's a small chance it would happen to be running 104 | future.cancel(false); 105 | try { 106 | future.get(5, TimeUnit.SECONDS); 107 | fail("Expected cancellation exception"); 108 | } catch (CancellationException e) { 109 | // expected 110 | } 111 | eqe.shutdown(); 112 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 113 | } finally { 114 | eqe.shutdownNow(); 115 | } 116 | } 117 | 118 | @Test 119 | public void testFixedDelayExecution() throws Exception { 120 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 121 | try { 122 | AtomicInteger ai = new AtomicInteger(); 123 | CountDownLatch completeLatch = new CountDownLatch(1); 124 | ScheduledFuture future = eqe.scheduleWithFixedDelay(() -> { 125 | if (ai.incrementAndGet() == 5) { 126 | completeLatch.countDown(); 127 | } 128 | }, 20, 50, TimeUnit.MILLISECONDS); 129 | assertTrue(completeLatch.await(5, TimeUnit.SECONDS), "Completion of enough iterations"); 130 | assertFalse(future.isDone()); // they're never done 131 | // don't assert, because there's a small chance it would happen to be running 132 | future.cancel(false); 133 | try { 134 | future.get(5, TimeUnit.SECONDS); 135 | fail("Expected cancellation exception"); 136 | } catch (CancellationException e) { 137 | // expected 138 | } 139 | eqe.shutdown(); 140 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 141 | } finally { 142 | eqe.shutdownNow(); 143 | } 144 | } 145 | 146 | @Test 147 | public void testThatFixedDelayTerminatesTask() { 148 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 149 | final ArrayList times = new ArrayList<>(); 150 | var r = new Runnable() { 151 | final CountDownLatch latch = new CountDownLatch(1); 152 | volatile ScheduledFuture future; 153 | 154 | public void run() { 155 | times.add(LocalDateTime.now()); 156 | if (times.size() >= 5) { 157 | try { 158 | latch.await(); 159 | } catch (InterruptedException e) { 160 | throw new RuntimeException(e); 161 | } 162 | future.cancel(false); 163 | } 164 | } 165 | 166 | public void schedule() { 167 | future = eqe.scheduleWithFixedDelay(this, 0, 100, TimeUnit.MILLISECONDS); 168 | } 169 | }; 170 | r.schedule(); 171 | r.latch.countDown(); 172 | assertThrows(CancellationException.class, () -> r.future.get(5, TimeUnit.SECONDS)); 173 | } 174 | 175 | @Test 176 | public void testCancelOnShutdown() throws Exception { 177 | EnhancedQueueExecutor eqe = new EnhancedQueueExecutor.Builder().build(); 178 | try { 179 | ScheduledFuture future = eqe.schedule(() -> fail("Should never run"), 1, TimeUnit.DAYS); 180 | eqe.shutdown(); 181 | assertTrue(eqe.awaitTermination(5, TimeUnit.SECONDS), "Timely shutdown"); 182 | try { 183 | future.get(5, TimeUnit.SECONDS); 184 | fail("Expected cancellation exception"); 185 | } catch (CancellationException e) { 186 | // expected 187 | } 188 | assertTrue(future.isCancelled(), "Was cancelled on shutdown"); 189 | } finally { 190 | eqe.shutdownNow(); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/ThreadFactoryTestCase.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | /** 14 | * 15 | */ 16 | public final class ThreadFactoryTestCase { 17 | private static final NullRunnable NULL_RUNNABLE = new NullRunnable(); 18 | 19 | private static class NullRunnable implements Runnable { 20 | public void run() { 21 | } 22 | } 23 | 24 | private static void doTestNamePattern(JBossThreadFactory threadFactory, int expectedPerFactoryId, int expectedGlobalId, int expectedFactoryId) { 25 | final String name = threadFactory.newThread(NULL_RUNNABLE).getName(); 26 | assertTrue(name.matches("-([a-z]+:)*one:two:three-%-" + expectedPerFactoryId + "-" + expectedGlobalId + "-" + expectedFactoryId + "-"), "Wrong thread name (" + name + ") "); 27 | } 28 | 29 | /** 30 | * This MUST be the first test, otherwise the sequence numbers will be wrong. 31 | */ 32 | @Test 33 | @Disabled("skip test for now since it depends on order") 34 | public void testNamePattern() { 35 | // TODO - skip test for now since it depends on order. 36 | if (true) return; 37 | final JBossThreadFactory threadFactory1 = new JBossThreadFactory(new ThreadGroup(new ThreadGroup(new ThreadGroup("one"), "two"), "three"), null, 38 | null, "-%p-%%-%t-%g-%f-", null, null); 39 | doTestNamePattern(threadFactory1, 1, 1, 1); 40 | doTestNamePattern(threadFactory1, 2, 2, 1); 41 | doTestNamePattern(threadFactory1, 3, 3, 1); 42 | final JBossThreadFactory threadFactory2 = new JBossThreadFactory(new ThreadGroup(new ThreadGroup(new ThreadGroup("one"), "two"), "three"), null, 43 | null, "-%p-%%-%t-%g-%f-", null, null); 44 | doTestNamePattern(threadFactory2, 1, 4, 2); 45 | doTestNamePattern(threadFactory2, 2, 5, 2); 46 | doTestNamePattern(threadFactory2, 3, 6, 2); 47 | doTestNamePattern(threadFactory2, 4, 7, 2); 48 | // back to the first factory... 49 | doTestNamePattern(threadFactory1, 4, 8, 1); 50 | } 51 | 52 | @Test 53 | public void testDaemon() { 54 | final JBossThreadFactory threadFactory1 = new JBossThreadFactory(null, Boolean.TRUE, null, "%t", null, null); 55 | assertTrue(threadFactory1.newThread(NULL_RUNNABLE).isDaemon(), "Thread is not a daemon thread"); 56 | final JBossThreadFactory threadFactory2 = new JBossThreadFactory(null, Boolean.FALSE, null, "%t", null, null); 57 | assertFalse(threadFactory2.newThread(NULL_RUNNABLE).isDaemon(),"Thread should not be a daemon thread"); 58 | } 59 | 60 | @Test 61 | public void testInterruptHandler() throws InterruptedException { 62 | final AtomicBoolean wasInterrupted = new AtomicBoolean(); 63 | final AtomicBoolean called = new AtomicBoolean(); 64 | final CountDownLatch latch = new CountDownLatch(1); 65 | final JBossThreadFactory threadFactory = new JBossThreadFactory(null, null, null, null, null, null); 66 | final Thread t = threadFactory.newThread(new Runnable() { 67 | public void run() { 68 | synchronized (this) { 69 | final InterruptHandler old = JBossThread.getAndSetInterruptHandler(new InterruptHandler() { 70 | public void handleInterrupt(final Thread thread) { 71 | called.set(true); 72 | } 73 | }); 74 | Thread.interrupted(); 75 | latch.countDown(); 76 | try { 77 | for (;;) wait(); 78 | } catch (InterruptedException e) { 79 | wasInterrupted.set(true); 80 | } 81 | } 82 | } 83 | }); 84 | t.start(); 85 | latch.await(); 86 | t.interrupt(); 87 | t.join(); 88 | assertTrue(wasInterrupted.get(), "Was not interrupted"); 89 | assertTrue(called.get(), "Handler was not called"); 90 | } 91 | 92 | @Test 93 | public void testUncaughtHandler() throws InterruptedException { 94 | final AtomicBoolean called = new AtomicBoolean(); 95 | final JBossThreadFactory factory = new JBossThreadFactory(null, null, null, null, new Thread.UncaughtExceptionHandler() { 96 | public void uncaughtException(final Thread t, final Throwable e) { 97 | called.set(true); 98 | } 99 | }, null); 100 | final Thread t = factory.newThread(new Runnable() { 101 | public void run() { 102 | throw new RuntimeException("..."); 103 | } 104 | }); 105 | t.start(); 106 | t.join(); 107 | assertTrue(called.get(), "Handler was not called"); 108 | } 109 | 110 | @Test 111 | public void testInitialPriority() { 112 | assertEquals(1, new JBossThreadFactory(null, null, Integer.valueOf(1), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority"); 113 | assertEquals(2, new JBossThreadFactory(null, null, Integer.valueOf(2), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority"); 114 | final ThreadGroup grp = new ThreadGroup("blah"); 115 | grp.setMaxPriority(5); 116 | assertEquals(5, new JBossThreadFactory(grp, null, Integer.valueOf(10), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority"); 117 | assertEquals(1, new JBossThreadFactory(grp, null, Integer.valueOf(1), null, null, null).newThread(NULL_RUNNABLE).getPriority(), "Wrong initial thread priority"); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/ThreadLocalResetterTests.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | public class ThreadLocalResetterTests { 6 | @Test 7 | public void testResetter() { 8 | JDKSpecific.ThreadAccess.clearThreadLocals(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/org/jboss/threads/ViewExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.jboss.threads; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.time.Duration; 6 | import java.util.ArrayDeque; 7 | import java.util.List; 8 | import java.util.concurrent.CopyOnWriteArrayList; 9 | import java.util.concurrent.CountDownLatch; 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.RejectedExecutionException; 14 | import java.util.concurrent.SynchronousQueue; 15 | import java.util.concurrent.ThreadPoolExecutor; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.concurrent.atomic.AtomicBoolean; 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | 20 | import org.awaitility.Awaitility; 21 | import org.junit.jupiter.api.Test; 22 | import org.junit.jupiter.api.Timeout; 23 | 24 | public final class ViewExecutorTest { 25 | 26 | @Test 27 | public void testExecution() throws InterruptedException { 28 | final ViewExecutor ve = ViewExecutor.builder(JBossExecutors.directExecutor()).build(); 29 | assertFalse(ve.isShutdown()); 30 | assertFalse(ve.isTerminated()); 31 | AtomicBoolean ran = new AtomicBoolean(); 32 | ve.execute(new Runnable() { 33 | public void run() { 34 | ran.set(true); 35 | } 36 | }); 37 | assertTrue(ran.get()); 38 | ve.shutdown(); 39 | assertTrue(ve.isShutdown()); 40 | assertTrue(ve.awaitTermination(10L, TimeUnit.SECONDS)); 41 | assertTrue(ve.isTerminated()); 42 | ve.shutdown(); 43 | assertTrue(ve.isTerminated()); 44 | } 45 | 46 | @Test 47 | public void testQueuedExecution() { 48 | final ArrayDeque executedTasks = new ArrayDeque<>(); 49 | Executor testExecutor = new QueuedExecutor(executedTasks); 50 | final ViewExecutor ve = ViewExecutor.builder(testExecutor).setMaxSize(1).build(); 51 | AtomicBoolean ran1 = new AtomicBoolean(); 52 | ve.execute(new Runnable() { 53 | public void run() { 54 | ran1.set(true); 55 | } 56 | }); 57 | assertEquals(1, executedTasks.size()); 58 | AtomicBoolean ran2 = new AtomicBoolean(); 59 | ve.execute(new Runnable() { 60 | public void run() { 61 | ran2.set(true); 62 | } 63 | }); 64 | assertEquals(1, executedTasks.size()); 65 | executedTasks.poll().run(); 66 | assertEquals(1, executedTasks.size()); 67 | assertTrue(ran1.get()); 68 | executedTasks.poll().run(); 69 | assertTrue(ran2.get()); 70 | assertEquals(0, executedTasks.size()); 71 | } 72 | 73 | @Test 74 | public void testInterruptedShutdown() throws InterruptedException { 75 | ExecutorService testExecutor = Executors.newSingleThreadExecutor(); 76 | final ViewExecutor ve = ViewExecutor.builder(testExecutor).build(); 77 | AtomicBoolean intr = new AtomicBoolean(); 78 | CountDownLatch runGate = new CountDownLatch(1); 79 | CountDownLatch finishGate = new CountDownLatch(1); 80 | ve.execute(new Runnable() { 81 | public void run() { 82 | runGate.countDown(); 83 | try { 84 | Thread.sleep(60_000L); 85 | } catch (InterruptedException e) { 86 | intr.set(true); 87 | } finally { 88 | finishGate.countDown(); 89 | } 90 | } 91 | }); 92 | runGate.await(); 93 | assertFalse(intr.get()); 94 | ve.shutdown(true); 95 | finishGate.await(); 96 | assertTrue(intr.get()); 97 | testExecutor.shutdown(); 98 | assertTrue(testExecutor.awaitTermination(5L, TimeUnit.SECONDS)); 99 | } 100 | 101 | // TaskWrapper instances are relatively small and take a long time to knock over a JVM, 102 | // however when they aren't collected properly they will cause a GC spiral for much longer 103 | // than ten seconds. Unfortunately this test is impacted by hardware changes and may flake 104 | // (or erroneously pass on fast enough hardware). 105 | @Test 106 | @Timeout(10_000) 107 | public void testViewExecutorMemoryOverhead() { 108 | Executor directExecutor = new Executor() { 109 | @Override 110 | public void execute(Runnable command) { 111 | try { 112 | command.run(); 113 | } catch (Throwable t) { 114 | Thread currentThread = Thread.currentThread(); 115 | currentThread.getUncaughtExceptionHandler().uncaughtException(currentThread, t); 116 | } 117 | } 118 | }; 119 | ExecutorService executorService = ViewExecutor.builder(directExecutor).build(); 120 | for (long i = 0; i < 20_000_000L; i++) { 121 | executorService.execute(JBossExecutors.nullRunnable()); 122 | } 123 | executorService.shutdown(); 124 | assertTrue(executorService.isTerminated()); 125 | } 126 | 127 | @Test 128 | public void testSameThreadDelegateDoesNotDeadlock() throws InterruptedException { 129 | Executor direct = Runnable::run; 130 | AtomicInteger completed = new AtomicInteger(); 131 | ExecutorService view = ViewExecutor.builder(direct).setQueueLimit(1).setMaxSize(1).build(); 132 | ExecutorService submittingExecutor = Executors.newCachedThreadPool(); 133 | try { 134 | submittingExecutor.execute(() -> view.execute(() -> view.execute(completed::incrementAndGet))); 135 | Awaitility.waitAtMost(Duration.ofSeconds(1)).untilAsserted(() -> assertEquals(1, completed.get())); 136 | view.shutdown(); 137 | assertTrue(view.awaitTermination(100, TimeUnit.SECONDS)); 138 | } finally { 139 | submittingExecutor.shutdown(); 140 | assertTrue(submittingExecutor.awaitTermination(1, TimeUnit.SECONDS)); 141 | } 142 | } 143 | 144 | @Test 145 | public void testDelegateQueueProcessingRejection() throws InterruptedException { 146 | // When the active task pulls from the queue, submitting the task to the delegate will fail because it's 147 | // already consuming the only available thread. The task should be handled on the same thread. 148 | CountDownLatch taskLatch = new CountDownLatch(1); 149 | // One permit, throws RejectedExecutionException if a second task is provided 150 | ExecutorService delegate = new ThreadPoolExecutor(0, 1, 151 | 5, TimeUnit.SECONDS, new SynchronousQueue<>()); 152 | try { 153 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build(); 154 | List throwables = new CopyOnWriteArrayList<>(); 155 | for (int i = 0; i < 2; i++) { 156 | view.execute(() -> { 157 | try { 158 | // latch used to ensure the second task is queued, otherwise the first task may complete 159 | // before the second is submitted. 160 | taskLatch.await(); 161 | } catch (InterruptedException e) { 162 | throw new RuntimeException(e); 163 | } 164 | throwables.add(new RuntimeException("task trace")); 165 | }); 166 | } 167 | taskLatch.countDown(); 168 | Awaitility.waitAtMost(Duration.ofSeconds(1)) 169 | .untilAsserted(() -> { 170 | assertEquals(2, throwables.size()); 171 | // The stack size mustn't grow with each queued task, otherwise processing will eventually 172 | // fail running out of stack space. 173 | assertEquals(throwables.get(0).getStackTrace().length, throwables.get(1).getStackTrace().length); 174 | }); 175 | } finally { 176 | delegate.shutdown(); 177 | assertTrue(delegate.awaitTermination(1, TimeUnit.SECONDS)); 178 | } 179 | } 180 | 181 | @Test 182 | @Timeout(5_000) 183 | public void testDelegateQueueProcessingRejectionTaskIsInterrupted() throws InterruptedException { 184 | // Subsequent queued tasks run by the same wrapper should support interruption 185 | CountDownLatch firstTaskLatch = new CountDownLatch(1); 186 | // One permit, throws RejectedExecutionException if a second task is provided 187 | ExecutorService delegate = new ThreadPoolExecutor(0, 1, 188 | 5, TimeUnit.SECONDS, new SynchronousQueue<>()); 189 | try { 190 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build(); 191 | view.submit(() -> { 192 | // latch used to ensure the second task is queued, otherwise the first task may complete 193 | // before the second is submitted. 194 | firstTaskLatch.await(); 195 | return null; 196 | }); 197 | AtomicBoolean interrupted = new AtomicBoolean(); 198 | CountDownLatch secondTaskStartedLatch = new CountDownLatch(1); 199 | view.execute(() -> { 200 | secondTaskStartedLatch.countDown(); 201 | try { 202 | Thread.sleep(10_000); 203 | } catch (InterruptedException e) { 204 | interrupted.set(true); 205 | } 206 | }); 207 | firstTaskLatch.countDown(); 208 | secondTaskStartedLatch.await(); 209 | view.shutdownNow(); 210 | assertTrue(view.awaitTermination(200, TimeUnit.MILLISECONDS)); 211 | assertTrue(interrupted.get()); 212 | } finally { 213 | delegate.shutdown(); 214 | assertTrue(delegate.awaitTermination(1, TimeUnit.SECONDS)); 215 | } 216 | } 217 | 218 | @Test 219 | public void testTestSlowExecuteInParallelWithEnqueue() throws InterruptedException { 220 | // When a thread (threadA) submits a task to a delegate, a parallel task should not be able to 221 | // successfully enqueue work. If an enqueue succeeded but delegate.execute did not, the queue 222 | // would become detached from the executor, and never flush. 223 | CountDownLatch taskLatch = new CountDownLatch(1); 224 | // One permit, throws RejectedExecutionException if a second task is provided 225 | Executor delegate = new Executor() { 226 | private final AtomicBoolean firstCall = new AtomicBoolean(true); 227 | @Override 228 | public void execute(Runnable command) { 229 | if (firstCall.getAndSet(false)) { 230 | try { 231 | taskLatch.await(); 232 | } catch (InterruptedException e) { 233 | throw new RuntimeException(e); 234 | } 235 | } 236 | throw new RejectedExecutionException(); 237 | } 238 | }; 239 | 240 | ExecutorService testRunner = Executors.newCachedThreadPool(); 241 | ExecutorService view = ViewExecutor.builder(delegate).setQueueLimit(1).setMaxSize(1).build(); 242 | List throwables = new CopyOnWriteArrayList<>(); 243 | for (int i = 0; i < 2; i++) { 244 | testRunner.execute(() -> { 245 | try { 246 | view.execute(NullRunnable.getInstance()); 247 | throw new AssertionError("should not be reached"); 248 | } catch (Throwable t) { 249 | throwables.add(t); 250 | } 251 | }); 252 | } 253 | assertEquals(0, throwables.size()); 254 | taskLatch.countDown(); 255 | testRunner.shutdown(); 256 | assertTrue(testRunner.awaitTermination(1, TimeUnit.SECONDS)); 257 | assertEquals(2, throwables.size()); 258 | for (Throwable throwable : throwables) { 259 | assertTrue(throwable instanceof RejectedExecutionException); 260 | } 261 | } 262 | 263 | private static class QueuedExecutor implements Executor { 264 | private final ArrayDeque executedTasks; 265 | 266 | public QueuedExecutor(final ArrayDeque executedTasks) { 267 | this.executedTasks = executedTasks; 268 | } 269 | 270 | public void execute(final Runnable command) { 271 | executedTasks.add(command); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | # 2 | # JBoss, Home of Professional Open Source. 3 | # Copyright 2017 Red Hat, Inc., and individual contributors 4 | # as indicated by the @author tags. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | # Additional logger names to configure (root logger is always configured) 20 | loggers=org.jboss.threads 21 | 22 | # Root logger configuration 23 | logger.level=INFO 24 | logger.handlers=CONSOLE, FILE 25 | 26 | logger.org.jboss.threads.level=${test.level:INFO} 27 | 28 | # Console handler configuration 29 | handler.CONSOLE=org.jboss.logmanager.handlers.ConsoleHandler 30 | handler.CONSOLE.properties=autoFlush 31 | handler.CONSOLE.level=ALL 32 | handler.CONSOLE.autoFlush=true 33 | handler.CONSOLE.formatter=PATTERN 34 | 35 | # File handler configuration 36 | handler.FILE=org.jboss.logmanager.handlers.FileHandler 37 | handler.FILE.level=ALL 38 | handler.FILE.properties=autoFlush,fileName 39 | handler.FILE.autoFlush=true 40 | handler.FILE.fileName=./target/test.log 41 | handler.FILE.formatter=PATTERN 42 | 43 | # Formatter pattern configuration 44 | formatter.PATTERN=org.jboss.logmanager.formatters.PatternFormatter 45 | formatter.PATTERN.properties=pattern 46 | formatter.PATTERN.pattern=%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n 47 | 48 | --------------------------------------------------------------------------------