Skip to content

Commit 9502034

Browse files
committed
Merge pull request #24 from sflyphotobooks/make_categories_concurrent_modification_safe
Switch Lists used to store categories to a concurrent-safe implementatio...
2 parents ce26d7b + 819c49a commit 9502034

File tree

2 files changed

+108
-10
lines changed

2 files changed

+108
-10
lines changed

src/main/java/hudson/plugins/throttleconcurrents/ThrottleJobProperty.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.WeakHashMap;
26+
import java.util.concurrent.CopyOnWriteArrayList;
2627
import javax.annotation.CheckForNull;
2728
import javax.annotation.Nonnull;
2829
import jenkins.model.Jenkins;
@@ -61,7 +62,9 @@ public ThrottleJobProperty(Integer maxConcurrentPerNode,
6162
) {
6263
this.maxConcurrentPerNode = maxConcurrentPerNode == null ? 0 : maxConcurrentPerNode;
6364
this.maxConcurrentTotal = maxConcurrentTotal == null ? 0 : maxConcurrentTotal;
64-
this.categories = categories;
65+
this.categories = categories == null ?
66+
new CopyOnWriteArrayList<String>() :
67+
new CopyOnWriteArrayList<String>(categories);
6568
this.throttleEnabled = throttleEnabled;
6669
this.throttleOption = throttleOption;
6770
this.matrixOptions = matrixOptions;
@@ -76,7 +79,7 @@ public Object readResolve() {
7679
configVersion = 0L;
7780
}
7881
if (categories == null) {
79-
categories = new ArrayList<String>();
82+
categories = new CopyOnWriteArrayList<String>();
8083
}
8184
if (category != null) {
8285
categories.add(category);
@@ -212,7 +215,7 @@ public static final class DescriptorImpl extends JobPropertyDescriptor {
212215
= new HashMap<String,Map<ThrottleJobProperty,Void>>();
213216
/** A sync object for {@link #propertiesByCategory} */
214217
private final transient Object propertiesByCategoryLock = new Object();
215-
218+
216219
public DescriptorImpl() {
217220
super(ThrottleJobProperty.class);
218221
synchronized(propertiesByCategoryLock) {
@@ -227,7 +230,7 @@ public DescriptorImpl() {
227230
}
228231
}
229232
}
230-
233+
231234
@Override
232235
public String getDisplayName() {
233236
return "Throttle Concurrent Builds";
@@ -290,12 +293,12 @@ public ThrottleCategory getCategoryByName(String categoryName) {
290293
}
291294

292295
public void setCategories(List<ThrottleCategory> categories) {
293-
this.categories = categories;
296+
this.categories = new CopyOnWriteArrayList<ThrottleCategory>(categories);
294297
}
295298

296299
public List<ThrottleCategory> getCategories() {
297300
if (categories == null) {
298-
categories = new ArrayList<ThrottleCategory>();
301+
categories = new CopyOnWriteArrayList<ThrottleCategory>();
299302
}
300303

301304
return categories;

src/test/java/hudson/plugins/throttleconcurrents/ThrottleJobPropertyTest.java

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import hudson.model.Job;
66
import hudson.security.ACL;
77
import hudson.security.AuthorizationStrategy;
8-
import java.util.Arrays;
9-
import java.util.Collection;
10-
import java.util.Collections;
11-
import java.util.HashSet;
128
import org.jvnet.hudson.test.Bug;
139
import org.jvnet.hudson.test.HudsonTestCase;
1410

11+
import java.util.*;
12+
import java.util.concurrent.CopyOnWriteArrayList;
13+
1514
public class ThrottleJobPropertyTest extends HudsonTestCase {
1615

1716
private static final String THROTTLE_OPTION_CATEGORY = "category"; // TODO move this into ThrottleJobProperty and use consistently; same for "project"
17+
private final Random random = new Random(System.currentTimeMillis());
1818

1919
@Bug(19623)
2020
public void testGetCategoryProjects() throws Exception {
@@ -40,6 +40,88 @@ public void testGetCategoryProjects() throws Exception {
4040
p3.removeProperty(ThrottleJobProperty.class);
4141
assertProjects(beta, p3b);
4242
}
43+
44+
45+
46+
public void testToString_withNulls(){
47+
ThrottleJobProperty tjp = new ThrottleJobProperty(0,0, null, false, null, ThrottleMatrixProjectOptions.DEFAULT);
48+
assertNotNull(tjp.toString());
49+
}
50+
51+
public void testThrottleJob_constructor_should_store_arguments() {
52+
Integer expectedMaxConcurrentPerNode = anyInt();
53+
Integer expectedMaxConcurrentTotal = anyInt();
54+
List<String> expectedCategories = Collections.emptyList();
55+
boolean expectedThrottleEnabled = anyBoolean();
56+
String expectedThrottleOption = anyString();
57+
58+
ThrottleJobProperty property = new ThrottleJobProperty(expectedMaxConcurrentPerNode,
59+
expectedMaxConcurrentTotal,
60+
expectedCategories, expectedThrottleEnabled, expectedThrottleOption,
61+
ThrottleMatrixProjectOptions.DEFAULT);
62+
63+
assertEquals(expectedMaxConcurrentPerNode, property.getMaxConcurrentPerNode());
64+
assertEquals(expectedMaxConcurrentTotal, property.getMaxConcurrentTotal());
65+
assertEquals(expectedCategories, property.getCategories());
66+
assertEquals(expectedThrottleEnabled, property.getThrottleEnabled());
67+
assertEquals(expectedThrottleOption, property.getThrottleOption());
68+
}
69+
70+
public void testThrottleJob_should_copy_categories_to_concurrency_safe_list() {
71+
final String category = anyString();
72+
73+
ArrayList<String> unsafeList = new ArrayList<String>() {{
74+
add(category);
75+
}};
76+
77+
ThrottleJobProperty property = new ThrottleJobProperty(anyInt(),
78+
anyInt(),
79+
unsafeList,
80+
anyBoolean(),
81+
"throttle_option",
82+
ThrottleMatrixProjectOptions.DEFAULT);
83+
84+
List<String> storedCategories = property.getCategories();
85+
assertEquals("contents of original and stored list should be the equal", unsafeList, storedCategories);
86+
assertTrue("expected unsafe list to be converted to a converted to some other concurrency-safe impl",
87+
unsafeList != storedCategories);
88+
assertTrue(storedCategories instanceof CopyOnWriteArrayList);
89+
}
90+
91+
public void testThrottleJob_constructor_handles_null_categories(){
92+
ThrottleJobProperty property = new ThrottleJobProperty(anyInt(),
93+
anyInt(),
94+
null,
95+
anyBoolean(),
96+
"throttle_option",
97+
ThrottleMatrixProjectOptions.DEFAULT);
98+
99+
assertEquals(Collections.<String>emptyList(), property.getCategories());
100+
}
101+
102+
public void testDescriptorImpl_should_a_concurrency_safe_list_for_categories(){
103+
ThrottleJobProperty.DescriptorImpl descriptor = new ThrottleJobProperty.DescriptorImpl();
104+
105+
assertTrue(descriptor.getCategories() instanceof CopyOnWriteArrayList);
106+
107+
final ThrottleJobProperty.ThrottleCategory category = new ThrottleJobProperty.ThrottleCategory(
108+
anyString(), anyInt(), anyInt(), null);
109+
110+
ArrayList<ThrottleJobProperty.ThrottleCategory> unsafeList =
111+
new ArrayList<ThrottleJobProperty.ThrottleCategory>() {{
112+
add(category);
113+
}};
114+
115+
116+
descriptor.setCategories(unsafeList);
117+
List<ThrottleJobProperty.ThrottleCategory> storedCategories = descriptor.getCategories();
118+
assertEquals("contents of original and stored list should be the equal", unsafeList, storedCategories);
119+
assertTrue("expected unsafe list to be converted to a converted to some other concurrency-safe impl",
120+
unsafeList != storedCategories);
121+
assertTrue(storedCategories instanceof CopyOnWriteArrayList);
122+
}
123+
124+
43125
private void assertProjects(String category, AbstractProject<?,?>... projects) {
44126
jenkins.setAuthorizationStrategy(new RejectAllAuthorizationStrategy());
45127
try {
@@ -61,4 +143,17 @@ private static class RejectAllAuthorizationStrategy extends AuthorizationStrateg
61143
return super.getACL(project);
62144
}
63145
}
146+
147+
private String anyString() {
148+
return "concurrency_" + anyInt();
149+
}
150+
151+
private boolean anyBoolean() {
152+
return random.nextBoolean();
153+
}
154+
155+
private int anyInt() {
156+
return random.nextInt(10000);
157+
}
158+
64159
}

0 commit comments

Comments
 (0)