Skip to content

Commit b145d55

Browse files
Implement new rule S9132
Detect calls to setMaximumPoolSize() on ScheduledThreadPoolExecutor instances. This method has no effect because ScheduledThreadPoolExecutor uses a fixed-size pool determined solely by corePoolSize.
1 parent 15e8a57 commit b145d55

5 files changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package checks;
2+
3+
import java.util.concurrent.ScheduledThreadPoolExecutor;
4+
import java.util.concurrent.ThreadPoolExecutor;
5+
6+
class ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample {
7+
8+
void directCall() {
9+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
10+
executor.setMaximumPoolSize(10); // Noncompliant {{Remove this "setMaximumPoolSize" call; it has no effect on ScheduledThreadPoolExecutor.}}
11+
// ^^^^^^^^^^^^^^^^^^
12+
}
13+
14+
void afterOtherConfig() {
15+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
16+
executor.setCorePoolSize(8);
17+
executor.setMaximumPoolSize(10); // Noncompliant
18+
// ^^^^^^^^^^^^^^^^^^
19+
}
20+
21+
void variableArgument() {
22+
int maxSize = 20;
23+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
24+
executor.setMaximumPoolSize(maxSize); // Noncompliant
25+
// ^^^^^^^^^^^^^^^^^^
26+
}
27+
28+
ScheduledThreadPoolExecutor createPool() {
29+
return new ScheduledThreadPoolExecutor(5);
30+
}
31+
32+
void methodReturnedInstance() {
33+
createPool().setMaximumPoolSize(10); // Noncompliant
34+
// ^^^^^^^^^^^^^^^^^^
35+
}
36+
37+
void onSubtype() {
38+
CustomScheduledExecutor custom = new CustomScheduledExecutor(5);
39+
custom.setMaximumPoolSize(10); // Noncompliant
40+
// ^^^^^^^^^^^^^^^^^^
41+
}
42+
43+
// Compliant cases
44+
45+
void setCorePoolSizeOnScheduled() {
46+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
47+
executor.setCorePoolSize(10); // Compliant - correct way to control pool size
48+
}
49+
50+
void setMaximumPoolSizeOnThreadPoolExecutor() {
51+
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60L,
52+
java.util.concurrent.TimeUnit.SECONDS,
53+
new java.util.concurrent.LinkedBlockingQueue<>());
54+
executor.setMaximumPoolSize(20); // Compliant - has effect on ThreadPoolExecutor
55+
}
56+
57+
void otherConfigOnScheduled() {
58+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
59+
executor.setKeepAliveTime(60L, java.util.concurrent.TimeUnit.SECONDS); // Compliant
60+
executor.allowCoreThreadTimeOut(true); // Compliant
61+
}
62+
63+
static class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {
64+
CustomScheduledExecutor(int corePoolSize) {
65+
super(corePoolSize);
66+
}
67+
}
68+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* You can redistribute and/or modify this program under the terms of
7+
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.sonar.check.Rule;
20+
import org.sonar.java.checks.methods.AbstractMethodDetection;
21+
import org.sonar.java.model.ExpressionUtils;
22+
import org.sonar.plugins.java.api.semantic.MethodMatchers;
23+
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
24+
25+
@Rule(key = "S9132")
26+
public class ScheduledThreadPoolExecutorMaximumPoolSizeCheck extends AbstractMethodDetection {
27+
28+
@Override
29+
protected MethodMatchers getMethodInvocationMatchers() {
30+
return MethodMatchers.create()
31+
.ofSubTypes("java.util.concurrent.ScheduledThreadPoolExecutor")
32+
.names("setMaximumPoolSize")
33+
.addParametersMatcher("int")
34+
.build();
35+
}
36+
37+
@Override
38+
protected void onMethodInvocationFound(MethodInvocationTree mit) {
39+
reportIssue(ExpressionUtils.methodName(mit),
40+
"Remove this \"setMaximumPoolSize\" call; it has no effect on ScheduledThreadPoolExecutor.");
41+
}
42+
43+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* You can redistribute and/or modify this program under the terms of
7+
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.sonar.java.checks.verifier.CheckVerifier;
21+
22+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
24+
class ScheduledThreadPoolExecutorMaximumPoolSizeCheckTest {
25+
@Test
26+
void test() {
27+
CheckVerifier.newVerifier()
28+
.onFile(mainCodeSourcesPath("checks/ScheduledThreadPoolExecutorMaximumPoolSizeCheckSample.java"))
29+
.withCheck(new ScheduledThreadPoolExecutorMaximumPoolSizeCheck())
30+
.verifyIssues();
31+
}
32+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<h2>Why is this an issue?</h2>
2+
<p>Scheduled thread pool executors are designed with a fixed-size thread pool architecture. Unlike standard thread pool implementations, they use an
3+
unbounded work queue and maintain a fixed number of threads equal to the core pool size configuration.</p>
4+
<p>When you call <code>setMaximumPoolSize()</code> on a <code>ScheduledThreadPoolExecutor</code>, the call completes without error, but it has no effect
5+
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.</p>
6+
<p>This happens because scheduled thread pool executors override the task queuing behavior. All tasks go into an unbounded queue, and the executor
7+
never needs to create additional threads beyond the core pool size to handle the workload.</p>
8+
<p>Attempting to adjust the maximum pool size suggests a misunderstanding of how this executor works, and indicates that the code may not behave as
9+
intended.</p>
10+
<h2>How to fix it</h2>
11+
<p>Remove the call to <code>setMaximumPoolSize()</code> and use <code>setCorePoolSize()</code> instead to control the number of threads in the pool.</p>
12+
<h3>Noncompliant code example</h3>
13+
<pre>
14+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
15+
executor.setMaximumPoolSize(10); // Noncompliant
16+
</pre>
17+
<h3>Compliant solution</h3>
18+
<pre>
19+
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
20+
executor.setCorePoolSize(10); // Pool now maintains exactly 10 threads
21+
</pre>
22+
<h2>Resources</h2>
23+
<h3>Documentation</h3>
24+
<ul>
25+
<li><a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledThreadPoolExecutor.html">Oracle Java SE -
26+
ScheduledThreadPoolExecutor (Java SE 17)</a></li>
27+
</ul>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"title": "\"ScheduledThreadPoolExecutor.setMaximumPoolSize\" should not be called",
3+
"type": "CODE_SMELL",
4+
"code": {
5+
"impacts": {
6+
"RELIABILITY": "MEDIUM"
7+
},
8+
"attribute": "LOGICAL"
9+
},
10+
"status": "ready",
11+
"remediation": {
12+
"func": "Constant\/Issue",
13+
"constantCost": "5min"
14+
},
15+
"tags": [],
16+
"defaultSeverity": "Major",
17+
"ruleSpecification": "RSPEC-9132",
18+
"sqKey": "S9132",
19+
"scope": "All",
20+
"quickfix": "unknown"
21+
}

0 commit comments

Comments
 (0)