/** * The semaphore module provides a general use semaphore for synchronization. * * Copyright: Copyright Sean Kelly 2005 - 2009. * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Authors: Sean Kelly * Source: $(DRUNTIMESRC core/sync/_semaphore.d) */ /* Copyright Sean Kelly 2005 - 2009. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE or copy at * http://www.boost.org/LICENSE_1_0.txt) */module core.sync.semaphore; public import core.sync.exception;public import core.time; version (OSX) version = Darwin;else version (iOS) version = Darwin;else version (TVOS) version = Darwin;else version (WatchOS) version = Darwin; version (Windows){ import core.sys.windows.basetsd /+: HANDLE+/; import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, INFINITE, ReleaseSemaphore, WAIT_OBJECT_0, WaitForSingleObject+/; import core.sys.windows.windef /+: BOOL, DWORD+/; import core.sys.windows.winerror /+: WAIT_TIMEOUT+/;}else version (Darwin){ import core.stdc.errno : EINTR, errno; import core.sync.config; import core.sys.darwin.mach.kern_return : KERN_ABORTED, KERN_OPERATION_TIMED_OUT; import core.sys.darwin.mach.semaphore : mach_task_self, mach_timespec_t, semaphore_create, semaphore_destroy, semaphore_signal, semaphore_t, semaphore_timedwait, semaphore_wait, SYNC_POLICY_FIFO;}else version (Posix){ import core.stdc.errno : EAGAIN, EINTR, errno, ETIMEDOUT; import core.sync.config; import core.sys.posix.semaphore : sem_destroy, sem_init, sem_post, sem_t, sem_timedwait, sem_trywait, sem_wait; import core.sys.posix.time : clock_gettime, CLOCK_REALTIME, timespec;}else{ static assert(false, "Platform not supported");} ////////////////////////////////////////////////////////////////////////////////// Semaphore//// void wait();// void notify();// bool tryWait();//////////////////////////////////////////////////////////////////////////////// /** * This class represents a general counting semaphore as concieved by Edsger * Dijkstra. As per Mesa type monitors however, "signal" has been replaced * with "notify" to indicate that control is not transferred to the waiter when * a notification is sent. */class Semaphore{ //////////////////////////////////////////////////////////////////////////// // Initialization //////////////////////////////////////////////////////////////////////////// /** * Initializes a semaphore object with the specified initial count. * * Params: * count = The initial count for the semaphore. * * Throws: * SyncError on error. */ this( uint count = 0 ) { version (Windows) { m_hndl = CreateSemaphoreA( null, count, int.max, null ); if ( m_hndl == m_hndl.init ) throw new SyncError( "Unable to create semaphore" ); } else version (Darwin) { auto rc = semaphore_create( mach_task_self(), &m_hndl, SYNC_POLICY_FIFO, count ); if ( rc ) throw new SyncError( "Unable to create semaphore" ); } else version (Posix) { int rc = sem_init( &m_hndl, 0, count ); if ( rc ) throw new SyncError( "Unable to create semaphore" ); } } ~this() { version (Windows) { BOOL rc = CloseHandle( m_hndl ); assert( rc, "Unable to destroy semaphore" ); } else version (Darwin) { auto rc = semaphore_destroy( mach_task_self(), m_hndl ); assert( !rc, "Unable to destroy semaphore" ); } else version (Posix) { int rc = sem_destroy( &m_hndl ); assert( !rc, "Unable to destroy semaphore" ); } } //////////////////////////////////////////////////////////////////////////// // General Actions //////////////////////////////////////////////////////////////////////////// /** * Wait until the current count is above zero, then atomically decrement * the count by one and return. * * Throws: * SyncError on error. */ void wait() { version (Windows) { DWORD rc = WaitForSingleObject( m_hndl, INFINITE ); if ( rc != WAIT_OBJECT_0 ) throw new SyncError( "Unable to wait for semaphore" ); } else version (Darwin) { while ( true ) { auto rc = semaphore_wait( m_hndl ); if ( !rc ) return; if ( rc == KERN_ABORTED && errno == EINTR ) continue; throw new SyncError( "Unable to wait for semaphore" ); } } else version (Posix) { while ( true ) { if ( !sem_wait( &m_hndl ) ) return; if ( errno != EINTR ) throw new SyncError( "Unable to wait for semaphore" ); } } } /** * Suspends the calling thread until the current count moves above zero or * until the supplied time period has elapsed. If the count moves above * zero in this interval, then atomically decrement the count by one and * return true. Otherwise, return false. * * Params: * period = The time to wait. * * In: * period must be non-negative. * * Throws: * SyncError on error. * * Returns: * true if notified before the timeout and false if not. */ bool wait( Duration period ) in { assert( !period.isNegative ); } do { version (Windows) { auto maxWaitMillis = dur!("msecs")( uint.max - 1 ); while ( period > maxWaitMillis ) { auto rc = WaitForSingleObject( m_hndl, cast(uint) maxWaitMillis.total!"msecs" ); switch ( rc ) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: period -= maxWaitMillis; continue; default: throw new SyncError( "Unable to wait for semaphore" ); } } switch ( WaitForSingleObject( m_hndl, cast(uint) period.total!"msecs" ) ) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: return false; default: throw new SyncError( "Unable to wait for semaphore" ); } } else version (Darwin) { mach_timespec_t t = void; (cast(byte*) &t)[0 .. t.sizeof] = 0; if ( period.total!"seconds" > t.tv_sec.max ) { t.tv_sec = t.tv_sec.max; t.tv_nsec = cast(typeof(t.tv_nsec)) period.split!("seconds", "nsecs")().nsecs; } else period.split!("seconds", "nsecs")(t.tv_sec, t.tv_nsec); while ( true ) { auto rc = semaphore_timedwait( m_hndl, t ); if ( !rc ) return true; if ( rc == KERN_OPERATION_TIMED_OUT ) return false; if ( rc != KERN_ABORTED || errno != EINTR ) throw new SyncError( "Unable to wait for semaphore" ); } } else version (Posix) { timespec t = void; clock_gettime( CLOCK_REALTIME, &t ); mvtspec( t, period ); while ( true ) { if ( !sem_timedwait( &m_hndl, &t ) ) return true; if ( errno == ETIMEDOUT ) return false; if ( errno != EINTR ) throw new SyncError( "Unable to wait for semaphore" ); } } } /** * Atomically increment the current count by one. This will notify one * waiter, if there are any in the queue. * * Throws: * SyncError on error. */ void notify() { version (Windows) { if ( !ReleaseSemaphore( m_hndl, 1, null ) ) throw new SyncError( "Unable to notify semaphore" ); } else version (Darwin) { auto rc = semaphore_signal( m_hndl ); if ( rc ) throw new SyncError( "Unable to notify semaphore" ); } else version (Posix) { int rc = sem_post( &m_hndl ); if ( rc ) throw new SyncError( "Unable to notify semaphore" ); } } /** * If the current count is equal to zero, return. Otherwise, atomically * decrement the count by one and return true. * * Throws: * SyncError on error. * * Returns: * true if the count was above zero and false if not. */ bool tryWait() { version (Windows) { switch ( WaitForSingleObject( m_hndl, 0 ) ) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: return false; default: throw new SyncError( "Unable to wait for semaphore" ); } } else version (Darwin) { return wait( dur!"hnsecs"(0) ); } else version (Posix) { while ( true ) { if ( !sem_trywait( &m_hndl ) ) return true; if ( errno == EAGAIN ) return false; if ( errno != EINTR ) throw new SyncError( "Unable to wait for semaphore" ); } } } protected: /// Aliases the operating-system-specific semaphore type. version (Windows) alias Handle = HANDLE; /// ditto else version (Darwin) alias Handle = semaphore_t; /// ditto else version (Posix) alias Handle = sem_t; /// Handle to the system-specific semaphore. Handle m_hndl;} ////////////////////////////////////////////////////////////////////////////////// Unit Tests//////////////////////////////////////////////////////////////////////////////// unittest{ import core.atomic; import core.thread; void testWait() { auto semaphore = new Semaphore; shared bool stopConsumption = false; immutable numToProduce = 20; immutable numConsumers = 10; shared size_t numConsumed; shared size_t numComplete; void consumer() { while (true) { semaphore.wait(); if (atomicLoad(stopConsumption)) break; atomicOp!"+="(numConsumed, 1); } atomicOp!"+="(numComplete, 1); } void producer() { assert(!semaphore.tryWait()); foreach (_; 0 .. numToProduce) semaphore.notify(); // wait until all items are consumed while (atomicLoad(numConsumed) != numToProduce) Thread.yield(); // mark consumption as finished atomicStore(stopConsumption, true); // wake all consumers foreach (_; 0 .. numConsumers) semaphore.notify(); // wait until all consumers completed while (atomicLoad(numComplete) != numConsumers) Thread.yield(); assert(!semaphore.tryWait()); semaphore.notify(); assert(semaphore.tryWait()); assert(!semaphore.tryWait()); } auto group = new ThreadGroup; for ( int i = 0; i < numConsumers; ++i ) group.create(&consumer); group.create(&producer); group.joinAll(); } void testWaitTimeout() { auto sem = new Semaphore; shared bool semReady; bool alertedOne, alertedTwo; void waiter() { while (!atomicLoad(semReady)) Thread.yield(); alertedOne = sem.wait(dur!"msecs"(1)); alertedTwo = sem.wait(dur!"msecs"(1)); assert(alertedOne && !alertedTwo); } auto thread = new Thread(&waiter); thread.start(); sem.notify(); atomicStore(semReady, true); thread.join(); assert(alertedOne && !alertedTwo); } testWait(); testWaitTimeout();}
/**
* The semaphore module provides a general use semaphore for synchronization.
*
* Copyright: Copyright Sean Kelly 2005 - 2009.
* License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Authors: Sean Kelly
* Source: $(DRUNTIMESRC core/sync/_semaphore.d)
*/
/* Copyright Sean Kelly 2005 - 2009.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
module core.sync.semaphore;
public import core.sync.exception;
public import core.time;
version (OSX)
version = Darwin;
else version (iOS)
version = Darwin;
else version (TVOS)
version = Darwin;
else version (WatchOS)
version = Darwin;
version (Windows)
{
import core.sys.windows.basetsd /+: HANDLE+/;
import core.sys.windows.winbase /+: CloseHandle, CreateSemaphoreA, INFINITE,
ReleaseSemaphore, WAIT_OBJECT_0, WaitForSingleObject+/;
import core.sys.windows.windef /+: BOOL, DWORD+/;
import core.sys.windows.winerror /+: WAIT_TIMEOUT+/;
}
else version (Darwin)
{
import core.stdc.errno : EINTR, errno;
import core.sync.config;
import core.sys.darwin.mach.kern_return : KERN_ABORTED, KERN_OPERATION_TIMED_OUT;
import core.sys.darwin.mach.semaphore : mach_task_self, mach_timespec_t, semaphore_create, semaphore_destroy,
semaphore_signal, semaphore_t, semaphore_timedwait, semaphore_wait, SYNC_POLICY_FIFO;
}
else version (Posix)
{
import core.stdc.errno : EAGAIN, EINTR, errno, ETIMEDOUT;
import core.sync.config;
import core.sys.posix.semaphore : sem_destroy, sem_init, sem_post, sem_t, sem_timedwait, sem_trywait, sem_wait;
import core.sys.posix.time : clock_gettime, CLOCK_REALTIME, timespec;
}
else
{
static assert(false, "Platform not supported");
}
////////////////////////////////////////////////////////////////////////////////
// Semaphore
//
// void wait();
// void notify();
// bool tryWait();
////////////////////////////////////////////////////////////////////////////////
/**
* This class represents a general counting semaphore as concieved by Edsger
* Dijkstra. As per Mesa type monitors however, "signal" has been replaced
* with "notify" to indicate that control is not transferred to the waiter when
* a notification is sent.
*/
class Semaphore
{
////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////
/**
* Initializes a semaphore object with the specified initial count.
*
* Params:
* count = The initial count for the semaphore.
*
* Throws:
* SyncError on error.
*/
this( uint count = 0 )
{
version (Windows)
{
m_hndl = CreateSemaphoreA( null, count, int.max, null );
if ( m_hndl == m_hndl.init )
throw new SyncError( "Unable to create semaphore" );
}
else version (Darwin)
{
auto rc = semaphore_create( mach_task_self(), &m_hndl, SYNC_POLICY_FIFO, count );
if ( rc )
throw new SyncError( "Unable to create semaphore" );
}
else version (Posix)
{
int rc = sem_init( &m_hndl, 0, count );
if ( rc )
throw new SyncError( "Unable to create semaphore" );
}
}
~this()
{
version (Windows)
{
BOOL rc = CloseHandle( m_hndl );
assert( rc, "Unable to destroy semaphore" );
}
else version (Darwin)
{
auto rc = semaphore_destroy( mach_task_self(), m_hndl );
assert( !rc, "Unable to destroy semaphore" );
}
else version (Posix)
{
int rc = sem_destroy( &m_hndl );
assert( !rc, "Unable to destroy semaphore" );
}
}
////////////////////////////////////////////////////////////////////////////
// General Actions
////////////////////////////////////////////////////////////////////////////
/**
* Wait until the current count is above zero, then atomically decrement
* the count by one and return.
*
* Throws:
* SyncError on error.
*/
void wait()
{
version (Windows)
{
DWORD rc = WaitForSingleObject( m_hndl, INFINITE );
if ( rc != WAIT_OBJECT_0 )
throw new SyncError( "Unable to wait for semaphore" );
}
else version (Darwin)
{
while ( true )
{
auto rc = semaphore_wait( m_hndl );
if ( !rc )
return;
if ( rc == KERN_ABORTED && errno == EINTR )
continue;
throw new SyncError( "Unable to wait for semaphore" );
}
}
else version (Posix)
{
while ( true )
{
if ( !sem_wait( &m_hndl ) )
return;
if ( errno != EINTR )
throw new SyncError( "Unable to wait for semaphore" );
}
}
}
/**
* Suspends the calling thread until the current count moves above zero or
* until the supplied time period has elapsed. If the count moves above
* zero in this interval, then atomically decrement the count by one and
* return true. Otherwise, return false.
*
* Params:
* period = The time to wait.
*
* In:
* period must be non-negative.
*
* Throws:
* SyncError on error.
*
* Returns:
* true if notified before the timeout and false if not.
*/
bool wait( Duration period )
in
{
assert( !period.isNegative );
}
do
{
version (Windows)
{
auto maxWaitMillis = dur!("msecs")( uint.max - 1 );
while ( period > maxWaitMillis )
{
auto rc = WaitForSingleObject( m_hndl, cast(uint)
maxWaitMillis.total!"msecs" );
switch ( rc )
{
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
period -= maxWaitMillis;
continue;
default:
throw new SyncError( "Unable to wait for semaphore" );
}
}
switch ( WaitForSingleObject( m_hndl, cast(uint) period.total!"msecs" ) )
{
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
default:
throw new SyncError( "Unable to wait for semaphore" );
}
}
else version (Darwin)
{
mach_timespec_t t = void;
(cast(byte*) &t)[0 .. t.sizeof] = 0;
if ( period.total!"seconds" > t.tv_sec.max )
{
t.tv_sec = t.tv_sec.max;
t.tv_nsec = cast(typeof(t.tv_nsec)) period.split!("seconds", "nsecs")().nsecs;
}
else
period.split!("seconds", "nsecs")(t.tv_sec, t.tv_nsec);
while ( true )
{
auto rc = semaphore_timedwait( m_hndl, t );
if ( !rc )
return true;
if ( rc == KERN_OPERATION_TIMED_OUT )
return false;
if ( rc != KERN_ABORTED || errno != EINTR )
throw new SyncError( "Unable to wait for semaphore" );
}
}
else version (Posix)
{
timespec t = void;
clock_gettime( CLOCK_REALTIME, &t );
mvtspec( t, period );
while ( true )
{
if ( !sem_timedwait( &m_hndl, &t ) )
return true;
if ( errno == ETIMEDOUT )
return false;
if ( errno != EINTR )
throw new SyncError( "Unable to wait for semaphore" );
}
}
}
/**
* Atomically increment the current count by one. This will notify one
* waiter, if there are any in the queue.
*
* Throws:
* SyncError on error.
*/
void notify()
{
version (Windows)
{
if ( !ReleaseSemaphore( m_hndl, 1, null ) )
throw new SyncError( "Unable to notify semaphore" );
}
else version (Darwin)
{
auto rc = semaphore_signal( m_hndl );
if ( rc )
throw new SyncError( "Unable to notify semaphore" );
}
else version (Posix)
{
int rc = sem_post( &m_hndl );
if ( rc )
throw new SyncError( "Unable to notify semaphore" );
}
}
/**
* If the current count is equal to zero, return. Otherwise, atomically
* decrement the count by one and return true.
*
* Throws:
* SyncError on error.
*
* Returns:
* true if the count was above zero and false if not.
*/
bool tryWait()
{
version (Windows)
{
switch ( WaitForSingleObject( m_hndl, 0 ) )
{
case WAIT_OBJECT_0:
return true;
case WAIT_TIMEOUT:
return false;
default:
throw new SyncError( "Unable to wait for semaphore" );
}
}
else version (Darwin)
{
return wait( dur!"hnsecs"(0) );
}
else version (Posix)
{
while ( true )
{
if ( !sem_trywait( &m_hndl ) )
return true;
if ( errno == EAGAIN )
return false;
if ( errno != EINTR )
throw new SyncError( "Unable to wait for semaphore" );
}
}
}
protected:
/// Aliases the operating-system-specific semaphore type.
version (Windows) alias Handle = HANDLE;
/// ditto
else version (Darwin) alias Handle = semaphore_t;
/// ditto
else version (Posix) alias Handle = sem_t;
/// Handle to the system-specific semaphore.
Handle m_hndl;
}
////////////////////////////////////////////////////////////////////////////////
// Unit Tests
////////////////////////////////////////////////////////////////////////////////
unittest
{
import core.atomic;
import core.thread;
void testWait()
{
auto semaphore = new Semaphore;
shared bool stopConsumption = false;
immutable numToProduce = 20;
immutable numConsumers = 10;
shared size_t numConsumed;
shared size_t numComplete;
void consumer()
{
while (true)
{
semaphore.wait();
if (atomicLoad(stopConsumption))
break;
atomicOp!"+="(numConsumed, 1);
}
atomicOp!"+="(numComplete, 1);
}
void producer()
{
assert(!semaphore.tryWait());
foreach (_; 0 .. numToProduce)
semaphore.notify();
// wait until all items are consumed
while (atomicLoad(numConsumed) != numToProduce)
Thread.yield();
// mark consumption as finished
atomicStore(stopConsumption, true);
// wake all consumers
foreach (_; 0 .. numConsumers)
semaphore.notify();
// wait until all consumers completed
while (atomicLoad(numComplete) != numConsumers)
Thread.yield();
assert(!semaphore.tryWait());
semaphore.notify();
assert(semaphore.tryWait());
assert(!semaphore.tryWait());
}
auto group = new ThreadGroup;
for ( int i = 0; i < numConsumers; ++i )
group.create(&consumer);
group.create(&producer);
group.joinAll();
}
void testWaitTimeout()
{
auto sem = new Semaphore;
shared bool semReady;
bool alertedOne, alertedTwo;
void waiter()
{
while (!atomicLoad(semReady))
Thread.yield();
alertedOne = sem.wait(dur!"msecs"(1));
alertedTwo = sem.wait(dur!"msecs"(1));
assert(alertedOne && !alertedTwo);
}
auto thread = new Thread(&waiter);
thread.start();
sem.notify();
atomicStore(semReady, true);
thread.join();
assert(alertedOne && !alertedTwo);
}
testWait();
testWaitTimeout();
}