NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers) { PooledConnection* pConnection = NULL; m_mutexConnections.Lock(); time_t tCurTime = time(NULL); if (iLevel < (int)m_Levels.size() && m_Levels[iLevel] > 0) { Connections candidates; candidates.reserve(m_Connections.size()); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pCandidateConnection = *it; NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer(); if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() && pCandidateServer->GetNormLevel() == iLevel && (!pWantServer || pCandidateServer == pWantServer || (pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup())) && (pCandidateConnection->GetStatus() == Connection::csConnected || !pCandidateServer->GetBlockTime() || pCandidateServer->GetBlockTime() + m_iRetryInterval <= tCurTime || pCandidateServer->GetBlockTime() > tCurTime)) { // free connection found, check if it's not from the server which should be ignored bool bUseConnection = true; if (pIgnoreServers && !pWantServer) { for (Servers::iterator it = pIgnoreServers->begin(); it != pIgnoreServers->end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bUseConnection = false; break; } } } pCandidateServer->SetBlockTime(0); if (bUseConnection) { candidates.push_back(pCandidateConnection); } } } if (!candidates.empty()) { // Peeking a random free connection. This is better than taking the first // available connection because provides better distribution across news servers, // especially when one of servers becomes unavailable or doesn't have requested articles. int iRandomIndex = rand() % candidates.size(); pConnection = candidates[iRandomIndex]; pConnection->SetInUse(true); } if (pConnection) { m_Levels[iLevel]--; } } m_mutexConnections.Unlock(); return pConnection; }
/* * How server management (for one particular article) works: - there is a list of failed servers which is initially empty; - level is initially 0; <loop> - request a connection from server pool for current level; Exception: this step is skipped for the very first download attempt, because a level-0 connection is initially passed from queue manager; - try to download from server; - if connection to server cannot be established or download fails due to interrupted connection, try again (as many times as needed without limit) the same server until connection is OK; - if download fails with error "Not-Found" (article or group not found) or with CRC error, add the server to failed server list; - if download fails with general failure error (article incomplete, other unknown error codes), try the same server again as many times as defined by option <Retries>; if all attempts fail, add the server to failed server list; - if all servers from current level were tried, increase level; - if all servers from all levels were tried, break the loop with failure status. <end-loop> */ void ArticleDownloader::Run() { debug("Entering ArticleDownloader-loop"); SetStatus(adRunning); BuildOutputFilename(); m_szResultFilename = m_pArticleInfo->GetResultFilename(); if (g_pOptions->GetContinuePartial()) { if (Util::FileExists(m_szResultFilename)) { // file exists from previous program's start detail("Article %s already downloaded, skipping", m_szInfoName); FreeConnection(true); SetStatus(adFinished); Notify(NULL); return; } } EStatus Status = adFailed; int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1; int iRemainedRetries = iRetries; ServerPool::Servers failedServers; failedServers.reserve(g_pServerPool->GetServers()->size()); NewsServer* pWantServer = NULL; NewsServer* pLastServer = NULL; int iLevel = 0; int iServerConfigGeneration = g_pServerPool->GetGeneration(); while (!IsStopped()) { Status = adFailed; SetStatus(adWaiting); while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration())) { m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers); usleep(5 * 1000); } SetLastUpdateTimeNow(); SetStatus(adRunning); if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } pLastServer = m_pConnection->GetNewsServer(); m_pConnection->SetSuppressErrors(false); // test connection bool bConnected = m_pConnection && m_pConnection->Connect(); if (bConnected && !IsStopped()) { // Okay, we got a Connection. Now start downloading. detail("Downloading %s @ %s (%s)", m_szInfoName, m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost()); Status = Download(); } if (bConnected) { if (Status == adConnectError) { m_pConnection->Disconnect(); bConnected = false; Status = adFailed; } else { // freeing connection allows other threads to start. // we doing this only if the problem was with article or group. // if the problem occurs by connecting or authorization we do not // free the connection, to prevent starting of thousands of threads // (cause each of them will also free it's connection after the // same connect-error). FreeConnection(Status == adFinished || Status == adNotFound); } } if (Status == adFinished || Status == adFatalError) { break; } pWantServer = NULL; if (bConnected && Status == adFailed) { iRemainedRetries--; } if (!bConnected || (Status == adFailed && iRemainedRetries > 0)) { pWantServer = pLastServer; } if (pWantServer && !(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration())) { detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval()); SetStatus(adWaiting); int msec = 0; while (!(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) && msec < g_pOptions->GetRetryInterval() * 1000) { usleep(100 * 1000); msec += 100; } SetLastUpdateTimeNow(); SetStatus(adRunning); } if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } if (!pWantServer) { failedServers.push_back(pLastServer); // if all servers from current level were tried, increase level // if all servers from all levels were tried, break the loop with failure status bool bAllServersOnLevelFailed = true; for (ServerPool::Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pCandidateServer = *it; if (pCandidateServer->GetNormLevel() == iLevel) { bool bServerFailed = !pCandidateServer->GetActive(); if (!bServerFailed) { for (ServerPool::Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bServerFailed = true; break; } } } if (!bServerFailed) { bAllServersOnLevelFailed = false; break; } } } if (bAllServersOnLevelFailed) { if (iLevel < g_pServerPool->GetMaxNormLevel()) { detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel); iLevel++; } else { warn("Article %s @ all servers failed", m_szInfoName); Status = adFailed; break; } } iRemainedRetries = iRetries; } } FreeConnection(Status == adFinished); if (m_bDuplicate) { Status = adFinished; } if (Status != adFinished && Status != adRetry) { Status = adFailed; } if (IsStopped()) { detail("Download %s cancelled", m_szInfoName); Status = adRetry; } if (Status == adFailed) { warn("Download %s failed", m_szInfoName); } SetStatus(Status); Notify(NULL); debug("Exiting ArticleDownloader-loop"); }