diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/concurrency/PointcutLatch.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/concurrency/PointcutLatch.java index e4369458a50..bbed0ee0fbd 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/concurrency/PointcutLatch.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/concurrency/PointcutLatch.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -70,8 +71,8 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { } private void createLatch(int count) { - myFailures.set(new ArrayList<>()); - myCalledWith.set(new ArrayList<>()); + myFailures.set(Collections.synchronizedList(new ArrayList<>())); + myCalledWith.set(Collections.synchronizedList(new ArrayList<>())); myCountdownLatch.set(new CountDownLatch(count)); myInitialCount = count; } @@ -102,16 +103,17 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { throw new AssertionError(getName() + " timed out waiting " + timeoutSecond + " seconds for latch to countdown from " + myInitialCount + " to 0. Is " + latch.getCount() + "."); } - List failures = myFailures.get(); + // Defend against ConcurrentModificationException String error = getName(); - if (failures != null && failures.size() > 0) { + if (myFailures.get() != null && myFailures.get().size() > 0) { + List failures = new ArrayList<>(myFailures.get()); if (failures.size() > 1) { error += " ERRORS: \n"; } else { error += " ERROR: "; } - error += failures.stream().collect(Collectors.joining("\n")); - error += "\nLatch called with values: " + myCalledWithString(); + error += String.join("\n", failures); + error += "\nLatch called with values: " + toCalledWithString(); throw new AssertionError(error); } } finally { @@ -126,11 +128,12 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { myCountdownLatch.set(null); } - private String myCalledWithString() { + private String toCalledWithString() { if (myCalledWith.get() == null) { return "[]"; } - List calledWith = myCalledWith.get(); + // Defend against ConcurrentModificationException + List calledWith = new ArrayList<>(myCalledWith.get()); if (calledWith.isEmpty()) { return "[]"; } @@ -162,11 +165,13 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch { } private class PointcutLatchException extends IllegalStateException { - public PointcutLatchException(String message, HookParams theArgs) { + private static final long serialVersionUID = 1372636272233536829L; + + PointcutLatchException(String message, HookParams theArgs) { super(getName() + ": " + message + " called with values: " + hookParamsToString(theArgs)); } - public PointcutLatchException(String message) { + PointcutLatchException(String message) { super(getName() + ": " + message); } }