bt.net.MessageDispatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-core Show documentation
Show all versions of bt-core Show documentation
BitTorrent Client Library (Core)
package bt.net;
import bt.metainfo.TorrentId;
import bt.protocol.Message;
import bt.runtime.Config;
import bt.service.IRuntimeLifecycleBinder;
import bt.torrent.TorrentDescriptor;
import bt.torrent.TorrentRegistry;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* Default single-threaded message dispatcher implementation.
*
*Note that this class implements a service.
* Hence, is not a part of the public API and is a subject to change.
*/
public class MessageDispatcher implements IMessageDispatcher {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageDispatcher.class);
private final Map>> consumers;
private final Map>> suppliers;
private TorrentRegistry torrentRegistry;
@Inject
public MessageDispatcher(IRuntimeLifecycleBinder lifecycleBinder,
IPeerConnectionPool pool,
TorrentRegistry torrentRegistry,
Config config) {
this.consumers = new ConcurrentHashMap<>();
this.suppliers = new ConcurrentHashMap<>();
this.torrentRegistry = torrentRegistry;
ExecutorService executor = Executors.newSingleThreadExecutor(r -> new Thread(r, "bt.net.message-dispatcher"));
Worker worker = new Worker(pool, config.getMaxMessageProcessingInterval());
lifecycleBinder.onStartup("Initialize message dispatcher", () -> executor.execute(worker));
lifecycleBinder.onShutdown("Shutdown message dispatcher worker", () -> {
try {
worker.shutdown();
} finally {
executor.shutdownNow();
}
});
}
private class Worker implements Runnable {
private IPeerConnectionPool pool;
private LoopControl loopControl;
private volatile boolean shutdown;
Worker(IPeerConnectionPool pool, Duration maxProcessingInterval) {
this.pool = pool;
this.loopControl = new LoopControl(maxProcessingInterval.toMillis());
}
@Override
public void run() {
while (!shutdown) {
if (!consumers.isEmpty()) {
Iterator>>> iter = consumers.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry>> entry = iter.next();
Peer peer = entry.getKey();
Collection> consumers = entry.getValue();
PeerConnection connection = pool.getConnection(peer);
if (connection != null && !connection.isClosed()) {
if (isSupportedAndActive(connection.getTorrentId())) {
Message message = null;
try {
message = connection.readMessageNow();
} catch (Exception e) {
LOGGER.error("Error when reading message from peer: " + peer, e);
iter.remove();
suppliers.remove(peer);
}
if (message != null) {
loopControl.incrementProcessed();
for (Consumer messageConsumer : consumers) {
try {
messageConsumer.accept(message);
} catch (Exception e) {
LOGGER.warn("Error in message consumer", e);
}
}
}
}
} else {
iter.remove();
suppliers.remove(peer);
}
}
}
if (!suppliers.isEmpty()) {
Iterator>>> iter = suppliers.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry>> entry = iter.next();
Peer peer = entry.getKey();
Collection> suppliers = entry.getValue();
PeerConnection connection = pool.getConnection(peer);
if (connection != null && !connection.isClosed()) {
if (isSupportedAndActive(connection.getTorrentId())) {
for (Supplier messageSupplier : suppliers) {
Message message = null;
try {
message = messageSupplier.get();
} catch (Exception e) {
LOGGER.warn("Error in message supplier", e);
}
if (message != null) {
loopControl.incrementProcessed();
try {
connection.postMessage(message);
} catch (Exception e) {
LOGGER.error("Error when writing message", e);
iter.remove();
consumers.remove(peer);
}
}
}
}
} else {
iter.remove();
consumers.remove(peer);
}
}
}
loopControl.iterationFinished();
}
}
public void shutdown() {
shutdown = true;
}
}
private boolean isSupportedAndActive(TorrentId torrentId) {
Optional descriptor = torrentRegistry.getDescriptor(torrentId);
// it's OK if descriptor is not present -- torrent might be being fetched at the time
return torrentRegistry.getTorrentIds().contains(torrentId)
&& (!descriptor.isPresent() || descriptor.get().isActive());
}
/**
* Controls the amount of time to sleep after each iteration of the main message processing loop.
* It implements an adaptive strategy and increases the amount of time for the dispatcher to sleep
* after each iteration during which no messages were either received or sent.
* This strategy greatly reduces CPU load when there is little network activity.
*/
private static class LoopControl {
private ReentrantLock lock;
private Condition timer;
private long maxTimeToSleep;
private int messagesProcessed;
private long timeToSleep;
LoopControl(long maxTimeToSleep) {
this.maxTimeToSleep = maxTimeToSleep;
lock = new ReentrantLock();
timer = lock.newCondition();
}
void incrementProcessed() {
messagesProcessed++;
timeToSleep = 1;
}
void iterationFinished() {
if (messagesProcessed == 0) {
lock.lock();
try {
timer.await(timeToSleep, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// ignore
} finally {
lock.unlock();
}
timeToSleep = Math.min(timeToSleep << 1, maxTimeToSleep);
}
messagesProcessed = 0;
}
}
@Override
public synchronized void addMessageConsumer(Peer sender, Consumer messageConsumer) {
Collection> peerConsumers = consumers.get(sender);
if (peerConsumers == null) {
peerConsumers = ConcurrentHashMap.newKeySet();
consumers.put(sender, peerConsumers);
}
peerConsumers.add(messageConsumer);
}
@Override
public synchronized void addMessageSupplier(Peer recipient, Supplier messageSupplier) {
Collection> peerSuppliers = suppliers.get(recipient);
if (peerSuppliers == null) {
peerSuppliers = ConcurrentHashMap.newKeySet();
suppliers.put(recipient, peerSuppliers);
}
peerSuppliers.add(messageSupplier);
}
}