2626import java .io .IOException ;
2727import java .nio .file .Path ;
2828import java .util .*;
29+ import java .util .concurrent .CountDownLatch ;
2930import java .util .concurrent .ExecutorService ;
3031import java .util .concurrent .Executors ;
3132import java .util .concurrent .TimeUnit ;
5859import org .xml .sax .SAXException ;
5960import org .xmldb .api .DatabaseManager ;
6061import org .xmldb .api .base .Database ;
62+ import org .xmldb .api .base .ErrorCodes ;
6163import org .xmldb .api .base .ResourceSet ;
6264import org .xmldb .api .base .XMLDBException ;
6365import org .xmldb .api .base .Resource ;
@@ -95,12 +97,35 @@ public class DeadlockIT {
9597 /** Max attempts to find and remove an existing document before failing. */
9698 private static final int MAX_REMOVE_ATTEMPTS = 100 ;
9799
98- private static final AtomicReference <Throwable > taskFailure = new AtomicReference <>();
100+ private final AtomicReference <Throwable > taskFailure = new AtomicReference <>();
99101
100- private static void recordTaskFailure (final Throwable t ) {
102+ private void recordTaskFailure (final Throwable t ) {
101103 taskFailure .compareAndSet (null , t );
102104 }
103105
106+ private void rethrowTaskFailure () {
107+ final Throwable failure = taskFailure .get ();
108+ if (failure != null ) {
109+ if (failure instanceof RuntimeException re ) {
110+ throw re ;
111+ }
112+ if (failure instanceof Error err ) {
113+ throw err ;
114+ }
115+ throw new AssertionError (failure .getMessage (), failure );
116+ }
117+ }
118+
119+ /** Matches {@link StoreTask} global document numbering. */
120+ private static String documentName (final int collectionId , final int indexInCollection ) {
121+ return "test" + (collectionId * DOC_COUNT + indexInCollection ) + ".xml" ;
122+ }
123+
124+ private static boolean isConcurrentRemoveRace (final XMLDBException e ) {
125+ return e .errorCode == ErrorCodes .INVALID_RESOURCE
126+ || e .errorCode == ErrorCodes .NO_SUCH_RESOURCE ;
127+ }
128+
104129 /** Use 4 test runs, querying different collections */
105130 @ Parameters (name = "{0}" )
106131 public static java .util .Collection <Object []> data () {
@@ -197,7 +222,8 @@ public void clearDB() throws XMLDBException {
197222 public void runTasks () {
198223 taskFailure .set (null );
199224 final ExecutorService executor = Executors .newFixedThreadPool (N_THREADS );
200- executor .submit (new StoreTask ("store" , COLL_COUNT , DOC_COUNT ));
225+ final CountDownLatch storeComplete = new CountDownLatch (1 );
226+ executor .submit (new StoreTask ("store" , COLL_COUNT , DOC_COUNT , storeComplete ));
201227 synchronized (this ) {
202228 try {
203229 wait (DELAY );
@@ -211,6 +237,15 @@ public void runTasks() {
211237 executor .submit (new QueryTask (COLL_COUNT ));
212238 }
213239 if (mode == TEST_REMOVE ) {
240+ try {
241+ assertTrue ("Store task did not finish before document removals started" ,
242+ storeComplete .await (AWAIT_TERMINATION_MINUTES , TimeUnit .MINUTES ));
243+ } catch (InterruptedException e ) {
244+ Thread .currentThread ().interrupt ();
245+ LOG .error (e .getMessage (), e );
246+ fail (e .getMessage ());
247+ }
248+ rethrowTaskFailure ();
214249 for (int i = 0 ; i < REMOVE_COUNT ; i ++) {
215250 executor .submit (new RemoveDocumentTask (COLL_COUNT , DOC_COUNT ));
216251 }
@@ -228,29 +263,23 @@ public void runTasks() {
228263 executor .shutdownNow ();
229264 assertTrue ("Executor did not terminate within " + AWAIT_TERMINATION_MINUTES + " minutes; possible deadlock or hang" , terminated );
230265 }
231- final Throwable failure = taskFailure .get ();
232- if (failure != null ) {
233- if (failure instanceof RuntimeException re ) {
234- throw re ;
235- }
236- if (failure instanceof Error err ) {
237- throw err ;
238- }
239- throw new AssertionError (failure .getMessage (), failure );
240- }
266+ rethrowTaskFailure ();
241267 }
242268
243- private static class StoreTask implements Runnable {
269+ private class StoreTask implements Runnable {
244270
245271 @ SuppressWarnings ("unused" )
246272 private final String id ;
247273 private final int docCount ;
248274 private final int collectionCount ;
275+ private final CountDownLatch storeComplete ;
249276
250- public StoreTask (final String id , final int collectionCount , final int docCount ) {
277+ public StoreTask (final String id , final int collectionCount , final int docCount ,
278+ final CountDownLatch storeComplete ) {
251279 this .id = id ;
252280 this .collectionCount = collectionCount ;
253281 this .docCount = docCount ;
282+ this .storeComplete = storeComplete ;
254283 }
255284
256285 @ Override
@@ -261,7 +290,6 @@ public void run() {
261290
262291 final TestDataGenerator generator = new TestDataGenerator ("xdb" , docCount );
263292 Collection coll ;
264- int fileCount = 0 ;
265293 for (int i = 0 ; i < collectionCount ; i ++) {
266294 try (final Txn transaction = transact .beginTransaction ()) {
267295 coll = broker .getOrCreateCollection (transaction ,
@@ -273,12 +301,12 @@ public void run() {
273301 }
274302
275303 final Path [] files = generator .generate (broker , coll , generateXQ );
276- for (int j = 0 ; j < files .length ; j ++, fileCount ++ ) {
304+ for (int j = 0 ; j < files .length ; j ++) {
277305 try (final Txn transaction = transact .beginTransaction ()) {
278306 final InputSource is = new InputSource (files [j ].toUri ()
279307 .toASCIIString ());
280308
281- broker .storeDocument (transaction , XmldbURI .create ("test" + fileCount + ".xml" ), is , MimeType .XML_TYPE , coll );
309+ broker .storeDocument (transaction , XmldbURI .create (documentName ( i , j ) ), is , MimeType .XML_TYPE , coll );
282310 transact .commit (transaction );
283311 }
284312 }
@@ -287,7 +315,9 @@ public void run() {
287315 } catch (Exception e ) {
288316 LOG .error (e .getMessage (), e );
289317 recordTaskFailure (e );
290- }
318+ } finally {
319+ storeComplete .countDown ();
320+ }
291321 }
292322 }
293323
@@ -372,8 +402,7 @@ public void run() {
372402 for (int attempt = 0 ; !removed && attempt < MAX_REMOVE_ATTEMPTS ; attempt ++) {
373403 final int collectionId = random .nextInt (collectionCount );
374404 final String collection = "/db/test/" + collectionId ;
375- final int docId = collectionId * documentCount + random .nextInt (documentCount );
376- final String document = "test" + docId + ".xml" ;
405+ final String document = documentName (collectionId , random .nextInt (documentCount ));
377406 try {
378407 final org .xmldb .api .base .Collection testCollection = DatabaseManager .getCollection ("xmldb:exist://" + collection , "admin" , "" );
379408 final Resource resource = testCollection .getResource (document );
@@ -382,6 +411,9 @@ public void run() {
382411 removed = true ;
383412 }
384413 } catch (final XMLDBException e ) {
414+ if (isConcurrentRemoveRace (e )) {
415+ continue ;
416+ }
385417 LOG .error (e .getMessage (), e );
386418 recordTaskFailure (e );
387419 return ;
0 commit comments