Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Commit 35e6398

Browse files
authored
Merge pull request #3114 from n8sh/issue-20859
Add overloads of core.sync.rwmutex.ReadWriteMutex.Reader/Writer.tryLock that take a timeout duration merged-on-behalf-of: Nicholas Wilson <thewilsonator@users.noreply.github.com>
2 parents 59814dd + e33e76b commit 35e6398

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

src/core/sync/rwmutex.d

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,41 @@ class ReadWriteMutex
225225
}
226226
}
227227

228+
/**
229+
* Attempts to acquire a read lock on the enclosing mutex. If one can
230+
* be obtained without blocking, the lock is acquired and true is
231+
* returned. If not, the function blocks until either the lock can be
232+
* obtained or the time elapsed exceeds $(D_PARAM timeout), returning
233+
* true if the lock was acquired and false if the function timed out.
234+
*
235+
* Params:
236+
* timeout = maximum amount of time to wait for the lock
237+
* Returns:
238+
* true if the lock was acquired and false if not.
239+
*/
240+
bool tryLock(Duration timeout)
241+
{
242+
const initialTime = MonoTime.currTime;
243+
synchronized( m_commonMutex )
244+
{
245+
++m_numQueuedReaders;
246+
scope(exit) --m_numQueuedReaders;
247+
248+
while (shouldQueueReader)
249+
{
250+
const timeElapsed = MonoTime.currTime - initialTime;
251+
if (timeElapsed >= timeout)
252+
return false;
253+
auto nextWait = timeout - timeElapsed;
254+
// Avoid problems calling wait(Duration) with huge arguments.
255+
enum maxWaitPerCall = dur!"hours"(24 * 365);
256+
m_readerQueue.wait(nextWait < maxWaitPerCall ? nextWait : maxWaitPerCall);
257+
}
258+
++m_numActiveReaders;
259+
return true;
260+
}
261+
}
262+
228263

229264
private:
230265
@property bool shouldQueueReader()
@@ -341,6 +376,40 @@ class ReadWriteMutex
341376
}
342377
}
343378

379+
/**
380+
* Attempts to acquire a write lock on the enclosing mutex. If one can
381+
* be obtained without blocking, the lock is acquired and true is
382+
* returned. If not, the function blocks until either the lock can be
383+
* obtained or the time elapsed exceeds $(D_PARAM timeout), returning
384+
* true if the lock was acquired and false if the function timed out.
385+
*
386+
* Params:
387+
* timeout = maximum amount of time to wait for the lock
388+
* Returns:
389+
* true if the lock was acquired and false if not.
390+
*/
391+
bool tryLock(Duration timeout)
392+
{
393+
const initialTime = MonoTime.currTime;
394+
synchronized( m_commonMutex )
395+
{
396+
++m_numQueuedWriters;
397+
scope(exit) --m_numQueuedWriters;
398+
399+
while (shouldQueueWriter)
400+
{
401+
const timeElapsed = MonoTime.currTime - initialTime;
402+
if (timeElapsed >= timeout)
403+
return false;
404+
auto nextWait = timeout - timeElapsed;
405+
// Avoid problems calling wait(Duration) with huge arguments.
406+
enum maxWaitPerCall = dur!"hours"(24 * 365);
407+
m_writerQueue.wait(nextWait < maxWaitPerCall ? nextWait : maxWaitPerCall);
408+
}
409+
++m_numActiveWriters;
410+
return true;
411+
}
412+
}
344413

345414
private:
346415
@property bool shouldQueueWriter()
@@ -526,3 +595,79 @@ unittest
526595
runTest(ReadWriteMutex.Policy.PREFER_READERS);
527596
runTest(ReadWriteMutex.Policy.PREFER_WRITERS);
528597
}
598+
599+
unittest
600+
{
601+
import core.atomic, core.thread;
602+
__gshared ReadWriteMutex rwmutex;
603+
shared static bool threadTriedOnceToGetLock;
604+
shared static bool threadFinallyGotLock;
605+
606+
rwmutex = new ReadWriteMutex();
607+
atomicFence;
608+
const maxTimeAllowedForTest = dur!"seconds"(20);
609+
// Test ReadWriteMutex.Reader.tryLock(Duration).
610+
{
611+
static void testReaderTryLock()
612+
{
613+
assert(!rwmutex.reader.tryLock(Duration.min));
614+
threadTriedOnceToGetLock.atomicStore(true);
615+
assert(rwmutex.reader.tryLock(Duration.max));
616+
threadFinallyGotLock.atomicStore(true);
617+
rwmutex.reader.unlock;
618+
}
619+
assert(rwmutex.writer.tryLock(Duration.zero), "should have been able to obtain lock without blocking");
620+
auto otherThread = new Thread(&testReaderTryLock).start;
621+
const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest;
622+
Thread.yield;
623+
// We started otherThread with the writer lock held so otherThread's
624+
// first rwlock.reader.tryLock with timeout Duration.min should fail.
625+
while (!threadTriedOnceToGetLock.atomicLoad)
626+
{
627+
assert(MonoTime.currTime < failIfThisTimeisReached, "timed out");
628+
Thread.yield;
629+
}
630+
rwmutex.writer.unlock;
631+
// Soon after we release the writer lock otherThread's second
632+
// rwlock.reader.tryLock with timeout Duration.max should succeed.
633+
while (!threadFinallyGotLock.atomicLoad)
634+
{
635+
assert(MonoTime.currTime < failIfThisTimeisReached, "timed out");
636+
Thread.yield;
637+
}
638+
otherThread.join;
639+
}
640+
threadTriedOnceToGetLock.atomicStore(false); // Reset.
641+
threadFinallyGotLock.atomicStore(false); // Reset.
642+
// Test ReadWriteMutex.Writer.tryLock(Duration).
643+
{
644+
static void testWriterTryLock()
645+
{
646+
assert(!rwmutex.writer.tryLock(Duration.min));
647+
threadTriedOnceToGetLock.atomicStore(true);
648+
assert(rwmutex.writer.tryLock(Duration.max));
649+
threadFinallyGotLock.atomicStore(true);
650+
rwmutex.writer.unlock;
651+
}
652+
assert(rwmutex.reader.tryLock(Duration.zero), "should have been able to obtain lock without blocking");
653+
auto otherThread = new Thread(&testWriterTryLock).start;
654+
const failIfThisTimeisReached = MonoTime.currTime + maxTimeAllowedForTest;
655+
Thread.yield;
656+
// We started otherThread with the reader lock held so otherThread's
657+
// first rwlock.writer.tryLock with timeout Duration.min should fail.
658+
while (!threadTriedOnceToGetLock.atomicLoad)
659+
{
660+
assert(MonoTime.currTime < failIfThisTimeisReached, "timed out");
661+
Thread.yield;
662+
}
663+
rwmutex.reader.unlock;
664+
// Soon after we release the reader lock otherThread's second
665+
// rwlock.writer.tryLock with timeout Duration.max should succeed.
666+
while (!threadFinallyGotLock.atomicLoad)
667+
{
668+
assert(MonoTime.currTime < failIfThisTimeisReached, "timed out");
669+
Thread.yield;
670+
}
671+
otherThread.join;
672+
}
673+
}

0 commit comments

Comments
 (0)