/** * The event module provides a primitive for lightweight signaling of other threads * (emulating Windows events on Posix) * * Copyright: Copyright (c) 2019 D Language Foundation * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Rainer Schuetze * Source: $(DRUNTIMESRC core/sync/event.d) */module core.sync.event; version (Windows){ import core.sys.windows.basetsd /+: HANDLE +/; import core.sys.windows.winerror /+: WAIT_TIMEOUT +/; import core.sys.windows.winbase /+: CreateEvent, CloseHandle, SetEvent, ResetEvent, WaitForSingleObject, INFINITE, WAIT_OBJECT_0+/;}else version (Posix){ import core.sys.posix.pthread : pthread_cond_broadcast, pthread_cond_destroy, pthread_cond_init, pthread_cond_timedwait, pthread_cond_wait, pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock; import core.sys.posix.sys.types : pthread_cond_t, pthread_mutex_t; import core.sys.posix.time : timespec;}else{ static assert(false, "Platform not supported");} import core.internal.abort : abort;import core.time; /** * represents an event. Clients of an event are suspended while waiting * for the event to be "signaled". * * Implemented using `pthread_mutex` and `pthread_condition` on Posix and * `CreateEvent` and `SetEvent` on Windows.---import core.sync.event, core.thread, std.file; struct ProcessFile{ ThreadGroup group; Event event; void[] buffer; void doProcess() { event.wait(); // process buffer } void process(string filename) { event.initialize(true, false); group = new ThreadGroup; for (int i = 0; i < 10; ++i) group.create(&doProcess); buffer = std.file.read(filename); event.setIfInitialized(); group.joinAll(); event.terminate(); }}--- */struct Event{nothrow @nogc: /** * Creates an event object. * * Params: * manualReset = the state of the event is not reset automatically after resuming waiting clients * initialState = initial state of the signal */ this(bool manualReset, bool initialState) { initialize(manualReset, initialState); } /** * Initializes an event object. Does nothing if the event is already initialized. * * Params: * manualReset = the state of the event is not reset automatically after resuming waiting clients * initialState = initial state of the signal */ void initialize(bool manualReset, bool initialState) { version (Windows) { if (m_event) return; m_event = CreateEvent(null, manualReset, initialState, null); m_event || abort("Error: CreateEvent failed."); } else version (Posix) { if (m_initalized) return; pthread_mutex_init(cast(pthread_mutex_t*) &m_mutex, null) == 0 || abort("Error: pthread_mutex_init failed."); static if ( is( typeof( imported!"core.sys.posix.pthread".pthread_condattr_setclock ) ) ) { import core.sys.posix.pthread : CLOCK_MONOTONIC, pthread_condattr_destroy, pthread_condattr_init, pthread_condattr_setclock; import core.sys.posix.sys.types : pthread_condattr_t; pthread_condattr_t attr = void; pthread_condattr_init(&attr) == 0 || abort("Error: pthread_condattr_init failed."); pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) == 0 || abort("Error: pthread_condattr_setclock failed."); pthread_cond_init(&m_cond, &attr) == 0 || abort("Error: pthread_cond_init failed."); pthread_condattr_destroy(&attr) == 0 || abort("Error: pthread_condattr_destroy failed."); } else { pthread_cond_init(&m_cond, null) == 0 || abort("Error: pthread_cond_init failed."); } m_state = initialState; m_manualReset = manualReset; m_initalized = true; } } // copying not allowed, can produce resource leaks @disable this(this); @disable void opAssign(Event); ~this() { terminate(); } /** * deinitialize event. Does nothing if the event is not initialized. There must not be * threads currently waiting for the event to be signaled. */ void terminate() { version (Windows) { if (m_event) CloseHandle(m_event); m_event = null; } else version (Posix) { if (m_initalized) { pthread_mutex_destroy(&m_mutex) == 0 || abort("Error: pthread_mutex_destroy failed."); pthread_cond_destroy(&m_cond) == 0 || abort("Error: pthread_cond_destroy failed."); m_initalized = false; } } } deprecated ("Use setIfInitialized() instead") void set() { setIfInitialized(); } /// Set the event to "signaled", so that waiting clients are resumed void setIfInitialized() { version (Windows) { if (m_event) SetEvent(m_event); } else version (Posix) { if (m_initalized) { pthread_mutex_lock(&m_mutex); m_state = true; pthread_cond_broadcast(&m_cond); pthread_mutex_unlock(&m_mutex); } } } /// Reset the event manually void reset() { version (Windows) { if (m_event) ResetEvent(m_event); } else version (Posix) { if (m_initalized) { pthread_mutex_lock(&m_mutex); m_state = false; pthread_mutex_unlock(&m_mutex); } } } /** * Wait for the event to be signaled without timeout. * * Returns: * `true` if the event is in signaled state, `false` if the event is uninitialized or another error occured */ bool wait() { version (Windows) { return m_event && WaitForSingleObject(m_event, INFINITE) == WAIT_OBJECT_0; } else version (Posix) { return wait(Duration.max); } } /** * Wait for the event to be signaled with timeout. * * Params: * tmout = the maximum time to wait * Returns: * `true` if the event is in signaled state, `false` if the event was nonsignaled for the given time or * the event is uninitialized or another error occured */ bool wait(Duration tmout) { version (Windows) { if (!m_event) return false; auto maxWaitMillis = dur!("msecs")(uint.max - 1); while (tmout > maxWaitMillis) { auto res = WaitForSingleObject(m_event, uint.max - 1); if (res != WAIT_TIMEOUT) return res == WAIT_OBJECT_0; tmout -= maxWaitMillis; } auto ms = cast(uint)(tmout.total!"msecs"); return WaitForSingleObject(m_event, ms) == WAIT_OBJECT_0; } else version (Posix) { if (!m_initalized) return false; pthread_mutex_lock(&m_mutex); int result = 0; if (!m_state) { if (tmout == Duration.max) { result = pthread_cond_wait(&m_cond, &m_mutex); } else { import core.sync.config; timespec t = void; mktspec(t, tmout); result = pthread_cond_timedwait(&m_cond, &m_mutex, &t); } } if (result == 0 && !m_manualReset) m_state = false; pthread_mutex_unlock(&m_mutex); return result == 0; } } private: version (Windows) { HANDLE m_event; } else version (Posix) { pthread_mutex_t m_mutex; pthread_cond_t m_cond; bool m_initalized; bool m_state; bool m_manualReset; }} // Test single-thread (non-shared) use.@nogc nothrow unittest{ // auto-reset, initial state false Event ev1 = Event(false, false); assert(!ev1.wait(1.dur!"msecs")); ev1.setIfInitialized(); assert(ev1.wait()); assert(!ev1.wait(1.dur!"msecs")); // manual-reset, initial state true Event ev2 = Event(true, true); assert(ev2.wait()); assert(ev2.wait()); ev2.reset(); assert(!ev2.wait(1.dur!"msecs"));} unittest{ import core.atomic; import core.thread; scope event = new Event(true, false); int numThreads = 10; shared int numRunning = 0; void testFn() { event.wait(8.dur!"seconds"); // timeout below limit for druntime test_runner numRunning.atomicOp!"+="(1); } auto group = new ThreadGroup; for (int i = 0; i < numThreads; ++i) group.create(&testFn); auto start = MonoTime.currTime; assert(numRunning == 0); event.setIfInitialized(); group.joinAll(); assert(numRunning == numThreads); assert(MonoTime.currTime - start < 5.dur!"seconds");}
/**
* The event module provides a primitive for lightweight signaling of other threads
* (emulating Windows events on Posix)
*
* Copyright: Copyright (c) 2019 D Language Foundation
* License: Distributed under the
* $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
* (See accompanying file LICENSE)
* Authors: Rainer Schuetze
* Source: $(DRUNTIMESRC core/sync/event.d)
*/
module core.sync.event;
version (Windows)
{
import core.sys.windows.basetsd /+: HANDLE +/;
import core.sys.windows.winerror /+: WAIT_TIMEOUT +/;
import core.sys.windows.winbase /+: CreateEvent, CloseHandle, SetEvent, ResetEvent,
WaitForSingleObject, INFINITE, WAIT_OBJECT_0+/;
}
else version (Posix)
{
import core.sys.posix.pthread : pthread_cond_broadcast, pthread_cond_destroy, pthread_cond_init,
pthread_cond_timedwait, pthread_cond_wait, pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock,
pthread_mutex_unlock;
import core.sys.posix.sys.types : pthread_cond_t, pthread_mutex_t;
import core.sys.posix.time : timespec;
}
else
{
static assert(false, "Platform not supported");
}
import core.internal.abort : abort;
import core.time;
/**
* represents an event. Clients of an event are suspended while waiting
* for the event to be "signaled".
*
* Implemented using `pthread_mutex` and `pthread_condition` on Posix and
* `CreateEvent` and `SetEvent` on Windows.
---
import core.sync.event, core.thread, std.file;
struct ProcessFile
{
ThreadGroup group;
Event event;
void[] buffer;
void doProcess()
{
event.wait();
// process buffer
}
void process(string filename)
{
event.initialize(true, false);
group = new ThreadGroup;
for (int i = 0; i < 10; ++i)
group.create(&doProcess);
buffer = std.file.read(filename);
event.setIfInitialized();
group.joinAll();
event.terminate();
}
}
---
*/
struct Event
{
nothrow @nogc:
/**
* Creates an event object.
*
* Params:
* manualReset = the state of the event is not reset automatically after resuming waiting clients
* initialState = initial state of the signal
*/
this(bool manualReset, bool initialState)
{
initialize(manualReset, initialState);
}
/**
* Initializes an event object. Does nothing if the event is already initialized.
*
* Params:
* manualReset = the state of the event is not reset automatically after resuming waiting clients
* initialState = initial state of the signal
*/
void initialize(bool manualReset, bool initialState)
{
version (Windows)
{
if (m_event)
return;
m_event = CreateEvent(null, manualReset, initialState, null);
m_event || abort("Error: CreateEvent failed.");
}
else version (Posix)
{
if (m_initalized)
return;
pthread_mutex_init(cast(pthread_mutex_t*) &m_mutex, null) == 0 ||
abort("Error: pthread_mutex_init failed.");
static if ( is( typeof( imported!"core.sys.posix.pthread".pthread_condattr_setclock ) ) )
{
import core.sys.posix.pthread : CLOCK_MONOTONIC, pthread_condattr_destroy, pthread_condattr_init,
pthread_condattr_setclock;
import core.sys.posix.sys.types : pthread_condattr_t;
pthread_condattr_t attr = void;
pthread_condattr_init(&attr) == 0 ||
abort("Error: pthread_condattr_init failed.");
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC) == 0 ||
abort("Error: pthread_condattr_setclock failed.");
pthread_cond_init(&m_cond, &attr) == 0 ||
abort("Error: pthread_cond_init failed.");
pthread_condattr_destroy(&attr) == 0 ||
abort("Error: pthread_condattr_destroy failed.");
}
else
{
pthread_cond_init(&m_cond, null) == 0 ||
abort("Error: pthread_cond_init failed.");
}
m_state = initialState;
m_manualReset = manualReset;
m_initalized = true;
}
}
// copying not allowed, can produce resource leaks
@disable this(this);
@disable void opAssign(Event);
~this()
{
terminate();
}
/**
* deinitialize event. Does nothing if the event is not initialized. There must not be
* threads currently waiting for the event to be signaled.
*/
void terminate()
{
version (Windows)
{
if (m_event)
CloseHandle(m_event);
m_event = null;
}
else version (Posix)
{
if (m_initalized)
{
pthread_mutex_destroy(&m_mutex) == 0 ||
abort("Error: pthread_mutex_destroy failed.");
pthread_cond_destroy(&m_cond) == 0 ||
abort("Error: pthread_cond_destroy failed.");
m_initalized = false;
}
}
}
deprecated ("Use setIfInitialized() instead") void set()
{
setIfInitialized();
}
/// Set the event to "signaled", so that waiting clients are resumed
void setIfInitialized()
{
version (Windows)
{
if (m_event)
SetEvent(m_event);
}
else version (Posix)
{
if (m_initalized)
{
pthread_mutex_lock(&m_mutex);
m_state = true;
pthread_cond_broadcast(&m_cond);
pthread_mutex_unlock(&m_mutex);
}
}
}
/// Reset the event manually
void reset()
{
version (Windows)
{
if (m_event)
ResetEvent(m_event);
}
else version (Posix)
{
if (m_initalized)
{
pthread_mutex_lock(&m_mutex);
m_state = false;
pthread_mutex_unlock(&m_mutex);
}
}
}
/**
* Wait for the event to be signaled without timeout.
*
* Returns:
* `true` if the event is in signaled state, `false` if the event is uninitialized or another error occured
*/
bool wait()
{
version (Windows)
{
return m_event && WaitForSingleObject(m_event, INFINITE) == WAIT_OBJECT_0;
}
else version (Posix)
{
return wait(Duration.max);
}
}
/**
* Wait for the event to be signaled with timeout.
*
* Params:
* tmout = the maximum time to wait
* Returns:
* `true` if the event is in signaled state, `false` if the event was nonsignaled for the given time or
* the event is uninitialized or another error occured
*/
bool wait(Duration tmout)
{
version (Windows)
{
if (!m_event)
return false;
auto maxWaitMillis = dur!("msecs")(uint.max - 1);
while (tmout > maxWaitMillis)
{
auto res = WaitForSingleObject(m_event, uint.max - 1);
if (res != WAIT_TIMEOUT)
return res == WAIT_OBJECT_0;
tmout -= maxWaitMillis;
}
auto ms = cast(uint)(tmout.total!"msecs");
return WaitForSingleObject(m_event, ms) == WAIT_OBJECT_0;
}
else version (Posix)
{
if (!m_initalized)
return false;
pthread_mutex_lock(&m_mutex);
int result = 0;
if (!m_state)
{
if (tmout == Duration.max)
{
result = pthread_cond_wait(&m_cond, &m_mutex);
}
else
{
import core.sync.config;
timespec t = void;
mktspec(t, tmout);
result = pthread_cond_timedwait(&m_cond, &m_mutex, &t);
}
}
if (result == 0 && !m_manualReset)
m_state = false;
pthread_mutex_unlock(&m_mutex);
return result == 0;
}
}
private:
version (Windows)
{
HANDLE m_event;
}
else version (Posix)
{
pthread_mutex_t m_mutex;
pthread_cond_t m_cond;
bool m_initalized;
bool m_state;
bool m_manualReset;
}
}
// Test single-thread (non-shared) use.
@nogc nothrow unittest
{
// auto-reset, initial state false
Event ev1 = Event(false, false);
assert(!ev1.wait(1.dur!"msecs"));
ev1.setIfInitialized();
assert(ev1.wait());
assert(!ev1.wait(1.dur!"msecs"));
// manual-reset, initial state true
Event ev2 = Event(true, true);
assert(ev2.wait());
assert(ev2.wait());
ev2.reset();
assert(!ev2.wait(1.dur!"msecs"));
}
unittest
{
import core.atomic;
import core.thread;
scope event = new Event(true, false);
int numThreads = 10;
shared int numRunning = 0;
void testFn()
{
event.wait(8.dur!"seconds"); // timeout below limit for druntime test_runner
numRunning.atomicOp!"+="(1);
}
auto group = new ThreadGroup;
for (int i = 0; i < numThreads; ++i)
group.create(&testFn);
auto start = MonoTime.currTime;
assert(numRunning == 0);
event.setIfInitialized();
group.joinAll();
assert(numRunning == numThreads);
assert(MonoTime.currTime - start < 5.dur!"seconds");
}