add exception handling to runnables

This commit is contained in:
Manfred Karrer 2015-11-03 20:56:08 +01:00
parent 4d82467fa3
commit 0b7e45ca55
15 changed files with 463 additions and 385 deletions

View file

@ -39,7 +39,6 @@ import com.google.common.io.Files;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import org.bitcoinj.core.Utils; import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.Threading;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -49,7 +48,6 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -62,8 +60,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public class FileManager<T> { public class FileManager<T> {
private static final Logger log = LoggerFactory.getLogger(FileManager.class); private static final Logger log = LoggerFactory.getLogger(FileManager.class);
private static final ReentrantLock lock = Threading.lock("FileManager");
private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
private final File dir; private final File dir;
private final File storageFile; private final File storageFile;
@ -85,7 +81,7 @@ public class FileManager<T> {
ThreadFactoryBuilder builder = new ThreadFactoryBuilder() ThreadFactoryBuilder builder = new ThreadFactoryBuilder()
.setDaemon(true) .setDaemon(true)
.setNameFormat("FileManager thread") .setNameFormat("FileManager-%d")
.setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread. .setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread.
// An executor that starts up threads when needed and shuts them down later. // An executor that starts up threads when needed and shuts them down later.
@ -144,40 +140,32 @@ public class FileManager<T> {
executor.schedule(saver, delay, delayTimeUnit); executor.schedule(saver, delay, delayTimeUnit);
} }
public T read(File file) { public synchronized T read(File file) {
log.debug("read" + file); log.debug("read" + file);
lock.lock();
try (final FileInputStream fileInputStream = new FileInputStream(file); try (final FileInputStream fileInputStream = new FileInputStream(file);
final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
return (T) objectInputStream.readObject(); return (T) objectInputStream.readObject();
} catch (Throwable t) { } catch (Throwable t) {
log.error("Exception at read: " + t.getMessage()); log.error("Exception at read: " + t.getMessage());
return null; return null;
} finally {
lock.unlock();
} }
} }
public void removeFile(String fileName) { public synchronized void removeFile(String fileName) {
log.debug("removeFile" + fileName); log.debug("removeFile" + fileName);
File file = new File(dir, fileName); File file = new File(dir, fileName);
lock.lock(); boolean result = file.delete();
try { if (!result)
boolean result = file.delete(); log.warn("Could not delete file: " + file.toString());
if (!result)
log.warn("Could not delete file: " + file.toString());
File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString());
if (backupDir.exists()) { if (backupDir.exists()) {
File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString()); File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString());
if (backupFile.exists()) { if (backupFile.exists()) {
result = backupFile.delete(); result = backupFile.delete();
if (!result) if (!result)
log.warn("Could not delete backupFile: " + file.toString()); log.warn("Could not delete backupFile: " + file.toString());
}
} }
} finally {
lock.unlock();
} }
} }
@ -200,34 +188,24 @@ public class FileManager<T> {
} }
} }
public void removeAndBackupFile(String fileName) throws IOException { public synchronized void removeAndBackupFile(String fileName) throws IOException {
lock.lock(); File corruptedBackupDir = new File(Paths.get(dir.getAbsolutePath(), "corrupted").toString());
try { if (!corruptedBackupDir.exists())
File corruptedBackupDir = new File(Paths.get(dir.getAbsolutePath(), "corrupted").toString()); if (!corruptedBackupDir.mkdir())
if (!corruptedBackupDir.exists()) log.warn("make dir failed");
if (!corruptedBackupDir.mkdir())
log.warn("make dir failed");
File corruptedFile = new File(Paths.get(dir.getAbsolutePath(), "corrupted", fileName).toString()); File corruptedFile = new File(Paths.get(dir.getAbsolutePath(), "corrupted", fileName).toString());
renameTempFileToFile(storageFile, corruptedFile); renameTempFileToFile(storageFile, corruptedFile);
} finally {
lock.unlock();
}
} }
public void backupFile(String fileName) throws IOException { public synchronized void backupFile(String fileName) throws IOException {
lock.lock(); File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString());
try { if (!backupDir.exists())
File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); if (!backupDir.mkdir())
if (!backupDir.exists()) log.warn("make dir failed");
if (!backupDir.mkdir())
log.warn("make dir failed");
File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString()); File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString());
Files.copy(storageFile, backupFile); Files.copy(storageFile, backupFile);
} finally {
lock.unlock();
}
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -240,8 +218,7 @@ public class FileManager<T> {
UserThread.execute(() -> log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now)); UserThread.execute(() -> log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now));
} }
private void saveToFile(T serializable, File dir, File storageFile) { private synchronized void saveToFile(T serializable, File dir, File storageFile) {
lock.lock();
File tempFile = null; File tempFile = null;
FileOutputStream fileOutputStream = null; FileOutputStream fileOutputStream = null;
ObjectOutputStream objectOutputStream = null; ObjectOutputStream objectOutputStream = null;
@ -292,27 +269,21 @@ public class FileManager<T> {
e.printStackTrace(); e.printStackTrace();
log.error("Cannot close resources." + e.getMessage()); log.error("Cannot close resources." + e.getMessage());
} }
lock.unlock();
} }
} }
private void renameTempFileToFile(File tempFile, File file) throws IOException { private synchronized void renameTempFileToFile(File tempFile, File file) throws IOException {
lock.lock(); if (Utils.isWindows()) {
try { // Work around an issue on Windows whereby you can't rename over existing files.
if (Utils.isWindows()) { final File canonical = file.getCanonicalFile();
// Work around an issue on Windows whereby you can't rename over existing files. if (canonical.exists() && !canonical.delete()) {
final File canonical = file.getCanonicalFile(); throw new IOException("Failed to delete canonical file for replacement with save");
if (canonical.exists() && !canonical.delete()) {
throw new IOException("Failed to delete canonical file for replacement with save");
}
if (!tempFile.renameTo(canonical)) {
throw new IOException("Failed to rename " + tempFile + " to " + canonical);
}
} else if (!tempFile.renameTo(file)) {
throw new IOException("Failed to rename " + tempFile + " to " + file);
} }
} finally { if (!tempFile.renameTo(canonical)) {
lock.unlock(); throw new IOException("Failed to rename " + tempFile + " to " + canonical);
}
} else if (!tempFile.renameTo(file)) {
throw new IOException("Failed to rename " + tempFile + " to " + file);
} }
} }
} }

View file

@ -245,9 +245,18 @@ public class WalletService {
} }
public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) { public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
walletAppKit.stopAsync(); Context ctx = Context.get();
walletAppKit.awaitTerminated(); new Thread(() -> {
initialize(seed, resultHandler, exceptionHandler); try {
Context.propagate(ctx);
walletAppKit.stopAsync();
walletAppKit.awaitTerminated();
initialize(seed, resultHandler, exceptionHandler);
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
}, "RestoreWallet-%d").start();
} }

View file

@ -1,5 +1,6 @@
package io.bitsquare.crypto; package io.bitsquare.crypto;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;
@ -8,6 +9,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.KeyParameter;
import java.util.concurrent.*;
//TODO: Borrowed form BitcoinJ/Lighthouse. Remove Protos dependency, check complete code logic. //TODO: Borrowed form BitcoinJ/Lighthouse. Remove Protos dependency, check complete code logic.
public class ScryptUtil { public class ScryptUtil {
private static final Logger log = LoggerFactory.getLogger(ScryptUtil.class); private static final Logger log = LoggerFactory.getLogger(ScryptUtil.class);
@ -27,14 +30,31 @@ public class ScryptUtil {
} }
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) { public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) {
new Thread(() -> { final ThreadFactory threadFactory = new ThreadFactoryBuilder()
log.info("Doing key derivation"); .setNameFormat("Routing-%d")
.setDaemon(true)
.build();
long start = System.currentTimeMillis(); ExecutorService executorService = new ThreadPoolExecutor(5, 50, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), threadFactory);
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password); executorService.submit(() -> {
long duration = System.currentTimeMillis() - start; try {
log.info("Key derivation took {} msec", duration); log.info("Doing key derivation");
UserThread.execute(() -> resultHandler.handleResult(aesKey)); long start = System.currentTimeMillis();
}).start(); KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
long duration = System.currentTimeMillis() - start;
log.info("Key derivation took {} msec", duration);
UserThread.execute(() -> {
try {
resultHandler.handleResult(aesKey);
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
});
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
});
} }
} }

View file

@ -17,7 +17,6 @@
package io.bitsquare.trade.offer; package io.bitsquare.trade.offer;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
@ -47,10 +46,11 @@ import javax.inject.Named;
import java.io.File; import java.io.File;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.inject.internal.util.$Preconditions.checkNotNull; import static com.google.inject.internal.util.$Preconditions.checkNotNull;
import static io.bitsquare.util.Validator.nonEmptyStringOf; import static io.bitsquare.util.Validator.nonEmptyStringOf;
@ -70,7 +70,7 @@ public class OpenOfferManager {
private boolean shutDownRequested; private boolean shutDownRequested;
private ScheduledThreadPoolExecutor executor; private ScheduledThreadPoolExecutor executor;
private P2PServiceListener p2PServiceListener; private P2PServiceListener p2PServiceListener;
private final Timer timer = new Timer();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -96,9 +96,13 @@ public class OpenOfferManager {
openOffersStorage = new Storage<>(storageDir); openOffersStorage = new Storage<>(storageDir);
this.openOffers = new TradableList<>(openOffersStorage, "OpenOffers"); this.openOffers = new TradableList<>(openOffersStorage, "OpenOffers");
init();
}
private void init() {
// In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook // In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook
Thread shutDownHookThread = new Thread(OpenOfferManager.this::shutDown, "OpenOfferManager.ShutDownHook"); Runtime.getRuntime().addShutdownHook(new Thread(OpenOfferManager.this::shutDown,
Runtime.getRuntime().addShutdownHook(shutDownHookThread); "OpenOfferManager.ShutDownHook"));
// Handler for incoming offer availability requests // Handler for incoming offer availability requests
p2PService.addDecryptedMailListener((decryptedMessageWithPubKey, peerAddress) -> { p2PService.addDecryptedMailListener((decryptedMessageWithPubKey, peerAddress) -> {
@ -155,25 +159,27 @@ public class OpenOfferManager {
} }
private void startRePublishThread() { private void startRePublishThread() {
if (p2PServiceListener != null) p2PService.removeP2PServiceListener(p2PServiceListener); if (p2PServiceListener != null)
p2PService.removeP2PServiceListener(p2PServiceListener);
ThreadFactoryBuilder builder = new ThreadFactoryBuilder() long period = (long) (Offer.TTL * 0.8);
.setDaemon(true) TimerTask timerTask = new TimerTask() {
.setNameFormat("Re-publish offers thread") @Override
.setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread. public void run() {
Thread.currentThread().setName("RepublishOffers-%d");
// An executor that starts up threads when needed and shuts them down later. rePublishOffers();
executor = new ScheduledThreadPoolExecutor(1, builder.build()); try {
executor.setKeepAliveTime(5, TimeUnit.SECONDS); } catch (Throwable t) {
executor.allowCoreThreadTimeOut(true); t.printStackTrace();
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); log.error("Executing task failed. " + t.getMessage());
}
checkArgument(Offer.TTL > 120000, "Offer.TTL <= 120"); }
long period = Offer.TTL - 120000; // 2 min before TTL expires };
executor.scheduleAtFixedRate(this::rePublishOffers, 500, period, TimeUnit.MILLISECONDS); timer.scheduleAtFixedRate(timerTask, 500, period);
} }
private void rePublishOffers() { private void rePublishOffers() {
log.trace("rePublishOffers");
for (OpenOffer openOffer : openOffers) { for (OpenOffer openOffer : openOffers) {
offerBookService.addOffer(openOffer.getOffer(), offerBookService.addOffer(openOffer.getOffer(),
() -> log.debug("Successful added offer to P2P network"), () -> log.debug("Successful added offer to P2P network"),

View file

@ -51,9 +51,10 @@ public class SetupPayoutTxLockTimeReachedListener extends TradeTask {
() -> { () -> {
try { try {
log.debug("Block height reached " + blockHeightFuture.get().getHeight()); log.debug("Block height reached " + blockHeightFuture.get().getHeight());
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (ExecutionException e) {
e.printStackTrace();
} }
broadcastTx(); broadcastTx();
}, },

View file

@ -33,7 +33,6 @@ import javafx.scene.control.Button;
import javafx.scene.control.DatePicker; import javafx.scene.control.DatePicker;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.Wallet; import org.bitcoinj.core.Wallet;
import org.bitcoinj.crypto.KeyCrypter; import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.crypto.MnemonicCode; import org.bitcoinj.crypto.MnemonicCode;
@ -198,31 +197,27 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
log.info("Attempting wallet restore using seed '{}' from date {}", seedWordsTextArea.getText(), datePicker.getValue()); log.info("Attempting wallet restore using seed '{}' from date {}", seedWordsTextArea.getText(), datePicker.getValue());
long date = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC); long date = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date); DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
Context ctx = Context.get(); walletService.restoreSeedWords(seed,
new Thread(() -> { () -> UserThread.execute(() -> {
Context.propagate(ctx); log.debug("Wallet restored with seed words");
walletService.restoreSeedWords(seed,
() -> UserThread.execute(() -> {
log.debug("Wallet restored with seed words");
new Popup() new Popup()
.information("Wallet restored successfully with the new seed words.\n\n" + .information("Wallet restored successfully with the new seed words.\n\n" +
"You need to restart now the application.") "You need to restart now the application.")
.closeButtonText("Restart") .closeButtonText("Restart")
.onClose(() -> BitsquareApp.restartDownHandler.run()).show(); .onClose(() -> BitsquareApp.restartDownHandler.run()).show();
}), }),
throwable -> UserThread.execute(() -> { throwable -> UserThread.execute(() -> {
log.error(throwable.getMessage()); log.error(throwable.getMessage());
new Popup() new Popup()
.headLine("Wrong password") .headLine("Wrong password")
.warning("Please try entering your password again, carefully checking for typos or spelling errors.") .warning("Please try entering your password again, carefully checking for typos or spelling errors.")
.show(); .show();
new Popup() new Popup()
.error("An error occurred when restoring the wallet with seed words.\n" + .error("An error occurred when restoring the wallet with seed words.\n" +
"Error message: " + throwable.getMessage()) "Error message: " + throwable.getMessage())
.show(); .show();
})); }));
}, "Restore wallet thread").start();
} }
} }

View file

@ -3,6 +3,7 @@ package io.bitsquare.p2p;
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.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import io.bitsquare.app.ProgramArguments; import io.bitsquare.app.ProgramArguments;
@ -35,8 +36,7 @@ import java.io.File;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.CopyOnWriteArrayList;
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;
@ -47,15 +47,20 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class P2PService { public class P2PService {
private static final Logger log = LoggerFactory.getLogger(P2PService.class); private static final Logger log = LoggerFactory.getLogger(P2PService.class);
private final SeedNodesRepository seedNodesRepository;
private final int port;
private final File torDir;
private final boolean useLocalhost;
@Nullable @Nullable
private final EncryptionService encryptionService; private final EncryptionService encryptionService;
private final SetupListener setupListener; private SetupListener setupListener;
private KeyRing keyRing; private KeyRing keyRing;
private final File storageDir;
private final NetworkStatistics networkStatistics; private final NetworkStatistics networkStatistics;
private final NetworkNode networkNode; private NetworkNode networkNode;
private final Routing routing; private Routing routing;
private final ProtectedExpirableDataStorage dataStorage; private ProtectedExpirableDataStorage dataStorage;
private final List<DecryptedMailListener> decryptedMailListeners = new CopyOnWriteArrayList<>(); private final List<DecryptedMailListener> decryptedMailListeners = new CopyOnWriteArrayList<>();
private final List<DecryptedMailboxListener> decryptedMailboxListeners = new CopyOnWriteArrayList<>(); private final List<DecryptedMailboxListener> decryptedMailboxListeners = new CopyOnWriteArrayList<>();
private final List<P2PServiceListener> p2pServiceListeners = new CopyOnWriteArrayList<>(); private final List<P2PServiceListener> p2pServiceListeners = new CopyOnWriteArrayList<>();
@ -73,7 +78,7 @@ public class P2PService {
private boolean allSeedNodesRequested; private boolean allSeedNodesRequested;
private Timer sendGetAllDataMessageTimer; private Timer sendGetAllDataMessageTimer;
private volatile boolean hiddenServiceReady; private volatile boolean hiddenServiceReady;
private final ExecutorService executorService;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -87,11 +92,27 @@ public class P2PService {
@Nullable EncryptionService encryptionService, @Nullable EncryptionService encryptionService,
KeyRing keyRing, KeyRing keyRing,
@Named("storage.dir") File storageDir) { @Named("storage.dir") File storageDir) {
this.seedNodesRepository = seedNodesRepository;
this.port = port;
this.torDir = torDir;
this.useLocalhost = useLocalhost;
this.encryptionService = encryptionService; this.encryptionService = encryptionService;
this.keyRing = keyRing; this.keyRing = keyRing;
this.storageDir = storageDir;
networkStatistics = new NetworkStatistics(); networkStatistics = new NetworkStatistics();
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("P2PService-%d")
.setDaemon(true)
.build();
executorService = new ThreadPoolExecutor(5, 50, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), threadFactory);
init();
}
private void init() {
// network layer // network layer
if (useLocalhost) { if (useLocalhost) {
networkNode = new LocalhostNetworkNode(port); networkNode = new LocalhostNetworkNode(port);
@ -578,7 +599,12 @@ public class P2PService {
sendGetAllDataMessageTimer.schedule(new TimerTask() { sendGetAllDataMessageTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
sendGetAllDataMessage(remainingSeedNodeAddresses); try {
sendGetAllDataMessage(remainingSeedNodeAddresses);
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
} }
}, new Random().nextInt(2000) + 1000); }, new Random().nextInt(2000) + 1000);
} else { } else {

View file

@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -37,11 +38,13 @@ public class Utils {
public static void shutDownExecutorService(ExecutorService executorService, long waitBeforeShutDown) { public static void shutDownExecutorService(ExecutorService executorService, long waitBeforeShutDown) {
executorService.shutdown(); executorService.shutdown();
try { try {
executorService.awaitTermination(waitBeforeShutDown, TimeUnit.MILLISECONDS); boolean done = executorService.awaitTermination(waitBeforeShutDown, TimeUnit.MILLISECONDS);
if (!done) log.trace("Not all tasks completed at shutdown.");
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
executorService.shutdownNow(); final List<Runnable> rejected = executorService.shutdownNow();
log.debug("Rejected tasks: {}", rejected.size());
} }
public static byte[] compress(Serializable input) { public static byte[] compress(Serializable input) {

View file

@ -1,10 +1,13 @@
package io.bitsquare.p2p.network; package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.common.util.concurrent.Uninterruptibles;
import io.bitsquare.common.ByteArrayUtils; import io.bitsquare.common.ByteArrayUtils;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Message; import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Utils; import io.bitsquare.p2p.Utils;
import io.bitsquare.p2p.network.messages.CloseConnectionMessage; import io.bitsquare.p2p.network.messages.CloseConnectionMessage;
import javafx.concurrent.Task;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -16,9 +19,7 @@ import java.net.SocketTimeoutException;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Connection { public class Connection {
private static final Logger log = LoggerFactory.getLogger(Connection.class); private static final Logger log = LoggerFactory.getLogger(Connection.class);
@ -37,18 +38,18 @@ public class Connection {
private final String uid; private final String uid;
private final Map<IllegalRequest, Integer> illegalRequests = new ConcurrentHashMap<>(); private final Map<IllegalRequest, Integer> illegalRequests = new ConcurrentHashMap<>();
private final ExecutorService executorService = Executors.newCachedThreadPool(); private final ExecutorService executorService;
private ObjectOutputStream out; private ObjectOutputStream out;
private ObjectInputStream in; private ObjectInputStream in;
@Nullable
private Address peerAddress;
private boolean isAuthenticated;
private volatile boolean stopped; private volatile boolean stopped;
private volatile boolean shutDownInProgress; private volatile boolean shutDownInProgress;
private volatile boolean inputHandlerStopped; private volatile boolean inputHandlerStopped;
private volatile Date lastActivityDate; private volatile Date lastActivityDate;
@Nullable
private Address peerAddress;
private boolean isAuthenticated;
//TODO got java.util.zip.DataFormatException: invalid distance too far back //TODO got java.util.zip.DataFormatException: invalid distance too far back
@ -69,6 +70,17 @@ public class Connection {
uid = UUID.randomUUID().toString(); uid = UUID.randomUUID().toString();
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("Connection-%d")
.setDaemon(true)
.build();
executorService = new ThreadPoolExecutor(5, 50, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), threadFactory);
init();
}
private void init() {
try { try {
socket.setSoTimeout(SOCKET_TIMEOUT); socket.setSoTimeout(SOCKET_TIMEOUT);
// Need to access first the ObjectOutputStream otherwise the ObjectInputStream would block // Need to access first the ObjectOutputStream otherwise the ObjectInputStream would block
@ -203,12 +215,8 @@ public class Connection {
if (sendCloseConnectionMessage) { if (sendCloseConnectionMessage) {
sendMessage(new CloseConnectionMessage()); sendMessage(new CloseConnectionMessage());
try { // give a bit of time for closing gracefully
// give a bit of time for closing gracefully Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
} }
try { try {
@ -296,61 +304,79 @@ public class Connection {
private class InputHandler implements Runnable { private class InputHandler implements Runnable {
@Override @Override
public void run() { public void run() {
Thread.currentThread().setName("InputHandler-" + socket.getLocalPort()); try {
while (!inputHandlerStopped) { Thread.currentThread().setName("InputHandler-" + socket.getLocalPort());
try { while (!inputHandlerStopped) {
log.trace("InputHandler waiting for incoming messages connection=" + Connection.this.getObjectId()); try {
Object rawInputObject = in.readObject(); log.trace("InputHandler waiting for incoming messages connection=" + Connection.this.getObjectId());
log.trace("New data arrived at inputHandler of connection=" + Connection.this.toString() Object rawInputObject = in.readObject();
+ " rawInputObject " + rawInputObject); log.trace("New data arrived at inputHandler of connection=" + Connection.this.toString()
+ " rawInputObject " + rawInputObject);
int size = ByteArrayUtils.objectToByteArray(rawInputObject).length; int size = ByteArrayUtils.objectToByteArray(rawInputObject).length;
if (size <= MAX_MSG_SIZE) {
Serializable serializable = null;
if (useCompression) {
if (rawInputObject instanceof byte[]) {
byte[] compressedObjectAsBytes = (byte[]) rawInputObject;
size = compressedObjectAsBytes.length;
//log.trace("Read object compressed data size: " + size);
serializable = Utils.decompress(compressedObjectAsBytes);
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
} else {
if (rawInputObject instanceof Serializable) {
serializable = (Serializable) rawInputObject;
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
}
//log.trace("Read object decompressed data size: " + ByteArrayUtils.objectToByteArray(serializable).length);
// compressed size might be bigger theoretically so we check again after decompression
if (size <= MAX_MSG_SIZE) { if (size <= MAX_MSG_SIZE) {
if (serializable instanceof Message) { Serializable serializable = null;
lastActivityDate = new Date(); if (useCompression) {
Message message = (Message) serializable; if (rawInputObject instanceof byte[]) {
if (message instanceof CloseConnectionMessage) { byte[] compressedObjectAsBytes = (byte[]) rawInputObject;
inputHandlerStopped = true; size = compressedObjectAsBytes.length;
shutDown(false); //log.trace("Read object compressed data size: " + size);
serializable = Utils.decompress(compressedObjectAsBytes);
} else { } else {
executorService.submit(() -> messageListener.onMessage(message, Connection.this)); reportIllegalRequest(IllegalRequest.InvalidDataType);
} }
} else { } else {
reportIllegalRequest(IllegalRequest.InvalidDataType); if (rawInputObject instanceof Serializable) {
serializable = (Serializable) rawInputObject;
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
}
//log.trace("Read object decompressed data size: " + ByteArrayUtils.objectToByteArray(serializable).length);
// compressed size might be bigger theoretically so we check again after decompression
if (size <= MAX_MSG_SIZE) {
if (serializable instanceof Message) {
lastActivityDate = new Date();
Message message = (Message) serializable;
if (message instanceof CloseConnectionMessage) {
inputHandlerStopped = true;
shutDown(false);
} else {
Task task = new Task() {
@Override
protected Object call() throws Exception {
return null;
}
};
executorService.submit(() -> {
try {
messageListener.onMessage(message, Connection.this);
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
});
}
} else {
reportIllegalRequest(IllegalRequest.InvalidDataType);
}
} else {
log.error("Received decompressed data exceeds max. msg size.");
reportIllegalRequest(IllegalRequest.MaxSizeExceeded);
} }
} else { } else {
log.error("Received decompressed data exceeds max. msg size."); log.error("Received compressed data exceeds max. msg size.");
reportIllegalRequest(IllegalRequest.MaxSizeExceeded); reportIllegalRequest(IllegalRequest.MaxSizeExceeded);
} }
} else { } catch (IOException | ClassNotFoundException e) {
log.error("Received compressed data exceeds max. msg size."); inputHandlerStopped = true;
reportIllegalRequest(IllegalRequest.MaxSizeExceeded); handleConnectionException(e);
} }
} catch (IOException | ClassNotFoundException e) {
inputHandlerStopped = true;
handleConnectionException(e);
} }
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
} }
} }
} }

View file

@ -1,9 +1,6 @@
package io.bitsquare.p2p.network; package io.bitsquare.p2p.network;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.*;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext; import com.msopentech.thali.java.toronionproxy.JavaOnionProxyContext;
import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager; import com.msopentech.thali.java.toronionproxy.JavaOnionProxyManager;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
@ -19,6 +16,7 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
public class LocalhostNetworkNode extends NetworkNode { public class LocalhostNetworkNode extends NetworkNode {
@ -92,8 +90,9 @@ public class LocalhostNetworkNode extends NetworkNode {
private void createTorNode(final Consumer<TorNode> resultHandler) { private void createTorNode(final Consumer<TorNode> resultHandler) {
Callable<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> task = () -> { Callable<TorNode<JavaOnionProxyManager, JavaOnionProxyContext>> task = () -> {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
log.trace("[simulation] Create TorNode"); if (simulateTorDelayTorNode > 0)
if (simulateTorDelayTorNode > 0) Thread.sleep(simulateTorDelayTorNode); Uninterruptibles.sleepUninterruptibly(simulateTorDelayTorNode, TimeUnit.MILLISECONDS);
log.info("\n\n############################################################\n" + log.info("\n\n############################################################\n" +
"TorNode created [simulation]:" + "TorNode created [simulation]:" +
"\nTook " + (System.currentTimeMillis() - ts) + " ms" "\nTook " + (System.currentTimeMillis() - ts) + " ms"
@ -115,8 +114,9 @@ public class LocalhostNetworkNode extends NetworkNode {
private void createHiddenService(final Consumer<HiddenServiceDescriptor> resultHandler) { private void createHiddenService(final Consumer<HiddenServiceDescriptor> resultHandler) {
Callable<HiddenServiceDescriptor> task = () -> { Callable<HiddenServiceDescriptor> task = () -> {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
log.debug("[simulation] Create hidden service"); if (simulateTorDelayHiddenService > 0)
if (simulateTorDelayHiddenService > 0) Thread.sleep(simulateTorDelayHiddenService); Uninterruptibles.sleepUninterruptibly(simulateTorDelayHiddenService, TimeUnit.MILLISECONDS);
log.info("\n\n############################################################\n" + log.info("\n\n############################################################\n" +
"Hidden service created [simulation]:" + "Hidden service created [simulation]:" +
"\nTook " + (System.currentTimeMillis() - ts) + " ms" "\nTook " + (System.currentTimeMillis() - ts) + " ms"

View file

@ -58,71 +58,77 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener
final SettableFuture<Connection> resultFuture = SettableFuture.create(); final SettableFuture<Connection> resultFuture = SettableFuture.create();
Callable<Connection> task = () -> { Callable<Connection> task = () -> {
Thread.currentThread().setName("Outgoing-connection-to-" + peerAddress); try {
Thread.currentThread().setName("Outgoing-connection-to-" + peerAddress);
Optional<Connection> outboundConnectionOptional = getOutboundConnection(peerAddress); Optional<Connection> outboundConnectionOptional = getOutboundConnection(peerAddress);
Connection connection = outboundConnectionOptional.isPresent() ? outboundConnectionOptional.get() : null; Connection connection = outboundConnectionOptional.isPresent() ? outboundConnectionOptional.get() : null;
if (connection != null && connection.isStopped()) { if (connection != null && connection.isStopped()) {
log.trace("We have a connection which is already stopped in outBoundConnections. Connection.uid=" + connection.getUid()); log.trace("We have a connection which is already stopped in outBoundConnections. Connection.uid=" + connection.getUid());
outBoundConnections.remove(connection); outBoundConnections.remove(connection);
connection = null; connection = null;
}
if (connection == null) {
Optional<Connection> inboundConnectionOptional = getInboundConnection(peerAddress);
if (inboundConnectionOptional.isPresent()) connection = inboundConnectionOptional.get();
if (connection != null)
log.trace("We have found a connection in inBoundConnections. Connection.uid=" + connection.getUid());
}
if (connection == null) {
try {
Socket socket = getSocket(peerAddress); // can take a while when using tor
connection = new Connection(socket,
(message1, connection1) -> NetworkNode.this.onMessage(message1, connection1),
new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
NetworkNode.this.onConnection(connection);
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
NetworkNode.this.onPeerAddressAuthenticated(peerAddress, connection);
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
log.trace("onDisconnect at outgoing connection to peerAddress " + peerAddress);
NetworkNode.this.onDisconnect(reason, connection);
}
@Override
public void onError(Throwable throwable) {
NetworkNode.this.onError(throwable);
}
});
if (!outBoundConnections.contains(connection))
outBoundConnections.add(connection);
else
log.error("We have already that connection in our list. That must not happen. "
+ outBoundConnections + " / connection=" + connection);
log.info("\n\nNetworkNode created new outbound connection:"
+ "\npeerAddress=" + peerAddress.port
+ "\nconnection.uid=" + connection.getUid()
+ "\nmessage=" + message
+ "\n\n");
} catch (Throwable t) {
resultFuture.setException(t);
return null;
} }
if (connection == null) {
Optional<Connection> inboundConnectionOptional = getInboundConnection(peerAddress);
if (inboundConnectionOptional.isPresent()) connection = inboundConnectionOptional.get();
if (connection != null)
log.trace("We have found a connection in inBoundConnections. Connection.uid=" + connection.getUid());
}
if (connection == null) {
try {
Socket socket = getSocket(peerAddress); // can take a while when using tor
connection = new Connection(socket,
(message1, connection1) -> NetworkNode.this.onMessage(message1, connection1),
new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
NetworkNode.this.onConnection(connection);
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
NetworkNode.this.onPeerAddressAuthenticated(peerAddress, connection);
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
log.trace("onDisconnect at outgoing connection to peerAddress " + peerAddress);
NetworkNode.this.onDisconnect(reason, connection);
}
@Override
public void onError(Throwable throwable) {
NetworkNode.this.onError(throwable);
}
});
if (!outBoundConnections.contains(connection))
outBoundConnections.add(connection);
else
log.error("We have already that connection in our list. That must not happen. "
+ outBoundConnections + " / connection=" + connection);
log.info("\n\nNetworkNode created new outbound connection:"
+ "\npeerAddress=" + peerAddress.port
+ "\nconnection.uid=" + connection.getUid()
+ "\nmessage=" + message
+ "\n\n");
} catch (Throwable t) {
resultFuture.setException(t);
return null;
}
}
connection.sendMessage(message);
return connection;
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
throw t;
} }
connection.sendMessage(message);
return connection;
}; };
ListenableFuture<Connection> future = executorService.submit(task); ListenableFuture<Connection> future = executorService.submit(task);

View file

@ -1,6 +1,5 @@
package io.bitsquare.p2p.network; package io.bitsquare.p2p.network;
import io.bitsquare.p2p.Utils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -10,8 +9,6 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server implements Runnable { public class Server implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Server.class); private static final Logger log = LoggerFactory.getLogger(Server.class);
@ -19,7 +16,6 @@ public class Server implements Runnable {
private final ServerSocket serverSocket; private final ServerSocket serverSocket;
private final MessageListener messageListener; private final MessageListener messageListener;
private final ConnectionListener connectionListener; private final ConnectionListener connectionListener;
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final List<Connection> connections = new CopyOnWriteArrayList<>(); private final List<Connection> connections = new CopyOnWriteArrayList<>();
private volatile boolean stopped; private volatile boolean stopped;
@ -32,25 +28,30 @@ public class Server implements Runnable {
@Override @Override
public void run() { public void run() {
Thread.currentThread().setName("Server-" + serverSocket.getLocalPort()); try {
while (!stopped) { Thread.currentThread().setName("Server-" + serverSocket.getLocalPort());
try { while (!stopped) {
log.info("Ready to accept new clients on port " + serverSocket.getLocalPort()); try {
final Socket socket = serverSocket.accept(); log.info("Ready to accept new clients on port " + serverSocket.getLocalPort());
log.info("Accepted new client on port " + socket.getLocalPort()); final Socket socket = serverSocket.accept();
Connection connection = new Connection(socket, messageListener, connectionListener); log.info("Accepted new client on port " + socket.getLocalPort());
log.info("\n\nServer created new inbound connection:" Connection connection = new Connection(socket, messageListener, connectionListener);
+ "\nserverSocket.getLocalPort()=" + serverSocket.getLocalPort() log.info("\n\nServer created new inbound connection:"
+ "\nsocket.getPort()=" + socket.getPort() + "\nserverSocket.getLocalPort()=" + serverSocket.getLocalPort()
+ "\nconnection.uid=" + connection.getUid() + "\nsocket.getPort()=" + socket.getPort()
+ "\n\n"); + "\nconnection.uid=" + connection.getUid()
+ "\n\n");
log.info("Server created new socket with port " + socket.getPort()); log.info("Server created new socket with port " + socket.getPort());
connections.add(connection); connections.add(connection);
} catch (IOException e) { } catch (IOException e) {
if (!stopped) if (!stopped)
e.printStackTrace(); e.printStackTrace();
}
} }
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
} }
} }
@ -67,7 +68,6 @@ public class Server implements Runnable {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
Utils.shutDownExecutorService(executorService);
log.debug("Server shutdown complete"); log.debug("Server shutdown complete");
} }
} }

View file

@ -21,6 +21,7 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -60,48 +61,62 @@ public class TorNetworkNode extends NetworkNode {
this.torDir = torDir; this.torDir = torDir;
init();
}
private void init() {
selfTestTimeoutTask = new TimerTask() { selfTestTimeoutTask = new TimerTask() {
@Override @Override
public void run() { public void run() {
log.error("A timeout occurred at self test"); try {
stopSelfTestTimer(); log.error("A timeout occurred at self test");
selfTestFailed(); stopSelfTestTimer();
selfTestFailed();
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
} }
}; };
selfTestTask = new TimerTask() { selfTestTask = new TimerTask() {
@Override @Override
public void run() { public void run() {
stopTimeoutTimer(); try {
if (selfTestRunning.get()) { stopTimeoutTimer();
log.debug("running self test");
selfTestTimeoutTimer = new Timer();
selfTestTimeoutTimer.schedule(selfTestTimeoutTask, TIMEOUT);
// might be interrupted by timeout task
if (selfTestRunning.get()) { if (selfTestRunning.get()) {
nonce = random.nextLong(); log.debug("running self test");
log.trace("send msg with nonce " + nonce); selfTestTimeoutTimer = new Timer();
selfTestTimeoutTimer.schedule(selfTestTimeoutTask, TIMEOUT);
// might be interrupted by timeout task
if (selfTestRunning.get()) {
nonce = random.nextLong();
log.trace("send msg with nonce " + nonce);
try { try {
SettableFuture<Connection> future = sendMessage(new Address(hiddenServiceDescriptor.getFullAddress()), new SelfTestMessage(nonce)); SettableFuture<Connection> future = sendMessage(new Address(hiddenServiceDescriptor.getFullAddress()), new SelfTestMessage(nonce));
Futures.addCallback(future, new FutureCallback<Connection>() { Futures.addCallback(future, new FutureCallback<Connection>() {
@Override @Override
public void onSuccess(Connection connection) { public void onSuccess(Connection connection) {
log.trace("Sending self test message succeeded"); log.trace("Sending self test message succeeded");
} }
@Override @Override
public void onFailure(Throwable throwable) { public void onFailure(Throwable throwable) {
log.error("Error at sending self test message. Exception = " + throwable); log.error("Error at sending self test message. Exception = " + throwable);
stopTimeoutTimer(); stopTimeoutTimer();
throwable.printStackTrace(); throwable.printStackTrace();
selfTestFailed(); selfTestFailed();
} }
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
}
} }
} }
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
} }
} }
}; };
@ -144,11 +159,7 @@ public class TorNetworkNode extends NetworkNode {
TorNetworkNode.this.hiddenServiceDescriptor = hiddenServiceDescriptor; TorNetworkNode.this.hiddenServiceDescriptor = hiddenServiceDescriptor;
startServer(hiddenServiceDescriptor.getServerSocket()); startServer(hiddenServiceDescriptor.getServerSocket());
try { Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS);
Thread.sleep(500);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
setupListeners.stream().forEach(e -> e.onHiddenServiceReady()); setupListeners.stream().forEach(e -> e.onHiddenServiceReady());
@ -252,12 +263,7 @@ public class TorNetworkNode extends NetworkNode {
restartCounter++; restartCounter++;
if (restartCounter <= MAX_RESTART_ATTEMPTS) { if (restartCounter <= MAX_RESTART_ATTEMPTS) {
shutDown(() -> { shutDown(() -> {
try { Uninterruptibles.sleepUninterruptibly(WAIT_BEFORE_RESTART, TimeUnit.MILLISECONDS);
Thread.sleep(WAIT_BEFORE_RESTART);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
log.warn("We restart tor as too many self tests failed."); log.warn("We restart tor as too many self tests failed.");
start(null); start(null);
}); });

View file

@ -1,8 +1,6 @@
package io.bitsquare.p2p.routing; package io.bitsquare.p2p.routing;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.*;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import io.bitsquare.common.UserThread; import io.bitsquare.common.UserThread;
import io.bitsquare.p2p.Address; import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.Utils; import io.bitsquare.p2p.Utils;
@ -15,10 +13,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Routing { public class Routing {
@ -47,7 +42,7 @@ public class Routing {
private final List<Address> reportedNeighborAddresses = new CopyOnWriteArrayList<>(); private final List<Address> reportedNeighborAddresses = new CopyOnWriteArrayList<>();
private final Map<Address, Runnable> authenticationCompleteHandlers = new ConcurrentHashMap<>(); private final Map<Address, Runnable> authenticationCompleteHandlers = new ConcurrentHashMap<>();
private final Timer maintenanceTimer = new Timer(); private final Timer maintenanceTimer = new Timer();
private final ExecutorService executorService = Executors.newCachedThreadPool(); private final ExecutorService executorService;
private volatile boolean shutDownInProgress; private volatile boolean shutDownInProgress;
@ -61,6 +56,17 @@ public class Routing {
// We copy it as we remove ourselves later from the list if we are a seed node // We copy it as we remove ourselves later from the list if we are a seed node
this.seedNodes = new CopyOnWriteArrayList<>(seeds); this.seedNodes = new CopyOnWriteArrayList<>(seeds);
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("Routing-%d")
.setDaemon(true)
.build();
executorService = new ThreadPoolExecutor(5, 50, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50), threadFactory);
init(networkNode);
}
private void init(NetworkNode networkNode) {
networkNode.addMessageListener((message, connection) -> { networkNode.addMessageListener((message, connection) -> {
if (message instanceof AuthenticationMessage) if (message instanceof AuthenticationMessage)
processAuthenticationMessage((AuthenticationMessage) message, connection); processAuthenticationMessage((AuthenticationMessage) message, connection);
@ -110,8 +116,13 @@ public class Routing {
maintenanceTimer.scheduleAtFixedRate(new TimerTask() { maintenanceTimer.scheduleAtFixedRate(new TimerTask() {
@Override @Override
public void run() { public void run() {
disconnectOldConnections(); try {
pingNeighbors(); disconnectOldConnections();
pingNeighbors();
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
} }
}, MAINTENANCE_INTERVAL, MAINTENANCE_INTERVAL); }, MAINTENANCE_INTERVAL, MAINTENANCE_INTERVAL);
} }
@ -126,11 +137,7 @@ public class Routing {
Connection connection = authenticatedConnections.remove(0); Connection connection = authenticatedConnections.remove(0);
log.info("Shutdown oldest connection with last activity date=" + connection.getLastActivityDate() + " / connection=" + connection); log.info("Shutdown oldest connection with last activity date=" + connection.getLastActivityDate() + " / connection=" + connection);
connection.shutDown(() -> disconnectOldConnections()); connection.shutDown(() -> disconnectOldConnections());
try { Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS);
Thread.sleep(200);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
} }
} }
@ -153,11 +160,7 @@ public class Routing {
removeNeighbor(e.address); removeNeighbor(e.address);
} }
}); });
try { Uninterruptibles.sleepUninterruptibly(new Random().nextInt(5000) + 5000, TimeUnit.MILLISECONDS);
Thread.sleep(new Random().nextInt(5000) + 5000);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}); });
} }
@ -247,16 +250,16 @@ public class Routing {
public void startAuthentication(List<Address> connectedSeedNodes) { public void startAuthentication(List<Address> connectedSeedNodes) {
connectedSeedNodes.forEach(connectedSeedNode -> { connectedSeedNodes.forEach(connectedSeedNode -> {
executorService.submit(() -> { executorService.submit(() -> {
sendRequestAuthenticationMessage(seedNodes, connectedSeedNode);
try { try {
sendRequestAuthenticationMessage(seedNodes, connectedSeedNode);
// give a random pause of 3-5 sec. before using the next // give a random pause of 3-5 sec. before using the next
Thread.sleep(new Random().nextInt(2000) + 3000); Uninterruptibles.sleepUninterruptibly(new Random().nextInt(2000) + 3000, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) { } catch (Throwable t) {
Thread.currentThread().interrupt(); t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
} }
}); });
}); });
} }
private void sendRequestAuthenticationMessage(final List<Address> remainingSeedNodes, final Address address) { private void sendRequestAuthenticationMessage(final List<Address> remainingSeedNodes, final Address address) {
@ -315,17 +318,11 @@ public class Routing {
connection.shutDown(() -> { connection.shutDown(() -> {
// we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to // we delay a bit as listeners for connection.onDisconnect are on other threads and might lead to
// inconsistent state (removal of connection from NetworkNode.authenticatedConnections) // inconsistent state (removal of connection from NetworkNode.authenticatedConnections)
try { Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
Thread.sleep(100);
} catch (InterruptedException e) { if (simulateAuthTorNode > 0)
Thread.currentThread().interrupt(); Uninterruptibles.sleepUninterruptibly(simulateAuthTorNode, TimeUnit.MILLISECONDS);
}
try {
if (simulateAuthTorNode > 0) Thread.sleep(simulateAuthTorNode);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
log.trace("processAuthenticationMessage: connection.shutDown complete. RequestAuthenticationMessage from " + peerAddress + " at " + getAddress()); log.trace("processAuthenticationMessage: connection.shutDown complete. RequestAuthenticationMessage from " + peerAddress + " at " + getAddress());
long nonce = addToMapAndGetNonce(peerAddress); long nonce = addToMapAndGetNonce(peerAddress);
SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new ChallengeMessage(getAddress(), requestAuthenticationMessage.nonce, nonce)); SettableFuture<Connection> future = networkNode.sendMessage(peerAddress, new ChallengeMessage(getAddress(), requestAuthenticationMessage.nonce, nonce));
@ -413,25 +410,25 @@ public class Routing {
ArrayList<Address> neighborAddresses = ((GetNeighborsMessage) message).neighborAddresses; ArrayList<Address> neighborAddresses = ((GetNeighborsMessage) message).neighborAddresses;
log.trace("Received neighbors: " + neighborAddresses); log.trace("Received neighbors: " + neighborAddresses);
// remove ourselves // remove ourselves
neighborAddresses.remove(getAddress());
addToReportedNeighbors(neighborAddresses, connection); addToReportedNeighbors(neighborAddresses, connection);
} }
} else if (message instanceof NeighborsMessage) { } else if (message instanceof NeighborsMessage) {
log.trace("NeighborsMessage from " + connection.getPeerAddress() + " at " + getAddress());
NeighborsMessage neighborsMessage = (NeighborsMessage) message; NeighborsMessage neighborsMessage = (NeighborsMessage) message;
Address peerAddress = neighborsMessage.address;
log.trace("NeighborsMessage from " + peerAddress + " at " + getAddress());
ArrayList<Address> neighborAddresses = neighborsMessage.neighborAddresses; ArrayList<Address> neighborAddresses = neighborsMessage.neighborAddresses;
log.trace("Received neighbors: " + neighborAddresses); log.trace("Received neighbors: " + neighborAddresses);
// remove ourselves // remove ourselves
neighborAddresses.remove(getAddress());
addToReportedNeighbors(neighborAddresses, connection); addToReportedNeighbors(neighborAddresses, connection);
log.info("\n\nAuthenticationComplete\nPeer with address " + connection.getPeerAddress().toString()
+ " authenticated (" + connection.getObjectId() + "). Took "
+ (System.currentTimeMillis() - startAuthTs) + " ms. \n\n");
// we wait until the handshake is completed before setting the authenticate flag // we wait until the handshake is completed before setting the authenticate flag
// authentication at both sides of the connection // authentication at both sides of the connection
setAuthenticated(connection, neighborsMessage.address);
log.info("\n\nAuthenticationComplete\nPeer with address " + peerAddress
+ " authenticated (" + connection.getObjectId() + "). Took "
+ (System.currentTimeMillis() - startAuthTs) + " ms. \n\n");
setAuthenticated(connection, peerAddress);
Runnable authenticationCompleteHandler = authenticationCompleteHandlers.remove(connection.getPeerAddress()); Runnable authenticationCompleteHandler = authenticationCompleteHandlers.remove(connection.getPeerAddress());
if (authenticationCompleteHandler != null) if (authenticationCompleteHandler != null)
@ -442,18 +439,21 @@ public class Routing {
} }
private void addToReportedNeighbors(ArrayList<Address> neighborAddresses, Connection connection) { private void addToReportedNeighbors(ArrayList<Address> neighborAddresses, Connection connection) {
log.trace("addToReportedNeighbors");
// we disconnect misbehaving nodes trying to send too many neighbors // we disconnect misbehaving nodes trying to send too many neighbors
// reported neighbors include the peers connected neighbors which is normally max. 8 but we give some headroom // reported neighbors include the peers connected neighbors which is normally max. 8 but we give some headroom
// for safety // for safety
if (neighborAddresses.size() > 1100) { if (neighborAddresses.size() > 1100) {
connection.shutDown(); connection.shutDown();
} else { } else {
neighborAddresses.remove(getAddress());
reportedNeighborAddresses.addAll(neighborAddresses); reportedNeighborAddresses.addAll(neighborAddresses);
purgeReportedNeighbors(); purgeReportedNeighbors();
} }
} }
private void purgeReportedNeighbors() { private void purgeReportedNeighbors() {
log.trace("purgeReportedNeighbors");
int all = getAllNeighborAddresses().size(); int all = getAllNeighborAddresses().size();
if (all > 1000) { if (all > 1000) {
int diff = all - 100; int diff = all - 100;
@ -478,20 +478,21 @@ public class Routing {
private void authenticateToNextRandomNeighbor() { private void authenticateToNextRandomNeighbor() {
executorService.submit(() -> { executorService.submit(() -> {
try { try {
Thread.sleep(new Random().nextInt(200) + 200); Uninterruptibles.sleepUninterruptibly(new Random().nextInt(200) + 200, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) { if (getConnectedNeighbors().size() <= MAX_CONNECTIONS) {
Thread.currentThread().interrupt(); Address randomNotConnectedNeighborAddress = getRandomNotConnectedNeighborAddress();
} if (randomNotConnectedNeighborAddress != null) {
if (getConnectedNeighbors().size() <= MAX_CONNECTIONS) { log.info("We try to build an authenticated connection to a random neighbor. " + randomNotConnectedNeighborAddress);
Address randomNotConnectedNeighborAddress = getRandomNotConnectedNeighborAddress(); authenticateToPeer(randomNotConnectedNeighborAddress, null, () -> authenticateToNextRandomNeighbor());
if (randomNotConnectedNeighborAddress != null) { } else {
log.info("We try to build an authenticated connection to a random neighbor. " + randomNotConnectedNeighborAddress); log.info("No more neighbors available for connecting.");
authenticateToPeer(randomNotConnectedNeighborAddress, null, () -> authenticateToNextRandomNeighbor()); }
} else { } else {
log.info("No more neighbors available for connecting."); log.info("We have already enough connections.");
} }
} else { } catch (Throwable t) {
log.info("We have already enough connections."); t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
} }
}); });
} }
@ -531,9 +532,8 @@ public class Routing {
private boolean verifyNonceAndAuthenticatePeerAddress(long peersNonce, Address peerAddress) { private boolean verifyNonceAndAuthenticatePeerAddress(long peersNonce, Address peerAddress) {
log.trace("verifyNonceAndAuthenticatePeerAddress nonceMap=" + nonceMap + " / peerAddress=" + peerAddress); log.trace("verifyNonceAndAuthenticatePeerAddress nonceMap=" + nonceMap + " / peerAddress=" + peerAddress);
long nonce = nonceMap.remove(peerAddress); Long nonce = nonceMap.remove(peerAddress);
boolean result = nonce == peersNonce; return nonce != null && nonce == peersNonce;
return result;
} }
private void setAuthenticated(Connection connection, Address peerAddress) { private void setAuthenticated(Connection connection, Address peerAddress) {

View file

@ -52,6 +52,10 @@ public class ProtectedExpirableDataStorage {
storage = new Storage<>(storageDir); storage = new Storage<>(storageDir);
init();
}
private void init() {
ConcurrentHashMap<BigInteger, Integer> persisted = storage.initAndGetPersisted(sequenceNumberMap, "sequenceNumberMap"); ConcurrentHashMap<BigInteger, Integer> persisted = storage.initAndGetPersisted(sequenceNumberMap, "sequenceNumberMap");
if (persisted != null) { if (persisted != null) {
sequenceNumberMap = persisted; sequenceNumberMap = persisted;
@ -79,9 +83,14 @@ public class ProtectedExpirableDataStorage {
timer.scheduleAtFixedRate(new TimerTask() { timer.scheduleAtFixedRate(new TimerTask() {
@Override @Override
public void run() { public void run() {
log.info("removeExpiredEntries called "); try {
map.entrySet().stream().filter(entry -> entry.getValue().isExpired()) log.info("removeExpiredEntries called ");
.forEach(entry -> map.remove(entry.getKey())); map.entrySet().stream().filter(entry -> entry.getValue().isExpired())
.forEach(entry -> map.remove(entry.getKey()));
} catch (Throwable t) {
t.printStackTrace();
log.error("Executing task failed. " + t.getMessage());
}
} }
}, },
CHECK_TTL_INTERVAL, CHECK_TTL_INTERVAL,