/* +------------------------------------+
* | Inspire Internet Relay Chat Daemon |
* +------------------------------------+
*
* InspIRCd: (C) 2002-2011 InspIRCd Development Team
* See: http://wiki.inspircd.org/Credits
*
* This program is free but copyrighted software; see
* the file COPYING for details.
*
* ---------------------------------------------------
*/
#include "inspircd.h"
#include "cull_list.h"
#include "threadengine.h"
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#ifdef HAS_EVENTFD
#include <sys/eventfd.h>
class ThreadSignalSocket : public EventHandler
{
public:
ThreadSignalSocket()
{
SetFd(eventfd(0, EFD_NONBLOCK));
if (fd < 0)
throw CoreException("Could not create eventfd " + std::string(strerror(errno)));
ServerInstance->SE->AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE);
}
~ThreadSignalSocket()
{
close(fd);
}
void Notify()
{
eventfd_write(fd, 1);
}
void HandleEvent(EventType et, int errornum)
{
if (et == EVENT_READ)
{
eventfd_t dummy;
eventfd_read(fd, &dummy);
ServerInstance->Threads->result_loop();
}
else
{
ServerInstance->Threads->job_lock.lock();
ServerInstance->Threads->result_ss = NULL;
ServerInstance->Threads->job_lock.unlock();
ServerInstance->GlobalCulls->AddItem(this);
}
}
};
#else
class ThreadSignalSocket : public EventHandler
{
int send_fd;
public:
ThreadSignalSocket()
{
int fds[2];
if (pipe(fds))
throw CoreException("Could not create pipe " + std::string(strerror(errno)));
SetFd(fds[0]);
send_fd = fds[1];
ServerInstance->SE->NonBlocking(fd);
ServerInstance->SE->AddFd(this, FD_WANT_FAST_READ | FD_WANT_NO_WRITE);
}
~ThreadSignalSocket()
{
close(send_fd);
close(fd);
}
void Notify()
{
static const char dummy = '*';
write(send_fd, &dummy, 1);
}
void HandleEvent(EventType et, int errornum)
{
if (et == EVENT_READ)
{
char dummy[128];
read(fd, dummy, 128);
ServerInstance->Threads->result_loop();
}
else
{
ServerInstance->Threads->job_lock.lock();
ServerInstance->Threads->result_ss = NULL;
ServerInstance->Threads->job_lock.unlock();
ServerInstance->GlobalCulls->AddItem(this);
}
}
};
#endif
ThreadEngine::ThreadEngine() : result_ss(NULL)
{
}
ThreadEngine::~ThreadEngine()
{
delete result_ss;
}
void* ThreadEngine::Runner::entry_point(void* parameter)
{
/* Recommended by nenolod, signal safety on a per-thread basis */
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &set, NULL);
Runner* te = static_cast<Runner*>(parameter);
te->main_loop();
return parameter;
}
ThreadEngine::Runner::Runner(ThreadEngine* t)
: te(t), current(NULL)
{
if (pthread_create(&id, NULL, entry_point, this) != 0)
throw CoreException("Unable to create new thread: " + std::string(strerror(errno)));
}
ThreadEngine::Runner::~Runner()
{
pthread_join(id, NULL);
}
void ThreadEngine::Submit(Job* job)
{
Mutex::Lock lock(job_lock);
// TODO launch more threads if we have a queue and more are allowed
// TODO clean up threads at garbage collection time (all but one)
if (threads.empty())
threads.push_back(new Runner(this));
if (result_ss == NULL)
result_ss = new ThreadSignalSocket();
submit_q.push_back(job);
submit_s.signal_one();
}
static class TerminateJob : public Job
{
public:
TerminateJob() : Job(NULL) { cancel(); }
void run() {}
void finish() {}
} terminator;
void ThreadEngine::Runner::main_loop()
{
te->job_lock.lock();
while (1)
{
while (te->submit_q.empty())
te->submit_s.wait(te->job_lock);
current = te->submit_q.front();
te->submit_q.pop_front();
if (!current->IsCancelled())
{
te->job_lock.unlock();
try
{
current->run();
}
catch (...)
{
}
te->job_lock.lock();
}
te->result_q.push_back(current);
if (current == &terminator)
break;
current = NULL;
te->result_sc.signal_one();
if (te->result_ss)
te->result_ss->Notify();
}
te->job_lock.unlock();
// current == terminator, we die
}
void ThreadEngine::result_loop()
{
job_lock.lock();
while (!result_q.empty())
{
Job* job = result_q.front();
result_q.pop_front();
job_lock.unlock();
job->finish();
job_lock.lock();
}
job_lock.unlock();
}
void ThreadEngine::BlockForUnload(Module* mod)
{
job_lock.lock();
while (1)
{
bool found = false;
for(std::list<Job*>::iterator i = submit_q.begin(); i != submit_q.end(); i++)
{
Job* j = *i;
if (j->BlocksUnload(mod))
{
found = true;
j->cancel();
}
}
for(std::vector<Runner*>::iterator i = threads.begin(); i != threads.end(); i++)
{
Runner* r = *i;
if (r->current && r->current->BlocksUnload(mod))
{
found = true;
r->current->cancel();
}
}
if (!found)
break;
// wait for an item to finish so we can check again
result_sc.wait(job_lock);
}
job_lock.unlock();
// clean up any references remaining in the result loop
// (that's where any we were waiting for are sitting)
result_loop();
}