Skip to content

Commit df8dbdf

Browse files
jeanouiijbonofre
authored andcommitted
AMQ9844 Refactor ReloadableProperties and PropertiesLoginModuleRaceConditionTest to improve error handling and concurrency management (#1636)
1 parent bc30324 commit df8dbdf

2 files changed

Lines changed: 39 additions & 33 deletions

File tree

activemq-jaas/src/main/java/org/apache/activemq/jaas/ReloadableProperties.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,25 @@ public synchronized Properties getProps() {
4949

5050
public synchronized ReloadableProperties obtained() {
5151
if (reloadTime < 0 || (key.isReload() && hasModificationAfter(reloadTime))) {
52-
props = new Properties();
52+
// Load into a local variable first to preserve old data if reload fails
53+
// If we assigned 'props = new Properties()' first and load() throws IOException,
54+
// we'd expose empty Properties. By loading into newProps first, we preserve the
55+
// old data if the reload fails.
56+
final Properties newProps = new Properties();
5357
try {
54-
load(key.file(), props);
58+
load(key.file(), newProps);
59+
// Only assign to the instance field after successful load
60+
props = newProps;
5561
invertedProps = null;
5662
invertedValueProps = null;
5763
regexpProps = null;
5864
if (key.isDebug()) {
59-
LOG.debug("Load of: " + key);
65+
LOG.debug("Load of: {}", key);
6066
}
6167
} catch (IOException e) {
62-
LOG.error("Failed to load: " + key + ", reason:" + e.getLocalizedMessage());
68+
LOG.error("Failed to load: {}, reason:{}", key, e.getLocalizedMessage());
6369
if (key.isDebug()) {
64-
LOG.debug("Load of: " + key + ", failure exception" + e);
70+
LOG.debug("Load of: {}, failure exception{}", key, e);
6571
}
6672
}
6773
reloadTime = System.currentTimeMillis();
@@ -119,14 +125,15 @@ public synchronized Map<String, Pattern> regexpPropertiesMap() {
119125
}
120126

121127
private void load(final File source, Properties props) throws IOException {
122-
FileInputStream in = new FileInputStream(source);
128+
final FileInputStream in = new FileInputStream(source);
123129
try {
124130
props.load(in);
125131
if (key.isDecrypt()) {
126132
try {
127-
EncryptionSupport.decrypt(this.props, key.getAlgorithm());
133+
// Decrypt the parameter props, not this.props (which may be the old instance)
134+
EncryptionSupport.decrypt(props, key.getAlgorithm());
128135
} catch (NoClassDefFoundError e) {
129-
// this Happens whe jasypt is not on the classpath..
136+
// this Happens when jasypt is not on the classpath..
130137
key.setDecrypt(false);
131138
LOG.info("jasypt is not on the classpath: password decryption disabled.");
132139
}

activemq-jaas/src/test/java/org/apache/activemq/jaas/PropertiesLoginModuleRaceConditionTest.java

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public class PropertiesLoginModuleRaceConditionTest {
4949
private static final String USERS_FILE = "users.properties";
5050
private static final String USERNAME = "first";
5151
private static final String PASSWORD = "secret";
52+
private static final int TOTAL_LOGIN_ATTEMPTS = 25000;
5253

5354
@Rule
5455
public final ErrorCollector e = new ErrorCollector();
@@ -112,7 +113,8 @@ public void before() throws FileNotFoundException, IOException {
112113
options.put("org.apache.activemq.jaas.properties.group", GROUPS_FILE);
113114
options.put("baseDir", temp.getRoot().getAbsolutePath());
114115

115-
errors = new ArrayBlockingQueue<Exception>(processorCount());
116+
// Large enough to hold all potential errors from concurrent attempts
117+
errors = new ArrayBlockingQueue<Exception>(TOTAL_LOGIN_ATTEMPTS);
116118
pool = Executors.newFixedThreadPool(processorCount());
117119
callback = new JassCredentialCallbackHandler(USERNAME, PASSWORD);
118120
}
@@ -128,24 +130,27 @@ public void after() throws InterruptedException {
128130
public void raceConditionInUsersAndGroupsLoading() throws InterruptedException, FileNotFoundException, IOException {
129131

130132
// Brute force approach to increase the likelihood of the race condition occurring
131-
for (int i = 0; i < 25000; i++) {
132-
final CountDownLatch start = new CountDownLatch(1);
133-
final CountDownLatch finished = new CountDownLatch(processorCount());
134-
prepareLoginThreads(start, finished);
135-
136-
// Releases every login thread simultaneously to increase our chances of
137-
// encountering the race condition
138-
start.countDown();
139-
140-
finished.await();
141-
if (isRaceConditionDetected()) {
142-
e.addError(new AssertionError("At least one race condition in PropertiesLoginModule "
143-
+ "has been encountered. Please examine the "
144-
+ "following stack traces for more details:"));
145-
for (Exception exception : errors) {
146-
e.addError(exception);
147-
}
148-
return;
133+
// Submit many concurrent login attempts and let the thread pool handle concurrency
134+
final CountDownLatch start = new CountDownLatch(1);
135+
final CountDownLatch finished = new CountDownLatch(TOTAL_LOGIN_ATTEMPTS);
136+
137+
// Submit all login tasks to the pool
138+
for (int i = 0; i < TOTAL_LOGIN_ATTEMPTS; i++) {
139+
pool.submit(new LoginTester(start, finished, errors, options, callback));
140+
}
141+
142+
// Release all threads simultaneously to maximize concurrent load
143+
start.countDown();
144+
145+
// Wait for all attempts to complete
146+
finished.await();
147+
148+
if (isRaceConditionDetected()) {
149+
e.addError(new AssertionError("At least one race condition in PropertiesLoginModule "
150+
+ "has been encountered. Please examine the "
151+
+ "following stack traces for more details:"));
152+
for (Exception exception : errors) {
153+
e.addError(exception);
149154
}
150155
}
151156
}
@@ -154,12 +159,6 @@ private boolean isRaceConditionDetected() {
154159
return errors.size() > 0;
155160
}
156161

157-
private void prepareLoginThreads(final CountDownLatch start, final CountDownLatch finished) {
158-
for (int processor = 1; processor <= processorCount() * 2; processor++) {
159-
pool.submit(new LoginTester(start, finished, errors, options, callback));
160-
}
161-
}
162-
163162
private int processorCount() {
164163
return Runtime.getRuntime().availableProcessors();
165164
}

0 commit comments

Comments
 (0)