Skip to content

Commit 1654890

Browse files
committed
Adds test to produce deadlock
Signed-off-by: Patrick Reinhart <[email protected]>
1 parent f3424d7 commit 1654890

File tree

1 file changed

+76
-17
lines changed

1 file changed

+76
-17
lines changed

exist-core/src/test/java/org/exist/storage/BrokerPoolTest.java

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
import org.junit.Test;
3131
import org.xmldb.api.base.XMLDBException;
3232

33+
import java.util.List;
34+
import java.util.ArrayList;
3335
import java.util.Optional;
3436
import java.util.concurrent.*;
37+
import java.util.function.Consumer;
3538

3639
import static junit.framework.TestCase.assertTrue;
37-
import static org.junit.Assert.assertEquals;
38-
import static org.junit.Assert.fail;
40+
import static org.junit.Assert.*;
3941

4042
/**
4143
* @author <a href="mailto:[email protected]">Adam Retter</a>
@@ -135,20 +137,17 @@ public void canReleaseWhenSaturated() throws InterruptedException, ExecutionExce
135137
// test requires at least 2 leasedBrokers to prove the issue
136138
assertTrue(maxBrokers > 1);
137139

140+
final ExecutorService executor = Executors.newFixedThreadPool(maxBrokers + 1);
141+
final List<Future<Void>> tasks = new ArrayList<>(maxBrokers);
138142
final CountDownLatch firstBrokerReleaseLatch = new CountDownLatch(1);
139143
final CountDownLatch releaseLatch = new CountDownLatch(1);
140144
try {
141145

142146
// lease all brokers
143-
final Thread brokerUsers[] = new Thread[maxBrokers];
144147
final CountDownLatch acquiredLatch = new CountDownLatch(maxBrokers);
145-
146-
final Thread firstBrokerUser = new Thread(new BrokerUser(pool, acquiredLatch, firstBrokerReleaseLatch), "first-brokerUser");
147-
brokerUsers[0] = firstBrokerUser;
148-
brokerUsers[0].start();
149-
for (int i = 1; i < maxBrokers; i++) {
150-
brokerUsers[i] = new Thread(new BrokerUser(pool, acquiredLatch, releaseLatch));
151-
brokerUsers[i].start();
148+
Future<Void> firstBrokerUser = executor.submit(new BrokerUser(pool, acquiredLatch, firstBrokerReleaseLatch));
149+
for (int count = 1; count < maxBrokers; count++) {
150+
tasks.add(executor.submit(new BrokerUser(pool, acquiredLatch, releaseLatch)));
152151
}
153152

154153
// wait for all brokers to be acquired
@@ -160,9 +159,8 @@ public void canReleaseWhenSaturated() throws InterruptedException, ExecutionExce
160159

161160
// create a new thread and attempt to get an additional broker
162161
final CountDownLatch additionalBrokerAcquiredLatch = new CountDownLatch(1);
163-
final Thread additionalBrokerUser = new Thread(new BrokerUser(pool, additionalBrokerAcquiredLatch, releaseLatch), "additional-brokerUser");
164162
assertEquals(1, additionalBrokerAcquiredLatch.getCount());
165-
additionalBrokerUser.start();
163+
executor.submit(new BrokerUser(pool, additionalBrokerAcquiredLatch, releaseLatch));
166164

167165
// we should not be able to acquire an additional broker, as we have already leased max
168166
Thread.sleep(500); // just to ensure the other thread has done something
@@ -172,23 +170,85 @@ public void canReleaseWhenSaturated() throws InterruptedException, ExecutionExce
172170
assertEquals(1, firstBrokerReleaseLatch.getCount());
173171
firstBrokerReleaseLatch.countDown();
174172
assertEquals(0, firstBrokerReleaseLatch.getCount());
175-
firstBrokerUser.join(); // wait for the first broker lease thread to complete
173+
firstBrokerUser.get(); // wait for the first broker lease thread to complete
176174

177175
// check that the additional broker lease has now been acquired
178176
Thread.sleep(500); // just to ensure the other thread has done something
179177
assertEquals(0, additionalBrokerAcquiredLatch.getCount());
180178

179+
executor.shutdown();
181180
} finally {
182181
// release all brokers from brokerUsers
183182
if(firstBrokerReleaseLatch.getCount() == 1) {
184183
firstBrokerReleaseLatch.countDown();
185184
}
186185
releaseLatch.countDown();
186+
assertTrue(executor.awaitTermination(1, TimeUnit.SECONDS));
187+
for (Future<Void> task : tasks) {
188+
task.get();
189+
}
190+
for (Runnable task: executor.shutdownNow()) {
191+
assertNotNull(task);
192+
}
193+
}
194+
}
195+
196+
@Test
197+
public void concurrentShutdownAndUse() throws InterruptedException, ExecutionException {
198+
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
199+
final int maxBrokers = pool.getMax();
200+
201+
// test requires at least 5 leasedBrokers to prove the issue
202+
assertTrue(maxBrokers > 4);
203+
204+
final CountDownLatch readyLatch = new CountDownLatch(1);
205+
final CountDownLatch executeLatch = new CountDownLatch(1);
206+
final ExecutorService executor = Executors.newFixedThreadPool(maxBrokers);
207+
final List<Future<Void>> tasks = new ArrayList<>(maxBrokers);
208+
final Consumer<BrokerPool> brokerAquire = brokerPool -> {
209+
try (final DBBroker broker = brokerPool.getBroker()) {
210+
} catch (EXistException e) {
211+
throw new RuntimeException(e);
212+
}
213+
};
214+
for (int count=0; count < maxBrokers; count++) {
215+
tasks.add(executor.submit(new PoolAction(pool, readyLatch, executeLatch, count == 0 ? BrokerPool::shutdown: brokerAquire)));
216+
}
217+
218+
TimeUnit.SECONDS.sleep(1);
219+
readyLatch.countDown();
220+
221+
assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
222+
for (Future<Void> task : tasks) {
223+
task.get();
224+
}
225+
for (Runnable task: executor.shutdownNow()) {
226+
assertNotNull(task);
227+
}
228+
}
229+
230+
static class PoolAction implements Callable<Void> {
231+
private final BrokerPool brokerPool;
232+
private final CountDownLatch readyLatch;
233+
private final CountDownLatch excuteLatch;
234+
private final Consumer<BrokerPool> action;
235+
236+
PoolAction(final BrokerPool brokerPool, CountDownLatch readyLatch, CountDownLatch excuteLatch, Consumer<BrokerPool> action) {
237+
this.brokerPool = brokerPool;
238+
this.readyLatch = readyLatch;
239+
this.excuteLatch = excuteLatch;
240+
this.action = action;
241+
}
242+
@Override
243+
public Void call() throws InterruptedException {
244+
readyLatch.await();
245+
action.accept(brokerPool);
246+
return null;
187247
}
188248
}
189249

190-
public static class BrokerUser implements Runnable {
191250

251+
public static class BrokerUser implements Callable<Void> {
192252
final BrokerPool brokerPool;
193253
private final CountDownLatch acquiredLatch;
194254
private final CountDownLatch releaseLatch;
@@ -200,7 +260,7 @@ public BrokerUser(final BrokerPool brokerPool, final CountDownLatch acquiredLatc
200260
}
201261

202262
@Override
203-
public void run() {
263+
public Void call() throws EXistException, InterruptedException {
204264
try(final DBBroker broker = brokerPool.getBroker()) {
205265

206266
// signal that we have acquired the broker
@@ -210,9 +270,8 @@ public void run() {
210270
// wait for signal to release the broker
211271
releaseLatch.await();
212272

213-
} catch(final EXistException | InterruptedException e) {
214-
fail(e.getMessage());
215273
}
274+
return null;
216275
}
217276
}
218277

0 commit comments

Comments
 (0)