Commit b89d1107 authored by Joris Guisson's avatar Joris Guisson
Browse files

Webseeds now follow connection limits

BUG: 184941
parent 38ace46a
......@@ -2,6 +2,7 @@ Changes in 1.3:
- Implement BEP 32, IPv6 DHT (295194)
- Revamp DHT code and follow spec more closely on routing table
- Revamp SingleFileCache, MultiFileCache and data preallocation
- Webseeds now follow connection limits (184941)
Changes in 1.2.1:
- Fix deadlock in webseeding code (295719)
......
......@@ -5,7 +5,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${LIBGCRYPT_INCLUDE_DIR} ${QCA2_INCLUDE_DIR})
remove_definitions(-DQT_NO_HTTP)
set(libktorrent_SRC
set(libktorrent_SRC
util/bitset.cpp
util/timer.cpp
util/urlencoder.cpp
......@@ -79,6 +79,7 @@ set(libktorrent_SRC
peer/badpeerslist.cpp
peer/peerconnector.cpp
peer/superseeder.cpp
peer/connectionlimit.cpp
download/piece.cpp
download/request.cpp
......
......@@ -20,11 +20,12 @@
#include "cache.h"
#include <util/functions.h>
#include <util/log.h>
#include <peer/connectionlimit.h>
#include <peer/peermanager.h>
#include <torrent/torrent.h>
#include "chunk.h"
#include "cachefile.h"
#include "piecedata.h"
#include <peer/peermanager.h>
namespace bt
{
......@@ -69,7 +70,7 @@ namespace bt
bool Cache::mappedModeAllowed()
{
#ifndef Q_WS_WIN
return MaxOpenFiles() - bt::PeerManager::getTotalConnections() > 100;
return MaxOpenFiles() - bt::PeerManager::connectionLimits().totalConnections() > 100;
#else
return true; //there isn't a file handle limit on windows
#endif
......
......@@ -29,6 +29,7 @@
#include <diskio/chunkmanager.h>
#include <diskio/piecedata.h>
#include <net/socketmonitor.h>
#include <peer/peermanager.h>
#include "httpconnection.h"
namespace bt
......@@ -126,6 +127,15 @@ namespace bt
void WebSeed::connectToServer()
{
if(!token)
token = PeerManager::connectionLimits().acquire(tor.getInfoHash());
if(!token)
{
retryLater();
return;
}
KUrl dst = url;
if (redirected_url.isValid())
dst = redirected_url;
......@@ -317,6 +327,7 @@ namespace bt
}
delete conn;
conn = 0;
token.clear();
chunkStopped();
first_chunk = last_chunk = cur_chunk = tor.getNumChunks() + 1;
num_failures++;
......@@ -332,6 +343,7 @@ namespace bt
Out(SYS_CON|LOG_DEBUG) << "WebSeed: connection closed" << endl;
delete conn;
conn = 0;
token.clear();
status = i18n("Connection closed");
chunkStopped();
......@@ -526,6 +538,7 @@ namespace bt
{
delete conn;
conn = 0;
token.clear();
if (to_url.isValid() && to_url.protocol() == "http")
{
redirected_url = to_url;
......
......@@ -21,14 +21,15 @@
#ifndef BTWEBSEED_H
#define BTWEBSEED_H
#include <QObject>
#include <QTimer>
#include <kurl.h>
#include <ktorrent_export.h>
#include <util/constants.h>
#include <interfaces/webseedinterface.h>
#include <interfaces/chunkdownloadinterface.h>
#include <diskio/piecedata.h>
#include <QTimer>
#include <peer/connectionlimit.h>
namespace bt
......@@ -196,6 +197,7 @@ namespace bt
KUrl redirected_url;
PieceData::Ptr cur_piece;
QTimer retry_timer;
ConnectionLimit::Token::Ptr token;
static QString proxy_host;
static Uint16 proxy_port;
......
......@@ -16,6 +16,7 @@ set (peer_HDR
accessmanager.h
peerconnector.h
superseeder.h
connectionlimit.h
)
......
/***************************************************************************
* Copyright (C) 2012 by *
* Joris Guisson <joris.guisson@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include "connectionlimit.h"
#include <util/functions.h>
namespace bt
{
static bt::Uint32 SystemConnectionLimit()
{
#ifndef Q_WS_WIN
return bt::MaxOpenFiles() - 50; // leave about 50 free for regular files
#else
return 9999; // there isn't a real limit on windows
#endif
}
ConnectionLimit::ConnectionLimit() :
global_limit(SystemConnectionLimit()),
global_total(0),
torrent_limit(0)
{
}
ConnectionLimit::~ConnectionLimit()
{
}
void ConnectionLimit::setLimits(Uint32 global_limit, Uint32 torrent_limit)
{
this->global_limit = global_limit;
this->torrent_limit = torrent_limit;
if(this->global_limit > SystemConnectionLimit())
this->global_limit = SystemConnectionLimit();
}
ConnectionLimit::Token::Ptr ConnectionLimit::acquire(const SHA1Hash& hash)
{
if(global_limit != 0 && global_total >= global_limit)
return Token::Ptr();
QMap<SHA1Hash, bt::Uint32>::iterator i = torrent_totals.find(hash);
if(i == torrent_totals.end())
{
torrent_totals[hash] = 1;
global_total++;
return Token::Ptr(new Token(*this, hash));
}
else if(torrent_limit == 0 || i.value() < torrent_limit)
{
i.value()++;
global_total++;
return Token::Ptr(new Token(*this, hash));
}
return Token::Ptr();
}
void ConnectionLimit::release(const ConnectionLimit::Token& token)
{
QMap<SHA1Hash, bt::Uint32>::iterator i = torrent_totals.find(token.infoHash());
if(i != torrent_totals.end())
{
if(i.value() > 0)
i.value()--;
// erase when torrent has no tokens left
if(i.value() == 0)
torrent_totals.erase(i);
if(global_total > 0)
global_total--;
}
}
ConnectionLimit::Token::Token(ConnectionLimit& limit, const SHA1Hash& hash) :
limit(limit),
hash(hash)
{
}
ConnectionLimit::Token::~Token()
{
limit.release(*this);
}
}
/***************************************************************************
* Copyright (C) 2012 by *
* Joris Guisson <joris.guisson@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#ifndef BT_CONNECTIONLIMIT_H
#define BT_CONNECTIONLIMIT_H
#include <QMap>
#include <QSharedPointer>
#include <util/constants.h>
#include <util/sha1hash.h>
#include <ktorrent_export.h>
namespace bt
{
/**
* Maintains the connection limit. It uses a Token for that.
*/
class KTORRENT_EXPORT ConnectionLimit
{
public:
ConnectionLimit();
virtual ~ConnectionLimit();
/// Get the total number of connections currently in use
bt::Uint32 totalConnections() const {return global_total;}
/**
* Set the connection limits
* @param global_limit Global limit
* @param torrent_limit Per torrent limit
**/
void setLimits(bt::Uint32 global_limit, bt::Uint32 torrent_limit);
/**
* Token representing the allowance to open a connection.
* When the token is destroyed, it will be automatically released.
*/
class Token
{
public:
Token(ConnectionLimit & limit, const bt::SHA1Hash & hash);
~Token();
/// Get the info hash
const bt::SHA1Hash & infoHash() const {return hash;}
typedef QSharedPointer<Token> Ptr;
private:
ConnectionLimit & limit;
bt::SHA1Hash hash;
};
/**
* Request a token for a given torrent
* @param hash Info hash of the torrent
* @return ConnectionLimit::Token::Ptr a valid token if a connection can be opened, a 0 pointer if not
**/
Token::Ptr acquire(const SHA1Hash & hash);
protected:
/**
* Release one Token. Will be done by destructor of Token.
* @param token The Token
**/
void release(const Token & token);
private:
bt::Uint32 global_limit;
bt::Uint32 global_total;
bt::Uint32 torrent_limit;
QMap<SHA1Hash, bt::Uint32> torrent_totals;
};
}
#endif // BT_CONNECTIONLIMIT_H
......@@ -51,9 +51,18 @@ namespace bt
bool Peer::resolve_hostname = true;
Peer::Peer(mse::EncryptedPacketSocket::Ptr sock, const PeerID & peer_id,
Uint32 num_chunks, Uint32 chunk_size, Uint32 support, bool local, PeerManager* pman)
: PeerInterface(peer_id, num_chunks), sock(sock), pman(pman)
Peer::Peer(mse::EncryptedPacketSocket::Ptr sock,
const PeerID & peer_id,
Uint32 num_chunks,
Uint32 chunk_size,
Uint32 support,
bool local,
ConnectionLimit::Token::Ptr token,
PeerManager* pman)
: PeerInterface(peer_id, num_chunks),
sock(sock),
token(token),
pman(pman)
{
id = peer_id_counter;
peer_id_counter++;
......@@ -125,6 +134,7 @@ namespace bt
{
sock->close();
killed = true;
token.clear();
}
void Peer::handleChoke(Uint32 len)
......
......@@ -30,6 +30,7 @@
#include <ktorrent_export.h>
#include "peerid.h"
#include "peerprotocolextension.h"
#include "connectionlimit.h"
namespace net
{
......@@ -69,6 +70,8 @@ namespace bt
* @param chunk_size Size of each chunk
* @param support Which extensions the peer supports
* @param local Whether or not it is a local peer
* @param token ConnectionLimit token
* @param pman The PeerManager
*/
Peer(mse::EncryptedPacketSocket::Ptr sock,
const PeerID & peer_id,
......@@ -76,6 +79,7 @@ namespace bt
Uint32 chunk_size,
Uint32 support,
bool local,
ConnectionLimit::Token::Ptr token,
PeerManager* pman);
virtual ~Peer();
......@@ -338,6 +342,7 @@ namespace bt
private:
mse::EncryptedPacketSocket::Ptr sock;
ConnectionLimit::Token::Ptr token;
Timer stalled_timer;
......
......@@ -31,28 +31,28 @@
namespace bt
{
static ResourceManager half_open_connections(50);
class PeerConnector::Private
{
public:
Private(PeerConnector* p,const net::Address & addr,bool local,PeerManager* pman)
: p(p),addr(addr),local(local),pman(pman),stopping(false),do_not_start(false)
Private(PeerConnector* p, const net::Address & addr, bool local, PeerManager* pman, ConnectionLimit::Token::Ptr token)
: p(p), addr(addr), local(local), pman(pman), stopping(false), do_not_start(false), token(token)
{
}
~Private()
{
if (auth.data())
if(auth.data())
{
stopping = true;
auth.data()->stop();
stopping = false;
}
}
void start(Method method);
void authenticationFinished(Authenticate* auth, bool ok);
public:
PeerConnector* p;
QSet<Method> tried_methods;
......@@ -64,11 +64,12 @@ namespace bt
bool stopping;
bool do_not_start;
PeerConnector::WPtr self;
ConnectionLimit::Token::Ptr token;
};
PeerConnector::PeerConnector(const net::Address & addr, bool local, bt::PeerManager* pman)
: Resource(&half_open_connections,pman->getTorrent().getInfoHash().toString()),
d(new Private(this,addr,local,pman))
PeerConnector::PeerConnector(const net::Address & addr, bool local, bt::PeerManager* pman, ConnectionLimit::Token::Ptr token)
: Resource(&half_open_connections, pman->getTorrent().getInfoHash().toString()),
d(new Private(this, addr, local, pman, token))
{
}
......@@ -76,43 +77,43 @@ namespace bt
{
delete d;
}
void PeerConnector::setWeakPointer(PeerConnector::WPtr ptr)
{
d->self = ptr;
}
void PeerConnector::setMaxActive(Uint32 mc)
{
half_open_connections.setMaxActive(mc);
}
void PeerConnector::start()
{
half_open_connections.add(this);
}
void PeerConnector::acquired()
{
PeerManager* pm = d->pman.data();
if (!pm || !pm->isStarted())
if(!pm || !pm->isStarted())
return;
bt::TransportProtocol primary = ServerInterface::primaryTransportProtocol();
bool encryption = ServerInterface::isEncryptionEnabled();
bool utp = ServerInterface::isUtpEnabled();
if (encryption)
if(encryption)
{
if (utp && primary == bt::UTP)
if(utp && primary == bt::UTP)
d->start(UTP_WITH_ENCRYPTION);
else
d->start(TCP_WITH_ENCRYPTION);
}
else
{
if (utp && primary == bt::UTP)
if(utp && primary == bt::UTP)
d->start(UTP_WITHOUT_ENCRYPTION);
else
d->start(TCP_WITHOUT_ENCRYPTION);
......@@ -121,81 +122,81 @@ namespace bt
void PeerConnector::authenticationFinished(Authenticate* auth, bool ok)
{
d->authenticationFinished(auth,ok);
d->authenticationFinished(auth, ok);
}
void PeerConnector::Private::authenticationFinished(Authenticate* auth, bool ok)
{
this->auth.clear();
if (stopping)
if(stopping)
return;
PeerManager* pm = pman.data();
if (!pm)
if(!pm)
return;
if (ok)
if(ok)
{
pm->peerAuthenticated(auth,self,ok);
pm->peerAuthenticated(auth, self, ok, token);
return;
}
tried_methods.insert(current_method);
bt::TransportProtocol primary = ServerInterface::primaryTransportProtocol();
QList<Method> allowed;
bool tcp_allowed = OpenFileAllowed();
bool encryption = ServerInterface::isEncryptionEnabled();
bool only_use_encryption = !ServerInterface::unencryptedConnectionsAllowed();
bool utp = ServerInterface::isUtpEnabled();
bool only_use_utp = ServerInterface::onlyUseUtp();
if (primary == bt::UTP)
if(primary == bt::UTP)
{
if (utp && encryption && !tried_methods.contains(UTP_WITH_ENCRYPTION))
if(utp && encryption && !tried_methods.contains(UTP_WITH_ENCRYPTION))
start(UTP_WITH_ENCRYPTION);
else if (utp && !only_use_encryption && !tried_methods.contains(UTP_WITHOUT_ENCRYPTION))
else if(utp && !only_use_encryption && !tried_methods.contains(UTP_WITHOUT_ENCRYPTION))
start(UTP_WITHOUT_ENCRYPTION);
else if (!only_use_utp && encryption && !tried_methods.contains(TCP_WITH_ENCRYPTION) && tcp_allowed)
else if(!only_use_utp && encryption && !tried_methods.contains(TCP_WITH_ENCRYPTION) && tcp_allowed)
start(TCP_WITH_ENCRYPTION);
else if (!only_use_utp && !only_use_encryption && !tried_methods.contains(TCP_WITHOUT_ENCRYPTION) && tcp_allowed)
else if(!only_use_utp && !only_use_encryption && !tried_methods.contains(TCP_WITHOUT_ENCRYPTION) && tcp_allowed)
start(TCP_WITHOUT_ENCRYPTION);
else
pm->peerAuthenticated(auth,self,false);
pm->peerAuthenticated(auth, self, false, token);
}
else // Primary is TCP
{
if (!only_use_utp && encryption && !tried_methods.contains(TCP_WITH_ENCRYPTION) && tcp_allowed)
if(!only_use_utp && encryption && !tried_methods.contains(TCP_WITH_ENCRYPTION) && tcp_allowed)
start(TCP_WITH_ENCRYPTION);
else if (!only_use_utp && !only_use_encryption && !tried_methods.contains(TCP_WITHOUT_ENCRYPTION) && tcp_allowed)
else if(!only_use_utp && !only_use_encryption && !tried_methods.contains(TCP_WITHOUT_ENCRYPTION) && tcp_allowed)
start(TCP_WITHOUT_ENCRYPTION);
else if (utp && encryption && !tried_methods.contains(UTP_WITH_ENCRYPTION))
else if(utp && encryption && !tried_methods.contains(UTP_WITH_ENCRYPTION))
start(UTP_WITH_ENCRYPTION);
else if (utp && !only_use_encryption && !tried_methods.contains(UTP_WITHOUT_ENCRYPTION))
else if(utp && !only_use_encryption && !tried_methods.contains(UTP_WITHOUT_ENCRYPTION))
start(UTP_WITHOUT_ENCRYPTION);
else
pm->peerAuthenticated(auth,self,false);
pm->peerAuthenticated(auth, self, false, token);
}
}
void PeerConnector::Private::start(PeerConnector::Method method)
{
PeerManager* pm = pman.data();
if (!pm)
if(!pm)
return;
current_method = method;
const Torrent & tor = pm->getTorrent();
TransportProtocol proto = (method == TCP_WITH_ENCRYPTION || method == TCP_WITHOUT_ENCRYPTION) ? TCP : UTP;
if (method == TCP_WITH_ENCRYPTION || method == UTP_WITH_ENCRYPTION)
auth = new mse::EncryptedAuthenticate(addr,proto,tor.getInfoHash(),tor.getPeerID(),self);
if(method == TCP_WITH_ENCRYPTION || method == UTP_WITH_ENCRYPTION)
auth = new mse::EncryptedAuthenticate(addr, proto, tor.getInfoHash(), tor.getPeerID(), self);
else
auth = new Authenticate(addr,proto,tor.getInfoHash(),tor.getPeerID(),self);
if (local)
auth = new Authenticate(addr, proto, tor.getInfoHash(), tor.getPeerID(), self);
if(local)
auth.data()->setLocal(true);
AuthenticationMonitor::instance().add(auth.data());
}
......
......@@ -26,6 +26,7 @@
#include <util/constants.h>
#include <util/resourcemanager.h>
#include <net/address.h>
#include "connectionlimit.h"