diff --git a/java-checks-test-sources/default/src/main/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample.java new file mode 100644 index 00000000000..6d66dfc8859 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample.java @@ -0,0 +1,68 @@ +package checks; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +class ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample { + + void directCall() { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + executor.setMaximumPoolSize(10); // Noncompliant {{Remove this "setMaximumPoolSize" call; it has no effect on ScheduledThreadPoolExecutor.}} +// ^^^^^^^^^^^^^^^^^^ + } + + void afterOtherConfig() { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + executor.setCorePoolSize(8); + executor.setMaximumPoolSize(10); // Noncompliant +// ^^^^^^^^^^^^^^^^^^ + } + + void variableArgument() { + int maxSize = 20; + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + executor.setMaximumPoolSize(maxSize); // Noncompliant +// ^^^^^^^^^^^^^^^^^^ + } + + ScheduledThreadPoolExecutor createPool() { + return new ScheduledThreadPoolExecutor(5); + } + + void methodReturnedInstance() { + createPool().setMaximumPoolSize(10); // Noncompliant +// ^^^^^^^^^^^^^^^^^^ + } + + void onSubtype() { + CustomScheduledExecutor custom = new CustomScheduledExecutor(5); + custom.setMaximumPoolSize(10); // Noncompliant +// ^^^^^^^^^^^^^^^^^^ + } + + // Compliant cases + + void setCorePoolSizeOnScheduled() { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + executor.setCorePoolSize(10); // Compliant - correct way to control pool size + } + + void setMaximumPoolSizeOnThreadPoolExecutor() { + ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60L, + java.util.concurrent.TimeUnit.SECONDS, + new java.util.concurrent.LinkedBlockingQueue<>()); + executor.setMaximumPoolSize(20); // Compliant - has effect on ThreadPoolExecutor + } + + void otherConfigOnScheduled() { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); + executor.setKeepAliveTime(60L, java.util.concurrent.TimeUnit.SECONDS); // Compliant + executor.allowCoreThreadTimeOut(true); // Compliant + } + + static class CustomScheduledExecutor extends ScheduledThreadPoolExecutor { + CustomScheduledExecutor(int corePoolSize) { + super(corePoolSize); + } + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheck.java new file mode 100644 index 00000000000..c5d8929dc79 --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheck.java @@ -0,0 +1,43 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.sonar.check.Rule; +import org.sonar.java.checks.methods.AbstractMethodDetection; +import org.sonar.java.model.ExpressionUtils; +import org.sonar.plugins.java.api.semantic.MethodMatchers; +import org.sonar.plugins.java.api.tree.MethodInvocationTree; + +@Rule(key = "S9132") +public class ScheduledThreadPoolExecutorMaximumPoolSizeCheck extends AbstractMethodDetection { + + @Override + protected MethodMatchers getMethodInvocationMatchers() { + return MethodMatchers.create() + .ofSubTypes("java.util.concurrent.ScheduledThreadPoolExecutor") + .names("setMaximumPoolSize") + .addParametersMatcher("int") + .build(); + } + + @Override + protected void onMethodInvocationFound(MethodInvocationTree mit) { + reportIssue(ExpressionUtils.methodName(mit), + "Remove this \"setMaximumPoolSize\" call; it has no effect on ScheduledThreadPoolExecutor."); + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckTest.java new file mode 100644 index 00000000000..c226a12a1e3 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckTest.java @@ -0,0 +1,32 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class ScheduledThreadPoolExecutorMaximumPoolSizeCheckTest { + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample.java")) + .withCheck(new ScheduledThreadPoolExecutorMaximumPoolSizeCheck()) + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.html new file mode 100644 index 00000000000..009e73fd4f3 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.html @@ -0,0 +1,27 @@ +
Scheduled thread pool executors are designed with a fixed-size thread pool architecture. Unlike standard thread pool implementations, they use an +unbounded work queue and maintain a fixed number of threads equal to the core pool size configuration.
+When you call setMaximumPoolSize() on a ScheduledThreadPoolExecutor, the call completes without error, but it has no effect
+on the executor's behavior. The pool will never create threads beyond the core pool size, regardless of how high you set the maximum pool size.
This happens because scheduled thread pool executors override the task queuing behavior. All tasks go into an unbounded queue, and the executor +never needs to create additional threads beyond the core pool size to handle the workload.
+Attempting to adjust the maximum pool size suggests a misunderstanding of how this executor works, and indicates that the code may not behave as +intended.
+Remove the call to setMaximumPoolSize() and use setCorePoolSize() instead to control the number of threads in the pool.
+ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); +executor.setMaximumPoolSize(10); // Noncompliant ++
+ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); +executor.setCorePoolSize(10); // Pool now maintains exactly 10 threads ++