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

bt.net.PeerConnectionPool Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
/*
 * Copyright (c) 2016—2017 Andrei Tomashpolskiy and individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package bt.net;

import bt.CountingThreadFactory;
import bt.event.EventSink;
import bt.metainfo.TorrentId;
import bt.runtime.Config;
import bt.service.IRuntimeLifecycleBinder;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

/**
 *

Note that this class implements a service. * Hence, is not a part of the public API and is a subject to change.

*/ public class PeerConnectionPool implements IPeerConnectionPool { private static final Logger LOGGER = LoggerFactory.getLogger(PeerConnectionPool.class); private Config config; private EventSink eventSink; private ScheduledExecutorService cleaner; private Connections connections; private ReentrantLock cleanerLock; private Duration peerConnectionInactivityThreshold; @Inject public PeerConnectionPool( EventSink eventSink, IRuntimeLifecycleBinder lifecycleBinder, Config config) { this.config = config; this.eventSink = eventSink; this.peerConnectionInactivityThreshold = config.getPeerConnectionInactivityThreshold(); this.connections = new Connections(); this.cleanerLock = new ReentrantLock(); this.cleaner = Executors.newScheduledThreadPool(1, r -> new Thread(r, "bt.net.pool.cleaner")); lifecycleBinder.onStartup("Schedule periodic cleanup of stale peer connections", () -> cleaner.scheduleAtFixedRate(new Cleaner(), 1, 1, TimeUnit.SECONDS)); ExecutorService executor = Executors.newFixedThreadPool( config.getMaxPendingConnectionRequests(), CountingThreadFactory.daemonFactory("bt.net.pool.connection-worker")); lifecycleBinder.onShutdown("Shutdown outgoing connection request processor", executor::shutdownNow); lifecycleBinder.onShutdown("Shutdown connection pool", this::shutdown); } @Override public PeerConnection getConnection(Peer peer) { return connections.get(peer).orElse(null); } @Override public void visitConnections(TorrentId torrentId, Consumer visitor) { connections.visitConnections(torrentId, visitor); } @Override public int size() { return connections.count(); } @Override public PeerConnection addConnectionIfAbsent(PeerConnection newConnection) { Peer peer = newConnection.getRemotePeer(); if (!addConnection(newConnection)) { // check if it was already added simultaneously by another connection worker PeerConnection existingConnection = connections.get(peer) .orElseThrow(() -> new RuntimeException("Failed to add new connection for peer: " + peer)); if (existingConnection == null) { throw new RuntimeException("Failed to add new connection for peer: " + newConnection.getRemotePeer()); } newConnection = existingConnection; } return newConnection; } private boolean addConnection(PeerConnection newConnection) { boolean added = false; PeerConnection existingConnection = null; cleanerLock.lock(); try { if (connections.count() >= config.getMaxPeerConnections()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Closing newly created connection with {} due to exceeding of connections limit", newConnection.getRemotePeer()); } newConnection.closeQuietly(); } else { existingConnection = connections.putIfAbsent(newConnection.getRemotePeer(), newConnection); added = (existingConnection == null); } } finally { cleanerLock.unlock(); } if (existingConnection != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Connection already exists for peer: " + newConnection.getRemotePeer()); } newConnection.closeQuietly(); newConnection = existingConnection; } if (added) { eventSink.firePeerConnected(newConnection.getTorrentId(), newConnection.getRemotePeer()); } return added; } private class Cleaner implements Runnable { @Override public void run() { if (connections.count() == 0) { return; } cleanerLock.lock(); try { connections.visitConnections(connection -> { Peer peer = connection.getRemotePeer(); if (connection.isClosed()) { purgeConnectionWithPeer(peer); } else if (System.currentTimeMillis() - connection.getLastActive() >= peerConnectionInactivityThreshold.toMillis()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Removing inactive peer connection: {}", peer); } purgeConnectionWithPeer(peer); } // can send keep-alives here based on lastActiveTime }); } finally { cleanerLock.unlock(); } } } private void purgeConnectionWithPeer(Peer peer) { PeerConnection purged = connections.remove(peer); if (purged != null) { if (!purged.isClosed()) { purged.closeQuietly(); } eventSink.firePeerDisconnected(purged.getTorrentId(), peer); } } private void shutdown() { shutdownCleaner(); connections.visitConnections(PeerConnection::closeQuietly); } private void shutdownCleaner() { cleaner.shutdown(); try { cleaner.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { LOGGER.warn("Interrupted while waiting for the cleaner's shutdown"); } if (!cleaner.isShutdown()) { cleaner.shutdownNow(); } } private static class Connections { private ConcurrentMap connections; private ConcurrentMap> connectionsByTorrent; Connections() { this.connections = new ConcurrentHashMap<>(); this.connectionsByTorrent = new ConcurrentHashMap<>(); } int count() { return connections.size(); } synchronized PeerConnection remove(Peer peer) { PeerConnection removed = connections.remove(peer); if (removed != null) { connectionsByTorrent.values().forEach(connections -> { connections.remove(removed); if (connections.isEmpty()) { connectionsByTorrent.remove(removed.getTorrentId()); } }); } return removed; } synchronized PeerConnection putIfAbsent(Peer peer, PeerConnection connection) { PeerConnection existing = connections.putIfAbsent(peer, connection); TorrentId torrentId = connection.getTorrentId(); if (existing == null && torrentId != null) { connectionsByTorrent.computeIfAbsent(torrentId, id -> ConcurrentHashMap.newKeySet()).add(connection); } return existing; } Optional get(Peer peer) { return Optional.ofNullable(connections.get(peer)); } void visitConnections(Consumer visitor) { connections.values().forEach(visitor::accept); } void visitConnections(TorrentId torrentId, Consumer visitor) { Collection connections = connectionsByTorrent.get(torrentId); if (connections != null) { connections.forEach(visitor::accept); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy