Files
2025-09-18 17:55:52 -04:00

500 lines
11 KiB
C++

/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include <queue>
#include "rbx/boost.hpp"
#include "rbx/Thread.hpp"
#include "rbx/rbxtime.h"
#include "rbx/atomic.h"
#include "boost/shared_ptr.hpp"
#include "boost/noncopyable.hpp"
#include "RbxFormat.h"
#include "FastLog.h"
#include "RbxPlatform.h"
#include "boost/thread/mutex.hpp"
using boost::shared_ptr;
LOGGROUP(MutexLifetime);
namespace RBX
{
// A lightweight mutex that uses CRITICAL_SECTION under Windows.
// This mutex is non-recursive.
class mutex
{
#ifdef _WIN32
CRITICAL_SECTION cs;
public:
mutex()
{
::InitializeCriticalSection( &cs );
FASTLOG1(FLog::MutexLifetime, "RBX::mutext init m = 0x%x", this);
}
~mutex()
{
::DeleteCriticalSection(&cs);
FASTLOG1(FLog::MutexLifetime, "RBX::mutext destroy m = 0x%x", this);
}
class scoped_lock : boost::noncopyable
{
public:
scoped_lock(mutex& m):m(m)
{
::EnterCriticalSection(&m.cs);
}
~scoped_lock()
{
::LeaveCriticalSection(&m.cs);
}
private:
mutex& m;
};
#else
pthread_mutex_t sl;
public:
mutex()
{
if ( pthread_mutex_init(&sl,NULL) != 0 )
throw std::runtime_error("failed in mutex to initialize pthread_mutex_init.");
}
~mutex()
{
if(pthread_mutex_destroy(&sl) != 0){
//printf("Error at pthread_spin_destroy()");
}
}
class scoped_lock : boost::noncopyable
{
public:
scoped_lock(mutex& m0):m(m0){
int rc = pthread_mutex_lock(&m.sl);
if(rc != 0)
{
//fprintf(stderr,"Test FAILED: child failed to get spin lock,error code:%d\n" , rc);
}
//::EnterCriticalSection(&m.sl);
}
~scoped_lock(){
if(pthread_mutex_unlock(&m.sl)!=0)
{
//fprintf(stderr,"child: Error at pthread_spin_unlock()\n");
}
//::LeaveCriticalSection(&m.sl);
}
private:
mutex& m;
};
#endif
};
// calls RBXCRASH() on contention.
class concurrency_catcher : boost::noncopyable
{
rbx::atomic<int> value;
static const long unlocked = 0;
static const long locked = 1;
public:
concurrency_catcher():value(unlocked) {}
class scoped_lock : boost::noncopyable
{
public:
scoped_lock(concurrency_catcher& m);
~scoped_lock();
private:
concurrency_catcher& m;
};
};
// calls RBXCRASH() on contention.
struct reentrant_concurrency_catcher : boost::noncopyable
{
rbx::atomic<int> value;
volatile unsigned long threadId;
static const long unlocked = 0;
static const long locked = 1;
static const unsigned long noThreadId;
public:
reentrant_concurrency_catcher():value(unlocked),threadId(noThreadId) {}
class scoped_lock : boost::noncopyable
{
public:
scoped_lock(reentrant_concurrency_catcher& m);
~scoped_lock();
private:
bool isChild;
reentrant_concurrency_catcher& m;
};
};
class readwrite_concurrency_catcher : boost::noncopyable
{
friend class scoped_write_request;
friend class scoped_read_request;
rbx::atomic<int> write_requested;
rbx::atomic<int> read_requested;
static const long unlocked = 0;
static const long locked = 1;
public:
readwrite_concurrency_catcher() : write_requested(unlocked), read_requested(0) {};
class scoped_write_request
{
readwrite_concurrency_catcher& m;
public:
// Place this code around tasks that write to a DataModel
scoped_write_request(readwrite_concurrency_catcher& mt);
~scoped_write_request();
};
class scoped_read_request
{
readwrite_concurrency_catcher& m;
public:
// Place this code around tasks that write to a DataModel
scoped_read_request(readwrite_concurrency_catcher& m);
~scoped_read_request();
};
};
}
namespace rbx
{
class spin_mutex
{
rbx::atomic<int> sl;
public:
spin_mutex()
{
// init
}
~spin_mutex()
{
// destroy
}
bool try_lock(){
return sl.compare_and_swap(1,0) == 0;
}
void lock(){
while(sl.compare_and_swap(1,0)!=0){}
}
void unlock(){
sl.compare_and_swap(0,1);
}
class scoped_lock : boost::noncopyable
{
public:
scoped_lock(spin_mutex& m0) : m(m0)
{
for(;;){
if(m.try_lock()) break;
}
}
~scoped_lock()
{
m.unlock();
}
private:
spin_mutex& m;
};
};
// Use this queue when you want fast performance, low
// resource usage, and the queue is not very busy.
// For very busy queues, use tbb::concurrent_queue (correction, we dont have tbb anymore).
template<typename T>
class safe_queue : boost::noncopyable
{
protected:
std::queue<T> queue;
// TODO: spin_mutex is possibly a bad choice for expensive T types
typedef spin_mutex mutex;
mutex m;
public:
void clear()
{
mutex::scoped_lock lock(m);
while (!queue.empty())
queue.pop();
}
void push(const T& value)
{
mutex::scoped_lock lock(m);
queue.push(value);
}
bool pop_if_present(T& value)
{
mutex::scoped_lock lock(m);
if (!queue.empty())
{
value = queue.front();
queue.pop();
return true;
}
else
return false;
}
bool pop_if_present()
{
mutex::scoped_lock lock(m);
if (!queue.empty())
{
queue.pop();
return true;
}
else
return false;
}
// WARNING: Peeking has side effects, if T has copy constructors and destructors
bool peek_if_present(T& value)
{
mutex::scoped_lock lock(m);
if (!queue.empty())
{
value = queue.front();
return true;
}
else
return false;
}
// Lock and spin-free calls:
inline size_t size() const { return queue.size(); }
inline bool empty() const { return queue.empty(); }
};
namespace implementation
{
template<typename T>
struct timestamped_safe_queue_item
{
T value;
RBX::Time timestamp;
timestamped_safe_queue_item() {}
timestamped_safe_queue_item(const T& value)
:timestamp(RBX::Time::now<RBX::Time::Fast>())
,value(value)
{}
};
}
template<typename T>
class timestamped_safe_queue : protected safe_queue< implementation::timestamped_safe_queue_item<T> >
{
typedef safe_queue< implementation::timestamped_safe_queue_item<T> > Super;
double headTimestamp;
#ifndef _WIN32
// GCC won't inherit the mutex type defined in Super. Therefore we redeclare it here. Yuck!
typedef spin_mutex mutex;
#endif
public:
void clear()
{
headTimestamp = 0.f;
Super::clear();
}
void push(const T& value)
{
implementation::timestamped_safe_queue_item<T> item(value);
Super::push(item);
headTimestamp = item.timestamp.timestampSeconds();
}
bool pop_if_present(T& value)
{
mutex::scoped_lock lock(this->m);
if (!this->queue.empty())
{
value = this->queue.front().value;
this->queue.pop();
if (!this->queue.empty())
{
headTimestamp = this->queue.front().timestamp.timestampSeconds();
}
else
{
headTimestamp = 0.f;
}
return true;
}
else
return false;
}
// pops the head item if it has been waiting at least waitTime
bool pop_if_waited(RBX::Time::Interval waitTime, T& value)
{
mutex::scoped_lock lock(this->m);
if (this->queue.empty())
return false;
if (RBX::Time::now<RBX::Time::Fast>() < this->queue.front().timestamp + waitTime)
return false;
value = this->queue.front().value;
this->queue.pop();
if (!this->queue.empty())
{
headTimestamp = this->queue.front().timestamp.timestampSeconds();
}
else
{
headTimestamp = 0.f;
}
return true;
}
// Returns the time that the head item has been waiting or zero.
double head_waittime_sec(const RBX::Time& timeNow) const
{
if (headTimestamp > 0.f)
{
return timeNow.timestampSeconds() - headTimestamp;
}
else
return 0.f;
}
inline size_t size() const { return this->queue.size(); }
inline bool empty() const { return this->queue.empty(); }
};
template<typename T>
class safe_heap : boost::noncopyable
{
std::vector<T> vector;
// TODO: spin_mutex is possibly a bad choice for expensive T types
typedef spin_mutex mutex;
mutex m;
public:
void clear()
{
mutex::scoped_lock lock(m);
vector.clear();
}
void push_heap(const T& value)
{
mutex::scoped_lock lock(m);
vector.push_back(value);
std::push_heap(vector.begin(), vector.end());
}
bool pop_heap_if_present(T& value)
{
mutex::scoped_lock lock(m);
if (!vector.empty())
{
std::pop_heap(vector.begin(), vector.end());
value = vector.back();
vector.pop_back();
return true;
}
else
return false;
}
bool pop_heap_if_present()
{
mutex::scoped_lock lock(m);
if (!vector.empty())
{
std::pop_heap(vector.begin(), vector.end());
vector.pop_back();
return true;
}
else
return false;
}
// Lock and spin-free calls:
inline size_t size() const { return vector.size(); }
inline bool empty() const { return vector.empty(); }
};
#define SAFE_STATIC(TYPE,NAME) \
static TYPE* safe_static_do_get_##NAME() { static TYPE value; return &value; }\
static void safe_static_init_##NAME() { safe_static_do_get_##NAME(); }\
static TYPE& NAME()\
{\
static boost::once_flag once_init_##NAME = BOOST_ONCE_INIT;\
boost::call_once(safe_static_init_##NAME, once_init_##NAME);\
return *safe_static_do_get_##NAME();\
}
#define SAFE_HEAP_STATIC(TYPE,NAME) \
static TYPE* safe_static_do_get_##NAME() { static TYPE* value = new TYPE; return value; }\
static void safe_static_init_##NAME() { safe_static_do_get_##NAME(); }\
static TYPE& NAME()\
{\
static boost::once_flag once_init_##NAME = BOOST_ONCE_INIT;\
boost::call_once(safe_static_init_##NAME, once_init_##NAME);\
return *safe_static_do_get_##NAME();\
}
// A wrapper around thread_specific_ptr that lets you
// have a thread-specific reference to an object
template<typename T>
class thread_specific_reference
{
typedef T* TPTR;
boost::thread_specific_ptr<TPTR> ptr;
public:
T* get()
{
TPTR* p = ptr.get();
if (p)
return *p;
else
return 0;
}
void reset(T* value)
{
TPTR* p = new TPTR(value);
ptr.reset(p);
}
};
// A wrapper around thread_specific_ptr that lets you
// have a thread-specific shared_ptr to an object
template<typename T>
class thread_specific_shared_ptr : boost::noncopyable
{
typedef shared_ptr<T> TPTR;
boost::thread_specific_ptr<TPTR> ptr;
public:
operator shared_ptr<T>() const
{
TPTR* p = ptr.get();
if (p)
return *p;
else
return shared_ptr<T>();
}
void reset(shared_ptr<T> value)
{
TPTR* p = new TPTR(value);
ptr.reset(p);
}
};
}