mirror of
https://github.com/privacore/open-source-search-engine.git
synced 2025-01-22 02:18:42 -05:00
e515e92dae
It has always been local time since ... forever. We rely on NTP doing its job.
1523 lines
45 KiB
C++
1523 lines
45 KiB
C++
#include "SpiderLoop.h"
|
|
#include "Spider.h"
|
|
#include "SpiderColl.h"
|
|
#include "SpiderCache.h"
|
|
#include "Doledb.h"
|
|
#include "UdpSlot.h"
|
|
#include "UdpServer.h"
|
|
#include "Collectiondb.h"
|
|
#include "SafeBuf.h"
|
|
#include "Repair.h"
|
|
#include "DailyMerge.h"
|
|
#include "Process.h"
|
|
#include "XmlDoc.h"
|
|
#include "HttpServer.h"
|
|
#include "Pages.h"
|
|
#include "Parms.h"
|
|
#include "ip.h"
|
|
#include "Conf.h"
|
|
#include "Mem.h"
|
|
#include "ScopedLock.h"
|
|
#include "Errno.h"
|
|
|
|
|
|
// . this was 10 but cpu is getting pegged, so i set to 45
|
|
// . we consider the collection done spidering when no urls to spider
|
|
// for this many seconds
|
|
// . i'd like to set back to 10 for speed... maybe even 5 or less
|
|
// . back to 30 from 20 to try to fix crawls thinking they are done
|
|
// maybe because of the empty doledb logic taking too long?
|
|
//#define SPIDER_DONE_TIMER 30
|
|
// try 45 to prevent false revivals
|
|
//#define SPIDER_DONE_TIMER 45
|
|
// try 30 again since we have new localcrawlinfo update logic much faster
|
|
//#define SPIDER_DONE_TIMER 30
|
|
// neo under heavy load go to 60
|
|
//#define SPIDER_DONE_TIMER 60
|
|
// super overloaded
|
|
//#define SPIDER_DONE_TIMER 90
|
|
#define SPIDER_DONE_TIMER 20
|
|
|
|
static int32_t doleDbRecSizes = 150000; //how much toe read from DoleDB in a chunk.
|
|
|
|
|
|
class UrlLock {
|
|
public:
|
|
int32_t m_firstIp;
|
|
char m_spiderOutstanding;
|
|
collnum_t m_collnum;
|
|
};
|
|
|
|
|
|
/////////////////////////
|
|
///////////////////////// SPIDERLOOP
|
|
/////////////////////////
|
|
|
|
// a global class extern'd in .h file
|
|
SpiderLoop g_spiderLoop;
|
|
|
|
SpiderLoop::SpiderLoop ( ) {
|
|
m_crx = NULL;
|
|
// clear array of ptrs to Doc's
|
|
memset ( m_docs , 0 , sizeof(XmlDoc *) * MAX_SPIDERS );
|
|
|
|
// Coverity
|
|
m_numSpidersOut = 0;
|
|
m_launches = 0;
|
|
m_maxUsed = 0;
|
|
m_sc = NULL;
|
|
m_gettingDoledbList = false;
|
|
m_activeList = NULL;
|
|
m_bookmark = NULL;
|
|
m_activeListValid = false;
|
|
m_activeListCount = 0;
|
|
m_recalcTime = 0;
|
|
m_recalcTimeValid = false;
|
|
m_doleStart = 0;
|
|
}
|
|
|
|
SpiderLoop::~SpiderLoop ( ) {
|
|
reset();
|
|
}
|
|
|
|
// free all doc's
|
|
void SpiderLoop::reset() {
|
|
// delete all doc's in use
|
|
for ( int32_t i = 0 ; i < MAX_SPIDERS ; i++ ) {
|
|
if ( m_docs[i] ) {
|
|
mdelete ( m_docs[i] , sizeof(XmlDoc) , "Doc" );
|
|
delete (m_docs[i]);
|
|
}
|
|
m_docs[i] = NULL;
|
|
}
|
|
m_list.freeList();
|
|
m_lockTable.reset();
|
|
m_winnerListCache.clear();
|
|
}
|
|
|
|
void SpiderLoop::init() {
|
|
logTrace( g_conf.m_logTraceSpider, "BEGIN" );
|
|
|
|
m_crx = NULL;
|
|
m_activeListValid = false;
|
|
m_activeList = NULL;
|
|
m_recalcTime = 0;
|
|
m_recalcTimeValid = false;
|
|
|
|
// we aren't in the middle of waiting to get a list of SpiderRequests
|
|
m_gettingDoledbList = false;
|
|
|
|
// clear array of ptrs to Doc's
|
|
memset ( m_docs , 0 , sizeof(XmlDoc *) * MAX_SPIDERS );
|
|
// . m_maxUsed is the largest i such that m_docs[i] is in use
|
|
// . -1 means there are no used m_docs's
|
|
m_maxUsed = -1;
|
|
m_numSpidersOut = 0;
|
|
|
|
// for locking. key size is 8 for easier debugging
|
|
m_lockTable.set ( 8,sizeof(UrlLock),0,NULL,0,false, "splocks", true ); // useKeyMagic? yes.
|
|
|
|
m_winnerListCache.configure(10000, // maxcachenodes
|
|
20000000 , // maxcachemem, 20MB
|
|
1200); //max age
|
|
|
|
initSettings();
|
|
|
|
// don't register callbacks when we're not using it
|
|
if (!g_hostdb.getMyHost()->m_spiderEnabled) {
|
|
logTrace(g_conf.m_logTraceSpider, "END");
|
|
return;
|
|
}
|
|
|
|
// sleep for .1 seconds = 100ms
|
|
if (!g_loop.registerSleepCallback(50, this, doneSleepingWrapperSL, "SpiderLoop::doneSleepingWrapperSL")) {
|
|
log(LOG_ERROR, "build: Failed to register timer callback. Spidering is permanently disabled. Restart to fix.");
|
|
}
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "END" );
|
|
}
|
|
|
|
void SpiderLoop::initSettings() {
|
|
m_urlCache.configure(g_conf.m_spiderUrlCacheMaxAge*1000, g_conf.m_spiderUrlCacheSize, g_conf.m_logTraceSpiderUrlCache, "spider url cache");
|
|
}
|
|
|
|
void SpiderLoop::nukeWinnerListCache(collnum_t collnum) {
|
|
FxBlobCacheLock<int32_t> rcl(m_winnerListCache);
|
|
m_winnerListCache.clear();
|
|
}
|
|
|
|
|
|
// call this every 50ms it seems to try to spider urls and populate doledb
|
|
// from the waiting tree
|
|
void SpiderLoop::doneSleepingWrapperSL ( int fd , void *state ) {
|
|
// if spidering disabled then do not do this crap
|
|
if ( ! g_conf.m_spideringEnabled ) return;
|
|
if ( ! g_hostdb.getMyHost( )->m_spiderEnabled ) return;
|
|
|
|
// or if trying to exit
|
|
if (g_process.isShuttingDown()) return;
|
|
// skip if udp table is full
|
|
if ( g_udpServer.getNumUsedSlotsIncoming() >= MAXUDPSLOTS ) return;
|
|
|
|
int32_t now = getTime();
|
|
|
|
// point to head of active linked list of collection recs
|
|
const CollectionRec *nextActive = g_spiderLoop.getActiveList();
|
|
collnum_t nextActiveCollnum = nextActive ? nextActive->m_collnum : static_cast<collnum_t>( -1 );
|
|
|
|
for ( ; nextActive ; ) {
|
|
// before we assign crp to nextActive, ensure that it did not get deleted on us.
|
|
// if the next collrec got deleted, tr will be NULL
|
|
CollectionRec *tr = g_collectiondb.getRec( nextActiveCollnum );
|
|
|
|
// if it got deleted or restarted then it will not
|
|
// match most likely
|
|
if ( tr != nextActive ) {
|
|
// this shouldn't happen much so log it
|
|
log("spider: collnum %" PRId32" got deleted. rebuilding active list", (int32_t)nextActiveCollnum);
|
|
|
|
// rebuild the active list now
|
|
nextActive = g_spiderLoop.getActiveList();
|
|
nextActiveCollnum = nextActive ? nextActive->m_collnum : static_cast<collnum_t>( -1 );
|
|
|
|
continue;
|
|
}
|
|
|
|
// now we become him
|
|
const CollectionRec *crp = nextActive;
|
|
|
|
// update these two vars for next iteration
|
|
nextActive = crp->m_nextActive;
|
|
nextActiveCollnum = nextActive ? nextActive->m_collnum : static_cast<collnum_t>( -1 );
|
|
|
|
// skip if not enabled
|
|
if ( ! crp->m_spideringEnabled ) {
|
|
continue;
|
|
}
|
|
|
|
// get it
|
|
SpiderColl *sc = g_spiderCache.getSpiderColl(crp->m_collnum);
|
|
|
|
// skip if none
|
|
if ( ! sc ) {
|
|
continue;
|
|
}
|
|
|
|
// always do a scan at startup & every 24 hrs
|
|
// AND at process startup!!!
|
|
if ( ! sc->m_waitingTreeNeedsRebuild && now - sc->getLastScanTime() > 24*3600 ) {
|
|
// if a scan is ongoing, this will re-set it
|
|
sc->resetWaitingTreeNextKey();
|
|
sc->m_waitingTreeNeedsRebuild = true;
|
|
log( LOG_INFO, "spider: hit spider queue rebuild timeout for %s (%" PRId32")",
|
|
crp->m_coll, (int32_t)crp->m_collnum );
|
|
}
|
|
|
|
// e.g. URL Filter config has changed
|
|
if (sc->m_waitingTreeNeedsRebuild) {
|
|
// re-entry is false because we are entering for the first time
|
|
logTrace(g_conf.m_logTraceSpider, "Calling populateWaitingTreeFromSpiderdb");
|
|
sc->populateWaitingTreeFromSpiderdb(false);
|
|
}
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "Calling populateDoledbFromWaitingTree" );
|
|
sc->populateDoledbFromWaitingTree();
|
|
}
|
|
|
|
// if we have a ton of collections, reduce cpu load from calling
|
|
// spiderDoledUrls()
|
|
static uint64_t s_skipCount = 0;
|
|
s_skipCount++;
|
|
|
|
// so instead of every 50ms make it every 200ms if we got 100+ collections in use.
|
|
g_spiderLoop.getActiveList();
|
|
int32_t activeListCount = g_spiderLoop.m_activeListCount;
|
|
if ( ! g_spiderLoop.m_activeListValid ) {
|
|
activeListCount = 0;
|
|
}
|
|
|
|
int32_t skip = 1;
|
|
if ( activeListCount >= 200 ) {
|
|
skip = 8;
|
|
} else if ( activeListCount >= 100 ) {
|
|
skip = 4;
|
|
} else if ( activeListCount >= 50 ) {
|
|
skip = 2;
|
|
}
|
|
|
|
if ( ( s_skipCount % skip ) != 0 ) {
|
|
return;
|
|
}
|
|
|
|
// spider some urls that were doled to us
|
|
logTrace( g_conf.m_logTraceSpider, "Calling spiderDoledUrls" );
|
|
|
|
g_spiderLoop.spiderDoledUrls( );
|
|
}
|
|
|
|
|
|
void SpiderLoop::gotDoledbListWrapper2 ( void *state , RdbList *list , Msg5 *msg5 ) {
|
|
// process the doledb list
|
|
g_spiderLoop.gotDoledbList2();
|
|
}
|
|
|
|
//////////////////////////
|
|
//////////////////////////
|
|
//
|
|
// The second KEYSTONE function.
|
|
//
|
|
// Scans doledb and spiders the doledb records.
|
|
//
|
|
// Doledb records contain SpiderRequests ready for spidering NOW.
|
|
//
|
|
// 1. gets all locks from all hosts in the shard
|
|
// 2. sends confirm msg to all hosts if lock acquired:
|
|
// - each host will remove from doledb then
|
|
// - assigned host will also add new "0" entry to waiting tree if need be
|
|
// - calling addToWaitingTree() will trigger populateDoledbFromWaitingTree()
|
|
// to add a new entry into waiting tree, not the one just locked.
|
|
// 3. makes a new xmldoc class for that url and calls indexDoc() on it
|
|
//
|
|
//////////////////////////
|
|
//////////////////////////
|
|
|
|
// now check our RDB_DOLEDB for SpiderRequests to spider!
|
|
void SpiderLoop::spiderDoledUrls ( ) {
|
|
logTrace( g_conf.m_logTraceSpider, "BEGIN" );
|
|
|
|
collLoop:
|
|
|
|
// start again at head if this is NULL
|
|
if ( ! m_crx ) m_crx = getActiveList();
|
|
|
|
bool firstTime = true;
|
|
|
|
// detect overlap
|
|
m_bookmark = m_crx;
|
|
|
|
// get this
|
|
m_sc = NULL;
|
|
|
|
// set this in the loop
|
|
const CollectionRec *cr = NULL;
|
|
uint32_t nowGlobal = 0;
|
|
|
|
m_launches = 0;
|
|
|
|
subloop:
|
|
// must be spidering to dole out
|
|
if ( ! g_conf.m_spideringEnabled ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, spidering disabled" );
|
|
return;
|
|
}
|
|
|
|
if ( ! g_hostdb.getMyHost( )->m_spiderEnabled ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, spidering disabled (2)" );
|
|
return;
|
|
}
|
|
|
|
// or if trying to exit
|
|
if (g_process.isShuttingDown()) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, shutting down" );
|
|
return;
|
|
}
|
|
|
|
// don't spider if we have dead host
|
|
if (g_hostdb.hasDeadHostCached()) {
|
|
logTrace(g_conf.m_logTraceSpider, "END, has dead host");
|
|
return;
|
|
}
|
|
|
|
// if we do not overlap ourselves
|
|
if ( m_gettingDoledbList ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, already getting DoledbList" );
|
|
return;
|
|
}
|
|
|
|
// bail instantly if in read-only mode (no RdbTrees!)
|
|
if ( g_conf.m_readOnlyMode ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, in read-only mode" );
|
|
return;
|
|
}
|
|
|
|
// or if doing a daily merge
|
|
if ( g_dailyMerge.m_mergeMode ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, doing daily merge" );
|
|
return;
|
|
}
|
|
|
|
// skip if too many udp slots being used
|
|
if ( g_udpServer.getNumUsedSlotsIncoming() >= MAXUDPSLOTS ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, using max UDP slots" );
|
|
return;
|
|
}
|
|
|
|
// stop if too many out. this is now 50 down from 500.
|
|
if ( m_numSpidersOut >= MAX_SPIDERS ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, reached max spiders" );
|
|
return;
|
|
}
|
|
|
|
// a new global conf rule
|
|
if ( m_numSpidersOut >= g_conf.m_maxTotalSpiders ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, reached max total spiders" );
|
|
return;
|
|
}
|
|
|
|
// bail if no collections
|
|
if ( g_collectiondb.getNumRecs() <= 0 ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, no collections" );
|
|
return;
|
|
}
|
|
|
|
// not while repairing
|
|
if ( g_repairMode ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, in repair mode" );
|
|
return;
|
|
}
|
|
|
|
// do not spider until collections/parms in sync with host #0
|
|
if ( ! g_parms.inSyncWithHost0() ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, not in sync with host#0" );
|
|
return;
|
|
}
|
|
|
|
// don't spider if not all hosts are up, or they do not all
|
|
// have the same hosts.conf.
|
|
if ( ! g_hostdb.hostsConfInAgreement() ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, host config disagreement" );
|
|
return;
|
|
}
|
|
|
|
// if nothin in the active list then return as well
|
|
if ( ! m_activeList ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, nothing in active list" );
|
|
return;
|
|
}
|
|
|
|
// if we hit the end of the list, wrap it around
|
|
if ( ! m_crx ) m_crx = m_activeList;
|
|
|
|
// we use m_bookmark to determine when we've done a round over all
|
|
// the collections. but it will be set to null sometimes when we
|
|
// are in this loop because the active list gets recomputed. so
|
|
// if we lost it because our bookmarked collection is no longer
|
|
// 'active' then just set it to the list head i guess
|
|
if ( ! m_bookmark || ! m_bookmark->m_isActive )
|
|
m_bookmark = m_activeList;
|
|
|
|
// i guess return at the end of the linked list if no collection
|
|
// launched a spider... otherwise do another cycle to launch another
|
|
// spider. i could see a single collection dominating all the spider
|
|
// slots in some scenarios with this approach unfortunately.
|
|
if ( m_crx == m_bookmark && ! firstTime && m_launches == 0 ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, end of list?" );
|
|
return;
|
|
}
|
|
|
|
// reset # launches after doing a round and having launched > 0
|
|
if ( m_crx == m_bookmark && ! firstTime )
|
|
m_launches = 0;
|
|
|
|
firstTime = false;
|
|
|
|
// if a collection got deleted re-calc the active list so
|
|
// we don't core trying to access a delete collectionrec.
|
|
// i'm not sure if this can happen here but i put this in as a
|
|
// precaution.
|
|
if ( ! m_activeListValid ) {
|
|
m_crx = NULL;
|
|
goto collLoop;
|
|
}
|
|
|
|
// return now if list is just empty
|
|
if ( ! m_activeList ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, active list empty" );
|
|
return;
|
|
}
|
|
|
|
|
|
cr = m_crx;
|
|
|
|
// Fix to shut up STACK
|
|
if( !m_crx ) {
|
|
goto collLoop;
|
|
}
|
|
|
|
|
|
// advance for next time we call goto subloop;
|
|
m_crx = m_crx->m_nextActive;
|
|
|
|
|
|
// get the spider collection for this collnum
|
|
m_sc = g_spiderCache.getSpiderColl(cr->m_collnum);
|
|
|
|
// skip if none
|
|
if ( ! m_sc ) {
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, no spider cache for this collection" );
|
|
goto subloop;
|
|
}
|
|
|
|
// always reset priority to max at start
|
|
m_sc->setPriority ( MAX_SPIDER_PRIORITIES - 1 );
|
|
|
|
subloopNextPriority:
|
|
// skip if gone
|
|
if ( ! cr ) goto subloop;
|
|
|
|
// stop if not enabled
|
|
if ( ! cr->m_spideringEnabled ) goto subloop;
|
|
|
|
// set current time, synced with host #0
|
|
nowGlobal = (uint32_t)getTime();
|
|
|
|
// get max spiders
|
|
int32_t maxSpiders = cr->m_maxNumSpiders;
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "maxSpiders: %" PRId32 , maxSpiders );
|
|
|
|
// obey max spiders per collection too
|
|
if ( m_sc->m_spidersOut >= maxSpiders ) {
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, Too many spiders active for collection" );
|
|
goto subloop;
|
|
}
|
|
|
|
if ( cr->m_spiderColl && cr->m_spiderColl->isDoledbIpTableEmpty() ) {
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, doleIpTable is empty" );
|
|
goto subloop;
|
|
}
|
|
|
|
// sanity check
|
|
if ( nowGlobal == 0 ) { g_process.shutdownAbort(true); }
|
|
|
|
// need this for msg5 call
|
|
key96_t endKey;
|
|
endKey.setMax();
|
|
|
|
for ( ; ; ) {
|
|
// reset priority when it goes bogus
|
|
if ( m_sc->m_pri2 < 0 ) {
|
|
// reset for next coll
|
|
m_sc->setPriority( MAX_SPIDER_PRIORITIES - 1 );
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, pri2 < 0" );
|
|
goto subloop;
|
|
}
|
|
|
|
// sanity
|
|
if ( cr != m_sc->getCollectionRec() ) {
|
|
g_process.shutdownAbort(true);
|
|
}
|
|
|
|
// skip the priority if we already have enough spiders on it
|
|
int32_t out = m_sc->m_outstandingSpiders[ m_sc->m_pri2 ];
|
|
|
|
// how many spiders can we have out?
|
|
int32_t max = 0;
|
|
for ( int32_t i = 0; i < cr->m_numRegExs; i++ ) {
|
|
if ( cr->m_spiderPriorities[ i ] != m_sc->m_pri2 ) {
|
|
continue;
|
|
}
|
|
|
|
if ( cr->m_maxSpidersPerRule[ i ] > max ) {
|
|
max = cr->m_maxSpidersPerRule[ i ];
|
|
}
|
|
}
|
|
|
|
// always allow at least 1, they can disable spidering otherwise
|
|
// no, we use this to disabled spiders... if ( max <= 0 ) max = 1;
|
|
// skip?
|
|
if ( out >= max ) {
|
|
// try the priority below us
|
|
m_sc->devancePriority();
|
|
|
|
// and try again
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, trying previous priority" );
|
|
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// we only launch one spider at a time... so lock it up
|
|
m_gettingDoledbList = true;
|
|
|
|
// log this now
|
|
if ( g_conf.m_logDebugSpider ) {
|
|
m_doleStart = gettimeofdayInMilliseconds();
|
|
|
|
if ( m_sc->m_msg5StartKey != m_sc->m_nextDoledbKey ) {
|
|
log( "spider: msg5startKey differs from nextdoledbkey" );
|
|
}
|
|
}
|
|
|
|
// seems like we need this reset here... strange
|
|
m_list.reset();
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "Getting list (msg5)" );
|
|
|
|
// get a spider rec for us to spider from doledb (mdw)
|
|
if ( ! m_msg5.getList ( RDB_DOLEDB ,
|
|
cr->m_collnum, // coll ,
|
|
&m_list ,
|
|
&m_sc->m_msg5StartKey,//m_sc->m_nextDoledbKey,
|
|
&endKey ,
|
|
// need to make this big because we don't
|
|
// want to end up getting just a negative key
|
|
//1 , // minRecSizes (~ 7000)
|
|
// we need to read in a lot because we call
|
|
// "goto listLoop" below if the url we want
|
|
// to dole is locked.
|
|
// seems like a ton of negative recs
|
|
// MDW: let's now read in 50k, not 2k,of doledb
|
|
// spiderrequests because often the first one
|
|
// has an ip already in use and then we'd
|
|
// just give up on the whole PRIORITY! which
|
|
// really freezes the spiders up.
|
|
// Also, if a spider request is corrupt in
|
|
// doledb it would cork us up too!
|
|
doleDbRecSizes , // minRecSizes
|
|
true , // includeTree
|
|
0 , // startFileNum
|
|
-1 , // numFiles (all)
|
|
this , // state
|
|
gotDoledbListWrapper2 ,
|
|
MAX_NICENESS , // niceness
|
|
true, // do err correction
|
|
-1, // maxRetries
|
|
false)) // isRealMerge
|
|
{
|
|
// return if it blocked
|
|
logTrace( g_conf.m_logTraceSpider, "END, getList blocked" );
|
|
|
|
return;
|
|
}
|
|
|
|
int32_t saved = m_launches;
|
|
|
|
// . add urls in list to cache
|
|
// . returns true if we should read another list
|
|
// . will set startKey to next key to start at
|
|
bool status = gotDoledbList2 ( );
|
|
logTrace( g_conf.m_logTraceSpider, "Back from gotDoledList2. Get more? %s", status ? "true" : "false" );
|
|
|
|
// if we did not launch anything, then decrement priority and
|
|
// try again. but if priority hits -1 then subloop2 will just go to
|
|
// the next collection.
|
|
if ( saved == m_launches ) {
|
|
m_sc->devancePriority();
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "Loop, get next priority" );
|
|
|
|
goto subloopNextPriority;
|
|
}
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "END, loop" );
|
|
|
|
// try another read
|
|
// now advance to next coll, launch one spider per coll
|
|
goto subloop;
|
|
}
|
|
|
|
// spider the spider rec in this list from doledb
|
|
// returns false if would block indexing a doc, returns true if would not,
|
|
// and returns true and sets g_errno on error
|
|
bool SpiderLoop::gotDoledbList2 ( ) {
|
|
// unlock
|
|
m_gettingDoledbList = false;
|
|
|
|
// shortcuts
|
|
CollectionRec *cr = m_sc->getCollectionRec();
|
|
|
|
// update m_msg5StartKey for next read
|
|
if ( m_list.getListSize() > 0 ) {
|
|
// what is m_list.m_ks ?
|
|
m_list.getLastKey((char *)&m_sc->m_msg5StartKey);
|
|
m_sc->m_msg5StartKey += 1;
|
|
}
|
|
|
|
// log this now
|
|
if ( g_conf.m_logDebugSpider ) {
|
|
int64_t now = gettimeofdayInMilliseconds();
|
|
int64_t took = now - m_doleStart;
|
|
if ( took > 2 )
|
|
logf(LOG_DEBUG,"spider: GOT list from doledb in "
|
|
"%" PRId64"ms "
|
|
"size=%" PRId32" bytes",
|
|
took,m_list.getListSize());
|
|
}
|
|
|
|
bool bail = false;
|
|
// bail instantly if in read-only mode (no RdbTrees!)
|
|
if ( g_conf.m_readOnlyMode ) bail = true;
|
|
// or if doing a daily merge
|
|
if ( g_dailyMerge.m_mergeMode ) bail = true;
|
|
// skip if too many udp slots being used
|
|
if (g_udpServer.getNumUsedSlotsIncoming() >= MAXUDPSLOTS ) bail =true;
|
|
// stop if too many out
|
|
if ( m_numSpidersOut >= MAX_SPIDERS ) bail = true;
|
|
|
|
if ( bail ) {
|
|
// return false to indicate to try another
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:bailing");
|
|
return false;
|
|
}
|
|
|
|
// bail if list is empty
|
|
if ( m_list.getListSize() <= 0 ) {
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:empty list");
|
|
return true;
|
|
}
|
|
|
|
time_t nowGlobal = getTime();
|
|
|
|
// reset ptr to point to first rec in list
|
|
m_list.resetListPtr();
|
|
|
|
listLoop:
|
|
// get the current rec from list ptr
|
|
char *rec = (char *)m_list.getCurrentRec();
|
|
|
|
// the doledbkey
|
|
const key96_t *doledbKey = (const key96_t *)rec;
|
|
|
|
// get record after it next time
|
|
m_sc->m_nextDoledbKey = *doledbKey ;
|
|
|
|
// sanity check -- wrap watch -- how can this really happen?
|
|
if ( m_sc->m_nextDoledbKey.n1 == 0xffffffff &&
|
|
m_sc->m_nextDoledbKey.n0 == 0xffffffffffffffffLL ) {
|
|
g_process.shutdownAbort(true);
|
|
}
|
|
|
|
// if its negative inc by two then! this fixes the bug where the
|
|
// list consisted only of one negative key and was spinning forever
|
|
if ( (m_sc->m_nextDoledbKey & 0x01) == 0x00 )
|
|
m_sc->m_nextDoledbKey += 2;
|
|
|
|
// did it hit zero? that means it wrapped around!
|
|
if ( m_sc->m_nextDoledbKey.n1 == 0x0 &&
|
|
m_sc->m_nextDoledbKey.n0 == 0x0 ) {
|
|
// TODO: work this out
|
|
g_process.shutdownAbort(true);
|
|
}
|
|
|
|
// get priority from doledb key
|
|
int32_t pri = Doledb::getPriority ( doledbKey );
|
|
|
|
// if the key went out of its priority because its priority had no
|
|
// spider requests then it will bleed over into another priority so
|
|
// in that case reset it to the top of its priority for next time
|
|
int32_t pri3 = Doledb::getPriority ( &m_sc->m_nextDoledbKey );
|
|
if ( pri3 != m_sc->m_pri2 ) {
|
|
m_sc->m_nextDoledbKey = Doledb::makeFirstKey2 ( m_sc->m_pri2);
|
|
}
|
|
|
|
if ( g_conf.m_logDebugSpider ) {
|
|
int32_t pri4 = Doledb::getPriority ( &m_sc->m_nextDoledbKey );
|
|
char keystrbuf[MAX_KEYSTR_BYTES];
|
|
log( LOG_DEBUG, "spider: setting pri2=%" PRId32" queue doledb nextkey to %s (pri=%" PRId32")",
|
|
m_sc->m_pri2, KEYSTR(&m_sc->m_nextDoledbKey,sizeof(m_sc->m_nextDoledbKey),keystrbuf), pri4 );
|
|
}
|
|
|
|
// update next doledbkey for this priority to avoid having to
|
|
// process excessive positive/negative key annihilations (mdw)
|
|
m_sc->m_nextKeys [ m_sc->m_pri2 ] = m_sc->m_nextDoledbKey;
|
|
|
|
// sanity
|
|
if ( pri < 0 || pri >= MAX_SPIDER_PRIORITIES ) { g_process.shutdownAbort(true); }
|
|
|
|
// skip the priority if we already have enough spiders on it
|
|
int32_t out = m_sc->m_outstandingSpiders[pri];
|
|
|
|
// how many spiders can we have out?
|
|
int32_t max = 0;
|
|
|
|
// in milliseconds. how long to wait between downloads from same IP.
|
|
// only for parnent urls, not including child docs like robots.txt
|
|
// iframe contents, etc.
|
|
int32_t maxSpidersOutPerIp = 1;
|
|
for ( int32_t i = 0 ; i < cr->m_numRegExs ; i++ ) {
|
|
if ( cr->m_spiderPriorities[i] != pri ) {
|
|
continue;
|
|
}
|
|
|
|
if ( cr->m_maxSpidersPerRule[i] > max ) {
|
|
max = cr->m_maxSpidersPerRule[i];
|
|
}
|
|
|
|
if ( cr->m_spiderIpMaxSpiders[i] > maxSpidersOutPerIp ) {
|
|
maxSpidersOutPerIp = cr->m_spiderIpMaxSpiders[i];
|
|
}
|
|
}
|
|
|
|
// skip? and re-get another doledb list from next priority...
|
|
if ( out >= max ) {
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:returning, out=%d max=%d", out, max);
|
|
return true;
|
|
}
|
|
|
|
// no negatives - wtf?
|
|
// if only the tree has doledb recs, Msg5.cpp does not remove
|
|
// the negative recs... it doesn't bother to merge.
|
|
if ( (doledbKey->n0 & 0x01) == 0 ) {
|
|
// just increment then i guess
|
|
m_list.skipCurrentRecord();
|
|
// if exhausted -- try another load with m_nextKey set
|
|
if ( m_list.isExhausted() ) return true;
|
|
// otherwise, try the next doledb rec in this list
|
|
goto listLoop;
|
|
}
|
|
|
|
// what is this? a dataless positive key?
|
|
if ( m_list.getCurrentRecSize() <= 16 ) { g_process.shutdownAbort(true); }
|
|
|
|
int32_t ipOut = 0;
|
|
int32_t globalOut = 0;
|
|
|
|
// get the "spider rec" (SpiderRequest) (embedded in the doledb rec)
|
|
SpiderRequest *sreq = (SpiderRequest *)(rec + sizeof(key96_t)+4);
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:Looking at spider record with firstIp=0x%08x", sreq->m_firstIp);
|
|
|
|
// sanity check. check for http(s)://
|
|
// might be a docid from a pagereindex.cpp
|
|
if ( sreq->m_url[0] != 'h' && ! is_digit(sreq->m_url[0]) ) {
|
|
log(LOG_WARN, "spider: got corrupt doledb record. ignoring. pls fix!!!" );
|
|
|
|
goto skipDoledbRec;
|
|
}
|
|
|
|
// recalculate uh48 to make sure it's the same as stored url
|
|
{
|
|
int64_t uh48 = (hash64b(sreq->m_url) & 0x0000ffffffffffffLL);
|
|
if (sreq->getUrlHash48() != uh48) {
|
|
logError("Recalculated uh48=%" PRId64" != stored uh48=%" PRId64" for url='%s'", uh48, sreq->getUrlHash48(), sreq->m_url);
|
|
goto skipDoledbRec;
|
|
}
|
|
}
|
|
|
|
// . how many spiders out for this ip now?
|
|
// . TODO: count locks in case twin is spidering... but it did not seem
|
|
// to work right for some reason
|
|
for ( int32_t i = 0 ; i <= m_maxUsed ; i++ ) {
|
|
// get it
|
|
const XmlDoc *xd = m_docs[i];
|
|
if ( ! xd ) continue;
|
|
if ( ! xd->m_sreqValid ) continue;
|
|
// to prevent one collection from hogging all the urls for
|
|
// particular IP and starving other collections, let's make
|
|
// this a per collection count.
|
|
// then allow msg13.cpp to handle the throttling on its end.
|
|
// also do a global count over all collections now
|
|
if ( xd->m_sreq.m_firstIp == sreq->m_firstIp ) globalOut++;
|
|
// only count for our same collection otherwise another
|
|
// collection can starve us out
|
|
if ( xd->m_collnum != cr->m_collnum ) continue;
|
|
if ( xd->m_sreq.m_firstIp == sreq->m_firstIp ) ipOut++;
|
|
}
|
|
|
|
// don't give up on this priority, just try next in the list.
|
|
// we now read 50k instead of 2k from doledb in order to fix
|
|
// one ip from bottle corking the whole priority!!
|
|
if ( ipOut >= maxSpidersOutPerIp ) {
|
|
skipDoledbRec:
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:Skipping spider record with firstIp=0x%08x", sreq->m_firstIp);
|
|
// skip
|
|
m_list.skipCurrentRecord();
|
|
|
|
// if not exhausted try the next doledb rec in this list
|
|
if ( ! m_list.isExhausted() ) {
|
|
goto listLoop;
|
|
}
|
|
|
|
// print a log msg if we corked things up even
|
|
// though we read 50k from doledb
|
|
// todo: how is this test supposed to work? We only asked Msg5 for x bytes, so testing if the list is larger than x bytes seems to never be true
|
|
if ( m_list.getListSize() > doleDbRecSizes ) {
|
|
log("spider: 50k not big enough");
|
|
}
|
|
|
|
// list is exhausted...
|
|
return true;
|
|
}
|
|
|
|
// but if the global is high, only allow one out per coll so at
|
|
// least we dont starve and at least we don't make a huge wait in
|
|
// line of queued results just sitting there taking up mem and
|
|
// spider slots so the crawlbot hourly can't pass.
|
|
if ( globalOut >= maxSpidersOutPerIp && ipOut >= 1 ) {
|
|
log(LOG_DEBUG,"spider:gotDoledbList2:too many outstanding spiders, globalOut=%d, maxSpidersOutPerIp=%d, ipOut=%d", globalOut, maxSpidersOutPerIp, ipOut);
|
|
goto skipDoledbRec;
|
|
}
|
|
|
|
char ipbuf[16];
|
|
logDebug( g_conf.m_logDebugSpider, "spider: %" PRId32" spiders out for %s for %s", ipOut, iptoa(sreq->m_firstIp,ipbuf), sreq->m_url );
|
|
|
|
// sometimes we have it locked, but is still in doledb i guess.
|
|
// seems like we might have give the lock to someone else and
|
|
// there confirmation has not come through yet, so it's still
|
|
// in doledb.
|
|
|
|
{
|
|
ScopedLock sl(m_lockTableMtx);
|
|
|
|
// get the lock... only avoid if confirmed!
|
|
int64_t lockKey = makeLockTableKey(sreq);
|
|
int32_t slot = m_lockTable.getSlot(&lockKey);
|
|
if (slot >= 0) {
|
|
// get the corresponding lock then if there
|
|
UrlLock *lock = (UrlLock *)m_lockTable.getValueFromSlot(slot);
|
|
|
|
// if there and confirmed, why still in doledb?
|
|
if (lock) {
|
|
// fight log spam
|
|
static int32_t s_lastTime = 0;
|
|
if (nowGlobal - s_lastTime >= 2) {
|
|
// why is it not getting unlocked!?!?!
|
|
log("spider: spider request locked but still in doledb. uh48=%" PRId64" firstip=%s %s",
|
|
sreq->getUrlHash48(), iptoa(sreq->m_firstIp,ipbuf), sreq->m_url);
|
|
s_lastTime = nowGlobal;
|
|
}
|
|
|
|
// just increment then i guess
|
|
m_list.skipCurrentRecord();
|
|
|
|
// let's return false here to avoid an infinite loop
|
|
// since we are not advancing nextkey and m_pri is not
|
|
// being changed, that is what happens!
|
|
if (m_list.isExhausted()) {
|
|
// crap. but then we never make it to lower priorities.
|
|
// since we are returning false. so let's try the
|
|
// next priority in line.
|
|
|
|
// try returning true now that we skipped to
|
|
// the next priority level to avoid the infinite
|
|
// loop as described above.
|
|
return true;
|
|
}
|
|
// try the next record in this list
|
|
goto listLoop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// log this now
|
|
if ( g_conf.m_logDebugSpider ) {
|
|
logf( LOG_DEBUG, "spider: trying to spider url %s", sreq->m_url );
|
|
}
|
|
|
|
// reset reason why crawl is not running, because we basically are now
|
|
cr->m_spiderStatus = spider_status_t::SP_INPROGRESS;
|
|
|
|
// be sure to save state so we do not re-send emails
|
|
cr->setNeedsSave();
|
|
|
|
// sometimes the spider coll is reset/deleted while we are
|
|
// trying to get the lock in spiderUrl() so let's use collnum
|
|
collnum_t collnum = m_sc->getCollectionRec()->m_collnum;
|
|
|
|
// . spider that. we don't care wheter it blocks or not
|
|
// . crap, it will need to block to get the locks!
|
|
// . so at least wait for that!!!
|
|
// . but if we end up launching the spider then this should NOT
|
|
// return false! only return false if we should hold up the doledb
|
|
// scan
|
|
// . this returns true right away if it failed to get the lock...
|
|
// which means the url is already locked by someone else...
|
|
// . it might also return true if we are already spidering the url
|
|
bool status = spiderUrl(sreq, doledbKey, collnum);
|
|
|
|
// just increment then i guess
|
|
m_list.skipCurrentRecord();
|
|
|
|
// if it blocked, wait for it to return to resume the doledb list
|
|
// processing because the msg12 is out and we gotta wait for it to
|
|
// come back. when lock reply comes back it tries to spider the url
|
|
// then it tries to call spiderDoledUrls() to keep the spider queue
|
|
// spidering fully.
|
|
if ( ! status ) {
|
|
return false;
|
|
}
|
|
|
|
// if exhausted -- try another load with m_nextKey set
|
|
if ( m_list.isExhausted() ) {
|
|
// if no more in list, fix the next doledbkey,
|
|
// m_sc->m_nextDoledbKey
|
|
log ( LOG_DEBUG, "spider: list exhausted." );
|
|
return true;
|
|
}
|
|
// otherwise, it might have been in the lock cache and quickly
|
|
// rejected, or rejected for some other reason, so try the next
|
|
// doledb rec in this list
|
|
goto listLoop;
|
|
}
|
|
|
|
|
|
|
|
// . spider the next url that needs it the most
|
|
// . returns false if blocked on a spider launch, otherwise true.
|
|
// . returns false if your callback will be called
|
|
// . returns true and sets g_errno on error
|
|
bool SpiderLoop::spiderUrl(SpiderRequest *sreq, const key96_t *doledbKey, collnum_t collnum) {
|
|
// sanity
|
|
if ( ! m_sc ) { g_process.shutdownAbort(true); }
|
|
|
|
// wait until our clock is synced with host #0 before spidering since
|
|
// we store time stamps in the domain and ip wait tables in
|
|
// SpiderCache.cpp. We don't want to freeze domain for a long time
|
|
// because we think we have to wait until tomorrow before we can
|
|
// spider it.
|
|
|
|
// turned off?
|
|
if ( ( (! g_conf.m_spideringEnabled ||
|
|
// or if trying to exit
|
|
g_process.isShuttingDown()
|
|
) && ! sreq->m_isInjecting ) ||
|
|
// repairing the collection's rdbs?
|
|
g_repairMode ) {
|
|
// try to cancel outstanding spiders, ignore injects
|
|
for ( int32_t i = 0 ; i <= m_maxUsed ; i++ ) {
|
|
// get it
|
|
XmlDoc *xd = m_docs[i];
|
|
if ( ! xd ) continue;
|
|
// let everyone know, TcpServer::cancel() uses this in
|
|
// destroySocket()
|
|
g_errno = ECANCELLED;
|
|
// cancel the socket trans who has "xd" as its state.
|
|
// this will cause XmlDoc::gotDocWrapper() to be called
|
|
// now, on this call stack with g_errno set to
|
|
// ECANCELLED. But if Msg16 was not in the middle of
|
|
// HttpServer::getDoc() then this will have no effect.
|
|
g_httpServer.cancel ( xd );//, g_msg13RobotsWrapper );
|
|
// cancel any Msg13 that xd might have been waiting for
|
|
g_udpServer.cancel ( &xd->m_msg13 , msg_type_13 );
|
|
}
|
|
return true;
|
|
}
|
|
// do not launch any new spiders if in repair mode
|
|
if ( g_repairMode ) {
|
|
g_conf.m_spideringEnabled = false;
|
|
return true;
|
|
}
|
|
// do not launch another spider if less than 25MB of memory available.
|
|
// this causes us to dead lock when spiders use up all the mem, and
|
|
// file merge operation can not get any, and spiders need to add to
|
|
// titledb but can not until the merge completes!!
|
|
int64_t freeMem = g_mem.getFreeMem();
|
|
if (freeMem < 25*1024*1024 ) {
|
|
static int32_t s_lastTime = 0;
|
|
static int32_t s_missed = 0;
|
|
s_missed++;
|
|
int32_t now = getTime();
|
|
// don't spam the log, bug let people know about it
|
|
if ( now - s_lastTime > 10 ) {
|
|
log("spider: Need 25MB of free mem to launch spider, "
|
|
"only have %" PRId64". Failed to launch %" PRId32" times so "
|
|
"far.", freeMem , s_missed );
|
|
s_lastTime = now;
|
|
}
|
|
}
|
|
|
|
// . now that we have to use msg12 to see if the thing is locked
|
|
// to avoid spidering it.. (see comment in above function)
|
|
// we often try to spider something we are already spidering. that
|
|
// is why we have an rdbcache, m_lockCache, to make these lock
|
|
// lookups quick, now that the locking group is usually different
|
|
// than our own!
|
|
// . we have to check this now because removeAllLocks() below will
|
|
// remove a lock that one of our spiders might have. it is only
|
|
// sensitive to our hostid, not "spider id"
|
|
// sometimes we exhaust the doledb and m_nextDoledbKey gets reset
|
|
// to zero, we do a re-scan and get a doledbkey that is currently
|
|
// being spidered or is waiting for its negative doledb key to
|
|
// get into our doledb tree
|
|
for ( int32_t i = 0 ; i <= m_maxUsed ; i++ ) {
|
|
// get it
|
|
XmlDoc *xd = m_docs[i];
|
|
if ( ! xd ) continue;
|
|
|
|
// jenkins was coring spidering the same url in different
|
|
// collections at the same time
|
|
if ( ! xd->m_collnumValid ) continue;
|
|
if ( xd->m_collnum != collnum ) continue;
|
|
|
|
// . problem if it has our doledb key!
|
|
// . this happens if we removed the lock above before the
|
|
// spider returned!! that's why you need to set
|
|
// MAX_LOCK_AGE to like an hour or so
|
|
// . i've also seen this happen because we got stuck looking
|
|
// up like 80,000 places and it was taking more than an
|
|
// hour. it had only reach about 30,000 after an hour.
|
|
// so at this point just set the lock timeout to
|
|
// 4 hours i guess.
|
|
// . i am seeing this again and we are trying over and over
|
|
// again to spider the same url and hogging the cpu so
|
|
|
|
// we need to keep this sanity check in here for times
|
|
// like this
|
|
if ( xd->m_doledbKey == *doledbKey ) {
|
|
// just note it for now
|
|
log("spider: spidering same url %s twice. "
|
|
"different firstips?",
|
|
xd->m_firstUrl.getUrl());
|
|
//g_process.shutdownAbort(true); }
|
|
}
|
|
}
|
|
|
|
// reset g_errno
|
|
g_errno = 0;
|
|
|
|
char doledbKeyStr[MAX_KEYSTR_BYTES];
|
|
logDebug(g_conf.m_logDebugSpider, "spider: deleting doledb tree key=%s", KEYSTR(doledbKey, sizeof(*doledbKey), doledbKeyStr));
|
|
|
|
// now we just take it out of doledb instantly
|
|
bool deleted = g_doledb.getRdb()->deleteTreeNode(collnum, (const char *)doledbKey);
|
|
|
|
// if url filters rebuilt then doledb gets reset and i've seen us hit
|
|
// this node == -1 condition here... so maybe ignore it... just log
|
|
// what happened? i think we did a quickpoll somewhere between here
|
|
// and the call to spiderDoledUrls() and it the url filters changed
|
|
// so it reset doledb's tree. so in that case we should bail on this
|
|
// url.
|
|
if (!deleted) {
|
|
g_errno = EADMININTERFERENCE;
|
|
log("spider: lost url about to spider from url filters "
|
|
"and doledb tree reset. %s",mstrerror(g_errno));
|
|
return true;
|
|
}
|
|
|
|
|
|
// now remove from doleiptable since we removed from doledb
|
|
m_sc->removeFromDoledbIpTable(sreq->m_firstIp);
|
|
|
|
// DO NOT add back to waiting tree if max spiders
|
|
// out per ip was 1 OR there was a crawldelay. but better
|
|
// yet, take care of that in the winReq code above.
|
|
|
|
// . now add to waiting tree so we add another spiderdb
|
|
// record for this firstip to doledb
|
|
// . true = callForScan
|
|
// . do not add to waiting tree if we have enough outstanding
|
|
// spiders for this ip. we will add to waiting tree when
|
|
// we receive a SpiderReply in addSpiderReply()
|
|
if (
|
|
// this will just return true if we are not the
|
|
// responsible host for this firstip
|
|
! m_sc->addToWaitingTree(sreq->m_firstIp) &&
|
|
// must be an error...
|
|
g_errno ) {
|
|
const char *msg = "FAILED TO ADD TO WAITING TREE";
|
|
log("spider: %s %s",msg,mstrerror(g_errno));
|
|
//us->sendErrorReply ( udpSlot , g_errno );
|
|
//return;
|
|
}
|
|
|
|
int64_t lockKeyUh48 = makeLockTableKey ( sreq );
|
|
|
|
logDebug(g_conf.m_logDebugSpider, "spider: adding lock uh48=%" PRId64" lockkey=%" PRId64,
|
|
sreq->getUrlHash48(),lockKeyUh48);
|
|
|
|
// . add it to lock table to avoid respider, removing from doledb
|
|
// is not enough because we re-add to doledb right away
|
|
// . return true on error here
|
|
UrlLock tmp;
|
|
tmp.m_firstIp = sreq->m_firstIp;
|
|
tmp.m_spiderOutstanding = 0;
|
|
tmp.m_collnum = collnum;
|
|
|
|
if (!addLock(lockKeyUh48, &tmp)) {
|
|
return true;
|
|
}
|
|
|
|
// now do it. this returns false if it would block, returns true if it
|
|
// would not block. sets g_errno on error. it spiders m_sreq.
|
|
return spiderUrl2(sreq, doledbKey, collnum);
|
|
}
|
|
|
|
bool SpiderLoop::spiderUrl2(SpiderRequest *sreq, const key96_t *doledbKey, collnum_t collnum) {
|
|
logTrace( g_conf.m_logTraceSpider, "BEGIN" );
|
|
|
|
if (!sreq->m_urlIsDocId) {
|
|
/// @todo ALC temporarily skip spider req url that are different after stripping parameters
|
|
size_t sreqUrlLen = strlen(sreq->m_url);
|
|
|
|
Url url;
|
|
url.set(sreq->m_url, sreqUrlLen, false, true);
|
|
|
|
if (strcmp(sreq->m_url, url.getUrl()) != 0) {
|
|
logError("SpiderReq url is different after stripping param. Dropping url='%s'", sreq->m_url);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// let's check if we have spidered this recently before (only if it's a normal spider)
|
|
if (!sreq->m_fakeFirstIp && !sreq->m_urlIsDocId && !sreq->m_isAddUrl && !sreq->m_isInjecting && !sreq->m_isPageParser && !sreq->m_isPageReindex) {
|
|
std::string url(sreq->m_url);
|
|
void *data = NULL;
|
|
if (m_urlCache.lookup(url, &data)) {
|
|
// this is not suppose to happen!
|
|
logError("Trying to respider url within %" PRId64" seconds. Dropping url='%s'", g_conf.m_spiderUrlCacheMaxAge, url.c_str());
|
|
return true;
|
|
}
|
|
|
|
m_urlCache.insert(url, NULL);
|
|
}
|
|
|
|
// . find an available doc slot
|
|
// . we can have up to MAX_SPIDERS spiders (300)
|
|
int32_t i;
|
|
for ( i=0 ; i<MAX_SPIDERS ; i++ ) if (! m_docs[i]) break;
|
|
|
|
// come back later if we're full
|
|
if ( i >= MAX_SPIDERS ) {
|
|
log(LOG_DEBUG,"build: Already have %" PRId32" outstanding spiders.",
|
|
(int32_t)MAX_SPIDERS);
|
|
g_process.shutdownAbort(true);
|
|
}
|
|
|
|
XmlDoc *xd;
|
|
// otherwise, make a new one if we have to
|
|
try { xd = new (XmlDoc); }
|
|
// bail on failure, sleep and try again
|
|
catch(std::bad_alloc&) {
|
|
g_errno = ENOMEM;
|
|
log("build: Could not allocate %" PRId32" bytes to spider "
|
|
"the url %s. Will retry later.",
|
|
(int32_t)sizeof(XmlDoc), sreq->m_url );
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "END, new XmlDoc failed" );
|
|
return true;
|
|
}
|
|
// register it's mem usage with Mem.cpp class
|
|
mnew ( xd , sizeof(XmlDoc) , "XmlDoc" );
|
|
// add to the array
|
|
m_docs [ i ] = xd;
|
|
|
|
CollectionRec *cr = g_collectiondb.getRec(collnum);
|
|
const char *coll = "collnumwasinvalid";
|
|
if ( cr ) coll = cr->m_coll;
|
|
|
|
if ( g_conf.m_logDebugSpider ) {
|
|
char ipbuf[16];
|
|
logf(LOG_DEBUG,"spider: spidering firstip9=%s(%" PRIu32") "
|
|
"uh48=%" PRIu64" prntdocid=%" PRIu64" k.n1=%" PRIu64" k.n0=%" PRIu64,
|
|
iptoa(sreq->m_firstIp,ipbuf),
|
|
(uint32_t)sreq->m_firstIp,
|
|
sreq->getUrlHash48(),
|
|
sreq->getParentDocId() ,
|
|
sreq->m_key.n1,
|
|
sreq->m_key.n0);
|
|
}
|
|
|
|
// this returns false and sets g_errno on error
|
|
if (!xd->set4(sreq, doledbKey, coll, NULL, MAX_NICENESS)) {
|
|
// i guess m_coll is no longer valid?
|
|
mdelete ( m_docs[i] , sizeof(XmlDoc) , "Doc" );
|
|
delete (m_docs[i]);
|
|
m_docs[i] = NULL;
|
|
// error, g_errno should be set!
|
|
logTrace( g_conf.m_logTraceSpider, "END, xd->set4 returned false" );
|
|
return true;
|
|
}
|
|
|
|
// call this after doc gets indexed
|
|
xd->setCallback ( xd , indexedDocWrapper );
|
|
|
|
// increase m_maxUsed if we have to
|
|
if ( i > m_maxUsed ) m_maxUsed = i;
|
|
// count it
|
|
m_numSpidersOut++;
|
|
// count this
|
|
m_sc->m_spidersOut++;
|
|
|
|
m_launches++;
|
|
|
|
// sanity check
|
|
if (sreq->m_priority <= -1 ) {
|
|
log("spider: fixing bogus spider req priority of %i for "
|
|
"url %s",
|
|
(int)sreq->m_priority,sreq->m_url);
|
|
sreq->m_priority = 0;
|
|
//g_process.shutdownAbort(true);
|
|
}
|
|
|
|
// update this
|
|
m_sc->m_outstandingSpiders[(unsigned char)sreq->m_priority]++;
|
|
|
|
if ( g_conf.m_logDebugSpider )
|
|
log(LOG_DEBUG,"spider: sc_out=%" PRId32" waiting=%" PRId32" url=%s",
|
|
m_sc->m_spidersOut,
|
|
m_sc->m_waitingTree.getNumUsedNodes(),
|
|
sreq->m_url);
|
|
|
|
// . return if this blocked
|
|
// . no, launch another spider!
|
|
logTrace( g_conf.m_logTraceSpider, "calling xd->indexDoc" );
|
|
bool status = xd->indexDoc();
|
|
logTrace( g_conf.m_logTraceSpider, "indexDoc status [%s]" , status?"true":"false");
|
|
|
|
// if we were injecting and it blocked... return false
|
|
if ( ! status ) {
|
|
logTrace( g_conf.m_logTraceSpider, "END, indexDoc blocked" );
|
|
return false;
|
|
}
|
|
|
|
// deal with this error
|
|
indexedDoc ( xd );
|
|
|
|
// "callback" will not be called cuz it should be NULL
|
|
logTrace( g_conf.m_logTraceSpider, "END, return true" );
|
|
return true;
|
|
}
|
|
|
|
void SpiderLoop::indexedDocWrapper ( void *state ) {
|
|
// . process the results
|
|
// . return if this blocks
|
|
if ( ! g_spiderLoop.indexedDoc ( (XmlDoc *)state ) ) return;
|
|
}
|
|
|
|
|
|
|
|
// . this will delete m_docs[i]
|
|
// . returns false if blocked, true otherwise
|
|
// . sets g_errno on error
|
|
bool SpiderLoop::indexedDoc ( XmlDoc *xd ) {
|
|
logTrace( g_conf.m_logTraceSpider, "BEGIN" );
|
|
|
|
// get our doc #, i
|
|
int32_t i = 0;
|
|
for ( ; i < MAX_SPIDERS ; i++ ) if ( m_docs[i] == xd) break;
|
|
// sanity check
|
|
if ( i >= MAX_SPIDERS ) { g_process.shutdownAbort(true); }
|
|
|
|
// . decrease m_maxUsed if we need to
|
|
// . we can decrease all the way to -1, which means no spiders going on
|
|
if ( m_maxUsed == i ) {
|
|
m_maxUsed--;
|
|
while ( m_maxUsed >= 0 && ! m_docs[m_maxUsed] ) m_maxUsed--;
|
|
}
|
|
// count it
|
|
m_numSpidersOut--;
|
|
|
|
// get coll
|
|
collnum_t collnum = xd->m_collnum;
|
|
// if coll was deleted while spidering, sc will be NULL
|
|
SpiderColl *sc = g_spiderCache.getSpiderColl(collnum);
|
|
// decrement this
|
|
if ( sc ) sc->m_spidersOut--;
|
|
// get the original request from xmldoc
|
|
SpiderRequest *sreq = &xd->m_sreq;
|
|
// update this.
|
|
if ( sc ) sc->m_outstandingSpiders[(unsigned char)sreq->m_priority]--;
|
|
|
|
// note it
|
|
// this should not happen any more since indexDoc() will take
|
|
// care of g_errno now by clearing it and adding an error spider
|
|
// reply to release the lock!!
|
|
if ( g_errno ) {
|
|
log("spider: spidering %s has error: %s. uh48=%" PRId64". "
|
|
"cn=%" PRId32,
|
|
xd->m_firstUrl.getUrl(),
|
|
mstrerror(g_errno),
|
|
xd->getFirstUrlHash48(),
|
|
(int32_t)collnum);
|
|
// don't release the lock on it right now. just let the
|
|
// lock expire on it after MAX_LOCK_AGE seconds. then it will
|
|
// be retried. we need to debug gb so these things never
|
|
// hapeen...
|
|
}
|
|
|
|
// we don't need this g_errno passed this point
|
|
g_errno = 0;
|
|
|
|
// we are responsible for deleting doc now
|
|
mdelete ( m_docs[i] , sizeof(XmlDoc) , "Doc" );
|
|
delete (m_docs[i]);
|
|
m_docs[i] = NULL;
|
|
|
|
// we did not block, so return true
|
|
logTrace( g_conf.m_logTraceSpider, "END" );
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// use -1 for any collnum
|
|
int32_t SpiderLoop::getNumSpidersOutPerIp(int32_t firstIp, collnum_t collnum) {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
int32_t count = 0;
|
|
|
|
// scan the slots
|
|
for (int32_t i = 0; i < m_lockTable.getNumSlots(); i++) {
|
|
// skip if empty
|
|
if (!m_lockTable.m_flags[i]) {
|
|
continue;
|
|
}
|
|
|
|
// cast lock
|
|
const UrlLock *lock = (const UrlLock *)m_lockTable.getValueFromSlot(i);
|
|
|
|
// skip if not outstanding, just a 5-second expiration wait
|
|
// when the spiderReply returns, so that in case a lock
|
|
// request for the same url was in progress, it will be denied.
|
|
if (!lock->m_spiderOutstanding) {
|
|
continue;
|
|
}
|
|
|
|
// correct collnum?
|
|
if (lock->m_collnum != collnum && collnum != -1) {
|
|
continue;
|
|
}
|
|
|
|
// skip if not yet expired
|
|
if (lock->m_firstIp == firstIp) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
|
|
CollectionRec *SpiderLoop::getActiveList() {
|
|
|
|
uint32_t nowGlobal = (uint32_t)getTime();
|
|
|
|
if ( nowGlobal >= m_recalcTime && m_recalcTimeValid )
|
|
m_activeListValid = false;
|
|
|
|
// we set m_activeListValid to false when enabling/disabling spiders,
|
|
// when rebuilding url filters in Collectiondb.cpp rebuildUrlFilters()
|
|
// and when updating the site list in updateSiteList(). all of these
|
|
// could possible make an inactive collection active again, or vice
|
|
// versa. also when deleting a collection in Collectiondb.cpp. this
|
|
// keeps the below loop fast when we have thousands of collections
|
|
// and most are inactive or empty/deleted.
|
|
if (!m_activeListValid) {
|
|
buildActiveList();
|
|
//m_crx = m_activeList;
|
|
// recompute every 3 seconds, it seems kinda buggy!!
|
|
m_recalcTime = nowGlobal + 3;
|
|
m_recalcTimeValid = true;
|
|
}
|
|
|
|
return m_activeList;
|
|
}
|
|
|
|
|
|
|
|
void SpiderLoop::buildActiveList ( ) {
|
|
logTrace( g_conf.m_logTraceSpider, "BEGIN" );
|
|
|
|
// when do we need to rebuild the active list again?
|
|
m_recalcTimeValid = false;
|
|
|
|
m_activeListValid = true;
|
|
|
|
m_activeListCount = 0;
|
|
|
|
// reset the linked list of active collections
|
|
m_activeList = NULL;
|
|
bool found = false;
|
|
|
|
CollectionRec *tail = NULL;
|
|
|
|
for ( int32_t i = 0 ; i < g_collectiondb.getNumRecs(); i++ ) {
|
|
// get rec
|
|
CollectionRec *cr = g_collectiondb.getRec(i);
|
|
// skip if gone
|
|
if ( ! cr ) continue;
|
|
// stop if not enabled
|
|
bool active = true;
|
|
if ( ! cr->m_spideringEnabled ) active = false;
|
|
|
|
// we are at the tail of the linked list OR not in the list
|
|
cr->m_nextActive = NULL;
|
|
|
|
cr->m_isActive = false;
|
|
|
|
if ( ! active ) continue;
|
|
|
|
cr->m_isActive = true;
|
|
|
|
m_activeListCount++;
|
|
|
|
if ( cr == m_crx ) found = true;
|
|
|
|
// if first one, set it to head
|
|
if ( ! tail ) {
|
|
m_activeList = cr;
|
|
tail = cr;
|
|
continue;
|
|
}
|
|
|
|
// if not first one, add it to end of tail
|
|
tail->m_nextActive = cr;
|
|
tail = cr;
|
|
}
|
|
|
|
// we use m_bookmark so we do not get into an infinite loop
|
|
// in spider urls logic above
|
|
if ( ! found ) {
|
|
m_bookmark = NULL;
|
|
m_crx = NULL;
|
|
}
|
|
|
|
logTrace( g_conf.m_logTraceSpider, "END" );
|
|
}
|
|
|
|
bool SpiderLoop::isLocked(int64_t key) const {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
return m_lockTable.isInTable(&key);
|
|
}
|
|
|
|
int32_t SpiderLoop::getLockCount() const {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
return m_lockTable.getNumUsedSlots();
|
|
}
|
|
|
|
bool SpiderLoop::addLock(int64_t key, const UrlLock *lock) {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
return m_lockTable.addKey(&key, lock);
|
|
}
|
|
|
|
void SpiderLoop::removeLock(int64_t key) {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
m_lockTable.removeKey(&key);
|
|
}
|
|
|
|
void SpiderLoop::clearLocks(collnum_t collnum) {
|
|
ScopedLock sl(m_lockTableMtx);
|
|
|
|
// remove locks from locktable for all spiders out
|
|
for (;;) {
|
|
bool restart = false;
|
|
|
|
// scan the slots
|
|
for (int32_t i = 0; i < m_lockTable.getNumSlots(); i++) {
|
|
// skip if empty
|
|
if (!m_lockTable.m_flags[i]) {
|
|
continue;
|
|
}
|
|
|
|
const UrlLock *lock = (const UrlLock *)m_lockTable.getValueFromSlot(i);
|
|
// skip if not our collnum
|
|
if (lock->m_collnum != collnum) {
|
|
continue;
|
|
}
|
|
|
|
// nuke it!
|
|
m_lockTable.removeSlot(i);
|
|
|
|
// restart since cells may have shifted
|
|
restart = true;
|
|
}
|
|
|
|
if (!restart) {
|
|
break;
|
|
}
|
|
}
|
|
}
|