#include "il2cpp-config.h"
|
|
#if IL2CPP_TARGET_POSIX && !IL2CPP_TINY_WITHOUT_DEBUGGER
|
|
#include <cerrno>
|
#include <ctime>
|
|
#include "PosixWaitObject.h"
|
#include "PosixHelpers.h"
|
#include "ThreadImpl.h"
|
#include "os/Time.h"
|
|
|
// Notes:
|
// - Situation
|
// - None of the pthread APIs are interruptible (they all explicitly forbid returning EINTR).
|
// - We cannot do any non-local transfers of control from signal handlers safely (C++ exceptions
|
// or longjmp). Thus we cannot use signals to inject interruptions into a thread.
|
// - Very few of the system APIs we have available support timeouts (at least not on all platforms).
|
// - Ergo: we need to roll our own synchronization primitives based on pthread condition variables
|
// (they support timeouts and have the functionality needed to model the other primitives).
|
// - BUT: the condition variables still involve mutexes which we cannot lock in way that allows
|
// interruptions. This means that there will be time windows where threads will wait and just
|
// block and not allow interruption.
|
|
|
namespace il2cpp
|
{
|
namespace os
|
{
|
namespace posix
|
{
|
static pthread_mutex_t s_WaitObjectDeletionLock = PTHREAD_MUTEX_INITIALIZER;
|
|
PosixWaitObject::PosixWaitObject(Type type)
|
: m_Type(type)
|
, m_Count(0)
|
, m_WaitingThreadCount(0)
|
{
|
pthread_mutex_init(&m_Mutex, NULL);
|
#if !IL2CPP_USE_POSIX_COND_TIMEDWAIT_REL
|
pthread_condattr_t attr;
|
int result = pthread_condattr_init(&attr);
|
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
|
pthread_cond_init(&m_Condition, &attr);
|
pthread_condattr_destroy(&attr);
|
#else
|
// On OSX and iOS we can use pthread_cond_timedwait_relative_np.
|
pthread_cond_init(&m_Condition, NULL);
|
#endif
|
}
|
|
PosixWaitObject::~PosixWaitObject()
|
{
|
// Make sure it's okay to delete wait objects.
|
AutoLockWaitObjectDeletion lockDeletion;
|
|
pthread_mutex_destroy(&m_Mutex);
|
pthread_cond_destroy(&m_Condition);
|
}
|
|
void PosixWaitObject::LockWaitObjectDeletion()
|
{
|
pthread_mutex_lock(&s_WaitObjectDeletionLock);
|
}
|
|
void PosixWaitObject::UnlockWaitObjectDeletion()
|
{
|
pthread_mutex_unlock(&s_WaitObjectDeletionLock);
|
}
|
|
WaitStatus PosixWaitObject::Wait(bool interruptible)
|
{
|
return Wait(kNoTimeout, interruptible);
|
}
|
|
WaitStatus PosixWaitObject::Wait(uint32_t timeoutMS, bool interruptible)
|
{
|
// IMPORTANT: This function must be exception-safe! APCs may throw.
|
|
ThreadImpl* currentThread = ThreadImpl::GetCurrentThread();
|
|
// Do up-front check about pending APC except this is a zero-timeout
|
// wait (i.e. a wait that is never supposed to block and thus go into
|
// an interruptible state).
|
if (interruptible && timeoutMS != 0)
|
currentThread->CheckForUserAPCAndHandle();
|
|
// Lock object. We release this mutex during waiting.
|
posix::PosixAutoLock lock(&m_Mutex);
|
|
// See if the object is in a state where we can acquire it right away.
|
if (m_Count == 0)
|
{
|
// No, hasn't. If we're not supposed to wait, we're done.
|
if (timeoutMS == 0)
|
return kWaitStatusTimeout;
|
|
try
|
{
|
// We should wait. Let the world know this thread is now waiting
|
// on this object.
|
if (interruptible)
|
currentThread->SetWaitObject(this);
|
|
// Check APC queue again to avoid race condition.
|
if (interruptible)
|
currentThread->CheckForUserAPCAndHandle();
|
|
// Go into wait until we either have a release or timeout or otherwise fail.
|
uint32_t remainingWaitTime = timeoutMS;
|
WaitStatus waitStatus = kWaitStatusSuccess;
|
while (m_Count == 0)
|
{
|
if (timeoutMS == posix::kNoTimeout)
|
{
|
// Infinite wait. Can only be interrupted by APC.
|
++m_WaitingThreadCount; // No synchronization necessary; we hold the mutex.
|
int status = pthread_cond_wait(&m_Condition, &m_Mutex);
|
--m_WaitingThreadCount;
|
|
if (status != 0)
|
{
|
waitStatus = kWaitStatusFailure;
|
break;
|
}
|
}
|
else
|
{
|
// Timed wait. Can be interrupted by APC or timeout.
|
const int64_t waitStartTime = Time::GetTicks100NanosecondsMonotonic();
|
timespec timeout = posix::MillisecondsToTimespec(remainingWaitTime);
|
|
#if !IL2CPP_USE_POSIX_COND_TIMEDWAIT_REL
|
timespec waitStartTimeSpec = posix::Ticks100NanosecondsToTimespec(waitStartTime);
|
timeout.tv_sec += waitStartTimeSpec.tv_sec;
|
timeout.tv_nsec += waitStartTimeSpec.tv_nsec;
|
if (timeout.tv_nsec >= 1000000000)
|
{
|
timeout.tv_nsec -= 1000000000;
|
++timeout.tv_sec;
|
}
|
#endif
|
|
++m_WaitingThreadCount;
|
#if !IL2CPP_USE_POSIX_COND_TIMEDWAIT_REL
|
// For plain POSIX, do an absolute timed wait.
|
int status = pthread_cond_timedwait(&m_Condition, &m_Mutex, &timeout);
|
#else
|
// For OSX and iOS, do a relative timed wait.
|
int status = pthread_cond_timedwait_relative_np(&m_Condition, &m_Mutex, &timeout);
|
#endif
|
--m_WaitingThreadCount; ////TODO: make this atomic for when we fail to reacquire the mutex
|
|
if (status == ETIMEDOUT)
|
{
|
waitStatus = kWaitStatusTimeout;
|
break;
|
}
|
else if (status != 0)
|
{
|
waitStatus = kWaitStatusFailure;
|
break;
|
}
|
|
// Update time we have have left to wait.
|
const uint32_t waitTimeThisRound = (Time::GetTicks100NanosecondsMonotonic() - waitStartTime) / 10000;
|
if (waitTimeThisRound > remainingWaitTime)
|
remainingWaitTime = 0;
|
else
|
remainingWaitTime -= waitTimeThisRound;
|
}
|
|
// We've received a signal but it may be because of an APC and not because
|
// the semaphore got signaled. If so, handle the APC and go back to waiting.
|
if (interruptible)
|
currentThread->CheckForUserAPCAndHandle();
|
}
|
|
// We're done waiting so untie us from the current thread.
|
// NOTE: A thread may have grabbed us and then got paused. If we return now and then our owner
|
// tries to delete us, we would pull the rug from under the other thread. This is prevented by
|
// having a central lock on wait object deletion which any thread trying to deal with wait
|
// objects from other threads has to acquire.
|
if (interruptible)
|
{
|
currentThread->SetWaitObject(NULL);
|
|
// Avoid race condition by checking APC queue again after unsetting wait object.
|
currentThread->CheckForUserAPCAndHandle();
|
}
|
|
// If we failed, bail out now.
|
if (waitStatus != kWaitStatusSuccess)
|
return waitStatus;
|
}
|
catch (...)
|
{
|
if (interruptible)
|
currentThread->SetWaitObject(NULL);
|
throw;
|
}
|
}
|
|
// At this point, we should be in signaled state and have the lock on
|
// the object.
|
|
// Object has been released. Acquire it for this thread.
|
IL2CPP_ASSERT(m_Count > 0);
|
switch (m_Type)
|
{
|
case kManualResetEvent:
|
// Nothing to do.
|
break;
|
|
case kMutex:
|
case kAutoResetEvent:
|
m_Count = 0;
|
break;
|
|
case kSemaphore:
|
if (m_Count > 0) // Defensive.
|
{
|
--m_Count;
|
if (m_Count > 0)
|
{
|
// There's more releases on the semaphore. Signal the next thread in line.
|
if (HaveWaitingThreads())
|
pthread_cond_signal(&m_Condition);
|
}
|
}
|
break;
|
}
|
|
return kWaitStatusSuccess;
|
}
|
|
void PosixWaitObject::InterruptWait()
|
{
|
pthread_cond_broadcast(&m_Condition);
|
}
|
|
void* PosixWaitObject::GetOSHandle()
|
{
|
IL2CPP_ASSERT(0 && "This function is not implemented and should not be called");
|
return NULL;
|
}
|
}
|
}
|
}
|
|
#endif // IL2CPP_TARGET_POSIX
|