polserver/pol-core/clib/threadhelp.cpp

586 lines
14 KiB
C++
Raw Permalink Normal View History

/** @file
*
* @par History
* - 2005/12/13 Shinigami: added error code printing in create_thread for debugging
* - 2006/02/06 Shinigami: smaller bugfix in logging
* error code printing in create_thread extended
* - 2007/02/28 Shinigami: error code printing in create_thread added for linux
* - 2007/03/08 Shinigami: added pthread_exit and _endhreadex to close threads
* - 2008/03/02 Nando: Added bool dec_child to create_thread, used to dec_child_thread_count()
* if there is an error on create_thread. Will fix some of the zombies.
*/
2015-11-11 00:23:20 +01:00
#include "threadhelp.h"
#include <cstring>
#include <exception>
#include <thread>
2015-11-11 00:23:20 +01:00
#include "esignal.h"
#include "logfacility.h"
#include "passert.h"
2018-01-01 21:49:30 +01:00
#ifndef _WIN32
#include <errno.h>
2015-11-11 00:23:20 +01:00
#include <pthread.h>
#include <unistd.h>
#endif
// TODO: fix trunc cast warnings
2015-11-11 00:23:20 +01:00
#ifdef _MSC_VER
#pragma warning( disable : 4311 ) // trunc cast
#pragma warning( disable : 4302 ) // trunc cast
2015-11-11 00:23:20 +01:00
#endif
namespace Pol
{
namespace threadhelp
{
ThreadMap threadmap;
std::atomic<unsigned int> child_threads( 0 );
static int threads = 0;
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
void init_threadhelp() {}
2015-11-11 00:23:20 +01:00
void thread_sleep_ms( unsigned millis )
{
Sleep( millis );
}
size_t thread_pid()
{
return GetCurrentThreadId();
}
2015-11-11 00:23:20 +01:00
const DWORD MS_VC_EXCEPTION = 0x406D1388;
#pragma pack( push, 8 )
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;
#pragma pack( pop )
void _SetThreadName( DWORD dwThreadID, const char* name )
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{ // oh my god i hate ms ...
RaiseException( MS_VC_EXCEPTION, 0, sizeof( info ) / sizeof( ULONG_PTR ), (ULONG_PTR*)&info );
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
}
}
void SetThreadName( int threadid, std::string threadName )
{
// This redirection is needed because std::string has a destructor
// which isn't compatible with __try
_SetThreadName( threadid, threadName.c_str() );
}
2015-11-11 00:23:20 +01:00
#else
static pthread_attr_t create_detached_attr;
static Clib::SpinLock pthread_attr_lock;
void init_threadhelp()
{
int res;
res = pthread_attr_init( &create_detached_attr );
passert_always( res == 0 );
res = pthread_attr_setdetachstate( &create_detached_attr, PTHREAD_CREATE_DETACHED );
passert_always( res == 0 );
}
2015-11-11 00:23:20 +01:00
void thread_sleep_ms( unsigned millis )
{
usleep( millis * 1000L );
}
size_t thread_pid()
{
#ifdef __APPLE__
return reinterpret_cast<size_t>( pthread_self() );
#else
return pthread_self();
#endif
}
2015-11-11 00:23:20 +01:00
#endif
void run_thread( void ( *threadf )( void ) )
{
// thread creator calls inc_child_thread_count before starting thread
try
{
( *threadf )();
}
catch ( std::exception& ex )
{
ERROR_PRINTLN( "Thread exception: {}", ex.what() );
}
2015-11-11 00:23:20 +01:00
--child_threads;
2015-11-11 00:23:20 +01:00
threadmap.Unregister( thread_pid() );
}
void run_thread( void ( *threadf )( void* ), void* arg )
{
// thread creator calls inc_child_thread_count before starting thread
try
{
( *threadf )( arg );
}
catch ( std::exception& ex )
{
ERROR_PRINTLN( "Thread exception: {}", ex.what() );
}
2015-11-11 00:23:20 +01:00
--child_threads;
2015-11-11 00:23:20 +01:00
threadmap.Unregister( thread_pid() );
}
2015-11-11 00:23:20 +01:00
class ThreadData
{
public:
std::string name;
void ( *entry )( void* );
void ( *entry_noparam )( void );
void* arg;
};
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
unsigned __stdcall thread_stub2( void* v_td )
2015-11-11 00:23:20 +01:00
#else
void* thread_stub2( void* v_td )
2015-11-11 00:23:20 +01:00
#endif
{
ThreadData* td = reinterpret_cast<ThreadData*>( v_td );
2015-11-11 00:23:20 +01:00
void ( *entry )( void* ) = td->entry;
void ( *entry_noparam )( void ) = td->entry_noparam;
void* arg = td->arg;
2015-11-11 00:23:20 +01:00
threadmap.Register( thread_pid(), td->name );
2015-11-11 00:23:20 +01:00
delete td;
td = nullptr;
2015-11-11 00:23:20 +01:00
if ( entry != nullptr )
run_thread( entry, arg );
else
run_thread( entry_noparam );
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
_endthreadex( 0 );
2018-10-09 16:54:56 +02:00
return 0;
2015-11-11 00:23:20 +01:00
#else
pthread_exit( nullptr );
return nullptr;
2018-10-09 16:54:56 +02:00
#endif
}
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
void create_thread( ThreadData* td, bool dec_child = false )
{
// If the thread starts successfully, td will be deleted by thread_stub2.
// So we must save the threadName for later.
std::string threadName = td->name;
unsigned threadid = 0;
HANDLE h = (HANDLE)_beginthreadex( nullptr, 0, thread_stub2, td, 0, &threadid );
if ( h == 0 ) // added for better debugging
{
POLLOG( "error in create_thread: {} {} \"{}\" \"{}\" {} {} {} {} {} {}\n", errno, _doserrno,
strerror( errno ), strerror( _doserrno ), threads++, (unsigned)thread_stub2,
td->name.c_str(), (unsigned)td->entry, (unsigned)td->entry_noparam, td->arg );
// dec_child says that we should dec_child_threads when there's an error... :)
if ( dec_child )
--child_threads;
}
else
{
SetThreadName( threadid, threadName );
CloseHandle( h );
}
}
2015-11-11 00:23:20 +01:00
#else
void create_thread( ThreadData* td, bool dec_child = false )
{
Clib::SpinLockGuard guard( pthread_attr_lock );
pthread_t thread;
int result = pthread_create( &thread, &create_detached_attr, thread_stub2, td );
if ( result != 0 ) // added for better debugging
{
POLLOG( "error in create_thread: {} {} \"{}\" {} {} {} {} {} :}\n", result, errno,
strerror( errno ), threads++, reinterpret_cast<const void*>( thread_stub2 ),
td->name.c_str(), reinterpret_cast<const void*>( td->entry ),
reinterpret_cast<const void*>( td->entry_noparam ), td->arg );
// dec_child says that we should dec_child_threads when there's an error... :)
if ( dec_child )
--child_threads;
}
}
2015-11-11 00:23:20 +01:00
#endif
void start_thread( void ( *entry )( void* ), const char* thread_name, void* arg )
{
auto td = new ThreadData;
td->name = thread_name;
td->entry = entry;
td->entry_noparam = nullptr;
td->arg = arg;
2015-11-11 00:23:20 +01:00
++child_threads;
2015-11-11 00:23:20 +01:00
create_thread( td, true );
}
2015-11-11 00:23:20 +01:00
void start_thread( void ( *entry )( void ), const char* thread_name )
{
auto td = new ThreadData;
td->name = thread_name;
td->entry = nullptr;
td->entry_noparam = entry;
td->arg = nullptr;
2015-11-11 00:23:20 +01:00
++child_threads;
2015-11-11 00:23:20 +01:00
create_thread( td, true );
}
2015-11-11 00:23:20 +01:00
ThreadMap::ThreadMap()
: _spinlock(),
_contents()
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
,
_handles()
2015-11-11 00:23:20 +01:00
#endif
{
}
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
HANDLE ThreadMap::getThreadHandle( size_t pid ) const
{
Clib::SpinLockGuard guard( _spinlock );
auto itr = _handles.find( pid );
if ( itr == _handles.end() )
{
return 0;
}
return itr->second;
}
2015-11-11 00:23:20 +01:00
#endif
void ThreadMap::Register( size_t pid, const std::string& name )
{
Clib::SpinLockGuard guard( _spinlock );
_contents.insert( std::make_pair( pid, name ) );
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
HANDLE hThread = 0;
if ( !DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0,
FALSE, DUPLICATE_SAME_ACCESS ) )
{
ERROR_PRINTLN( "failed to duplicate thread handle" );
return;
}
_handles.insert( std::make_pair( pid, hThread ) );
2015-11-11 00:23:20 +01:00
#endif
}
void ThreadMap::Unregister( size_t pid )
{
Clib::SpinLockGuard guard( _spinlock );
_contents.erase( pid );
2015-11-11 00:23:20 +01:00
#ifdef _WIN32
auto itr = _handles.find( pid );
if ( itr != _handles.end() )
CloseHandle( itr->second );
_handles.erase( pid );
2015-11-11 00:23:20 +01:00
#endif
}
void ThreadMap::CopyContents( Contents& out ) const
{
Clib::SpinLockGuard guard( _spinlock );
out = _contents;
}
2015-11-11 00:23:20 +01:00
ThreadRegister::ThreadRegister( const std::string& name )
{
threadmap.Register( thread_pid(), name );
}
ThreadRegister::~ThreadRegister()
{
threadmap.Unregister( thread_pid() );
}
2015-11-11 00:23:20 +01:00
/// Creates a threadpool of workers.
/// blocks on deconstruction
/// eg:
/// TaskThreadPool workers;
/// for (....)
/// workers.push([&](){dosomework();});
TaskThreadPool::TaskThreadPool() : _done( false ), _msg_queue() {}
TaskThreadPool::TaskThreadPool( const std::string& name ) : _done( false ), _msg_queue()
{
// get the count of processors
unsigned int max_count = std::thread::hardware_concurrency();
if ( !max_count ) // can fail so at least one
max_count = 1;
init( max_count, name );
}
2015-11-11 00:23:20 +01:00
TaskThreadPool::TaskThreadPool( unsigned int max_count, const std::string& name )
: _done( false ), _msg_queue()
{
init( max_count, name );
}
2015-11-11 00:23:20 +01:00
void TaskThreadPool::init( unsigned int max_count, const std::string& name )
{
for ( unsigned int i = 0; i < max_count; ++i )
{
_threads.emplace_back(
[=]()
{
ThreadRegister register_thread( "TaskPool " + name );
auto f = msg();
try
{
while ( !_done )
{
_msg_queue.pop_wait( &f );
f();
}
}
catch ( msg_queue::Canceled& )
{
}
catch ( std::exception& ex )
{
ERROR_PRINTLN( "Thread exception: {}", ex.what() );
Clib::force_backtrace( true );
return;
}
// purge the queue empty
std::list<msg> remaining;
_msg_queue.pop_remaining( &remaining );
for ( auto& _f : remaining )
_f();
} );
}
}
2015-11-11 00:23:20 +01:00
void TaskThreadPool::init_pool( unsigned int max_count, const std::string& name )
{
if ( !_threads.empty() )
return;
init( max_count, name );
}
void TaskThreadPool::deinit_pool()
{
if ( _threads.empty() )
return;
// send both done and cancel to wake up all workers
_msg_queue.push(
[&]()
{
_done = true;
_msg_queue.cancel();
} );
for ( auto& thread : _threads )
thread.join();
_threads.clear();
}
TaskThreadPool::~TaskThreadPool()
{
deinit_pool();
}
2015-11-11 00:23:20 +01:00
/// simply fire and forget only the deconstructor ensures the msg to be finished
2016-12-15 16:59:58 +01:00
void TaskThreadPool::push( const msg& msg )
{
_msg_queue.push( msg );
}
2015-11-11 00:23:20 +01:00
/// returns a future which will be set once the msg is processed
2016-12-15 16:59:58 +01:00
std::future<bool> TaskThreadPool::checked_push( const msg& msg )
{
auto promise = std::make_shared<std::promise<bool>>();
auto ret = promise->get_future();
_msg_queue.push(
[=]()
{
try
{
msg();
promise->set_value( true );
}
catch ( ... )
{
promise->set_exception( std::current_exception() );
}
} );
return ret;
}
2015-11-11 00:23:20 +01:00
size_t TaskThreadPool::size() const
{
return _threads.size();
}
2015-11-11 00:23:20 +01:00
class DynTaskThreadPool::PoolWorker
{
public:
PoolWorker( DynTaskThreadPool* parent, const std::string& name );
PoolWorker( const PoolWorker& ) = delete;
PoolWorker& operator=( const PoolWorker& ) = delete;
bool isbusy() const;
void join();
void run();
private:
std::string _name;
std::atomic<bool> _busy;
std::thread _thread;
DynTaskThreadPool* _parent;
struct BusyGuard
{
std::atomic<bool>* _busy;
BusyGuard( std::atomic<bool>* busy ) : _busy( busy ) { ( *_busy ) = true; }
~BusyGuard() { ( *_busy ) = false; }
};
};
DynTaskThreadPool::PoolWorker::PoolWorker( DynTaskThreadPool* parent, const std::string& name )
: _name( name ), _busy( false ), _thread(), _parent( parent )
{
run();
}
bool DynTaskThreadPool::PoolWorker::isbusy() const
{
return _busy;
}
2015-11-11 00:23:20 +01:00
void DynTaskThreadPool::PoolWorker::join()
{
_thread.join();
}
2015-11-11 00:23:20 +01:00
void DynTaskThreadPool::PoolWorker::run()
{
_thread = std::thread(
[&]()
{
ThreadRegister register_thread( _name );
auto f = msg();
try
{
while ( !_parent->_done && !Clib::exit_signalled )
{
_parent->_msg_queue.pop_wait( &f );
{
BusyGuard busy( &_busy );
f();
}
}
}
catch ( msg_queue::Canceled& )
{
}
catch ( std::exception& ex )
{
ERROR_PRINTLN( "Thread exception: {}", ex.what() );
Clib::force_backtrace( true );
return;
}
} );
}
2015-11-11 00:23:20 +01:00
/// Creates a dynamic threadpool of workers.
/// if no idle worker is found creates a new worker thread
/// blocks on deconstruction
/// eg:
/// DynTaskThreadPool workers;
/// for (....)
/// workers.push([&](){dosomework();});
DynTaskThreadPool::DynTaskThreadPool( const std::string& name )
: _done( false ), _msg_queue(), _pool_mutex(), _name( "DynTaskPool" + name )
{
}
2015-11-11 00:23:20 +01:00
size_t DynTaskThreadPool::threadpoolsize() const
{
std::lock_guard<std::mutex> guard( _pool_mutex );
return _threads.size();
}
2015-11-11 00:23:20 +01:00
void DynTaskThreadPool::create_thread()
{
std::lock_guard<std::mutex> guard( _pool_mutex );
for ( const auto& worker : _threads )
{
if ( !worker->isbusy() ) // check for a idle instance
2015-11-11 00:23:20 +01:00
{
return;
2015-11-11 00:23:20 +01:00
}
}
size_t thread_num = _threads.size();
_threads.emplace_back( new PoolWorker( this, _name + " " + std::to_string( thread_num ) ) );
ERROR_PRINTLN( "create pool worker {} {}", _name, thread_num );
}
2015-11-11 00:23:20 +01:00
DynTaskThreadPool::~DynTaskThreadPool()
{
// send both done and cancel to wake up all workers
_msg_queue.push(
[&]()
{
_done = true;
_msg_queue.cancel();
} );
for ( auto& thread : _threads )
thread->join();
}
2015-11-11 00:23:20 +01:00
/// simply fire and forget only the deconstructor ensures the msg to be finished
2016-12-15 16:59:58 +01:00
void DynTaskThreadPool::push( const msg& msg )
{
create_thread();
_msg_queue.push( msg );
}
2015-11-11 00:23:20 +01:00
/// returns a future which will be set once the msg is processed
2016-12-15 16:59:58 +01:00
std::future<bool> DynTaskThreadPool::checked_push( const msg& msg )
{
auto promise = std::make_shared<std::promise<bool>>();
auto ret = promise->get_future();
create_thread();
_msg_queue.push(
[=]()
{
try
{
msg();
promise->set_value( true );
}
catch ( ... )
{
promise->set_exception( std::current_exception() );
}
} );
return ret;
}
} // namespace threadhelp
} // namespace Pol