//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1993, 1996 by Borland International, All Rights Reserved
//
/// \file
/// TThread class implementation
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/thread.h>
#include <owl/pointer.h>
#include <owl/except.h>
#include <owl/module.h>
#if !defined(WINELIB)
#include <process.h>
#endif // !WINELIB
#include <stdlib.h>
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
# pragma option -w-inl // Disable "Functions containing 'statement' is not expanded inline".
#endif
namespace owl {
OWL_DIAG_DEFINE_GROUP_INIT(OWL_INI, OwlThread, 1, 0);
//----------------------------------------------------------------------------
// Semaphores Win32
//
//----------------------------------------
// TMutex WIN32
void TMutex::Release()
{
::ReleaseMutex(*this);
}
//----------------------------------------
// TCountedSemaphore WIN32
void TCountedSemaphore::Release()
{
::ReleaseSemaphore(*this, 1, 0);
}
//----------------------------------------
// TEventSemaphore WIN32
void TEventSemaphore::Release()
{
// Events don't need to be released
}
//----------------------------------------
// TWaitableTimer
//
//
//
TWaitableTimer::TWaitableTimer(bool manualReset, LPCTSTR name, LPSECURITY_ATTRIBUTES sa)
{
Handle = CreateWaitableTimer(sa, manualReset, name);
if (!Handle) throw TXOwl(_T("CreateWaitableTimer failed."));
}
//
// String-aware overload
//
TWaitableTimer::TWaitableTimer(bool manualReset, const tstring& name, LPSECURITY_ATTRIBUTES sa)
{
Handle = CreateWaitableTimer(sa, manualReset, name.c_str());
if (!Handle) throw TXOwl(_T("CreateWaitableTimer failed."));
}
//
TWaitableTimer::TWaitableTimer(LPCTSTR name, bool inherit, uint32 access)
{
Handle = OpenWaitableTimer(access, inherit, name);
if (!Handle) throw TXOwl(_T("OpenWaitableTimer failed."));
}
//
// String-aware overload
//
TWaitableTimer::TWaitableTimer(const tstring& name, bool inherit, uint32 access)
{
Handle = OpenWaitableTimer(access, inherit, name.c_str());
if (!Handle) throw TXOwl(_T("OpenWaitableTimer failed."));
}
//
bool
TWaitableTimer::Cancel()
{
return CancelWaitableTimer(Handle) ? true : false; // handle to a timer object
}
//
bool
TWaitableTimer::Set(const TFileTime& duetime,int32 period, PTIMERAPCROUTINE compFunc, void* param, bool resume)
{
return SetWaitableTimer(Handle, (const LARGE_INTEGER*)&duetime,period,compFunc,param, resume);
}
//
void TWaitableTimer::Release()
{
}
//----------------------------------------
// TSemaphoreSet & its TLock for Win32
//
TSemaphoreSet::TSemaphoreSet(const TSemaphore* sems[], int size)
:
Sems(0)
{
int count = 0;
if (sems)
while (sems[count])
count++;
Count = count;
Size = size >= 0 ? size : count;
if (Size) {
Sems = CONST_CAST(const TSemaphore**, new TSemaphorePtr[Size]);
int i = 0;
if (sems)
for (; i < Count; i++)
Sems[i] = sems[i];
for (; i < Size; i++)
Sems[i] = 0;
}
}
//
TSemaphoreSet::~TSemaphoreSet()
{
delete[] Sems;
}
//
void TSemaphoreSet::Add(const TSemaphore& sem)
{
if (Count < Size)
Sems[Count++] = &sem;
}
//
void TSemaphoreSet::Remove(const TSemaphore& sem)
{
CHECK(Count <= Size);
for (int i = 0; i < Count; i++)
if (Sems[i] == &sem) {
for (int j = i; j < Count-1; j++) // Shift rest down to keep packed
Sems[j] = Sems[j+1];
Sems[Count-1] = 0;
return;
}
}
//
void TSemaphoreSet::Release(int index)
{
if (index >= 0)
CONST_CAST(TSemaphore*,Sems[index])->Release();
else
for (int i = 0; i < Count; i++)
CONST_CAST(TSemaphore*,Sems[i])->Release();
}
//
static HANDLE* newHandles(const TSemaphoreSet& set)
{
HANDLE* handles = new HANDLE[set.GetCount()];
for (int i = 0; i < set.GetCount(); i++) {
CHECK(set[i]);
handles[i] = *set[i]; // Assumes non-0 since i is in set range
}
return handles;
}
//
TSemaphoreSet::TLock::TLock(const TSemaphoreSet& set, TWaitWhat wait,
ulong timeOut, bool alertable)
:
Set(0)
{
TAPointer<THandle> handles(newHandles(set));
if (InitLock(set.Count, wait,
::WaitForMultipleObjectsEx(set.Count, handles, wait, timeOut, alertable)))
Set = &set;
}
//
TSemaphoreSet::TLock::TLock(ulong msgMask, const TSemaphoreSet& set,
TWaitWhat wait, ulong timeOut)
{
TAPointer<THandle> handles(newHandles(set));
if (InitLock(set.Count, wait,
::MsgWaitForMultipleObjects(set.Count, handles, wait, timeOut, msgMask)))
Set = &set;
}
//
// Init the Set and Locked members after a system wait call
//
bool TSemaphoreSet::TLock::InitLock(int count, TWaitWhat wait, int index)
{
if ((index >= static_cast<int>(WAIT_OBJECT_0) && index < static_cast<int>(WAIT_OBJECT_0 + count)) ||
(index >= static_cast<int>(WAIT_ABANDONED_0) && index < static_cast<int>(WAIT_ABANDONED_0 + count)))
{
if (wait == WaitAny) {
if (index >= (int)WAIT_ABANDONED_0)
index -= WAIT_ABANDONED_0;
Locked = index; // Just this one is locked
}
else
Locked = AllAquired; // They are all locked
return true;
}
else if (index == int(WAIT_OBJECT_0+count))
Locked = MsgWaiting;
else if (index == WAIT_TIMEOUT)
Locked = TimedOut;
else if (index == (int)WAIT_IO_COMPLETION)
Locked = IoComplete;
return false;
}
//
TSemaphoreSet::TLock::~TLock()
{
Release();
}
//
void TSemaphoreSet::TLock::Release(bool relinquish)
{
if (Set) {
CONST_CAST(TSemaphoreSet*,Set)->Release(Locked);
if (relinquish)
Set = 0;
}
}
//----------------------------------------------------------------------------
// class TMRSWSection
//
namespace
{
//
// This class encapsulates the undocumented fat read/write lock functions in "ntdll.dll".
// See http://undoc.airesoft.co.uk/ntdll.dll/RtlInitializeResource.php
// These functions are used to implement TMRSWSection.
//
// TODO: Investigate alternatives that do not rely on this undocumented feature, such as the
// documented slim read/write (SRW) functions in the Windows API since Vista.
//
class TNtDll_ : public TModule
{
public:
//
// Returns the module instance.
// Exceptions are thrown if the module cannot be loaded.
//
static TNtDll_& GetInstance()
{
//
// Note that while this lazy initialization avoids problems with global initialization order,
// the initial call of this function is not thread-safe (pre C++11). As a work-around, we
// ensure this function is called during program start-up (single-thread, safe).
// See InitNtDllInstance below.
//
// The work-around can be removed when C++11 compliant compilers are mandated.
//
// Also note that the singleton construction may throw an exception. Since there is no way to
// continue without this singleton, we make no attempt to handle it here. We assume that
// the exception will terminate the program, or if it is handled, that subsequent calls will
// be retried still within a single thread.
//
static TNtDll_ instance; // initial call (construction) not thread-safe pre-C++11
return instance;
}
//
// Undocumented structure representing the critical section.
//
struct RTL_RESOURCE
{
CRITICAL_SECTION CSection;
HANDLE hSharedSemaphore;
ULONG sharedExclusive;
HANDLE hExclusiveSemaphore;
ULONG exclusiveWaiters;
ULONG numberOfWaiters;
HANDLE hOwnerThread;
ULONG flags;
PRTL_RESOURCE_DEBUG DebugInfo;
};
typedef RTL_RESOURCE* PRTL_RESOURCE;
TModuleProcV1<PRTL_RESOURCE> RtlInitializeResource;
TModuleProcV1<PRTL_RESOURCE> RtlDeleteResource;
TModuleProc2<BOOLEAN, PRTL_RESOURCE, BOOLEAN> RtlAcquireResourceExclusive;
TModuleProc2<BOOLEAN, PRTL_RESOURCE, BOOLEAN> RtlAcquireResourceShared;
TModuleProcV1<PRTL_RESOURCE> RtlReleaseResource;
TModuleProcV1<PRTL_RESOURCE> RtlDumpResource;
private:
TNtDll_()
: TModule(_T("ntdll.dll"), true, true, false), // shouldLoad, mustLoad and !addToList
RtlInitializeResource(*this, "RtlInitializeResource"),
RtlDeleteResource(*this, "RtlDeleteResource"),
RtlAcquireResourceExclusive(*this, "RtlAcquireResourceExclusive"),
RtlAcquireResourceShared(*this, "RtlAcquireResourceShared"),
RtlReleaseResource(*this, "RtlReleaseResource"),
RtlDumpResource(*this, "RtlDumpResource")
{
TRACEX(OwlThread, 1, "Initializing " << TraceId(this));
}
TNtDll_(const TNtDll_&); // prohibited
TNtDll_& operator =(const TNtDll_&); // prohibited
};
//
// Ensure singleton initialization at start-up (single-threaded, safe).
//
TNtDll_& InitNtDllInstance = TNtDll_::GetInstance();
} // namespace
struct TMRSWSection::TPimpl
{
TNtDll_& Dll;
TNtDll_::RTL_RESOURCE RtlResource;
TPimpl(TNtDll_& dll) : Dll(dll) {}
};
TMRSWSection::TMRSWSection()
: Pimpl(new TPimpl(TNtDll_::GetInstance()))
{
Pimpl->Dll.RtlInitializeResource(&Pimpl->RtlResource);
}
TMRSWSection::~TMRSWSection()
{
Pimpl->Dll.RtlDeleteResource(&Pimpl->RtlResource);
delete Pimpl;
}
void TMRSWSection::Dump()
{
Pimpl->Dll.RtlDumpResource(&Pimpl->RtlResource);
}
TMRSWSection::TLock::TLock(TMRSWSection& s, bool shared, bool wait)
: Section(s)
{
TNtDll_& dll = Section.Pimpl->Dll;
TNtDll_::PRTL_RESOURCE r = &Section.Pimpl->RtlResource;
bool ok = shared ?
dll.RtlAcquireResourceShared(r, wait) :
dll.RtlAcquireResourceExclusive(r, wait);
if (!ok)
throw TXLockFailure(_T("TMRSWSection::TLock failed to acquire lock"));
}
TMRSWSection::TLock::~TLock()
{
TNtDll_& dll = Section.Pimpl->Dll;
TNtDll_::PRTL_RESOURCE r = &Section.Pimpl->RtlResource;
dll.RtlReleaseResource(r);
}
TMRSWSection::TXLockFailure::TXLockFailure(const tstring& msg)
: TXBase(msg)
{}
//----------------------------------------------------------------------------
// TThread Win32
//
// TThread constructors
//
/// Creates an object of type TThread.
TThread::TThread()
:
ThreadId(0),
Handle(0),
Stat(Created),
TerminationRequested(0),
Attached(false)
{
}
//
/// Attach to a running thread
//
TThread::TThread(TCurrent)
:
ThreadId(::GetCurrentThreadId()),
Handle(0),
Stat(Running),
TerminationRequested(0),
Attached(true)
{
::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentThread(),
::GetCurrentProcess(), &Handle,
0, false, DUPLICATE_SAME_ACCESS);
}
//
/// Puts the object into the Created
/// state, just like the default constructor.
/// Does not copy any of the internal details of the thread being copied.
//
TThread::TThread(const TThread&)
:
ThreadId(0),
Handle(0),
Stat(Created),
TerminationRequested(0),
Attached(false)
{
}
//
/// TThread assignment operator
//
/// Used when assigning derived objects. Attempting to assign from a running
/// object is an error, since the data fields in the running object can be
/// changing asynchronously.
///
/// The target object must be in either the
/// Created state or the Finished state. If
/// so, puts the object into the Created
/// state. If the object is not in either the
/// Created state or the Finished state it
/// is an error and an exception will be
/// thrown.
const TThread& TThread::operator =(const TThread& thread)
{
switch (GetStatus()) {
case Created:
case Suspended:
case Finished: {
if (this != &thread) {
Handle = 0;
ThreadId = 0;
Stat = Created;
TerminationRequested = false;
Attached = false;
}
return *this;
}
default:
throw TThreadError(TThreadError::AssignError);
}
#if defined(BI_COMP_MSC)
return *this; // Bogus return to make MSVC happy
#endif
}
//
/// TThread destructor
//
/// If the thread hasn't finished, destroying its control object is an error.
//
TThread::~TThread()
{
const auto ok = Attached || !(GetStatus() == Running || GetStatus() == Suspended);
WARN(!ok, _T("TThread::~TThread: Terminating due to failed precondition. Error: ")
<< TThreadError::MakeString(TThreadError::DestroyBeforeExit));
if (!ok) std::terminate();
//
// The RTL calls CloseHandle in _endthread, so we shouldn't if the thread
// was started with _beginthreadNT(...).
# if !defined(BI_MULTI_THREAD_RTL) || !defined(BI_COMP_BORLANDC)
::CloseHandle(Handle);
# endif
}
//
/// Starts the thread executing. The actual call depends on the operating system.
/// Returns the handle of the thread.
/// After the system call we check status.
//
TThread::THandle TThread::Start()
{
// If Start() has already been called for this thread, release the
// previously created system thread object before launching a new one.
//
if ((GetStatus() != Created) && Handle) {
::CloseHandle(Handle);
}
# if defined(BI_MULTI_THREAD_RTL)
Handle = (HANDLE)(UINT_PTR)::_beginthreadex(0, 4096, &TThread::Execute, this, 0, (uint*)&ThreadId);
# else
Handle = ::CreateThread(0, 0, &TThread::Execute, this, 0, &ThreadId);
# endif
if (Handle) {
TRACEX(OwlThread, 1, _T("Thread started [id:") << Handle << _T(']'));
Stat = Running;
}
else {
TRACEX(OwlThread, 2, _T("Thread failed to start"));
Stat = Invalid;
throw TThreadError(TThreadError::CreationFailure);
}
return Handle;
}
/// Suspends execution of the thread.
//
/// It's an error to try to suspend a thread that hasn't been started or that
/// has already terminated.
//
ulong TThread::Suspend()
{
switch (GetStatus()) {
case Created:
TRACEX(OwlThread, 2, _T("Illegal Created thread suspension [id:") << Handle << _T(']'));
throw TThreadError(TThreadError::SuspendBeforeRun);
case Finished:
TRACEX(OwlThread, 2, _T("Illegal Finished thread suspension [id:") << Handle << _T(']'));
throw TThreadError(TThreadError::SuspendAfterExit);
default:
ulong res = ::SuspendThread(Handle);
if (res < MAXIMUM_SUSPEND_COUNT) // Else a problem
Stat = Suspended;
TRACEX(OwlThread, 0, _T("Thread suspended [id:") << Handle << _T(", Count:") << res << _T(']'));
return res;
}
}
/// Resumes execution of a suspended thread.
//
/// It's an error to try to resume a thread that isn't suspended.
//
ulong TThread::Resume()
{
switch (GetStatus()) {
case Created:
TRACEX(OwlThread, 2, _T("Illegal Created thread resumption [id:") << Handle << _T(']'));
throw TThreadError(TThreadError::ResumeBeforeRun);
case Running:
TRACEX(OwlThread, 2, _T("Illegal Running thread resumption [id:") << Handle << _T(']'));
throw TThreadError(TThreadError::ResumeDuringRun);
case Finished:
TRACEX(OwlThread, 2, _T("Illegal Finished thread resumption [id:") << Handle << _T(']'));
throw TThreadError(TThreadError::ResumeAfterExit);
default:
ulong res = ::ResumeThread(Handle);
TRACEX(OwlThread, 0, _T("Thread resumed [id:") << Handle << _T(", Count:") << res << _T(']'));
if (res == 1) // Y.B. suggested by Richard.D.Crossley and Fendy Riyanto
Stat = Running;
return res;
}
}
//
/// Mark the thread for termination.
//
/// Sets an internal flag that indicates that the
/// thread should exit. The derived class can check
/// the state of this flag by calling ShouldTerminate().
//
void TThread::Terminate()
{
TRACEX(OwlThread, 1, _T("Thread termination requested [handle:") << Handle << _T(']'));
TerminationRequested = true;
}
//
/// Blocks the calling thread until the internal
/// thread exits or until the time specified by
/// timeout, in milliseconds,expires. A timeout of
/// NoLimit says to wait indefinitely.
//
ulong TThread::WaitForExit(ulong timeout)
{
TRACEX(OwlThread, 1, _T("Waiting for thread exit [id:") << Handle << _T(']'));
if (Stat == Running)
return ::WaitForSingleObject(Handle, timeout);
else
return (ulong)-1;
}
//
/// Combines the behavior of Terminate() and
/// WaitForExit(). Sets an internal flag that
/// indicates that the thread should exit and blocks
/// the calling thread until the internal thread
/// exits or until the time specified by timeout, in
/// milliseconds, expires. A timeout of NoLimit says
/// to wait indefinitely.
//
ulong TThread::TerminateAndWait(ulong timeout)
{
Terminate();
return WaitForExit(timeout);
}
//
/// Sets the priority of the thread.
//
int TThread::SetPriority(int pri)
{
TRACEX(OwlThread, 1, _T("Thread priority changed to ") << pri <<
_T(" [id:") << Handle << _T(']'));
return ::SetThreadPriority(Handle, pri);
}
//
/// This function does the actual thread execution. Calling Start creates a thread
/// that begins executing Run with the this pointer pointing to the TThread-based
/// object. Every derived class should define its own version of Run. See the
/// example provided.
//
int
TThread::Run()
{
TRACEX(OwlThread, 1, _T("Illegal Run() on base TThread [id:") << Handle << _T(']'));
return -1;
}
//
// Run the thread. This static function is given as the thread start address,
// with 'this' thread object passed as the param. Invoke the Run() method on
// the thread
//
#if defined(BI_MULTI_THREAD_RTL)
uint __stdcall TThread::Execute(void* thread)
{
int code = STATIC_CAST(TThread*,thread)->Run();
STATIC_CAST(TThread*,thread)->Stat = Finished;
return code;
}
#else
DWORD WINAPI TThread::Execute(void* thread)
{
int code = STATIC_CAST(TThread*,thread)->Run();
STATIC_CAST(TThread*,thread)->Stat = Finished;
return code;
}
#endif
//
/// Exit provides an alternative to returning from Run.
//
/// Called from within the thread that wants to exit early.
//
void
TThread::Exit(ulong code)
{
Stat = Finished;
# if (defined(BI_COMP_MSC)||defined(BI_COMP_GNUC)) && defined(BI_MULTI_THREAD_RTL)
_endthreadex(code);
# else
::ExitThread(code);
# endif
}
//
// Call only when Stat claims that the thread is Running.
//
TThread::TStatus
TThread::CheckStatus() const
{
DWORD code;
::GetExitCodeThread(Handle, &code);
if (code == STILL_ACTIVE)
return Running;
else
return Finished;
}
//----------------------------------------------------------------------------
//
// TThread::TThreadError constructor
//
TThread::TThreadError::TThreadError(TErrorType type)
:
TXBase(MakeString(type)),
Type(type)
{
}
//
// TThread::TThreadError::MakeString()
//
// Translates an error code into a string.
//
tstring TThread::TThreadError::MakeString(TErrorType type)
{
static const tchar* const Names[] = {
_T("Suspend() before Run()"),
_T("Resume() before Run()"),
_T("Resume() during Run()"),
_T("Suspend() after Exit()"),
_T("Resume() after Exit()"),
_T("creation failure"),
_T("destroyed before Exit()"),
_T("illegal assignment"),
_T("Multithreaded Runtime not selected"),
};
tstring Msg;
Msg.reserve(80);
Msg = _T("Error[thread]: ");
Msg += Names[type];
return Msg;
}
} // OWL namespace
//
#if defined(BI_MULTI_THREAD_RTL) && defined(OWL5_COMPAT)
namespace owl {
void TLocalData::SetData(int index, TLocalObject* obj)
{
while(index >= (int)Data.Size())
Data.Add(0);
Data[index] = obj;
}
} // OWL namespace
#endif
//
// These are just dummies left over from the old implementation of thread-local storage.
//
static long _owlTlsRef = 0;
long TlsAddRefs()
{
return _owlTlsRef++;
}
long TlsRelease()
{
return --_owlTlsRef;
}
/* ========================================================================== */
↑ V773 The exception was thrown without releasing the 'handles' pointer. A memory leak is possible.
↑ V720 It is advised to utilize the 'SuspendThread' function only when developing a debugger (see documentation for details).
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: RtlResource.
↑ V1027 Pointer to an object of the 'TFileTime' class is cast to unrelated '_LARGE_INTEGER' class.