mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
(cherry picked from commit 8432f450001a6deb3d1d92542994a17b4dc049a0) (cherry picked from commit 3665d35df0f29fe625359a1daa475c1c4bcc4329)
382 lines
8.1 KiB
C++
382 lines
8.1 KiB
C++
/*************************************************************************
|
|
* UrBackup - Client/Server backup system
|
|
* Copyright (C) 2011-2016 Martin Raiber
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
|
|
#include "Interface/Thread.h"
|
|
|
|
#include "ThreadPool.h"
|
|
#include "Server.h"
|
|
#include "stringtools.h"
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
|
|
#if defined(_WIN32) && defined(_DEBUG)
|
|
#include <Windows.h>
|
|
#include <assert.h>
|
|
#elif defined(__linux__)
|
|
void assert_process_priority();
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
void checkThreadPriority()
|
|
{
|
|
#if defined(_WIN32) && defined(_DEBUG)
|
|
int thread_prio = GetThreadPriority(GetCurrentThread());
|
|
assert(thread_prio== THREAD_PRIORITY_ERROR_RETURN
|
|
|| thread_prio == THREAD_PRIORITY_NORMAL);
|
|
#elif defined(__linux__)
|
|
assert_process_priority();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
CPoolThread::CPoolThread(CThreadPool *pMgr)
|
|
{
|
|
mgr=pMgr;
|
|
dexit=false;
|
|
}
|
|
|
|
void CPoolThread::operator()(void)
|
|
{
|
|
checkThreadPriority();
|
|
|
|
THREAD_ID tid = Server->getThreadID();
|
|
THREADPOOL_TICKET ticket;
|
|
bool stop=false;
|
|
std::string name;
|
|
IThread *tr=mgr->getRunnable(&ticket, false, stop, name);
|
|
if(tr!=NULL)
|
|
{
|
|
if (!name.empty())
|
|
{
|
|
Server->setCurrentThreadName(name);
|
|
}
|
|
else
|
|
{
|
|
Server->setCurrentThreadName("unnamed");
|
|
}
|
|
(*tr)();
|
|
checkThreadPriority();
|
|
Server->clearDatabases(tid);
|
|
}
|
|
|
|
if(!stop)
|
|
{
|
|
while(!dexit)
|
|
{
|
|
stop=false;
|
|
std::string name;
|
|
IThread *tr=mgr->getRunnable(&ticket, true, stop, name);
|
|
if(tr!=NULL)
|
|
{
|
|
if (!name.empty())
|
|
{
|
|
Server->setCurrentThreadName(name);
|
|
}
|
|
else
|
|
{
|
|
Server->setCurrentThreadName("unnamed");
|
|
}
|
|
(*tr)();
|
|
checkThreadPriority();
|
|
Server->clearDatabases(tid);
|
|
}
|
|
else if(stop)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Server->destroyDatabases(tid);
|
|
mgr->Remove(this, !stop);
|
|
delete this;
|
|
}
|
|
|
|
void CPoolThread::shutdown(void)
|
|
{
|
|
dexit=true;
|
|
}
|
|
|
|
IThread * CThreadPool::getRunnable(THREADPOOL_TICKET *todel, bool del, bool& stop, std::string& name)
|
|
{
|
|
IScopedLock lock(mutex);
|
|
|
|
if( del==true )
|
|
{
|
|
--nRunning;
|
|
std::map<THREADPOOL_TICKET, SRunningConds>::iterator it=running.find(*todel);
|
|
if( it!=running.end() )
|
|
{
|
|
if( it->second.cond!=NULL )
|
|
it->second.cond->notify_all();
|
|
|
|
for (size_t i = 0; i < it->second.conds.size(); ++i)
|
|
{
|
|
it->second.conds[i]->notify_all();
|
|
}
|
|
|
|
running.erase(it);
|
|
}
|
|
}
|
|
|
|
IThread *ret=NULL;
|
|
while(ret==NULL && !dexit)
|
|
{
|
|
if( toexecute.size()==0)
|
|
{
|
|
if(nThreads>=nRunning
|
|
&& nThreads-nRunning>max_waiting_threads)
|
|
{
|
|
--nThreads;
|
|
stop=true;
|
|
return NULL;
|
|
}
|
|
Server->setCurrentThreadName(idle_name);
|
|
cond->wait(&lock);
|
|
}
|
|
else
|
|
{
|
|
ret=toexecute[0].runnable;
|
|
*todel=toexecute[0].ticket;
|
|
name = toexecute[0].name;
|
|
toexecute.erase( toexecute.begin() );
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CThreadPool::Remove(CPoolThread *pt, bool decr)
|
|
{
|
|
IScopedLock lock(mutex);
|
|
assert(!decr || nThreads > 0);
|
|
for(size_t i=0;i<threads.size();++i)
|
|
{
|
|
if( threads[i]==pt )
|
|
{
|
|
threads.erase( threads.begin()+i);
|
|
if (decr)
|
|
{
|
|
--nThreads;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
CThreadPool::CThreadPool(size_t max_threads,
|
|
size_t max_waiting_threads, std::string idle_name)
|
|
: max_threads(max_threads), max_waiting_threads(max_waiting_threads), idle_name(idle_name),
|
|
nRunning(0), nThreads(0), currticket(0), dexit(false), mutex(Server->createMutex()),
|
|
cond(Server->createCondition())
|
|
{
|
|
|
|
}
|
|
|
|
CThreadPool::~CThreadPool()
|
|
{
|
|
delete mutex;
|
|
delete cond;
|
|
}
|
|
|
|
void CThreadPool::Shutdown(void)
|
|
{
|
|
bool do_leak_check=(Server->getServerParameter("leak_check")=="true");
|
|
IScopedLock lock(mutex);
|
|
for(size_t i=0;i<threads.size();++i)
|
|
{
|
|
threads[i]->shutdown();
|
|
}
|
|
dexit=true;
|
|
|
|
unsigned int max=0;
|
|
while(threads.size()>0 )
|
|
{
|
|
lock.relock(NULL);
|
|
cond->notify_all();
|
|
Server->wait(100);
|
|
lock.relock(mutex);
|
|
|
|
//wait for max 300 msec
|
|
if( (!do_leak_check && max>=3) || (do_leak_check && max>=30) )
|
|
{
|
|
Server->Log("Maximum wait time for thread pool exceeded. Shutting down the hard way", LL_ERROR);
|
|
break;
|
|
}
|
|
++max;
|
|
}
|
|
}
|
|
|
|
bool CThreadPool::isRunningInt(THREADPOOL_TICKET ticket)
|
|
{
|
|
std::map<THREADPOOL_TICKET, SRunningConds>::iterator it=running.find(ticket);
|
|
if( it!=running.end() )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool CThreadPool::isRunning(THREADPOOL_TICKET ticket)
|
|
{
|
|
IScopedLock lock(mutex);
|
|
return isRunningInt(ticket);
|
|
}
|
|
|
|
bool CThreadPool::waitFor(std::vector<THREADPOOL_TICKET> tickets, int timems)
|
|
{
|
|
int64 starttime;
|
|
if(timems>=0)
|
|
{
|
|
starttime = Server->getTimeMS();
|
|
}
|
|
|
|
IScopedLock lock(mutex);
|
|
ICondition *cond=Server->createCondition();
|
|
|
|
for( size_t i=0;i<tickets.size();++i)
|
|
{
|
|
std::map<THREADPOOL_TICKET, SRunningConds>::iterator it=running.find(tickets[i]);
|
|
if( it!=running.end() )
|
|
{
|
|
if (it->second.cond == NULL)
|
|
it->second.cond = cond;
|
|
else
|
|
it->second.conds.push_back(cond);
|
|
}
|
|
}
|
|
|
|
bool ret=false;
|
|
|
|
while(true)
|
|
{
|
|
bool r=false;
|
|
for(size_t i=0;i<tickets.size();++i)
|
|
{
|
|
if( isRunningInt(tickets[i]) )
|
|
{
|
|
r=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !r )
|
|
{
|
|
ret = true;
|
|
break;
|
|
}
|
|
|
|
int left = timems;
|
|
if (timems >= 0)
|
|
{
|
|
int64 ctime = Server->getTimeMS();
|
|
if (ctime - starttime>=timems)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
left = timems - static_cast<int>(ctime - starttime);
|
|
}
|
|
}
|
|
|
|
cond->wait(&lock, left);
|
|
}
|
|
|
|
for( size_t i=0;i<tickets.size();++i)
|
|
{
|
|
std::map<THREADPOOL_TICKET, SRunningConds>::iterator it=running.find(tickets[i]);
|
|
if( it!=running.end() )
|
|
{
|
|
if(it->second.cond==cond)
|
|
{
|
|
it->second.cond=NULL;
|
|
}
|
|
else
|
|
{
|
|
std::vector<ICondition*>::iterator it_vec =
|
|
std::find(it->second.conds.begin(), it->second.conds.end(), cond);
|
|
if (it_vec != it->second.conds.end())
|
|
{
|
|
it->second.conds.erase(it_vec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Server->destroy(cond);
|
|
|
|
return ret;
|
|
}
|
|
|
|
THREADPOOL_TICKET CThreadPool::execute(IThread *runnable, const std::string& name)
|
|
{
|
|
IScopedLock lock(mutex);
|
|
size_t retries = 0;
|
|
while(nThreads>=nRunning
|
|
&& nThreads-nRunning==0
|
|
&& nThreads<max_threads )
|
|
{
|
|
CPoolThread *nt=new CPoolThread(this);
|
|
if (!Server->createThread(nt))
|
|
{
|
|
delete nt;
|
|
lock.relock(NULL);
|
|
unsigned int waittime = (std::min)(static_cast<unsigned int>(1000.*pow(2., static_cast<double>(++retries))), (unsigned int)30 * 60 * 1000); //30min
|
|
if (retries>20)
|
|
{
|
|
waittime = (unsigned int)30 * 60 * 1000;
|
|
}
|
|
Server->Log("Retrying creating thread for thread pool in "+PrettyPrintTime(waittime)+"...", LL_WARNING);
|
|
Server->wait(waittime);
|
|
lock.relock(mutex);
|
|
}
|
|
else
|
|
{
|
|
++nThreads;
|
|
threads.push_back(nt);
|
|
}
|
|
}
|
|
|
|
++currticket;
|
|
while (running.find(currticket) != running.end()
|
|
|| currticket==ILLEGAL_THREADPOOL_TICKET)
|
|
{
|
|
++currticket;
|
|
}
|
|
|
|
toexecute.push_back(SNewTask(runnable, currticket, name));
|
|
running.insert(std::pair<THREADPOOL_TICKET, SRunningConds>(currticket, SRunningConds()) );
|
|
++nRunning;
|
|
cond->notify_one();
|
|
return currticket;
|
|
}
|
|
|
|
void CThreadPool::executeWait(IThread *runnable, const std::string& name)
|
|
{
|
|
THREADPOOL_TICKET ticket=execute(runnable, name);
|
|
waitFor(ticket);
|
|
}
|
|
|
|
bool CThreadPool::waitFor(THREADPOOL_TICKET ticket, int timems)
|
|
{
|
|
std::vector<THREADPOOL_TICKET> t;
|
|
t.push_back(ticket);
|
|
return waitFor(t, timems);
|
|
}
|
|
|