All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bt.net.MessageDispatcher Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy