bool LLQueuedThread::completeRequest(handle_t handle) { bool res = false; lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); if (req) { llassert_always(req->getStatus() != STATUS_QUEUED); llassert_always(req->getStatus() != STATUS_INPROGRESS); #if _DEBUG // LL_INFOS() << llformat("LLQueuedThread::Completed req [%08d]",handle) << LL_ENDL; #endif if (!(req->getFlags() & FLAG_LOCKED)) { mRequestHash.erase(handle); req->deleteRequest(); // check(); } else { // Cause deletion immediately after FLAG_LOCKED is released. req->setFlags(FLAG_AUTO_COMPLETE); } res = true; } unlockData(); return res; }
void LLQueuedThread::shutdown() { setQuitting(); unpause(); // MAIN THREAD if (mThreaded) { S32 timeout = 100; for ( ; timeout>0; timeout--) { if (isStopped()) { break; } ms_sleep(100); LLThread::yield(); } if (timeout == 0) { llwarns << "~LLQueuedThread (" << mName << ") timed out!" << llendl; } } else { mStatus = STOPPED; } QueuedRequest* req; S32 active_count = 0; while ( (req = (QueuedRequest*)mRequestHash.pop_element()) ) { if (req->getStatus() == STATUS_QUEUED || req->getStatus() == STATUS_INPROGRESS) { ++active_count; req->setStatus(STATUS_ABORTED); // avoid assert in deleteRequest } req->deleteRequest(); } if (active_count) { llwarns << "~LLQueuedThread() called with active requests: " << active_count << llendl; } }
void LLQueuedThread::setPriority(handle_t handle, U32 priority) { lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); if (req) { if(req->getStatus() == STATUS_INPROGRESS) { // not in list req->setPriority(priority); } else if(req->getStatus() == STATUS_QUEUED) { // remove from list then re-insert llverify(mRequestQueue.erase(req) == 1); req->setPriority(priority); mRequestQueue.insert(req); } } unlockData(); }
LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) { status_t res = STATUS_EXPIRED; lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); if (req) { res = req->getStatus(); } unlockData(); return res; }
// MAIN thread void LLQueuedThread::printQueueStats() { lockData(); if (!mRequestQueue.empty()) { QueuedRequest *req = *mRequestQueue.begin(); llinfos << llformat("Pending Requests:%d Current status:%d", mRequestQueue.size(), req->getStatus()) << llendl; } else { llinfos << "Queued Thread Idle" << llendl; } unlockData(); }
// MAIN thread bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) { llassert (handle != nullHandle()); bool res = false; bool waspaused = isPaused(); bool done = false; while(!done) { update(0); // unpauses lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); if (!req) { done = true; // request does not exist } else if (req->getStatus() == STATUS_COMPLETE && !(req->getFlags() & FLAG_LOCKED)) { res = true; if (auto_complete) { mRequestHash.erase(handle); req->deleteRequest(); // check(); } done = true; } unlockData(); if (!done && mThreaded) { yield(); } } if (waspaused) { pause(); } return res; }
S32 LLQueuedThread::processNextRequest() { QueuedRequest *req; // Get next request from pool lockData(); while(1) { req = NULL; if (mRequestQueue.empty()) { break; } req = *mRequestQueue.begin(); mRequestQueue.erase(mRequestQueue.begin()); if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) { req->setStatus(STATUS_ABORTED); // Unlock, because we can't call finishRequest() while keeping this lock: // generateHandle() takes this lock too and is called while holding a lock // (ie LLTextureFetchWorker::mWorkMutex) that finishRequest will lock too, // causing a dead lock. // Although a complete rewrite of LLQueuedThread is in order because it's // far from robust the way it handles it's locking; the following rationale // at least makes plausible that releasing the lock here SHOULD work if // the original coder didn't completely f**k up: if before the QueuedRequest // req was only accessed while keeping the lock, then it still should // never happen that another thread is waiting for this lock in order to // access the QueuedRequest: a few lines lower we delete it. // In other words, if directly after releasing this lock another thread // would access req, then that can only happen by finding it again in // either mRequestQueue or mRequestHash. We already deleted it from the // first, so this access would have to happen by finding it in mRequestHash. // Such access happens in the following functions: // 1) LLQueuedThread::shutdown -- but it does that anyway, as it doesn't use any locking. // 2) LLQueuedThread::generateHandle -- might skip our handle while before it would block until we deleted it. Skipping it is actually better. // 3) LLQueuedThread::waitForResult -- this now doesn't touch the req when it has the flag FLAG_LOCKED set. // 4) LLQueuedThread::getRequest -- whereever this is used, FLAG_LOCKED is tested before using the req. // 5) LLQueuedThread::getRequestStatus -- this is a read-only operation on the status, which should never be changed from finishRequest(). // 6) LLQueuedThread::abortRequest -- it doesn't seem to hurt to add flags (if this happens at all), while calling finishRequest(). // 7) LLQueuedThread::setFlags -- same. // 8) LLQueuedThread::setPriority -- doesn't access req with status STATUS_ABORTED, STATUS_COMPLETE or STATUS_INPROGRESS. // 9) LLQueuedThread::completeRequest -- now sets FLAG_AUTO_COMPLETE instead of deleting the req, if FLAG_LOCKED is set, so that deletion happens here when finishRequest returns. req->setFlags(FLAG_LOCKED); unlockData(); req->finishRequest(false); lockData(); req->resetFlags(FLAG_LOCKED); if ((req->getFlags() & FLAG_AUTO_COMPLETE)) { req->resetFlags(FLAG_AUTO_COMPLETE); mRequestHash.erase(req); // check(); unlockData(); req->deleteRequest(); lockData(); } continue; } llassert_always(req->getStatus() == STATUS_QUEUED); break; } U32 start_priority = 0 ; if (req) { req->setStatus(STATUS_INPROGRESS); start_priority = req->getPriority(); } unlockData(); // This is the only place we will call req->setStatus() after // it has initially been seet to STATUS_QUEUED, so it is // safe to access req. if (req) { // process request bool complete = req->processRequest(); if (complete) { lockData(); req->setStatus(STATUS_COMPLETE); req->setFlags(FLAG_LOCKED); unlockData(); req->finishRequest(true); if ((req->getFlags() & FLAG_AUTO_COMPLETE)) { lockData(); req->resetFlags(FLAG_AUTO_COMPLETE); mRequestHash.erase(req); // check(); req->resetFlags(FLAG_LOCKED); unlockData(); req->deleteRequest(); } else { req->resetFlags(FLAG_LOCKED); } } else { lockData(); req->setStatus(STATUS_QUEUED); mRequestQueue.insert(req); unlockData(); if (mThreaded && start_priority < PRIORITY_NORMAL) { ms_sleep(1); // sleep the thread a little } } } S32 pending = getPending(); return pending; }