Repository: google/conscrypt
Issue: Possible java.util.ConcurrentModificationException when notifying handshake listeners

The listeners should be iterated that allows modification of the underlying list (such as using `toArray`). The listener implementation might remove the listener using `removeHandshakeCompletedListener` when notified.

```
	Caused by: java.util.ConcurrentModificationException
		at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
		at java.base/java.util.ArrayList$Itr.next(Unknown Source)
		at org.conscrypt.AbstractConscryptSocket.notifyHandshakeCompletedListeners(AbstractConscryptSocket.java:594)
		at org.conscrypt.ConscryptEngineSocket.onHandshakeFinished(ConscryptEngineSocket.java:515)
		at org.conscrypt.ConscryptEngineSocket.access$000(ConscryptEngineSocket.java:50)
		at org.conscrypt.ConscryptEngineSocket$1.onHandshakeFinished(ConscryptEngineSocket.java:120)
		at org.conscrypt.ConscryptEngine.finishHandshake(ConscryptEngine.java:1007)
		at org.conscrypt.ConscryptEngine.handshake(ConscryptEngine.java:996)
		... 58 more
```


Changed files:
common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java

diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
index 7d67285b..1fe7a238 100644
--- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
+++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
@@ -588,10 +588,11 @@ abstract class AbstractConscryptSocket extends SSLSocket {
     abstract void setApplicationProtocolSelector(ApplicationProtocolSelectorAdapter selector);
 
     final void notifyHandshakeCompletedListeners() {
-        if (listeners != null && !listeners.isEmpty()) {
+        List<HandshakeCompletedListener> listenersCopy = new ArrayList<>(listeners);
+        if (!listenersCopy.isEmpty()) {
             // notify the listeners
             HandshakeCompletedEvent event = new HandshakeCompletedEvent(this, getActiveSession());
-            for (HandshakeCompletedListener listener : listeners) {
+            for (HandshakeCompletedListener listener : listenersCopy) {
                 try {
                     listener.handshakeCompleted(event);
                 } catch (RuntimeException e) {
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
index bfbd3a17..8f6c14b6 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
@@ -417,6 +417,7 @@ public class SSLSocketVersionCompatibilityTest {
                         handshakeCompletedListenerCalled.notify();
                     }
                     handshakeCompletedListenerCalled[0] = true;
+                    socket.removeHandshakeCompletedListener(this);
                 } catch (RuntimeException e) {
                     throw e;
                 } catch (Exception e) {