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 @@ +

Why is this an issue?

+

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.

+

How to fix it

+

Remove the call to setMaximumPoolSize() and use setCorePoolSize() instead to control the number of threads in the pool.

+

Noncompliant code example

+
+ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
+executor.setMaximumPoolSize(10); // Noncompliant
+
+

Compliant solution

+
+ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
+executor.setCorePoolSize(10); // Pool now maintains exactly 10 threads
+
+

Resources

+

Documentation

+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.json new file mode 100644 index 00000000000..10b722c6268 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9132.json @@ -0,0 +1,21 @@ +{ + "title": "\"ScheduledThreadPoolExecutor.setMaximumPoolSize\" should not be called", + "type": "CODE_SMELL", + "code": { + "impacts": { + "RELIABILITY": "MEDIUM" + }, + "attribute": "LOGICAL" + }, + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-9132", + "sqKey": "S9132", + "scope": "All", + "quickfix": "unknown" +}