No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Monitor.cpp 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. #include "il2cpp-config.h"
  2. #include "il2cpp-object-internals.h"
  3. #include "vm/Monitor.h"
  4. #if IL2CPP_SUPPORT_THREADS
  5. #include "os/Atomic.h"
  6. #include "os/Event.h"
  7. #include "os/Semaphore.h"
  8. #include "os/Thread.h"
  9. #include "vm/Exception.h"
  10. #include "vm/Thread.h"
  11. #include "utils/ThreadSafeFreeList.h"
  12. #include <limits>
  13. #include <exception>
  14. #include "Baselib.h"
  15. #include "Cpp/Atomic.h"
  16. // Mostly follows the algorithm outlined in "Implementing Fast Java Monitors with Relaxed-Locks".
  17. /// State of a lock associated with an object.
  18. ///
  19. /// Allocated from the normal non-GC heap and kept on a free list. This means that an object that is
  20. /// not unlocked before it is reclaimed will leak its monitor. However, it allows us to not have to
  21. /// synchronize with the GC and to efficiently reuse a small number of monitor instances between an
  22. /// arbitrary number of managed objects.
  23. ///
  24. // NOTE: We do *NOT* allow deletion of monitors as threads may be hanging on to monitors even as they
  25. // are already back on the free list (and maybe even in use somewhere else already).
  26. struct MonitorData : public il2cpp::utils::ThreadSafeFreeListNode
  27. {
  28. static const il2cpp::os::Thread::ThreadId kCanBeAcquiredByOtherThread = il2cpp::os::Thread::kInvalidThreadId;
  29. static const il2cpp::os::Thread::ThreadId kHasBeenReturnedToFreeList = (il2cpp::os::Thread::ThreadId)-1;
  30. /// ID of thread that currently has the object locked or one of the two values above.
  31. ///
  32. /// This signals three possible states:
  33. ///
  34. /// 1) Contains a valid thread ID. Means the monitor is owned by that thread and only that thread can
  35. /// change the value of this field.
  36. /// 2) Contains kCanBeAcquiredByOtherThread. Means monitor is still live and attached to an object
  37. /// but is up for grabs by whichever thread manages to swap the value of this field for its own
  38. /// thread ID first.
  39. /// 3) Contains kHasBeenReturnedToFreeList. Means monitor is not attached to any object and can be
  40. /// acquired by any thread *but* only through the free list.
  41. baselib::atomic<il2cpp::os::Thread::ThreadId> owningThreadId;
  42. baselib::atomic<bool> threadAborted;
  43. /// Number of times the object has been locked on the owning thread. Everything above 1 indicates
  44. /// a recursive lock.
  45. /// NOTE: This field is never reset to zero.
  46. uint32_t recursiveLockingCount;
  47. /// Semaphore used to signal other blocked threads that the monitor has become available.
  48. /// The "ready queue" is implicit in the list of threads waiting on this semaphore.
  49. il2cpp::os::Semaphore semaphore;
  50. /// Number of threads that are already waiting or are about to wait for a lock on the monitor.
  51. baselib::atomic<uint32_t> numThreadsWaitingForSemaphore;
  52. /// Event that a waiting thread fires to acknowledge that it has been kicked off a monitor by the thread
  53. /// already holding a lock on the object being waited for. This happens when the locking thread decides
  54. /// to deflate the locked object and thus kill the monitor but then some other thread comes along and
  55. /// decides to wait on the monitor-to-be-killed.
  56. il2cpp::os::Event flushAcknowledged;
  57. /// Node in list of waiting threads.
  58. ///
  59. /// Memory management is done the same way as for MonitorData itself. The same constraints apply.
  60. /// Wait nodes are returned to the free list by the threads that have created them except for abandoned
  61. /// nodes which may be returned by the pulsing thread.
  62. ///
  63. /// NOTE: Every wait node must be cleaned up by the wait thread that allocated it.
  64. struct PulseWaitingListNode : public il2cpp::utils::ThreadSafeFreeListNode
  65. {
  66. enum State
  67. {
  68. /// Node is waiting to be reused.
  69. kUnused,
  70. /// Node is waiting to be signaled.
  71. kWaiting
  72. };
  73. /// Next node in "threadsWaitingForPulse" list.
  74. /// NOTE: Once on the list, this field may only be modified by the thread holding a lock
  75. /// on the respective monitor.
  76. PulseWaitingListNode* nextNode;
  77. /// Event to notify waiting thread of pulse.
  78. il2cpp::os::Event signalWaitingThread;
  79. /// Current usage state. This is not set atomically. Change this state only if you
  80. /// are at a known sequence point.
  81. int32_t state;
  82. static il2cpp::utils::ThreadSafeFreeList<PulseWaitingListNode>* s_FreeList;
  83. PulseWaitingListNode()
  84. : nextNode(NULL)
  85. , state(kUnused) {}
  86. void Release()
  87. {
  88. state = kUnused;
  89. signalWaitingThread.Reset();
  90. s_FreeList->Release(this);
  91. }
  92. static PulseWaitingListNode* Allocate()
  93. {
  94. PulseWaitingListNode* node = s_FreeList->Allocate();
  95. IL2CPP_ASSERT(node->state == kUnused);
  96. return node;
  97. }
  98. };
  99. /// List of threads waiting for a pulse on the monitor.
  100. /// NOTE: This field may be modified concurrently by several threads (no lock).
  101. PulseWaitingListNode* threadsWaitingForPulse;
  102. static il2cpp::utils::ThreadSafeFreeList<MonitorData>* s_FreeList;
  103. MonitorData()
  104. : owningThreadId(kHasBeenReturnedToFreeList)
  105. , threadAborted(false)
  106. , recursiveLockingCount(1)
  107. , numThreadsWaitingForSemaphore(0)
  108. , threadsWaitingForPulse(NULL)
  109. , semaphore(0, std::numeric_limits<int32_t>::max())
  110. {
  111. }
  112. bool IsAcquired() const
  113. {
  114. return (owningThreadId != kCanBeAcquiredByOtherThread && owningThreadId != kHasBeenReturnedToFreeList);
  115. }
  116. bool IsOwnedByThread(il2cpp::os::Thread::ThreadId threadId) const
  117. {
  118. return owningThreadId == threadId;
  119. }
  120. bool TryAcquire(size_t threadId)
  121. {
  122. // The compare_exchange_strong method can change its first argument.
  123. // We don't care about the changed valuem, though, so ignore it.
  124. il2cpp::os::Thread::ThreadId local = kCanBeAcquiredByOtherThread;
  125. return owningThreadId.compare_exchange_strong(local, threadId);
  126. }
  127. void Unacquire()
  128. {
  129. IL2CPP_ASSERT(owningThreadId == il2cpp::os::Thread::CurrentThreadId());
  130. // Use `exchange` rather than `store` to ensure this is a read-modify-write
  131. // operation and all threads observe modifications in the same order,
  132. // i.e. changes within the `lock` block occur before the acquisition
  133. // of the monitor by another thread.
  134. // See: https://en.cppreference.com/w/cpp/atomic/memory_order
  135. owningThreadId.exchange(kCanBeAcquiredByOtherThread);
  136. }
  137. /// Mark current thread as being blocked in Monitor.Enter(), i.e. as "ready to acquire monitor
  138. /// whenever it becomes available."
  139. void AddCurrentThreadToReadyList()
  140. {
  141. numThreadsWaitingForSemaphore++;
  142. il2cpp::vm::Thread::SetState(il2cpp::vm::Thread::Current(), il2cpp::vm::kThreadStateWaitSleepJoin);
  143. }
  144. /// Mark current thread is no longer being blocked on the monitor.
  145. int RemoveCurrentThreadFromReadyList()
  146. {
  147. int numRemainingWaitingThreads = --numThreadsWaitingForSemaphore;
  148. il2cpp::vm::Thread::ClrState(il2cpp::vm::Thread::Current(), il2cpp::vm::kThreadStateWaitSleepJoin);
  149. return numRemainingWaitingThreads;
  150. }
  151. /// Acknowledge that the owning thread has decided to kill the monitor (a.k.a. deflate the corresponding
  152. /// object) while we were waiting on it.
  153. void VacateDyingMonitor()
  154. {
  155. RemoveCurrentThreadFromReadyList();
  156. flushAcknowledged.Set();
  157. }
  158. void PushOntoPulseWaitingList(PulseWaitingListNode* node)
  159. {
  160. // Change state to waiting. Safe to not do this atomically as at this point,
  161. // the waiting thread is the only one with access to the node.
  162. node->state = PulseWaitingListNode::kWaiting;
  163. // Race other wait threads until we've successfully linked the
  164. // node into the list.
  165. while (true)
  166. {
  167. PulseWaitingListNode* nextNode = threadsWaitingForPulse;
  168. node->nextNode = nextNode;
  169. if (il2cpp::os::Atomic::CompareExchangePointer(&threadsWaitingForPulse, node, nextNode) == nextNode)
  170. break;
  171. }
  172. }
  173. /// Get the next wait node and remove it from the list.
  174. /// NOTE: Calling thread *must* have the monitor locked.
  175. PulseWaitingListNode* PopNextFromPulseWaitingList()
  176. {
  177. IL2CPP_ASSERT(owningThreadId == il2cpp::os::Thread::CurrentThreadId());
  178. PulseWaitingListNode* head = threadsWaitingForPulse;
  179. if (!head)
  180. return NULL;
  181. // Grab the node for ourselves. We take the node even if some other thread
  182. // changes "threadsWaitingForPulse" in the meantime. If that happens, we don't
  183. // unlink the node and the node will stay on the list until the waiting thread
  184. // cleans up the list.
  185. PulseWaitingListNode* next = head->nextNode;
  186. if (il2cpp::os::Atomic::CompareExchangePointer(&threadsWaitingForPulse, next, head) == head)
  187. head->nextNode = NULL;
  188. return head;
  189. }
  190. /// Remove the given waiting node from "threadsWaitingForPulse".
  191. /// NOTE: Calling thread *must* have the monitor locked.
  192. bool RemoveFromPulseWaitingList(PulseWaitingListNode* node)
  193. {
  194. IL2CPP_ASSERT(owningThreadId == il2cpp::os::Thread::CurrentThreadId());
  195. // This function works only because threads calling Wait() on the monitor will only
  196. // ever *prepend* nodes to the list. This means that only the "threadsWaitingForPulse"
  197. // variable is actually shared between threads whereas the list contents are owned
  198. // by the thread that has the monitor locked.
  199. tryAgain:
  200. PulseWaitingListNode * previous = NULL;
  201. for (PulseWaitingListNode* current = threadsWaitingForPulse; current != NULL;)
  202. {
  203. // Go through list looking for node.
  204. if (current != node)
  205. {
  206. previous = current;
  207. current = current->nextNode;
  208. continue;
  209. }
  210. // Node found. Remove.
  211. if (previous)
  212. previous->nextNode = node->nextNode;
  213. else
  214. {
  215. // We may have to change "threadsWaitingForPulse" and thus have to synchronize
  216. // with other threads.
  217. if (il2cpp::os::Atomic::CompareExchangePointer(&threadsWaitingForPulse, node->nextNode, node) != node)
  218. {
  219. // One or more other threads have changed the list.
  220. goto tryAgain;
  221. }
  222. }
  223. node->nextNode = NULL;
  224. return true;
  225. }
  226. // Not found in list.
  227. return false;
  228. }
  229. };
  230. il2cpp::utils::ThreadSafeFreeList<MonitorData>* MonitorData::s_FreeList;
  231. il2cpp::utils::ThreadSafeFreeList<MonitorData::PulseWaitingListNode>* MonitorData::PulseWaitingListNode::s_FreeList;
  232. static MonitorData* GetMonitorAndThrowIfNotLockedByCurrentThread(Il2CppObject* obj)
  233. {
  234. // Fetch monitor data.
  235. MonitorData* monitor = il2cpp::os::Atomic::ReadPointer(&obj->monitor);
  236. if (!monitor)
  237. {
  238. // No one locked this object.
  239. il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetSynchronizationLockException("Object is not locked."));
  240. }
  241. // Throw SynchronizationLockException if we're not holding a lock.
  242. // NOTE: Unlike .NET, Mono simply ignores this and does not throw.
  243. uint64_t currentThreadId = il2cpp::os::Thread::CurrentThreadId();
  244. if (monitor->owningThreadId != currentThreadId && !monitor->threadAborted)
  245. {
  246. il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetSynchronizationLockException
  247. ("Object has not been locked by this thread."));
  248. }
  249. return monitor;
  250. }
  251. namespace il2cpp
  252. {
  253. namespace vm
  254. {
  255. void Monitor::AllocateStaticData()
  256. {
  257. MonitorData::s_FreeList = new il2cpp::utils::ThreadSafeFreeList<MonitorData>;
  258. MonitorData::PulseWaitingListNode::s_FreeList = new il2cpp::utils::ThreadSafeFreeList<MonitorData::PulseWaitingListNode>;
  259. }
  260. void Monitor::FreeStaticData()
  261. {
  262. delete MonitorData::s_FreeList;
  263. MonitorData::s_FreeList = nullptr;
  264. delete MonitorData::PulseWaitingListNode::s_FreeList;
  265. MonitorData::PulseWaitingListNode::s_FreeList = nullptr;
  266. }
  267. void Monitor::Enter(Il2CppObject* object)
  268. {
  269. TryEnter(object, std::numeric_limits<uint32_t>::max());
  270. }
  271. bool Monitor::TryEnter(Il2CppObject* obj, uint32_t timeOutMilliseconds)
  272. {
  273. size_t currentThreadId = il2cpp::os::Thread::CurrentThreadId();
  274. while (true)
  275. {
  276. MonitorData* installedMonitor = il2cpp::os::Atomic::ReadPointer(&obj->monitor);
  277. if (!installedMonitor)
  278. {
  279. // Set up a new monitor.
  280. MonitorData* newlyAllocatedMonitorForThisThread = MonitorData::s_FreeList->Allocate();
  281. il2cpp::os::Thread::ThreadId previousOwnerThreadId = newlyAllocatedMonitorForThisThread->owningThreadId.exchange(currentThreadId);
  282. IL2CPP_ASSERT(previousOwnerThreadId == MonitorData::kHasBeenReturnedToFreeList && "Monitor on freelist cannot be owned by thread!");
  283. // Try to install the monitor on the object (aka "inflate" the object).
  284. if (il2cpp::os::Atomic::CompareExchangePointer(&obj->monitor, newlyAllocatedMonitorForThisThread, (MonitorData*)NULL) == NULL)
  285. {
  286. // Done. There was no contention on this object. This is
  287. // the fast path.
  288. IL2CPP_ASSERT(obj->monitor);
  289. IL2CPP_ASSERT(obj->monitor->recursiveLockingCount == 1);
  290. IL2CPP_ASSERT(obj->monitor->owningThreadId == currentThreadId);
  291. return true;
  292. }
  293. else
  294. {
  295. // Some other thread raced us and won. Retry.
  296. newlyAllocatedMonitorForThisThread->owningThreadId = MonitorData::kHasBeenReturnedToFreeList;
  297. MonitorData::s_FreeList->Release(newlyAllocatedMonitorForThisThread);
  298. continue;
  299. }
  300. }
  301. // Object was locked previously. See if we already have the lock.
  302. if (installedMonitor->owningThreadId == currentThreadId)
  303. {
  304. // Yes, recursive lock. Just increase count.
  305. ++installedMonitor->recursiveLockingCount;
  306. return true;
  307. }
  308. // Attempt to acquire lock if it's free
  309. if (installedMonitor->TryAcquire(currentThreadId))
  310. {
  311. // There is no locking around the sections of this logic to speed
  312. // things up, there is potential for race condition to reset the objects
  313. // monitor. If it has been reset prior to successfully coming out of
  314. // TryAquire, dont return, unaquire the installedMonitor, go back through the logic again to grab a
  315. // a valid monitor.
  316. if (il2cpp::os::Atomic::ReadPointer(&obj->monitor) != installedMonitor)
  317. {
  318. installedMonitor->Unacquire();
  319. continue;
  320. }
  321. // Ownership of monitor passed from previously locking thread to us.
  322. IL2CPP_ASSERT(installedMonitor->recursiveLockingCount == 1);
  323. IL2CPP_ASSERT(obj->monitor == installedMonitor);
  324. return true;
  325. }
  326. // Getting an immediate lock failed, so if we have a zero timeout now,
  327. // entering the monitor failed.
  328. if (timeOutMilliseconds == 0)
  329. return false;
  330. // Object was locked by other thread. Let the monitor know we are waiting for a lock.
  331. installedMonitor->AddCurrentThreadToReadyList();
  332. if (il2cpp::os::Atomic::ReadPointer(&obj->monitor) != installedMonitor)
  333. {
  334. // Another thread deflated the object while we tried to lock it. Get off
  335. // the monitor.
  336. // NOTE: By now we may already be dealing with a monitor that is back on the free list
  337. // or even installed on an object again.
  338. installedMonitor->VacateDyingMonitor();
  339. // NOTE: The "Implementing Fast Java Monitors with Relaxed-Locks" paper describes a path
  340. // that may lead to monitors being leaked if the thread currently holding a lock sees our
  341. // temporary increment of numWaitingThreads and ends up not deflating the object. However,
  342. // we can only ever end up inside this branch here if the locking thread has already decided to
  343. // deflate, so I don't see how we can leak here.
  344. // Retry.
  345. continue;
  346. }
  347. // NOTE: At this point, we are in the waiting line for the monitor. However, the thread currently
  348. // locking the monitor may still have already made the decision to deflate the object so we may
  349. // still get kicked off the monitor.
  350. // Wait for the locking thread to signal us.
  351. while (il2cpp::os::Atomic::ReadPointer(&obj->monitor) == installedMonitor)
  352. {
  353. // Try to grab the object for ourselves.
  354. if (installedMonitor->TryAcquire(currentThreadId))
  355. {
  356. // Ownership of monitor passed from previously locking thread to us.
  357. IL2CPP_ASSERT(installedMonitor->recursiveLockingCount == 1);
  358. IL2CPP_ASSERT(obj->monitor == installedMonitor);
  359. installedMonitor->RemoveCurrentThreadFromReadyList();
  360. return true;
  361. }
  362. // Wait for owner to signal us.
  363. il2cpp::os::WaitStatus waitStatus;
  364. try
  365. {
  366. if (timeOutMilliseconds != std::numeric_limits<uint32_t>::max())
  367. {
  368. // Perform a timed wait.
  369. waitStatus = installedMonitor->semaphore.Wait(timeOutMilliseconds, true);
  370. }
  371. else
  372. {
  373. // Perform an infinite wait. We may still be interrupted, however.
  374. waitStatus = installedMonitor->semaphore.Wait(true);
  375. }
  376. }
  377. catch (Thread::NativeThreadAbortException&)
  378. {
  379. // This signals that the monitor was not entered properly by this thread. Therefore
  380. // a later call to Exit on this monitor should not actually try to exit the monitor,
  381. // because it is not owned by this thread.
  382. installedMonitor->threadAborted = true;
  383. throw;
  384. }
  385. catch (...)
  386. {
  387. // A user APC could throw an exception from within Wait(). This can commonly happen
  388. // during shutdown, when vm::Thread::AbortAllThreads() causes an APC that throws a
  389. // NativeThreadAbortException. Just make sure we clean up properly.
  390. installedMonitor->RemoveCurrentThreadFromReadyList();
  391. throw;
  392. }
  393. ////TODO: adjust wait time if we have a Wait() failure and before going another round
  394. if (waitStatus == kWaitStatusTimeout)
  395. {
  396. // Wait failed. Get us off the list.
  397. int newNumWaitingThreads = installedMonitor->RemoveCurrentThreadFromReadyList();
  398. // If there are no more waiting threads on this monitor, we need to check for leaking.
  399. // This may happen if the locking thread has just been executing a Monitor.Exit(), seen
  400. // the positive numWaitingThread count, and decided that it thus cannot deflate the object
  401. // and will trigger the semaphore. However, we've just decided to give up waiting, so if
  402. // we were the only thread waiting and no one ever attempts to lock the object again, the
  403. // monitor will stick around with no one ever deflating the object.
  404. //
  405. // We solve this by simply trying to acquire ownership of the monitor if we were the last
  406. // waiting thread and if that succeeds, we simply change from returning with a time out
  407. // failure to returning with a successful lock.
  408. if (!newNumWaitingThreads && il2cpp::os::Atomic::ReadPointer(&obj->monitor) == installedMonitor)
  409. {
  410. if (installedMonitor->TryAcquire(currentThreadId))
  411. {
  412. // We've successfully acquired a lock on the object.
  413. IL2CPP_ASSERT(installedMonitor->recursiveLockingCount == 1);
  414. IL2CPP_ASSERT(obj->monitor == installedMonitor);
  415. return true;
  416. }
  417. }
  418. // Catch the case where a timeout expired the very moment the owning thread decided to
  419. // get us to vacate the monitor by sending an acknowledgement just to make sure.
  420. if (il2cpp::os::Atomic::ReadPointer(&obj->monitor) != installedMonitor)
  421. installedMonitor->flushAcknowledged.Set();
  422. return false;
  423. }
  424. }
  425. // Owner has deflated the object and the monitor is no longer associated with the
  426. // object we're trying to lock. Signal to the owner that we acknowledge this and
  427. // move off the monitor.
  428. installedMonitor->VacateDyingMonitor();
  429. }
  430. return false;
  431. }
  432. void Monitor::Exit(Il2CppObject* obj)
  433. {
  434. // Fetch monitor data.
  435. MonitorData* monitor = GetMonitorAndThrowIfNotLockedByCurrentThread(obj);
  436. // We have the object lock. Undo one single invocation of Enter().
  437. int newLockingCount = monitor->recursiveLockingCount - 1;
  438. if (newLockingCount > 0)
  439. {
  440. // Was recursively locked. Lock still held by us.
  441. monitor->recursiveLockingCount = newLockingCount;
  442. return;
  443. }
  444. // See if there are already threads ready to take over the lock.
  445. if (monitor->numThreadsWaitingForSemaphore != 0)
  446. {
  447. // Yes, so relinquish ownership of the object and signal the next thread.
  448. monitor->Unacquire();
  449. monitor->semaphore.Post();
  450. }
  451. else if (monitor->threadsWaitingForPulse)
  452. {
  453. // No, but there's threads waiting for a pulse so we can't deflate the object.
  454. // The wait nodes may already have been abandoned but that is for the pulsing
  455. // and waiting threads to sort out. Either way, if there ever is going to be a
  456. // pulse, *some* thread will get around to looking at this monitor again so all
  457. // we do here is relinquish ownership.
  458. monitor->Unacquire();
  459. // there is a race as follows: T1 is our thread and we own monitor lock
  460. // T1 - checks numThreadsWaitingForSemaphore and sees 0
  461. // T2 - sees T1 has lock. Increments numThreadsWaitingForSemaphore
  462. // T2 - tries to acquire monitor, but we hold it
  463. // T2 - waits on semaphore
  464. // T1 - we unacquire and wait to be pulsed (if Exit is called from Wait)
  465. // Result: deadlock as semaphore is never posted
  466. // Fix: double check 'numThreadsWaitingForSemaphore' after we've unacquired
  467. // Worst case might be an extra post, which will just incur an additional
  468. // pass through the loop with an extra attempt to acquire the monitor with a CAS
  469. if (monitor->numThreadsWaitingForSemaphore != 0)
  470. monitor->semaphore.Post();
  471. }
  472. else
  473. {
  474. // Seems like no other thread is interested in the monitor. Deflate the object.
  475. il2cpp::os::Atomic::ExchangePointer(&obj->monitor, (MonitorData*)NULL);
  476. // At this point the monitor is no longer associated with the object and we cannot safely
  477. // "re-attach" it. We need to make sure that all threads still having a reference to the
  478. // monitor let go of it before we put the monitor back on the free list.
  479. //
  480. // IMPORTANT: We still *own* the monitor at this point. No other thread can acquire it and
  481. // we must not let go of the monitor until we have kicked all other threads off of it.
  482. monitor->flushAcknowledged.Reset();
  483. while (monitor->numThreadsWaitingForSemaphore != 0)
  484. {
  485. monitor->semaphore.Post(monitor->numThreadsWaitingForSemaphore);
  486. // If a thread starts waiting right after we have read numThreadsWaitingForSemaphore,
  487. // we won't release the semaphore enough times. So don't wait spend a long time waiting
  488. // for acknowledgement here.
  489. monitor->flushAcknowledged.Wait(1, false);
  490. }
  491. // IMPORTANT: At this point, all other threads must have either already vacated the monitor or
  492. // be on a path that makes them vacate the monitor next. The latter may happen if a thread
  493. // is stopped right before adding itself to the ready list of our monitor in which case we
  494. // will not see the thread on numThreadsWaitingForSemaphore. If we then put the monitor back
  495. // on the freelist and then afterwards the other thread is resumed, it will still put itself
  496. // on the ready list only to then realize it got the wrong monitor.
  497. // So, even for monitors on the free list, we accept that a thread may temporarily add itself
  498. // to the wrong monitor's ready list as long as all it does it simply remove itself right after
  499. // realizing the mistake.
  500. // Release monitor back to free list.
  501. IL2CPP_ASSERT(monitor->owningThreadId == il2cpp::os::Thread::CurrentThreadId());
  502. monitor->owningThreadId = MonitorData::kHasBeenReturnedToFreeList;
  503. MonitorData::s_FreeList->Release(monitor);
  504. }
  505. }
  506. static void PulseMonitor(Il2CppObject* obj, bool all = false)
  507. {
  508. // Grab monitor.
  509. MonitorData* monitor = GetMonitorAndThrowIfNotLockedByCurrentThread(obj);
  510. bool isFirst = true;
  511. while (true)
  512. {
  513. // Grab next waiting thread, if any.
  514. MonitorData::PulseWaitingListNode* waitNode = monitor->PopNextFromPulseWaitingList();
  515. if (!waitNode)
  516. break;
  517. // Pulse thread.
  518. waitNode->signalWaitingThread.Set();
  519. // Stop if we're only supposed to pulse the one thread.
  520. if (isFirst && !all)
  521. break;
  522. isFirst = false;
  523. }
  524. }
  525. void Monitor::Pulse(Il2CppObject* object)
  526. {
  527. PulseMonitor(object, false);
  528. }
  529. void Monitor::PulseAll(Il2CppObject* object)
  530. {
  531. PulseMonitor(object, true);
  532. }
  533. void Monitor::Wait(Il2CppObject* object)
  534. {
  535. TryWait(object, std::numeric_limits<uint32_t>::max());
  536. }
  537. bool Monitor::TryWait(Il2CppObject* object, uint32_t timeoutMilliseconds)
  538. {
  539. MonitorData* monitor = GetMonitorAndThrowIfNotLockedByCurrentThread(object);
  540. // Undo any recursive locking but remember the count so we can restore it
  541. // after we have re-acquired the lock.
  542. uint32_t oldLockingCount = monitor->recursiveLockingCount;
  543. monitor->recursiveLockingCount = 1;
  544. // Add us to the pulse waiting list for the monitor (except if we won't be
  545. // waiting for a pulse at all).
  546. MonitorData::PulseWaitingListNode* waitNode = NULL;
  547. if (timeoutMilliseconds != 0)
  548. {
  549. waitNode = MonitorData::PulseWaitingListNode::Allocate();
  550. monitor->PushOntoPulseWaitingList(waitNode);
  551. }
  552. // Release the monitor.
  553. Exit(object);
  554. monitor = NULL;
  555. // Wait for pulse (if we either have a timeout or are supposed to
  556. // wait infinitely).
  557. il2cpp::os::WaitStatus pulseWaitStatus = kWaitStatusTimeout;
  558. std::exception_ptr exceptionThrownDuringWait = NULL;
  559. if (timeoutMilliseconds != 0)
  560. {
  561. pulseWaitStatus = kWaitStatusFailure;
  562. try
  563. {
  564. il2cpp::vm::ThreadStateSetter state(il2cpp::vm::kThreadStateWaitSleepJoin);
  565. pulseWaitStatus = waitNode->signalWaitingThread.Wait(timeoutMilliseconds, true);
  566. }
  567. catch (...)
  568. {
  569. // Exception occurred during wait. Remember exception but continue with reacquisition
  570. // and cleanup. We re-throw later.
  571. exceptionThrownDuringWait = std::current_exception();
  572. pulseWaitStatus = kWaitStatusFailure;
  573. }
  574. }
  575. // Reacquire the monitor.
  576. Enter(object);
  577. // Monitor *may* have changed.
  578. monitor = object->monitor;
  579. // Restore recursion count.
  580. monitor->recursiveLockingCount = oldLockingCount;
  581. // Get rid of wait list node.
  582. if (waitNode)
  583. {
  584. // Make sure the node is gone from the wait list. If the pulsing thread already did
  585. // that, this won't do anything.
  586. monitor->RemoveFromPulseWaitingList(waitNode);
  587. // And hand it back for reuse.
  588. waitNode->Release();
  589. waitNode = NULL;
  590. }
  591. // If the wait was interrupted by an exception (most likely a ThreadInterruptedException),
  592. // then re-throw now.
  593. //
  594. // NOTE: We delay this to until after we've gone through the reacquisition sequence as we
  595. // have to guarantee that when Monitor.Wait() exits -- whether successfully or not --, it
  596. // still holds a lock. Otherwise a lock() statement around the Wait() will throw an exception,
  597. // for example.
  598. if (exceptionThrownDuringWait)
  599. std::rethrow_exception(exceptionThrownDuringWait);
  600. ////TODO: According to MSDN, the timeout indicates whether we reacquired the lock in time
  601. //// and not just whether the pulse came in time. Thus the current code is imprecise.
  602. return (pulseWaitStatus != kWaitStatusTimeout);
  603. }
  604. bool Monitor::IsAcquired(Il2CppObject* object)
  605. {
  606. MonitorData* monitor = object->monitor;
  607. if (!monitor)
  608. return false;
  609. return monitor->IsAcquired();
  610. }
  611. bool Monitor::IsOwnedByCurrentThread(Il2CppObject* object)
  612. {
  613. MonitorData* monitor = object->monitor;
  614. if (!monitor)
  615. return false;
  616. return monitor->IsOwnedByThread(il2cpp::os::Thread::CurrentThreadId());
  617. }
  618. } /* namespace vm */
  619. } /* namespace il2cpp */
  620. #endif // IL2CPP_SUPPORT_THREADS