Add disconnect/awake handlers (WIP)

This commit is contained in:
Manfred Karrer 2016-02-20 12:04:07 +01:00
parent 3efca4101a
commit ae6b95eed8
17 changed files with 1029 additions and 736 deletions

View file

@ -1,19 +1,65 @@
package io.bitsquare.common; package io.bitsquare.common;
public interface Clock { import org.slf4j.Logger;
void start(); import org.slf4j.LoggerFactory;
void stop(); import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
void addListener(Listener listener); public class Clock {
private static final Logger log = LoggerFactory.getLogger(Clock.class);
void removeListener(Listener listener); public static final int IDLE_TOLERANCE = 5000;
interface Listener { public interface Listener {
void onSecondTick(); void onSecondTick();
void onMinuteTick(); void onMinuteTick();
void onMissedSecondTick(long missed); void onMissedSecondTick(long missed);
} }
private Timer timer;
private final List<Listener> listeners = new LinkedList<>();
private long counter = 0;
private long lastSecondTick;
public Clock() {
}
public void start() {
if (timer == null) {
lastSecondTick = System.currentTimeMillis();
timer = UserThread.runPeriodically(() -> {
listeners.stream().forEach(Listener::onSecondTick);
counter++;
if (counter >= 60) {
counter = 0;
listeners.stream().forEach(Listener::onMinuteTick);
}
long currentTimeMillis = System.currentTimeMillis();
long diff = currentTimeMillis - lastSecondTick;
if (diff > 1000)
listeners.stream().forEach(listener -> listener.onMissedSecondTick(diff - 1000));
lastSecondTick = currentTimeMillis;
}, 1, TimeUnit.SECONDS);
}
}
public void stop() {
timer.stop();
timer = null;
counter = 0;
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
} }

View file

@ -177,7 +177,7 @@ public class OpenOfferManager {
@Override @Override
public void onMissedSecondTick(long missed) { public void onMissedSecondTick(long missed) {
if (missed > 5000) { if (missed > Clock.IDLE_TOLERANCE) {
log.error("We have been idle for {} sec", missed / 1000); log.error("We have been idle for {} sec", missed / 1000);
// We have been idle for at least 5 sec. // We have been idle for at least 5 sec.

View file

@ -26,7 +26,6 @@ import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.KeyStorage; import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.crypto.EncryptionServiceModule; import io.bitsquare.crypto.EncryptionServiceModule;
import io.bitsquare.gui.GuiModule; import io.bitsquare.gui.GuiModule;
import io.bitsquare.gui.common.UIClock;
import io.bitsquare.gui.common.view.CachingViewLoader; import io.bitsquare.gui.common.view.CachingViewLoader;
import io.bitsquare.gui.main.intructions.InstructionCenter; import io.bitsquare.gui.main.intructions.InstructionCenter;
import io.bitsquare.gui.main.notifications.NotificationCenter; import io.bitsquare.gui.main.notifications.NotificationCenter;
@ -64,7 +63,7 @@ class BitsquareAppModule extends AppModule {
bind(Preferences.class).in(Singleton.class); bind(Preferences.class).in(Singleton.class);
bind(NotificationCenter.class).in(Singleton.class); bind(NotificationCenter.class).in(Singleton.class);
bind(InstructionCenter.class).in(Singleton.class); bind(InstructionCenter.class).in(Singleton.class);
bind(Clock.class).to(UIClock.class).in(Singleton.class); bind(Clock.class).in(Singleton.class);
File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY)); File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY));
bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir); bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir);

View file

@ -1,79 +0,0 @@
package io.bitsquare.gui.common;
import io.bitsquare.common.Clock;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class UIClock implements Clock {
private static final Logger log = LoggerFactory.getLogger(UIClock.class);
private Timer timer;
private final List<Listener> listeners = new LinkedList<>();
private long counter = 0;
private long lastSecondTick;
public UIClock() {
}
@Override
public void start() {
if (timer == null) {
lastSecondTick = System.currentTimeMillis();
timer = UserThread.runPeriodically(() -> {
listeners.stream().forEach(Listener::onSecondTick);
counter++;
if (counter >= 60) {
counter = 0;
listeners.stream().forEach(Listener::onMinuteTick);
}
long currentTimeMillis = System.currentTimeMillis();
long diff = currentTimeMillis - lastSecondTick;
if (diff > 1000)
listeners.stream().forEach(listener -> listener.onMissedSecondTick(diff - 1000));
lastSecondTick = currentTimeMillis;
}, 1, TimeUnit.SECONDS);
/* timer = FxTimer.runPeriodically(Duration.ofSeconds(1), () -> {
listeners.stream().forEach(Listener::onSecondTick);
counter++;
if (counter >= 60) {
counter = 0;
listeners.stream().forEach(Listener::onMinuteTick);
}
long currentTimeMillis = System.currentTimeMillis();
long diff = currentTimeMillis - lastSecondTick;
if (diff > 1000)
listeners.stream().forEach(listener -> listener.onMissedSecondTick(diff - 1000));
lastSecondTick = currentTimeMillis;
});*/
}
}
@Override
public void stop() {
timer.stop();
timer = null;
counter = 0;
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
}

View file

@ -8,6 +8,7 @@ import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.app.ProgramArguments; import io.bitsquare.app.ProgramArguments;
import io.bitsquare.common.Clock;
import io.bitsquare.common.Timer; import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.CryptoException; import io.bitsquare.common.crypto.CryptoException;
@ -56,6 +57,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
private final SeedNodesRepository seedNodesRepository; private final SeedNodesRepository seedNodesRepository;
private final int port; private final int port;
private final File torDir; private final File torDir;
private Clock clock;
private final Optional<EncryptionService> optionalEncryptionService; private final Optional<EncryptionService> optionalEncryptionService;
private final Optional<KeyRing> optionalKeyRing; private final Optional<KeyRing> optionalKeyRing;
@ -99,11 +101,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
@Named(ProgramArguments.USE_LOCALHOST) boolean useLocalhost, @Named(ProgramArguments.USE_LOCALHOST) boolean useLocalhost,
@Named(ProgramArguments.NETWORK_ID) int networkId, @Named(ProgramArguments.NETWORK_ID) int networkId,
@Named("storage.dir") File storageDir, @Named("storage.dir") File storageDir,
Clock clock,
@Nullable EncryptionService encryptionService, @Nullable EncryptionService encryptionService,
@Nullable KeyRing keyRing) { @Nullable KeyRing keyRing) {
this.seedNodesRepository = seedNodesRepository; this.seedNodesRepository = seedNodesRepository;
this.port = port; this.port = port;
this.torDir = torDir; this.torDir = torDir;
this.clock = clock;
optionalEncryptionService = encryptionService == null ? Optional.empty() : Optional.of(encryptionService); optionalEncryptionService = encryptionService == null ? Optional.empty() : Optional.of(encryptionService);
optionalKeyRing = keyRing == null ? Optional.empty() : Optional.of(keyRing); optionalKeyRing = keyRing == null ? Optional.empty() : Optional.of(keyRing);
@ -126,7 +130,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
p2PDataStorage.addHashMapChangedListener(this); p2PDataStorage.addHashMapChangedListener(this);
Set<NodeAddress> seedNodeAddresses = seedNodesRepository.getSeedNodeAddresses(useLocalhost, networkId); Set<NodeAddress> seedNodeAddresses = seedNodesRepository.getSeedNodeAddresses(useLocalhost, networkId);
peerManager = new PeerManager(networkNode, seedNodeAddresses, storageDir); peerManager = new PeerManager(networkNode, seedNodeAddresses, storageDir, clock);
requestDataManager = new RequestDataManager(networkNode, p2PDataStorage, peerManager, seedNodeAddresses, this); requestDataManager = new RequestDataManager(networkNode, p2PDataStorage, peerManager, seedNodeAddresses, this);
@ -247,11 +251,11 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
Log.traceCall(); Log.traceCall();
networkReadySubscription.unsubscribe(); networkReadySubscription.unsubscribe();
Optional<NodeAddress> seedNodeOfPreliminaryDataRequest = requestDataManager.getNodeOfPreliminaryDataRequest(); Optional<NodeAddress> seedNodeOfPreliminaryDataRequest = requestDataManager.getNodeAddressOfPreliminaryDataRequest();
checkArgument(seedNodeOfPreliminaryDataRequest.isPresent(), checkArgument(seedNodeOfPreliminaryDataRequest.isPresent(),
"seedNodeOfPreliminaryDataRequest must be present"); "seedNodeOfPreliminaryDataRequest must be present");
requestDataManager.requestUpdatesData(); requestDataManager.requestUpdateData();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -267,7 +271,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
@Override @Override
public void onUpdatedDataReceived() { public void onUpdatedDataReceived() {
Optional<NodeAddress> seedNodeOfPreliminaryDataRequest = requestDataManager.getNodeOfPreliminaryDataRequest(); Optional<NodeAddress> seedNodeOfPreliminaryDataRequest = requestDataManager.getNodeAddressOfPreliminaryDataRequest();
checkArgument(seedNodeOfPreliminaryDataRequest.isPresent(), checkArgument(seedNodeOfPreliminaryDataRequest.isPresent(),
"seedNodeOfPreliminaryDataRequest must be present"); "seedNodeOfPreliminaryDataRequest must be present");
peerExchangeManager.requestReportedPeersFromSeedNodes(seedNodeOfPreliminaryDataRequest.get()); peerExchangeManager.requestReportedPeersFromSeedNodes(seedNodeOfPreliminaryDataRequest.get());

View file

@ -1,6 +1,7 @@
package io.bitsquare.p2p.peers; package io.bitsquare.p2p.peers;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.common.Clock;
import io.bitsquare.common.Timer; import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
@ -36,6 +37,7 @@ public class PeerManager implements ConnectionListener, MessageListener {
private static int MAX_CONNECTIONS_ABSOLUTE; private static int MAX_CONNECTIONS_ABSOLUTE;
private final boolean printReportedPeersDetails = true; private final boolean printReportedPeersDetails = true;
private boolean lostAllConnections;
public static void setMaxConnections(int maxConnections) { public static void setMaxConnections(int maxConnections) {
MAX_CONNECTIONS = maxConnections; MAX_CONNECTIONS = maxConnections;
@ -54,7 +56,26 @@ public class PeerManager implements ConnectionListener, MessageListener {
private static final long MAX_AGE = TimeUnit.DAYS.toMillis(14); // max age for reported peers is 14 days private static final long MAX_AGE = TimeUnit.DAYS.toMillis(14); // max age for reported peers is 14 days
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onAllConnectionsLost();
void onNewConnectionAfterAllConnectionsLost();
void onAwakeFromStandby();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Instance fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode; private final NetworkNode networkNode;
private Clock clock;
private final Set<NodeAddress> seedNodeAddresses; private final Set<NodeAddress> seedNodeAddresses;
private final Storage<HashSet<ReportedPeer>> dbStorage; private final Storage<HashSet<ReportedPeer>> dbStorage;
@ -62,14 +83,16 @@ public class PeerManager implements ConnectionListener, MessageListener {
private final Set<ReportedPeer> reportedPeers = new HashSet<>(); private final Set<ReportedPeer> reportedPeers = new HashSet<>();
private Timer checkMaxConnectionsTimer; private Timer checkMaxConnectionsTimer;
private final ChangeListener<NodeAddress> connectionNodeAddressListener; private final ChangeListener<NodeAddress> connectionNodeAddressListener;
private final Clock.Listener listener;
private final List<Listener> listeners = new LinkedList<>();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public PeerManager(NetworkNode networkNode, Set<NodeAddress> seedNodeAddresses, File storageDir) { public PeerManager(NetworkNode networkNode, Set<NodeAddress> seedNodeAddresses, File storageDir, Clock clock) {
this.networkNode = networkNode; this.networkNode = networkNode;
this.clock = clock;
this.seedNodeAddresses = new HashSet<>(seedNodeAddresses); this.seedNodeAddresses = new HashSet<>(seedNodeAddresses);
networkNode.addConnectionListener(this); networkNode.addConnectionListener(this);
dbStorage = new Storage<>(storageDir); dbStorage = new Storage<>(storageDir);
@ -89,12 +112,33 @@ public class PeerManager implements ConnectionListener, MessageListener {
checkMaxConnections(MAX_CONNECTIONS); checkMaxConnections(MAX_CONNECTIONS);
}, 3); }, 3);
}; };
// we check if app was idle for more then 5 sec.
listener = new Clock.Listener() {
@Override
public void onSecondTick() {
}
@Override
public void onMinuteTick() {
}
@Override
public void onMissedSecondTick(long missed) {
if (missed > Clock.IDLE_TOLERANCE) {
log.error("We have been idle for {} sec", missed / 1000);
listeners.stream().forEach(Listener::onAwakeFromStandby);
}
}
};
clock.addListener(listener);
} }
public void shutDown() { public void shutDown() {
Log.traceCall(); Log.traceCall();
networkNode.removeConnectionListener(this); networkNode.removeConnectionListener(this);
clock.removeListener(listener);
stopCheckMaxConnectionsTimer(); stopCheckMaxConnectionsTimer();
} }
@ -102,6 +146,14 @@ public class PeerManager implements ConnectionListener, MessageListener {
return MAX_CONNECTIONS_ABSOLUTE; return MAX_CONNECTIONS_ABSOLUTE;
} }
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation // ConnectionListener implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -118,12 +170,21 @@ public class PeerManager implements ConnectionListener, MessageListener {
seedNodeAddresses.contains(peersNodeAddressOptional.get())) { seedNodeAddresses.contains(peersNodeAddressOptional.get())) {
connection.setPeerType(Connection.PeerType.SEED_NODE); connection.setPeerType(Connection.PeerType.SEED_NODE);
} }
if (lostAllConnections) {
lostAllConnections = false;
listeners.stream().forEach(Listener::onNewConnectionAfterAllConnectionsLost);
}
} }
@Override @Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) { public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
connection.getNodeAddressProperty().removeListener(connectionNodeAddressListener); connection.getNodeAddressProperty().removeListener(connectionNodeAddressListener);
handleConnectionFault(connection); handleConnectionFault(connection);
lostAllConnections = networkNode.getAllConnections().isEmpty();
if (lostAllConnections)
listeners.stream().forEach(Listener::onAllConnectionsLost);
} }
@Override @Override

View file

@ -0,0 +1,120 @@
package io.bitsquare.p2p.peers.getdata;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.getdata.messages.GetDataRequest;
import io.bitsquare.p2p.peers.getdata.messages.GetDataResponse;
import io.bitsquare.p2p.storage.P2PDataStorage;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
public class GetDataRequestHandler {
private static final Logger log = LoggerFactory.getLogger(GetDataRequestHandler.class);
private static final long TIME_OUT_SEC = 20;
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final PeerManager peerManager;
private P2PDataStorage dataStorage;
private final Listener listener;
private Timer timeoutTimer;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public GetDataRequestHandler(NetworkNode networkNode, PeerManager peerManager, P2PDataStorage dataStorage, Listener listener) {
this.networkNode = networkNode;
this.peerManager = peerManager;
this.dataStorage = dataStorage;
this.listener = listener;
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void handle(GetDataRequest getDataRequest, final Connection connection) {
Log.traceCall(getDataRequest + "\n\tconnection=" + connection);
GetDataResponse getDataResponse = new GetDataResponse(new HashSet<>(dataStorage.getMap().values()),
getDataRequest.getNonce());
SettableFuture<Connection> future = networkNode.sendMessage(connection, getDataResponse);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("Send DataResponse to {} succeeded. getDataResponse={}",
connection.getPeersNodeAddressOptional(), getDataResponse);
cleanup();
listener.onComplete();
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getDataRequest to " + connection +
" failed. That is expected if the peer is offline. getDataResponse=" + getDataResponse + "." +
"Exception: " + throwable.getMessage();
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_FAILURE, connection);
}
});
checkArgument(timeoutTimer == null, "requestData must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred for getDataResponse:" + getDataResponse +
" on connection:" + connection;
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
},
TIME_OUT_SEC, TimeUnit.SECONDS);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void handleFault(String errorMessage, CloseConnectionReason closeConnectionReason, Connection connection) {
log.info(errorMessage);
peerManager.shutDownConnection(connection, closeConnectionReason);
cleanup();
listener.onFault(errorMessage, connection);
}
private void cleanup() {
if (timeoutTimer != null) {
timeoutTimer.stop();
timeoutTimer = null;
}
}
}

View file

@ -0,0 +1,175 @@
package io.bitsquare.p2p.peers.getdata;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.getdata.messages.GetDataRequest;
import io.bitsquare.p2p.peers.getdata.messages.GetDataResponse;
import io.bitsquare.p2p.peers.getdata.messages.GetUpdatedDataRequest;
import io.bitsquare.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
import io.bitsquare.p2p.storage.P2PDataStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Random;
import static com.google.common.base.Preconditions.checkArgument;
public class RequestDataHandler implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(RequestDataHandler.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, @Nullable Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final P2PDataStorage dataStorage;
private final PeerManager peerManager;
private final Listener listener;
private Timer timeoutTimer;
private final long nonce = new Random().nextLong();
private boolean stopped;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public RequestDataHandler(NetworkNode networkNode, P2PDataStorage dataStorage, PeerManager peerManager,
Listener listener) {
this.networkNode = networkNode;
this.dataStorage = dataStorage;
this.peerManager = peerManager;
this.listener = listener;
networkNode.addMessageListener(this);
}
public void cleanup() {
Log.traceCall();
stopped = true;
networkNode.removeMessageListener(this);
stopTimeoutTimer();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void requestData(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress);
if (!stopped) {
GetDataRequest getDataRequest;
if (networkNode.getNodeAddress() == null)
getDataRequest = new PreliminaryGetDataRequest(nonce);
else
getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce);
log.info("We send a {} to peer {}. ", getDataRequest.getClass().getSimpleName(), nodeAddress);
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getDataRequest);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.trace("Send " + getDataRequest + " to " + nodeAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getDataRequest to " + nodeAddress +
" failed. That is expected if the peer is offline.\n\t" +
"getDataRequest=" + getDataRequest + "." +
"\n\tException=" + throwable.getMessage();
log.info(errorMessage);
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
}
});
checkArgument(timeoutTimer == null, "requestData must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getDataRequest:" + getDataRequest +
" on nodeAddress:" + nodeAddress;
log.info(errorMessage + " / RequestDataHandler=" + RequestDataHandler.this);
handleFault(errorMessage, nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
},
10);
} else {
log.warn("We have stopped already. We ignore that requestData call.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof GetDataResponse) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
if (!stopped) {
GetDataResponse getDataResponse = (GetDataResponse) message;
if (getDataResponse.requestNonce == nonce) {
stopTimeoutTimer();
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
"RequestDataHandler.onMessage: connection.getPeersNodeAddressOptional() must be present " +
"at that moment");
((GetDataResponse) message).dataSet.stream()
.forEach(protectedData -> dataStorage.add(protectedData,
connection.getPeersNodeAddressOptional().get()));
cleanup();
listener.onComplete();
} else {
log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled " +
"handshake (timeout causes connection close but peer might have sent a msg before " +
"connection was closed).\n\t" +
"We drop that message. nonce={} / requestNonce={}",
nonce, getDataResponse.requestNonce);
}
} else {
log.warn("We have stopped already. We ignore that onDataRequest call.");
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void stopTimeoutTimer() {
if (timeoutTimer != null) {
timeoutTimer.stop();
timeoutTimer = null;
}
}
private void handleFault(String errorMessage, NodeAddress nodeAddress, CloseConnectionReason closeConnectionReason) {
cleanup();
peerManager.shutDownConnection(nodeAddress, closeConnectionReason);
listener.onFault(errorMessage, null);
}
}

View file

@ -1,207 +0,0 @@
package io.bitsquare.p2p.peers.getdata;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.getdata.messages.GetDataRequest;
import io.bitsquare.p2p.peers.getdata.messages.GetDataResponse;
import io.bitsquare.p2p.peers.getdata.messages.GetUpdatedDataRequest;
import io.bitsquare.p2p.peers.getdata.messages.PreliminaryGetDataRequest;
import io.bitsquare.p2p.storage.P2PDataStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
public class RequestDataHandshake implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(RequestDataHandshake.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Listener
///////////////////////////////////////////////////////////////////////////////////////////
public interface Listener {
void onComplete();
void onFault(String errorMessage, @Nullable Connection connection);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Class fields
///////////////////////////////////////////////////////////////////////////////////////////
private final NetworkNode networkNode;
private final P2PDataStorage dataStorage;
private final PeerManager peerManager;
private final Listener listener;
private Timer timeoutTimer;
private final long nonce = new Random().nextLong();
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public RequestDataHandshake(NetworkNode networkNode, P2PDataStorage dataStorage, PeerManager peerManager,
Listener listener) {
this.networkNode = networkNode;
this.dataStorage = dataStorage;
this.peerManager = peerManager;
this.listener = listener;
networkNode.addMessageListener(this);
}
public void shutDown() {
Log.traceCall();
networkNode.removeMessageListener(this);
stopTimeoutTimer();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void requestData(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress);
GetDataRequest getDataRequest;
if (networkNode.getNodeAddress() == null)
getDataRequest = new PreliminaryGetDataRequest(nonce);
else
getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce);
log.info("We send a {} to peer {}. ", getDataRequest.getClass().getSimpleName(), nodeAddress);
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getDataRequest);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.trace("Send " + getDataRequest + " to " + nodeAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getDataRequest to " + nodeAddress +
" failed. That is expected if the peer is offline.\n\t" +
"getDataRequest=" + getDataRequest + "." +
"\n\tException=" + throwable.getMessage();
log.info(errorMessage);
peerManager.shutDownConnection(nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
shutDown();
listener.onFault(errorMessage, null);
}
});
checkArgument(timeoutTimer == null, "requestData must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred at sending getDataRequest:" + getDataRequest +
" on nodeAddress:" + nodeAddress;
log.info(errorMessage + " / RequestDataHandshake=" +
RequestDataHandshake.this);
peerManager.shutDownConnection(nodeAddress, CloseConnectionReason.SEND_MSG_TIMEOUT);
shutDown();
listener.onFault(errorMessage, null);
},
10);
}
public void onDataRequest(Message message, final Connection connection) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
GetDataResponse getDataResponse = new GetDataResponse(new HashSet<>(dataStorage.getMap().values()),
((GetDataRequest) message).getNonce());
SettableFuture<Connection> future = networkNode.sendMessage(connection, getDataResponse);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
log.trace("Send DataResponse to {} succeeded. getDataResponse={}",
connection.getPeersNodeAddressOptional(), getDataResponse);
shutDown();
listener.onComplete();
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending getDataRequest to " + connection +
" failed. That is expected if the peer is offline. getDataResponse=" + getDataResponse + "." +
"Exception: " + throwable.getMessage();
log.info(errorMessage);
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE);
shutDown();
listener.onFault(errorMessage, connection);
}
});
checkArgument(timeoutTimer == null, "requestData must not be called twice.");
timeoutTimer = UserThread.runAfter(() -> {
String errorMessage = "A timeout occurred for getDataResponse:" + getDataResponse +
" on connection:" + connection;
log.info(errorMessage + " / RequestDataHandshake=" +
RequestDataHandshake.this);
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_TIMEOUT);
shutDown();
listener.onFault(errorMessage, connection);
},
10, TimeUnit.SECONDS);
}
///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onMessage(Message message, Connection connection) {
if (message instanceof GetDataResponse) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
GetDataResponse getDataResponse = (GetDataResponse) message;
if (getDataResponse.requestNonce == nonce) {
stopTimeoutTimer();
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
"RequestDataHandshake.onMessage: connection.getPeersNodeAddressOptional() must be present " +
"at that moment");
((GetDataResponse) message).dataSet.stream()
.forEach(protectedData -> dataStorage.add(protectedData,
connection.getPeersNodeAddressOptional().get()));
shutDown();
listener.onComplete();
} else {
log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled " +
"handshake (timeout causes connection close but peer might have sent a msg before " +
"connection was closed).\n\t" +
"We drop that message. nonce={} / requestNonce={}",
nonce, getDataResponse.requestNonce);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void stopTimeoutTimer() {
if (timeoutTimer != null) {
timeoutTimer.stop();
timeoutTimer = null;
}
}
}

View file

@ -5,9 +5,7 @@ import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.network.MessageListener;
import io.bitsquare.p2p.network.NetworkNode;
import io.bitsquare.p2p.peers.PeerManager; import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.getdata.messages.GetDataRequest; import io.bitsquare.p2p.peers.getdata.messages.GetDataRequest;
import io.bitsquare.p2p.peers.peerexchange.ReportedPeer; import io.bitsquare.p2p.peers.peerexchange.ReportedPeer;
@ -17,12 +15,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
public class RequestDataManager implements MessageListener { public class RequestDataManager implements MessageListener, ConnectionListener, PeerManager.Listener {
private static final Logger log = LoggerFactory.getLogger(RequestDataManager.class); private static final Logger log = LoggerFactory.getLogger(RequestDataManager.class);
private static final long RETRY_DELAY_SEC = 10; private static final long RETRY_DELAY_SEC = 10;
@ -55,11 +52,11 @@ public class RequestDataManager implements MessageListener {
private final Collection<NodeAddress> seedNodeAddresses; private final Collection<NodeAddress> seedNodeAddresses;
private final Listener listener; private final Listener listener;
private final Map<NodeAddress, RequestDataHandshake> requestDataHandshakeMap = new HashMap<>(); private final Map<NodeAddress, RequestDataHandler> requestDataHandlerMap = new HashMap<>();
private Optional<NodeAddress> nodeOfPreliminaryDataRequest = Optional.empty(); private Optional<NodeAddress> nodeAddressOfPreliminaryDataRequest = Optional.empty();
private Timer requestDataTimer; private Timer retryTimer;
private boolean dataUpdateRequested; private boolean dataUpdateRequested;
private boolean shutDownInProgress; private boolean stopped;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -76,14 +73,16 @@ public class RequestDataManager implements MessageListener {
checkArgument(!seedNodeAddresses.isEmpty(), "seedNodeAddresses must not be empty."); checkArgument(!seedNodeAddresses.isEmpty(), "seedNodeAddresses must not be empty.");
networkNode.addMessageListener(this); networkNode.addMessageListener(this);
peerManager.addListener(this);
} }
public void shutDown() { public void shutDown() {
Log.traceCall(); Log.traceCall();
shutDownInProgress = true; stopped = true;
stopRequestDataTimer(); stopRetryTimer();
networkNode.removeMessageListener(this); networkNode.removeMessageListener(this);
requestDataHandshakeMap.values().stream().forEach(RequestDataHandshake::shutDown); peerManager.removeListener(this);
requestDataHandlerMap.values().stream().forEach(RequestDataHandler::cleanup);
} }
@ -100,19 +99,73 @@ public class RequestDataManager implements MessageListener {
requestData(nextCandidate, nodeAddresses); requestData(nextCandidate, nodeAddresses);
} }
public void requestUpdatesData() { public void requestUpdateData() {
Log.traceCall(); Log.traceCall();
checkArgument(nodeOfPreliminaryDataRequest.isPresent(), "seedNodeOfPreliminaryDataRequest must be present"); checkArgument(nodeAddressOfPreliminaryDataRequest.isPresent(), "seedNodeOfPreliminaryDataRequest must be present");
dataUpdateRequested = true; dataUpdateRequested = true;
List<NodeAddress> remainingNodeAddresses = new ArrayList<>(seedNodeAddresses); List<NodeAddress> remainingNodeAddresses = new ArrayList<>(seedNodeAddresses);
Collections.shuffle(remainingNodeAddresses); Collections.shuffle(remainingNodeAddresses);
NodeAddress candidate = nodeOfPreliminaryDataRequest.get(); NodeAddress candidate = nodeAddressOfPreliminaryDataRequest.get();
remainingNodeAddresses.remove(candidate); remainingNodeAddresses.remove(candidate);
requestData(candidate, remainingNodeAddresses); requestData(candidate, remainingNodeAddresses);
} }
public Optional<NodeAddress> getNodeOfPreliminaryDataRequest() { public Optional<NodeAddress> getNodeAddressOfPreliminaryDataRequest() {
return nodeOfPreliminaryDataRequest; return nodeAddressOfPreliminaryDataRequest;
}
///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onConnection(Connection connection) {
Log.traceCall();
// clean up in case we could not clean up at disconnect
closeRequestDataHandler(connection);
}
@Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
Log.traceCall();
closeRequestDataHandler(connection);
}
@Override
public void onError(Throwable throwable) {
}
///////////////////////////////////////////////////////////////////////////////////////////
// PeerManager.Listener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onAllConnectionsLost() {
Log.traceCall();
closeAllRequestDataHandlers();
stopRetryTimer();
stopped = true;
}
@Override
public void onNewConnectionAfterAllConnectionsLost() {
Log.traceCall();
closeAllRequestDataHandlers();
stopped = false;
retryAfterDelay();
}
@Override
public void onAwakeFromStandby() {
Log.traceCall();
closeAllRequestDataHandlers();
stopped = false;
if (!networkNode.getAllConnections().isEmpty())
retryAfterDelay();
} }
@ -124,47 +177,54 @@ public class RequestDataManager implements MessageListener {
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
if (message instanceof GetDataRequest) { if (message instanceof GetDataRequest) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection); Log.traceCall(message.toString() + "\n\tconnection=" + connection);
RequestDataHandshake requestDataHandshake = new RequestDataHandshake(networkNode, dataStorage, peerManager, if (!stopped) {
new RequestDataHandshake.Listener() { if (peerManager.isSeedNode(connection))
connection.setPeerType(Connection.PeerType.SEED_NODE);
GetDataRequestHandler getDataRequestHandler = new GetDataRequestHandler(networkNode, peerManager, dataStorage,
new GetDataRequestHandler.Listener() {
@Override @Override
public void onComplete() { public void onComplete() {
log.trace("requestDataHandshake of inbound connection complete.\n\tConnection={}", log.trace("requestDataHandshake completed.\n\tConnection={}",
connection); connection);
} }
@Override @Override
public void onFault(String errorMessage, @Nullable Connection connection) { public void onFault(String errorMessage, @Nullable Connection connection) {
log.trace("requestDataHandshake of inbound connection failed.\n\tConnection={}\n\t" + log.trace("GetDataRequestHandler failed.\n\tConnection={}\n\t" +
"ErrorMessage={}", connection, errorMessage); "ErrorMessage={}", connection, errorMessage);
peerManager.handleConnectionFault(connection); peerManager.handleConnectionFault(connection);
} }
}); });
requestDataHandshake.onDataRequest(message, connection); getDataRequestHandler.handle((GetDataRequest) message, connection);
} else {
log.warn("We have stopped already. We ignore that onMessage call.");
}
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private // RequestData
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void requestData(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) { private void requestData(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) {
Log.traceCall("nodeAddress=" + nodeAddress + " / remainingNodeAddresses=" + remainingNodeAddresses); Log.traceCall("nodeAddress=" + nodeAddress + " / remainingNodeAddresses=" + remainingNodeAddresses);
if (!requestDataHandshakeMap.containsKey(nodeAddress)) { if (!stopped) {
RequestDataHandshake requestDataHandshake = new RequestDataHandshake(networkNode, dataStorage, peerManager, if (!requestDataHandlerMap.containsKey(nodeAddress)) {
new RequestDataHandshake.Listener() { RequestDataHandler requestDataHandler = new RequestDataHandler(networkNode, dataStorage, peerManager,
new RequestDataHandler.Listener() {
@Override @Override
public void onComplete() { public void onComplete() {
log.trace("RequestDataHandshake of outbound connection complete. nodeAddress={}", log.trace("RequestDataHandshake of outbound connection complete. nodeAddress={}",
nodeAddress); nodeAddress);
stopRequestDataTimer(); stopRetryTimer();
// need to remove before listeners are notified as they cause the update call // need to remove before listeners are notified as they cause the update call
requestDataHandshakeMap.remove(nodeAddress); requestDataHandlerMap.remove(nodeAddress);
// 1. We get a response from requestPreliminaryData // 1. We get a response from requestPreliminaryData
if (!nodeOfPreliminaryDataRequest.isPresent()) { if (!nodeAddressOfPreliminaryDataRequest.isPresent()) {
nodeOfPreliminaryDataRequest = Optional.of(nodeAddress); nodeAddressOfPreliminaryDataRequest = Optional.of(nodeAddress);
listener.onPreliminaryDataReceived(); listener.onPreliminaryDataReceived();
} }
@ -182,9 +242,10 @@ public class RequestDataManager implements MessageListener {
log.trace("requestDataHandshake of outbound connection failed.\n\tnodeAddress={}\n\t" + log.trace("requestDataHandshake of outbound connection failed.\n\tnodeAddress={}\n\t" +
"ErrorMessage={}", nodeAddress, errorMessage); "ErrorMessage={}", nodeAddress, errorMessage);
requestDataHandlerMap.remove(nodeAddress);
peerManager.handleConnectionFault(nodeAddress, connection); peerManager.handleConnectionFault(nodeAddress, connection);
if (!shutDownInProgress) { if (!stopped) {
if (!remainingNodeAddresses.isEmpty()) { if (!remainingNodeAddresses.isEmpty()) {
log.info("There are remaining nodes available for requesting data. " + log.info("There are remaining nodes available for requesting data. " +
"We will try requestDataFromPeers again."); "We will try requestDataFromPeers again.");
@ -197,51 +258,66 @@ public class RequestDataManager implements MessageListener {
"We will try to use reported peers (if no available we use persisted peers) " + "We will try to use reported peers (if no available we use persisted peers) " +
"and try again to request data from our seed nodes after a random pause."); "and try again to request data from our seed nodes after a random pause.");
// try again after a pause
stopRequestDataTimer();
requestDataTimer = UserThread.runAfter(() -> {
log.trace("requestDataAfterDelayTimer called");
// We want to keep it sorted but avoid duplicates
// We don't filter out already established connections for seed nodes as it might be that
// we got from the other seed node contacted but we still have not requested the initial
// data set
List<NodeAddress> list = new ArrayList<>(seedNodeAddresses);
Collections.shuffle(list);
list.addAll(getFilteredAndSortedList(peerManager.getReportedPeers(), list));
list.addAll(getFilteredAndSortedList(peerManager.getPersistedPeers(), list));
checkArgument(!list.isEmpty(), "seedNodeAddresses must not be empty.");
NodeAddress nextCandidate = list.get(0);
list.remove(nextCandidate);
requestData(nextCandidate, list);
},
RETRY_DELAY_SEC, TimeUnit.SECONDS);
}
requestDataHandshakeMap.remove(nodeAddress);
// Notify listeners // Notify listeners
if (!nodeOfPreliminaryDataRequest.isPresent()) { if (!nodeAddressOfPreliminaryDataRequest.isPresent()) {
if (peerManager.isSeedNode(nodeAddress)) if (peerManager.isSeedNode(nodeAddress))
listener.onNoSeedNodeAvailable(); listener.onNoSeedNodeAvailable();
else else
listener.onNoPeersAvailable(); listener.onNoPeersAvailable();
} }
retryAfterDelay();
}
} else {
log.warn("We have stopped already. We ignore that requestData.onFault call.");
} }
} }
}); });
requestDataHandshakeMap.put(nodeAddress, requestDataHandshake); requestDataHandlerMap.put(nodeAddress, requestDataHandler);
requestDataHandshake.requestData(nodeAddress); requestDataHandler.requestData(nodeAddress);
} else { } else {
log.warn("We have started already a requestDataHandshake to peer. nodeAddress=" + nodeAddress); log.warn("We have started already a requestDataHandshake to peer. nodeAddress=" + nodeAddress);
} }
} else {
log.warn("We have stopped already. We ignore that requestData call.");
}
} }
// sorted by most recent lastActivityDate
private List<NodeAddress> getFilteredAndSortedList(Set<ReportedPeer> set, List<NodeAddress> list) { ///////////////////////////////////////////////////////////////////////////////////////////
return set.stream() // Utils
.filter(e -> !list.contains(e.nodeAddress) && ///////////////////////////////////////////////////////////////////////////////////////////
!peerManager.isSeedNode(e) &&
!peerManager.isSelf(e)) private void retryAfterDelay() {
if (retryTimer == null) {
retryTimer = UserThread.runAfter(() -> {
log.trace("retryTimer called");
stopRetryTimer();
// We create a new list of candidates
// 1. shuffled seedNodes
// 2. reported peers sorted by last activity date
// 3. Add as last persisted peers sorted by last activity date
List<NodeAddress> list = getFilteredList(new ArrayList<>(seedNodeAddresses), new ArrayList<>());
Collections.shuffle(list);
List<NodeAddress> filteredReportedPeers = getFilteredNonSeedNodeList(getSortedNodeAddresses(peerManager.getReportedPeers()), list);
list.addAll(filteredReportedPeers);
List<NodeAddress> filteredPersistedPeers = getFilteredNonSeedNodeList(getSortedNodeAddresses(peerManager.getPersistedPeers()), list);
list.addAll(filteredPersistedPeers);
checkArgument(!list.isEmpty(), "seedNodeAddresses must not be empty.");
NodeAddress nextCandidate = list.get(0);
list.remove(nextCandidate);
requestData(nextCandidate, list);
},
RETRY_DELAY_SEC);
}
}
private List<NodeAddress> getSortedNodeAddresses(Collection<ReportedPeer> collection) {
return collection.stream()
.collect(Collectors.toList()) .collect(Collectors.toList())
.stream() .stream()
.sorted((o1, o2) -> o2.date.compareTo(o1.date)) .sorted((o1, o2) -> o2.date.compareTo(o1.date))
@ -249,10 +325,37 @@ public class RequestDataManager implements MessageListener {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void stopRequestDataTimer() { private List<NodeAddress> getFilteredList(Collection<NodeAddress> collection, List<NodeAddress> list) {
if (requestDataTimer != null) { return collection.stream()
requestDataTimer.stop(); .filter(e -> !list.contains(e) &&
requestDataTimer = null; !peerManager.isSelf(e))
.collect(Collectors.toList());
}
private List<NodeAddress> getFilteredNonSeedNodeList(Collection<NodeAddress> collection, List<NodeAddress> list) {
return getFilteredList(collection, list).stream()
.filter(e -> !peerManager.isSeedNode(e))
.collect(Collectors.toList());
}
private void stopRetryTimer() {
if (retryTimer != null) {
retryTimer.stop();
retryTimer = null;
} }
} }
private void closeRequestDataHandler(Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) {
NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
requestDataHandlerMap.get(nodeAddress).cleanup();
requestDataHandlerMap.remove(nodeAddress);
}
}
private void closeAllRequestDataHandlers() {
requestDataHandlerMap.values().stream().forEach(RequestDataHandler::cleanup);
requestDataHandlerMap.clear();
}
} }

View file

@ -5,7 +5,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.CloseConnectionReason; import io.bitsquare.p2p.network.CloseConnectionReason;
import io.bitsquare.p2p.network.Connection; import io.bitsquare.p2p.network.Connection;
import io.bitsquare.p2p.network.MessageListener; import io.bitsquare.p2p.network.MessageListener;
@ -23,9 +22,6 @@ import java.util.Random;
class KeepAliveHandler implements MessageListener { class KeepAliveHandler implements MessageListener {
private static final Logger log = LoggerFactory.getLogger(KeepAliveHandler.class); private static final Logger log = LoggerFactory.getLogger(KeepAliveHandler.class);
@Nullable
private Connection connection;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Listener // Listener
@ -34,9 +30,7 @@ class KeepAliveHandler implements MessageListener {
public interface Listener { public interface Listener {
void onComplete(); void onComplete();
void onFault(String errorMessage, Connection connection); void onFault(String errorMessage);
void onFault(String errorMessage, NodeAddress nodeAddress);
} }
@ -48,6 +42,9 @@ class KeepAliveHandler implements MessageListener {
private final PeerManager peerManager; private final PeerManager peerManager;
private final Listener listener; private final Listener listener;
private final int nonce = new Random().nextInt(); private final int nonce = new Random().nextInt();
@Nullable
private Connection connection;
private boolean stopped;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -61,6 +58,7 @@ class KeepAliveHandler implements MessageListener {
} }
public void cleanup() { public void cleanup() {
stopped = true;
if (connection != null) if (connection != null)
connection.removeMessageListener(this); connection.removeMessageListener(this);
} }
@ -70,9 +68,9 @@ class KeepAliveHandler implements MessageListener {
// API // API
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void sendPing(Connection connection) { public void sendPing(Connection connection) {
Log.traceCall("connection=" + connection + " / this=" + this); Log.traceCall("connection=" + connection + " / this=" + this);
if (!stopped) {
this.connection = connection; this.connection = connection;
connection.addMessageListener(this); connection.addMessageListener(this);
Ping ping = new Ping(nonce); Ping ping = new Ping(nonce);
@ -91,37 +89,12 @@ class KeepAliveHandler implements MessageListener {
log.info(errorMessage); log.info(errorMessage);
cleanup(); cleanup();
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE); peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE);
listener.onFault(errorMessage, connection); listener.onFault(errorMessage);
} }
}); });
} else {
log.warn("We have stopped already. We ignore that sendPing call.");
} }
public void sendPing(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this);
Ping ping = new Ping(nonce);
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, ping);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(Connection connection) {
if (connection != null) {
KeepAliveHandler.this.connection = connection;
connection.addMessageListener(KeepAliveHandler.this);
}
log.trace("Send " + ping + " to " + nodeAddress + " succeeded.");
}
@Override
public void onFailure(@NotNull Throwable throwable) {
String errorMessage = "Sending ping to " + nodeAddress +
" failed. That is expected if the peer is offline.\n\tping=" + ping +
".\n\tException=" + throwable.getMessage();
log.info(errorMessage);
cleanup();
peerManager.shutDownConnection(nodeAddress, CloseConnectionReason.SEND_MSG_FAILURE);
listener.onFault(errorMessage, nodeAddress);
}
});
} }
@ -133,6 +106,7 @@ class KeepAliveHandler implements MessageListener {
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
if (message instanceof Pong) { if (message instanceof Pong) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection); Log.traceCall(message.toString() + "\n\tconnection=" + connection);
if (!stopped) {
Pong pong = (Pong) message; Pong pong = (Pong) message;
if (pong.requestNonce == nonce) { if (pong.requestNonce == nonce) {
cleanup(); cleanup();
@ -142,7 +116,9 @@ class KeepAliveHandler implements MessageListener {
"We drop that message. nonce={} / requestNonce={}", "We drop that message. nonce={} / requestNonce={}",
nonce, pong.requestNonce); nonce, pong.requestNonce);
} }
} else {
log.warn("We have stopped already. We ignore that onMessage call.");
}
} }
} }
} }

View file

@ -2,13 +2,11 @@ package io.bitsquare.p2p.peers.keepalive;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.*; import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.peers.PeerManager; import io.bitsquare.p2p.peers.PeerManager;
import io.bitsquare.p2p.peers.keepalive.messages.Ping; import io.bitsquare.p2p.peers.keepalive.messages.Ping;
@ -19,10 +17,8 @@ import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class KeepAliveManager implements MessageListener, ConnectionListener { public class KeepAliveManager implements MessageListener, ConnectionListener, PeerManager.Listener {
private static final Logger log = LoggerFactory.getLogger(KeepAliveManager.class); private static final Logger log = LoggerFactory.getLogger(KeepAliveManager.class);
//private static final int INTERVAL_SEC = new Random().nextInt(10) + 10; //private static final int INTERVAL_SEC = new Random().nextInt(10) + 10;
@ -31,9 +27,9 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
private final NetworkNode networkNode; private final NetworkNode networkNode;
private final PeerManager peerManager; private final PeerManager peerManager;
private ScheduledThreadPoolExecutor executor;
private final Map<String, KeepAliveHandler> maintenanceHandlerMap = new HashMap<>(); private final Map<String, KeepAliveHandler> maintenanceHandlerMap = new HashMap<>();
private boolean shutDownInProgress; private boolean stopped;
private Timer keepAliveTimer;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -46,18 +42,20 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
networkNode.addMessageListener(this); networkNode.addMessageListener(this);
networkNode.addConnectionListener(this); networkNode.addConnectionListener(this);
peerManager.addListener(this);
} }
public void shutDown() { public void shutDown() {
Log.traceCall(); Log.traceCall();
shutDownInProgress = true; stopped = true;
networkNode.removeMessageListener(this); networkNode.removeMessageListener(this);
networkNode.removeConnectionListener(this); networkNode.removeConnectionListener(this);
maintenanceHandlerMap.values().stream().forEach(KeepAliveHandler::cleanup); peerManager.removeListener(this);
if (executor != null) closeAllMaintenanceHandlers();
MoreExecutors.shutdownAndAwaitTermination(executor, 100, TimeUnit.MILLISECONDS);
stopKeepAliveTimer();
} }
@ -66,11 +64,9 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void start() { public void start() {
if (executor == null) { stopped = false;
executor = Utilities.getScheduledThreadPoolExecutor("KeepAliveManager", 1, 2, 5); if (keepAliveTimer == null)
executor.scheduleAtFixedRate(() -> UserThread.execute(this::keepAlive), keepAliveTimer = UserThread.runPeriodically(this::keepAlive, INTERVAL_SEC);
INTERVAL_SEC, INTERVAL_SEC, TimeUnit.SECONDS);
}
} }
@ -82,7 +78,7 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
if (message instanceof Ping) { if (message instanceof Ping) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection); Log.traceCall(message.toString() + "\n\tconnection=" + connection);
if (!stopped) {
Ping ping = (Ping) message; Ping ping = (Ping) message;
Pong pong = new Pong(ping.nonce); Pong pong = new Pong(ping.nonce);
SettableFuture<Connection> future = networkNode.sendMessage(connection, pong); SettableFuture<Connection> future = networkNode.sendMessage(connection, pong);
@ -102,6 +98,9 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE); peerManager.shutDownConnection(connection, CloseConnectionReason.SEND_MSG_FAILURE);
} }
}); });
} else {
log.warn("We have stopped already. We ignore that onMessage call.");
}
} }
} }
@ -112,15 +111,15 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
@Override @Override
public void onConnection(Connection connection) { public void onConnection(Connection connection) {
Log.traceCall();
// clean up in case we could not clean up at disconnect // clean up in case we could not clean up at disconnect
if (connection.getPeersNodeAddressOptional().isPresent()) closeMaintenanceHandler(connection);
maintenanceHandlerMap.remove(connection.getPeersNodeAddressOptional().get().getFullAddress());
} }
@Override @Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) { public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) Log.traceCall();
maintenanceHandlerMap.remove(connection.getPeersNodeAddressOptional().get().getFullAddress()); closeMaintenanceHandler(connection);
} }
@Override @Override
@ -128,58 +127,92 @@ public class KeepAliveManager implements MessageListener, ConnectionListener {
} }
///////////////////////////////////////////////////////////////////////////////////////////
// PeerManager.Listener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onAllConnectionsLost() {
Log.traceCall();
closeAllMaintenanceHandlers();
stopKeepAliveTimer();
}
@Override
public void onNewConnectionAfterAllConnectionsLost() {
Log.traceCall();
closeAllMaintenanceHandlers();
start();
}
@Override
public void onAwakeFromStandby() {
Log.traceCall();
closeAllMaintenanceHandlers();
if (!networkNode.getAllConnections().isEmpty())
start();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void keepAlive() { private void keepAlive() {
if (!stopped) {
Log.traceCall(); Log.traceCall();
if (!shutDownInProgress) {
networkNode.getConfirmedConnections().stream() networkNode.getConfirmedConnections().stream()
.filter(connection -> connection instanceof OutboundConnection) .filter(connection -> connection instanceof OutboundConnection)
.forEach(connection -> { .forEach(connection -> {
if (!maintenanceHandlerMap.containsKey(getKey(connection))) { final String uid = connection.getUid();
if (!maintenanceHandlerMap.containsKey(uid)) {
KeepAliveHandler keepAliveHandler = new KeepAliveHandler(networkNode, peerManager, new KeepAliveHandler.Listener() { KeepAliveHandler keepAliveHandler = new KeepAliveHandler(networkNode, peerManager, new KeepAliveHandler.Listener() {
@Override @Override
public void onComplete() { public void onComplete() {
maintenanceHandlerMap.remove(getKey(connection)); maintenanceHandlerMap.remove(uid);
} }
@Override @Override
public void onFault(String errorMessage, Connection connection) { public void onFault(String errorMessage) {
maintenanceHandlerMap.remove(getKey(connection)); maintenanceHandlerMap.remove(uid);
}
@Override
public void onFault(String errorMessage, NodeAddress nodeAddress) {
maintenanceHandlerMap.remove(nodeAddress.getFullAddress());
} }
}); });
maintenanceHandlerMap.put(getKey(connection), keepAliveHandler); maintenanceHandlerMap.put(uid, keepAliveHandler);
keepAliveHandler.sendPing(connection); keepAliveHandler.sendPing(connection);
} else { } else {
log.warn("Connection with id {} has not completed and is still in our map. " + log.warn("Connection with id {} has not completed and is still in our map. " +
"We will try to ping that peer at the next schedule.", getKey(connection)); "We will try to ping that peer at the next schedule.", uid);
} }
}); });
int size = maintenanceHandlerMap.size(); int size = maintenanceHandlerMap.size();
log.info("maintenanceHandlerMap size=" + size); log.info("maintenanceHandlerMap size=" + size);
if (size > peerManager.getMaxConnections()) if (size > peerManager.getMaxConnections())
log.warn("Seems we don't clean up out map correctly.\n" + log.warn("Seems we didn't clean up out map correctly.\n" +
"maintenanceHandlerMap size={}, peerManager.getMaxConnections()={}", size, peerManager.getMaxConnections()); "maintenanceHandlerMap size={}, peerManager.getMaxConnections()={}", size, peerManager.getMaxConnections());
} else {
log.warn("We have stopped already. We ignore that keepAlive call.");
} }
} }
private String getKey(Connection connection) { private void closeMaintenanceHandler(Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) { if (connection.getPeersNodeAddressOptional().isPresent()) {
return connection.getPeersNodeAddressOptional().get().getFullAddress(); String uid = connection.getUid();
} else { maintenanceHandlerMap.get(uid).cleanup();
// TODO not sure if that can be the case, but handle it otherwise we get an exception maintenanceHandlerMap.remove(uid);
log.warn("!connection.getPeersNodeAddressOptional().isPresent(). That should not happen."); }
return "null"; }
private void closeAllMaintenanceHandlers() {
maintenanceHandlerMap.values().stream().forEach(KeepAliveHandler::cleanup);
maintenanceHandlerMap.clear();
}
private void stopKeepAliveTimer() {
stopped = true;
if (keepAliveTimer != null) {
keepAliveTimer.stop();
keepAliveTimer = null;
} }
} }
} }

View file

@ -107,10 +107,10 @@ class GetPeersRequestHandler {
// Private // Private
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void handleFault(String errorMessage, CloseConnectionReason sendMsgFailure, Connection connection) { private void handleFault(String errorMessage, CloseConnectionReason closeConnectionReason, Connection connection) {
// TODO retry // TODO retry
cleanup(); cleanup();
peerManager.shutDownConnection(connection, sendMsgFailure); peerManager.shutDownConnection(connection, closeConnectionReason);
listener.onFault(errorMessage, connection); listener.onFault(errorMessage, connection);
} }

View file

@ -52,6 +52,7 @@ class PeerExchangeHandler implements MessageListener {
private final int nonce = new Random().nextInt(); private final int nonce = new Random().nextInt();
private Timer timeoutTimer; private Timer timeoutTimer;
private Connection connection; private Connection connection;
private boolean stopped;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -65,6 +66,7 @@ class PeerExchangeHandler implements MessageListener {
} }
public void cleanup() { public void cleanup() {
stopped = true;
if (connection != null) if (connection != null)
connection.removeMessageListener(this); connection.removeMessageListener(this);
@ -81,6 +83,7 @@ class PeerExchangeHandler implements MessageListener {
public void sendGetPeersRequest(NodeAddress nodeAddress) { public void sendGetPeersRequest(NodeAddress nodeAddress) {
Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this); Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this);
if (!stopped) {
if (networkNode.getNodeAddress() != null) { if (networkNode.getNodeAddress() != null) {
GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, peerManager.getConnectedPeersNonSeedNodes(nodeAddress)); GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, peerManager.getConnectedPeersNonSeedNodes(nodeAddress));
SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getPeersRequest); SettableFuture<Connection> future = networkNode.sendMessage(nodeAddress, getPeersRequest);
@ -117,7 +120,10 @@ class PeerExchangeHandler implements MessageListener {
}, },
TIME_OUT_SEC, TimeUnit.SECONDS); TIME_OUT_SEC, TimeUnit.SECONDS);
} else { } else {
log.trace("My node address is still null at sendGetPeersRequest. We ignore that call."); log.warn("My node address is still null at sendGetPeersRequest. We ignore that call.");
}
} else {
log.warn("We have stopped that handler already. We ignore that sendGetPeersRequest call.");
} }
} }
@ -128,17 +134,15 @@ class PeerExchangeHandler implements MessageListener {
@Override @Override
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersResponse) { if (message instanceof GetPeersResponse) {
if (!stopped) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
GetPeersResponse getPeersResponse = (GetPeersResponse) message; GetPeersResponse getPeersResponse = (GetPeersResponse) message;
if (peerManager.isSeedNode(connection)) if (peerManager.isSeedNode(connection))
connection.setPeerType(Connection.PeerType.SEED_NODE); connection.setPeerType(Connection.PeerType.SEED_NODE);
// Check if the response is for our request // Check if the response is for our request
if (getPeersResponse.requestNonce == nonce) { if (getPeersResponse.requestNonce == nonce) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection);
Log.traceCall("this=" + this);
peerManager.addToReportedPeers(getPeersResponse.reportedPeers, connection); peerManager.addToReportedPeers(getPeersResponse.reportedPeers, connection);
cleanup(); cleanup();
listener.onComplete(); listener.onComplete();
} else { } else {
@ -146,6 +150,9 @@ class PeerExchangeHandler implements MessageListener {
"We drop that message. nonce={} / requestNonce={}", "We drop that message. nonce={} / requestNonce={}",
nonce, getPeersResponse.requestNonce); nonce, getPeersResponse.requestNonce);
} }
} else {
log.warn("We have stopped that handler already. We ignore that onMessage call.");
}
} }
} }

View file

@ -1,10 +1,8 @@
package io.bitsquare.p2p.peers.peerexchange; package io.bitsquare.p2p.peers.peerexchange;
import com.google.common.util.concurrent.MoreExecutors;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.common.Timer; import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.network.*; import io.bitsquare.p2p.network.*;
@ -15,17 +13,16 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class PeerExchangeManager implements MessageListener, ConnectionListener { public class PeerExchangeManager implements MessageListener, ConnectionListener, PeerManager.Listener {
private static final Logger log = LoggerFactory.getLogger(PeerExchangeManager.class); private static final Logger log = LoggerFactory.getLogger(PeerExchangeManager.class);
private static final long RETRY_DELAY_SEC = 60; private static final long RETRY_DELAY_SEC = 10;
private static final long RETRY_DELAY_AFTER_ALL_CON_LOST_SEC = 3; private static final long RETRY_DELAY_AFTER_ALL_CON_LOST_SEC = 3;
private static final long REQUEST_PERIODICALLY_INTERVAL_MINUTES = 10; private static final long REQUEST_PERIODICALLY_INTERVAL_MINUTES = 10;
@ -33,9 +30,8 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
private final PeerManager peerManager; private final PeerManager peerManager;
private final Set<NodeAddress> seedNodeAddresses; private final Set<NodeAddress> seedNodeAddresses;
private final Map<NodeAddress, PeerExchangeHandler> peerExchangeHandlerMap = new HashMap<>(); private final Map<NodeAddress, PeerExchangeHandler> peerExchangeHandlerMap = new HashMap<>();
private Timer connectToMorePeersTimer; private Timer retryTimer, periodicTimer;
private boolean shutDownInProgress; private boolean stopped;
private ScheduledThreadPoolExecutor executor;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -50,18 +46,20 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
networkNode.addMessageListener(this); networkNode.addMessageListener(this);
networkNode.addConnectionListener(this); networkNode.addConnectionListener(this);
peerManager.addListener(this);
} }
public void shutDown() { public void shutDown() {
Log.traceCall(); Log.traceCall();
shutDownInProgress = true; stopped = true;
networkNode.removeMessageListener(this); networkNode.removeMessageListener(this);
networkNode.removeConnectionListener(this); networkNode.removeConnectionListener(this);
stopConnectToMorePeersTimer(); peerManager.removeListener(this);
peerExchangeHandlerMap.values().stream().forEach(PeerExchangeHandler::cleanup);
if (executor != null) stopPeriodicTimer();
MoreExecutors.shutdownAndAwaitTermination(executor, 100, TimeUnit.MILLISECONDS); stopRetryTimer();
closeAllPeerExchangeHandlers();
} }
@ -76,39 +74,32 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
Collections.shuffle(remainingNodeAddresses); Collections.shuffle(remainingNodeAddresses);
requestReportedPeers(nodeAddress, remainingNodeAddresses); requestReportedPeers(nodeAddress, remainingNodeAddresses);
if (executor == null) { startPeriodicTimer();
executor = Utilities.getScheduledThreadPoolExecutor("PeerExchangeManager", 1, 2, 5);
executor.scheduleAtFixedRate(() -> UserThread.execute(this::requestAgain),
REQUEST_PERIODICALLY_INTERVAL_MINUTES, REQUEST_PERIODICALLY_INTERVAL_MINUTES, TimeUnit.MINUTES);
}
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ConnectionListener implementation // ConnectionListener implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override @Override
public void onConnection(Connection connection) { public void onConnection(Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) Log.traceCall();
peerExchangeHandlerMap.remove(connection.getPeersNodeAddressOptional().get().getFullAddress()); // clean up in case we could not clean up at disconnect
closePeerExchangeHandler(connection);
} }
@Override @Override
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) { public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) Log.traceCall();
peerExchangeHandlerMap.remove(connection.getPeersNodeAddressOptional().get().getFullAddress()); closePeerExchangeHandler(connection);
boolean lostAllConnections = networkNode.getAllConnections().isEmpty(); if (retryTimer == null) {
if (lostAllConnections || connectToMorePeersTimer == null) { retryTimer = UserThread.runAfter(() -> {
long delaySec = lostAllConnections ? RETRY_DELAY_AFTER_ALL_CON_LOST_SEC : RETRY_DELAY_SEC;
if (lostAllConnections && connectToMorePeersTimer != null)
connectToMorePeersTimer.stop();
connectToMorePeersTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from onDisconnect code path"); log.trace("ConnectToMorePeersTimer called from onDisconnect code path");
stopConnectToMorePeersTimer(); stopRetryTimer();
requestWithAvailablePeers(); requestWithAvailablePeers();
}, delaySec); }, RETRY_DELAY_SEC);
} }
} }
@ -117,6 +108,36 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
} }
///////////////////////////////////////////////////////////////////////////////////////////
// PeerManager.Listener implementation
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onAllConnectionsLost() {
Log.traceCall();
closeAllPeerExchangeHandlers();
stopPeriodicTimer();
stopRetryTimer();
}
@Override
public void onNewConnectionAfterAllConnectionsLost() {
Log.traceCall();
closeAllPeerExchangeHandlers();
restart();
}
@Override
public void onAwakeFromStandby() {
Log.traceCall();
closeAllPeerExchangeHandlers();
if (!networkNode.getAllConnections().isEmpty())
restart();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// MessageListener implementation // MessageListener implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -125,7 +146,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
public void onMessage(Message message, Connection connection) { public void onMessage(Message message, Connection connection) {
if (message instanceof GetPeersRequest) { if (message instanceof GetPeersRequest) {
Log.traceCall(message.toString() + "\n\tconnection=" + connection); Log.traceCall(message.toString() + "\n\tconnection=" + connection);
if (!stopped) {
if (peerManager.isSeedNode(connection)) if (peerManager.isSeedNode(connection))
connection.setPeerType(Connection.PeerType.SEED_NODE); connection.setPeerType(Connection.PeerType.SEED_NODE);
@ -134,17 +155,20 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
new GetPeersRequestHandler.Listener() { new GetPeersRequestHandler.Listener() {
@Override @Override
public void onComplete() { public void onComplete() {
log.trace("PeerExchangeHandshake of inbound connection complete.\n\tConnection={}", connection); log.trace("PeerExchangeHandshake completed.\n\tConnection={}", connection);
} }
@Override @Override
public void onFault(String errorMessage, Connection connection) { public void onFault(String errorMessage, Connection connection) {
log.trace("PeerExchangeHandshake of outbound connection failed.\n\terrorMessage={}\n\t" + log.trace("PeerExchangeHandshake failed.\n\terrorMessage={}\n\t" +
"connection={}", errorMessage, connection); "connection={}", errorMessage, connection);
peerManager.handleConnectionFault(connection); peerManager.handleConnectionFault(connection);
} }
}); });
getPeersRequestHandler.handle((GetPeersRequest) message, connection); getPeersRequestHandler.handle((GetPeersRequest) message, connection);
} else {
log.warn("We have stopped already. We ignore that onMessage call.");
}
} }
} }
@ -155,6 +179,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
private void requestReportedPeers(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) { private void requestReportedPeers(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) {
Log.traceCall("nodeAddress=" + nodeAddress); Log.traceCall("nodeAddress=" + nodeAddress);
if (!stopped) {
if (!peerExchangeHandlerMap.containsKey(nodeAddress)) { if (!peerExchangeHandlerMap.containsKey(nodeAddress)) {
PeerExchangeHandler peerExchangeHandler = new PeerExchangeHandler(networkNode, PeerExchangeHandler peerExchangeHandler = new PeerExchangeHandler(networkNode,
peerManager, peerManager,
@ -173,7 +198,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
peerExchangeHandlerMap.remove(nodeAddress); peerExchangeHandlerMap.remove(nodeAddress);
peerManager.handleConnectionFault(nodeAddress, connection); peerManager.handleConnectionFault(nodeAddress, connection);
if (!shutDownInProgress) { if (!stopped) {
if (!remainingNodeAddresses.isEmpty()) { if (!remainingNodeAddresses.isEmpty()) {
if (!peerManager.hasSufficientConnections()) { if (!peerManager.hasSufficientConnections()) {
log.info("There are remaining nodes available for requesting peers. " + log.info("There are remaining nodes available for requesting peers. " +
@ -189,10 +214,10 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
log.info("There is no remaining node available for requesting peers. " + log.info("There is no remaining node available for requesting peers. " +
"That is expected if no other node is online.\n\t" + "That is expected if no other node is online.\n\t" +
"We will try again after a pause."); "We will try again after a pause.");
if (connectToMorePeersTimer == null) if (retryTimer == null)
connectToMorePeersTimer = UserThread.runAfter(() -> { retryTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from requestReportedPeers code path"); log.trace("ConnectToMorePeersTimer called from requestReportedPeers code path");
stopConnectToMorePeersTimer(); stopRetryTimer();
requestWithAvailablePeers(); requestWithAvailablePeers();
}, RETRY_DELAY_SEC); }, RETRY_DELAY_SEC);
} }
@ -203,19 +228,21 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
peerExchangeHandler.sendGetPeersRequest(nodeAddress); peerExchangeHandler.sendGetPeersRequest(nodeAddress);
} else { } else {
//TODO check when that happens //TODO check when that happens
log.warn("We have started already a peerExchangeHandshake. " + log.warn("We have started already a peerExchangeHandler. " +
"We ignore that call. " + "We ignore that call. nodeAddress=" + nodeAddress);
"nodeAddress=" + nodeAddress); }
} else {
log.warn("We have stopped already. We ignore that requestReportedPeers call.");
} }
} }
private void requestWithAvailablePeers() { private void requestWithAvailablePeers() {
Log.traceCall(); Log.traceCall();
if (!stopped) {
if (!peerManager.hasSufficientConnections()) { if (!peerManager.hasSufficientConnections()) {
// We create a new list of not connected candidates // We create a new list of not connected candidates
// 1. reported shuffled peers // 1. shuffled reported peers
// 2. persisted shuffled peers // 2. shuffled persisted peers
// 3. Add as last shuffled seedNodes (least priority) // 3. Add as last shuffled seedNodes (least priority)
List<NodeAddress> list = getFilteredNonSeedNodeList(getNodeAddresses(peerManager.getReportedPeers()), new ArrayList<>()); List<NodeAddress> list = getFilteredNonSeedNodeList(getNodeAddresses(peerManager.getReportedPeers()), new ArrayList<>());
Collections.shuffle(list); Collections.shuffle(list);
@ -237,33 +264,19 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
requestReportedPeers(nextCandidate, list); requestReportedPeers(nextCandidate, list);
} else { } else {
log.info("No more peers are available for requestReportedPeers. We will try again after a pause."); log.info("No more peers are available for requestReportedPeers. We will try again after a pause.");
if (connectToMorePeersTimer == null) if (retryTimer == null)
connectToMorePeersTimer = UserThread.runAfter(() -> { retryTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from requestWithAvailablePeers code path"); log.trace("ConnectToMorePeersTimer called from requestWithAvailablePeers code path");
stopConnectToMorePeersTimer(); stopRetryTimer();
requestWithAvailablePeers(); requestWithAvailablePeers();
}, RETRY_DELAY_SEC); }, RETRY_DELAY_SEC);
} }
} else { } else {
log.info("We have already sufficient connections."); log.info("We have already sufficient connections.");
} }
} else {
log.warn("We have stopped already. We ignore that requestWithAvailablePeers call.");
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Maintenance
///////////////////////////////////////////////////////////////////////////////////////////
private void requestAgain() {
checkNotNull(networkNode.getNodeAddress(), "My node address must not be null at requestAgain");
Set<NodeAddress> candidates = new HashSet<>(getNodeAddresses(peerManager.getReportedPeers()));
candidates.addAll(getNodeAddresses(peerManager.getPersistedPeers()));
candidates.addAll(seedNodeAddresses);
candidates.remove(networkNode.getNodeAddress());
ArrayList<NodeAddress> list = new ArrayList<>(candidates);
Collections.shuffle(list);
NodeAddress candidate = list.remove(0);
requestReportedPeers(candidate, list);
} }
@ -271,6 +284,25 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
// Utils // Utils
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void startPeriodicTimer() {
stopped = false;
if (periodicTimer == null)
periodicTimer = UserThread.runPeriodically(this::requestWithAvailablePeers,
REQUEST_PERIODICALLY_INTERVAL_MINUTES, TimeUnit.MINUTES);
}
private void restart() {
startPeriodicTimer();
if (retryTimer == null) {
retryTimer = UserThread.runAfter(() -> {
log.trace("ConnectToMorePeersTimer called from onNewConnectionAfterAllConnectionsLost");
stopRetryTimer();
requestWithAvailablePeers();
}, RETRY_DELAY_AFTER_ALL_CON_LOST_SEC);
}
}
private List<NodeAddress> getNodeAddresses(Collection<ReportedPeer> collection) { private List<NodeAddress> getNodeAddresses(Collection<ReportedPeer> collection) {
return collection.stream() return collection.stream()
.map(e -> e.nodeAddress) .map(e -> e.nodeAddress)
@ -291,10 +323,31 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private void stopConnectToMorePeersTimer() { private void stopPeriodicTimer() {
if (connectToMorePeersTimer != null) { stopped = true;
connectToMorePeersTimer.stop(); if (periodicTimer != null) {
connectToMorePeersTimer = null; periodicTimer.stop();
periodicTimer = null;
} }
} }
private void stopRetryTimer() {
if (retryTimer != null) {
retryTimer.stop();
retryTimer = null;
}
}
private void closePeerExchangeHandler(Connection connection) {
if (connection.getPeersNodeAddressOptional().isPresent()) {
NodeAddress nodeAddress = connection.getPeersNodeAddressOptional().get();
peerExchangeHandlerMap.get(nodeAddress).cleanup();
peerExchangeHandlerMap.remove(nodeAddress);
}
}
private void closeAllPeerExchangeHandlers() {
peerExchangeHandlerMap.values().stream().forEach(PeerExchangeHandler::cleanup);
peerExchangeHandlerMap.clear();
}
} }

View file

@ -3,6 +3,7 @@ package io.bitsquare.p2p.seed;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.app.Version; import io.bitsquare.app.Version;
import io.bitsquare.common.Clock;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.P2PService;
@ -137,7 +138,7 @@ public class SeedNode {
log.info("Created torDir at " + torDir.getAbsolutePath()); log.info("Created torDir at " + torDir.getAbsolutePath());
seedNodesRepository.setNodeAddressToExclude(mySeedNodeAddress); seedNodesRepository.setNodeAddressToExclude(mySeedNodeAddress);
seedNodeP2PService = new P2PService(seedNodesRepository, mySeedNodeAddress.port, torDir, useLocalhost, networkId, storageDir, null, null); seedNodeP2PService = new P2PService(seedNodesRepository, mySeedNodeAddress.port, torDir, useLocalhost, networkId, storageDir, new Clock(), null, null);
seedNodeP2PService.start(listener); seedNodeP2PService.start(listener);
} }

View file

@ -1,5 +1,6 @@
package io.bitsquare.p2p; package io.bitsquare.p2p;
import io.bitsquare.common.Clock;
import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.EncryptionService; import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.seed.SeedNode; import io.bitsquare.p2p.seed.SeedNode;
@ -130,7 +131,7 @@ public class TestUtils {
} }
P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost, P2PService p2PService = new P2PService(seedNodesRepository, port, new File("seed_node_" + port), useLocalhost,
2, new File("dummy"), encryptionService, keyRing); 2, new File("dummy"), new Clock(), encryptionService, keyRing);
p2PService.start(new P2PServiceListener() { p2PService.start(new P2PServiceListener() {
@Override @Override
public void onRequestingDataCompleted() { public void onRequestingDataCompleted() {