From ed1cefa1443f90ea0a5b713fec8e9f38b8da31ee Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Thu, 6 Jun 2019 14:31:18 +0100 Subject: [PATCH] Use concurrent map for pending contact states. --- .../bramble/contact/ContactManagerImpl.java | 50 +++++++++-------- .../contact/ContactManagerImplTest.java | 56 +++++++++++-------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java index 17f8b49dbe..09047255f6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java @@ -33,13 +33,12 @@ import org.briarproject.bramble.api.transport.KeyManager; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; @@ -64,10 +63,8 @@ class ContactManagerImpl implements ContactManager, EventListener { private final EventBus eventBus; private final List<ContactHook> hooks = new CopyOnWriteArrayList<>(); - private final Object statesLock = new Object(); - @GuardedBy("statesLock") - private final Map<PendingContactId, PendingContactState> states = - new HashMap<>(); + private final ConcurrentMap<PendingContactId, PendingContactState> states = + new ConcurrentHashMap<>(); @Inject ContactManagerImpl(DatabaseComponent db, @@ -176,10 +173,7 @@ class ContactManagerImpl implements ContactManager, EventListener { List<Pair<PendingContact, PendingContactState>> pairs = new ArrayList<>(pendingContacts.size()); for (PendingContact p : pendingContacts) { - PendingContactState state; - synchronized (statesLock) { - state = states.get(p.getId()); - } + PendingContactState state = states.get(p.getId()); if (state == null) state = WAITING_FOR_CONNECTION; pairs.add(new Pair<>(p, state)); } @@ -189,9 +183,7 @@ class ContactManagerImpl implements ContactManager, EventListener { @Override public void removePendingContact(PendingContactId p) throws DbException { db.transaction(false, txn -> db.removePendingContact(txn, p)); - synchronized (statesLock) { - states.remove(p); - } + states.remove(p); } @Override @@ -296,29 +288,39 @@ class ContactManagerImpl implements ContactManager, EventListener { if (e instanceof RendezvousConnectionOpenedEvent) { RendezvousConnectionOpenedEvent r = (RendezvousConnectionOpenedEvent) e; - setState(r.getPendingContactId(), ADDING_CONTACT); + PendingContactId p = r.getPendingContactId(); + setState(p, WAITING_FOR_CONNECTION, ADDING_CONTACT); } else if (e instanceof RendezvousConnectionClosedEvent) { RendezvousConnectionClosedEvent r = (RendezvousConnectionClosedEvent) e; // We're only interested in failures - if the rendezvous succeeds // the pending contact will be removed - if (!r.isSuccess()) - setState(r.getPendingContactId(), WAITING_FOR_CONNECTION); + if (!r.isSuccess()) { + PendingContactId p = r.getPendingContactId(); + setState(p, ADDING_CONTACT, WAITING_FOR_CONNECTION); + } } else if (e instanceof RendezvousFailedEvent) { RendezvousFailedEvent r = (RendezvousFailedEvent) e; setState(r.getPendingContactId(), FAILED); } } - /* - * Sets the state of the given pending contact and broadcasts an event, - * unless the current state is FAILED. + /** + * Sets the state of the given pending contact and broadcasts an event. */ private void setState(PendingContactId p, PendingContactState state) { - synchronized (statesLock) { - if (states.get(p) == FAILED) return; - states.put(p, state); + states.put(p, state); + eventBus.broadcast(new PendingContactStateChangedEvent(p, state)); + } + + /* + * Sets the state of the given pending contact and broadcasts an event if + * there is no current state or the current state equals {@code expected}. + */ + private void setState(PendingContactId p, PendingContactState expected, + PendingContactState state) { + PendingContactState old = states.putIfAbsent(p, state); + if (old == null || states.replace(p, expected, state)) eventBus.broadcast(new PendingContactStateChangedEvent(p, state)); - } } } diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java index 48928cefb0..b569d71568 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java @@ -329,41 +329,53 @@ public class ContactManagerImplTest extends BrambleMockTestCase { } @Test - public void testFailedStateIsNotReplaced() throws Exception { - Transaction txn = new Transaction(null, true); - + public void testPendingContactFailsBeforeConnection() { + // The pending contact expires - the FAILED state is broadcast context.checking(new Expectations() {{ - oneOf(eventBus).broadcast(with(new PredicateMatcher<>( - PendingContactStateChangedEvent.class, e -> - e.getPendingContactState() == ADDING_CONTACT))); oneOf(eventBus).broadcast(with(new PredicateMatcher<>( PendingContactStateChangedEvent.class, e -> e.getPendingContactState() == FAILED))); }}); + contactManager.eventOccurred(new RendezvousFailedEvent( + pendingContact.getId())); - // A rendezvous connection is opened, then the pending contact expires, - // then the rendezvous connection is closed + // A rendezvous connection is opened - no state is broadcast contactManager.eventOccurred(new RendezvousConnectionOpenedEvent( pendingContact.getId())); - contactManager.eventOccurred(new RendezvousFailedEvent( - pendingContact.getId())); + context.assertIsSatisfied(); + + // The rendezvous connection fails - no state is broadcast contactManager.eventOccurred(new RendezvousConnectionClosedEvent( pendingContact.getId(), false)); context.assertIsSatisfied(); + } - context.checking(new DbExpectations() {{ - oneOf(db).transactionWithResult(with(true), withDbCallable(txn)); - oneOf(db).getPendingContacts(txn); - will(returnValue(singletonList(pendingContact))); + @Test + public void testPendingContactFailsDuringConnection() { + // A rendezvous connection is opened - the ADDING_CONTACT state is + // broadcast + context.checking(new Expectations() {{ + oneOf(eventBus).broadcast(with(new PredicateMatcher<>( + PendingContactStateChangedEvent.class, e -> + e.getPendingContactState() == ADDING_CONTACT))); }}); - // The FAILED state should not have been overwritten - Collection<Pair<PendingContact, PendingContactState>> pairs = - contactManager.getPendingContacts(); - assertEquals(1, pairs.size()); - Pair<PendingContact, PendingContactState> pair = - pairs.iterator().next(); - assertEquals(pendingContact, pair.getFirst()); - assertEquals(FAILED, pair.getSecond()); + contactManager.eventOccurred(new RendezvousConnectionOpenedEvent( + pendingContact.getId())); + context.assertIsSatisfied(); + + // The pending contact expires - the FAILED state is broadcast + context.checking(new Expectations() {{ + oneOf(eventBus).broadcast(with(new PredicateMatcher<>( + PendingContactStateChangedEvent.class, e -> + e.getPendingContactState() == FAILED))); + }}); + contactManager.eventOccurred(new RendezvousFailedEvent( + pendingContact.getId())); + + // The rendezvous connection fails - no state is broadcast + contactManager.eventOccurred(new RendezvousConnectionClosedEvent( + pendingContact.getId(), false)); + context.assertIsSatisfied(); } } -- GitLab