diff --git a/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java index 25df85c999..b7b2f59e14 100644 --- a/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java +++ b/wpiutil/src/main/java/edu/wpi/first/util/WPIUtilJNI.java @@ -63,4 +63,63 @@ public final class WPIUtilJNI { public static native void addPortForwarder(int port, String remoteHost, int remotePort); public static native void removePortForwarder(int port); + + public static native int createEvent(boolean manualReset, boolean initialState); + + public static native void destroyEvent(int eventHandle); + + public static native void setEvent(int eventHandle); + + public static native void resetEvent(int eventHandle); + + public static native int createSemaphore(int initialCount, int maximumCount); + + public static native void destroySemaphore(int semHandle); + + public static native boolean releaseSemaphore(int semHandle, int releaseCount); + + /** + * Waits for an handle to be signaled. + * + * @param handle handle to wait on + * @throws InterruptedException on failure (e.g. object was destroyed) + */ + public static native void waitForObject(int handle) throws InterruptedException; + + /** + * Waits for an handle to be signaled, with timeout. + * + * @param handle handle to wait on + * @param timeout timeout in seconds + * @return True if timeout reached without handle being signaled + * @throws InterruptedException on failure (e.g. object was destroyed) + */ + public static native boolean waitForObjectTimeout(int handle, double timeout) + throws InterruptedException; + + /** + * Waits for one or more handles to be signaled. + * + *

Invalid handles are treated as signaled; the returned array will have the handle error bit + * set for any invalid handles. + * + * @param handles array of handles to wait on + * @return array of signaled handles + * @throws InterruptedException on failure (e.g. no objects were signaled) + */ + public static native int[] waitForObjects(int[] handles) throws InterruptedException; + + /** + * Waits for one or more handles to be signaled, with timeout. + * + *

Invalid handles are treated as signaled; the returned array will have the handle error bit + * set for any invalid handles. + * + * @param handles array of handles to wait on + * @param timeout timeout in seconds + * @return array of signaled handles; empty if timeout reached without any handle being signaled + * @throws InterruptedException on failure (e.g. no objects were signaled and no timeout) + */ + public static native int[] waitForObjectsTimeout(int[] handles, double timeout) + throws InterruptedException; } diff --git a/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java new file mode 100644 index 0000000000..bda2e8868f --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java @@ -0,0 +1,70 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.concurrent; + +import edu.wpi.first.util.WPIUtilJNI; + +/** + * An atomic signaling event for synchronization. + * + *

Events have binary state (signaled or not signaled) and may be either automatically reset or + * manually reset. Automatic-reset events go to non-signaled state when a waitForObject is woken up + * by the event; manual-reset events require reset() to be called to set the event to non-signaled + * state; if reset() is not called, any waiter on that event will immediately wake when called. + */ +public final class Event implements AutoCloseable { + /** + * Constructor. + * + * @param manualReset true for manual reset, false for automatic reset + * @param initialState true to make the event initially in signaled state + */ + public Event(boolean manualReset, boolean initialState) { + m_handle = WPIUtilJNI.createEvent(manualReset, initialState); + } + + /** + * Constructor. Initial state is false. + * + * @param manualReset true for manual reset, false for automatic reset + */ + public Event(boolean manualReset) { + this(manualReset, false); + } + + /** Constructor. Automatic reset, initial state is false. */ + public Event() { + this(false, false); + } + + @Override + public void close() { + if (m_handle != 0) { + WPIUtilJNI.destroyEvent(m_handle); + m_handle = 0; + } + } + + /** + * Gets the event handle (e.g. for waitForObject). + * + * @return handle + */ + public int getHandle() { + return m_handle; + } + + /** Sets the event to signaled state. */ + public void set() { + WPIUtilJNI.setEvent(m_handle); + } + + /** Sets the event to non-signaled state. */ + public void reset() { + WPIUtilJNI.resetEvent(m_handle); + } + + private int m_handle; +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java new file mode 100644 index 0000000000..4e9e85f6a9 --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java @@ -0,0 +1,80 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.concurrent; + +import edu.wpi.first.util.WPIUtilJNI; + +/** + * A semaphore for synchronization. + * + *

Semaphores keep an internal counter. Releasing the semaphore increases the count. A semaphore + * with a non-zero count is considered signaled. When a waiter wakes up it atomically decrements the + * count by 1. This is generally useful in a single-supplier, multiple-consumer scenario. + */ +public final class Semaphore implements AutoCloseable { + /** + * Constructor. + * + * @param initialCount initial value for the semaphore's internal counter + * @param maximumCount maximum value for the samephore's internal counter + */ + public Semaphore(int initialCount, int maximumCount) { + m_handle = WPIUtilJNI.createSemaphore(initialCount, maximumCount); + } + + /** + * Constructor. Maximum count is Integer.MAX_VALUE. + * + * @param initialCount initial value for the semaphore's internal counter + */ + public Semaphore(int initialCount) { + this(initialCount, Integer.MAX_VALUE); + } + + /** Constructor. Initial count is 0, maximum count is Integer.MAX_VALUE. */ + public Semaphore() { + this(0, Integer.MAX_VALUE); + } + + @Override + public void close() { + if (m_handle != 0) { + WPIUtilJNI.destroySemaphore(m_handle); + m_handle = 0; + } + } + + /** + * Gets the semaphore handle (e.g. for waitForObject). + * + * @return handle + */ + public int getHandle() { + return m_handle; + } + + /** + * Releases N counts of the semaphore. + * + * @param releaseCount amount to add to semaphore's internal counter; must be positive + * @return True on successful release, false on failure (e.g. release count would exceed maximum + * value, or handle invalid) + */ + public boolean release(int releaseCount) { + return WPIUtilJNI.releaseSemaphore(m_handle, releaseCount); + } + + /** + * Releases 1 count of the semaphore. + * + * @return True on successful release, false on failure (e.g. release count would exceed maximum + * value, or handle invalid) + */ + public boolean release() { + return release(1); + } + + private int m_handle; +} diff --git a/wpiutil/src/main/native/cpp/Synchronization.cpp b/wpiutil/src/main/native/cpp/Synchronization.cpp new file mode 100644 index 0000000000..da97897b32 --- /dev/null +++ b/wpiutil/src/main/native/cpp/Synchronization.cpp @@ -0,0 +1,368 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/Synchronization.h" + +#include +#include +#include + +#include "wpi/DenseMap.h" +#include "wpi/SmallVector.h" +#include "wpi/UidVector.h" +#include "wpi/condition_variable.h" +#include "wpi/mutex.h" + +using namespace wpi; + +namespace { + +struct State { + int signaled{0}; + bool autoReset{false}; + wpi::SmallVector waiters; +}; + +struct HandleManager { + wpi::mutex mutex; + wpi::UidVector eventIds; + wpi::UidVector semaphoreIds; + wpi::DenseMap states; +}; + +} // namespace + +static HandleManager& GetManager() { + static HandleManager manager; + return manager; +} + +WPI_EventHandle wpi::CreateEvent(bool manualReset, bool initialState) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + + auto index = manager.eventIds.emplace_back(0); + WPI_EventHandle handle = (kHandleTypeEvent << 24) | (index & 0xffffff); + + // configure state data + auto& state = manager.states[handle]; + state.signaled = initialState ? 1 : 0; + state.autoReset = !manualReset; + + return handle; +} + +void wpi::DestroyEvent(WPI_EventHandle handle) { + if ((handle >> 24) != kHandleTypeEvent) { + return; + } + + DestroySignalObject(handle); + + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + manager.eventIds.erase(handle & 0xffffff); +} + +void wpi::SetEvent(WPI_EventHandle handle) { + if ((handle >> 24) != kHandleTypeEvent) { + return; + } + + SetSignalObject(handle); +} + +void wpi::ResetEvent(WPI_EventHandle handle) { + if ((handle >> 24) != kHandleTypeEvent) { + return; + } + + ResetSignalObject(handle); +} + +WPI_SemaphoreHandle wpi::CreateSemaphore(int initialCount, int maximumCount) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + + auto index = manager.semaphoreIds.emplace_back(maximumCount); + WPI_EventHandle handle = (kHandleTypeSemaphore << 24) | (index & 0xffffff); + + // configure state data + auto& state = manager.states[handle]; + state.signaled = initialCount; + state.autoReset = true; + + return handle; +} + +void wpi::DestroySemaphore(WPI_SemaphoreHandle handle) { + if ((handle >> 24) != kHandleTypeSemaphore) { + return; + } + + DestroySignalObject(handle); + + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + manager.eventIds.erase(handle & 0xffffff); +} + +bool wpi::ReleaseSemaphore(WPI_SemaphoreHandle handle, int releaseCount, + int* prevCount) { + if ((handle >> 24) != kHandleTypeSemaphore) { + return false; + } + if (releaseCount <= 0) { + return false; + } + int index = handle & 0xffffff; + + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + auto it = manager.states.find(handle); + if (it == manager.states.end()) { + return false; + } + auto& state = it->second; + int maxCount = manager.eventIds[index]; + if (prevCount) { + *prevCount = state.signaled; + } + if ((maxCount - state.signaled) < releaseCount) { + return false; + } + state.signaled += releaseCount; + for (auto& waiter : state.waiters) { + waiter->notify_all(); + } + return true; +} + +bool wpi::WaitForObject(WPI_Handle handle) { + return WaitForObject(handle, -1, nullptr); +} + +bool wpi::WaitForObject(WPI_Handle handle, double timeout, bool* timedOut) { + WPI_Handle signaledValue; + auto signaled = WaitForObjects( + wpi::span(&handle, 1), wpi::span(&signaledValue, 1), timeout, timedOut); + if (signaled.empty()) { + return false; + } + return (signaled[0] & 0x80000000ul) == 0; +} + +wpi::span wpi::WaitForObjects(wpi::span handles, + wpi::span signaled) { + return WaitForObjects(handles, signaled, -1, nullptr); +} + +wpi::span wpi::WaitForObjects(wpi::span handles, + wpi::span signaled, + double timeout, bool* timedOut) { + auto& manager = GetManager(); + std::unique_lock lock{manager.mutex}; + wpi::condition_variable cv; + bool addedWaiters = false; + bool timedOutVal = false; + size_t count = 0; + + for (;;) { + for (auto handle : handles) { + auto it = manager.states.find(handle); + if (it == manager.states.end()) { + if (count < signaled.size()) { + // treat a non-existent handle as signaled, but set the error bit + signaled[count++] = handle | 0x80000000ul; + } + } else { + auto& state = it->second; + if (state.signaled > 0) { + if (count < signaled.size()) { + signaled[count++] = handle; + } + if (state.autoReset) { + --state.signaled; + if (state.signaled < 0) { + state.signaled = 0; + } + } + } + } + } + + if (timedOutVal || count != 0) { + break; + } + + if (timeout == 0) { + timedOutVal = true; + break; + } + + if (!addedWaiters) { + addedWaiters = true; + for (auto handle : handles) { + auto& state = manager.states[handle]; + state.waiters.emplace_back(&cv); + } + } + + if (timeout < 0) { + cv.wait(lock); + } else { + auto timeoutTime = std::chrono::steady_clock::now() + + std::chrono::duration(timeout); + if (cv.wait_until(lock, timeoutTime) == std::cv_status::timeout) { + timedOutVal = true; + } + } + } + + if (addedWaiters) { + for (auto handle : handles) { + auto& state = manager.states[handle]; + auto it = std::find(state.waiters.begin(), state.waiters.end(), &cv); + if (it != state.waiters.end()) { + state.waiters.erase(it); + } + } + } + + if (timedOut) { + *timedOut = timedOutVal; + } + + return signaled.subspan(0, count); +} + +void wpi::CreateSignalObject(WPI_Handle handle, bool manualReset, + bool initialState) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + auto& state = manager.states[handle]; + state.signaled = initialState ? 1 : 0; + state.autoReset = !manualReset; +} + +void wpi::SetSignalObject(WPI_Handle handle) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + auto it = manager.states.find(handle); + if (it == manager.states.end()) { + return; + } + auto& state = it->second; + state.signaled = 1; + for (auto& waiter : state.waiters) { + waiter->notify_all(); + if (state.autoReset) { + // expect the first waiter to reset it + break; + } + } +} + +void wpi::ResetSignalObject(WPI_Handle handle) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + auto it = manager.states.find(handle); + if (it != manager.states.end()) { + it->second.signaled = 0; + } +} + +void wpi::DestroySignalObject(WPI_Handle handle) { + auto& manager = GetManager(); + std::scoped_lock lock{manager.mutex}; + + auto it = manager.states.find(handle); + if (it != manager.states.end()) { + // wake up any waiters + for (auto& waiter : it->second.waiters) { + waiter->notify_all(); + } + manager.states.erase(it); + } +} + +extern "C" { + +WPI_EventHandle WPI_CreateEvent(int manual_reset, int initial_state) { + return wpi::CreateEvent(manual_reset != 0, initial_state != 0); +} + +void WPI_DestroyEvent(WPI_EventHandle handle) { + wpi::DestroyEvent(handle); +} + +void WPI_SetEvent(WPI_EventHandle handle) { + wpi::SetEvent(handle); +} + +void WPI_ResetEvent(WPI_EventHandle handle) { + wpi::ResetEvent(handle); +} + +WPI_SemaphoreHandle WPI_CreateSemaphore(int initial_count, int maximum_count) { + return wpi::CreateSemaphore(initial_count, maximum_count); +} + +void WPI_DestroySemaphore(WPI_SemaphoreHandle handle) { + wpi::DestroySemaphore(handle); +} + +int WPI_ReleaseSemaphore(WPI_SemaphoreHandle handle, int release_count, + int* prev_count) { + return wpi::ReleaseSemaphore(handle, release_count, prev_count); +} + +int WPI_WaitForObject(WPI_Handle handle) { + return wpi::WaitForObject(handle); +} + +int WPI_WaitForObjectTimeout(WPI_Handle handle, double timeout, + int* timed_out) { + bool timedOutBool; + int rv = wpi::WaitForObject(handle, timeout, &timedOutBool); + *timed_out = timedOutBool ? 1 : 0; + return rv; +} + +int WPI_WaitForObjects(const WPI_Handle* handles, int handles_count, + WPI_Handle* signaled) { + return wpi::WaitForObjects(wpi::span(handles, handles_count), + wpi::span(signaled, handles_count)) + .size(); +} + +int WPI_WaitForObjectsTimeout(const WPI_Handle* handles, int handles_count, + WPI_Handle* signaled, double timeout, + int* timed_out) { + bool timedOutBool; + auto signaledResult = wpi::WaitForObjects(wpi::span(handles, handles_count), + wpi::span(signaled, handles_count), + timeout, &timedOutBool); + *timed_out = timedOutBool ? 1 : 0; + return signaledResult.size(); +} + +void WPI_CreateSignalObject(WPI_Handle handle, int manual_reset, + int initial_state) { + wpi::CreateSignalObject(handle, manual_reset, initial_state); +} + +void WPI_SetSignalObject(WPI_Handle handle) { + wpi::SetSignalObject(handle); +} + +void WPI_ResetSignalObject(WPI_Handle handle) { + wpi::ResetSignalObject(handle); +} + +void WPI_DestroySignalObject(WPI_Handle handle) { + wpi::DestroySignalObject(handle); +} + +} // extern "C" diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp index 1cfdb1a507..4fc5f45354 100644 --- a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp +++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp @@ -6,6 +6,7 @@ #include "edu_wpi_first_util_WPIUtilJNI.h" #include "wpi/PortForwarder.h" +#include "wpi/Synchronization.h" #include "wpi/jni_util.h" #include "wpi/timestamp.h" @@ -14,6 +15,8 @@ using namespace wpi::java; static bool mockTimeEnabled = false; static uint64_t mockNow = 0; +static JException interruptedEx; + extern "C" { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { @@ -22,10 +25,21 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { return JNI_ERR; } + interruptedEx = JException(env, "java/lang/InterruptedException"); + if (!interruptedEx) { + return JNI_ERR; + } + return JNI_VERSION_1_6; } -JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {} +JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return; + } + interruptedEx.free(env); +} /* * Class: edu_wpi_first_util_WPIUtilJNI @@ -94,4 +108,169 @@ Java_edu_wpi_first_util_WPIUtilJNI_removePortForwarder wpi::PortForwarder::GetInstance().Remove(port); } +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: createEvent + * Signature: (ZZ)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_createEvent + (JNIEnv*, jclass, jboolean manualReset, jboolean initialState) +{ + return wpi::CreateEvent(manualReset, initialState); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: destroyEvent + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_destroyEvent + (JNIEnv*, jclass, jint eventHandle) +{ + wpi::DestroyEvent(eventHandle); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: setEvent + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_setEvent + (JNIEnv*, jclass, jint eventHandle) +{ + wpi::SetEvent(eventHandle); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: resetEvent + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_resetEvent + (JNIEnv*, jclass, jint eventHandle) +{ + wpi::ResetEvent(eventHandle); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: createSemaphore + * Signature: (II)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_createSemaphore + (JNIEnv*, jclass, jint initialCount, jint maximumCount) +{ + return wpi::CreateSemaphore(initialCount, maximumCount); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: destroySemaphore + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_destroySemaphore + (JNIEnv*, jclass, jint semHandle) +{ + wpi::DestroySemaphore(semHandle); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: releaseSemaphore + * Signature: (II)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_releaseSemaphore + (JNIEnv*, jclass, jint semHandle, jint releaseCount) +{ + return wpi::ReleaseSemaphore(semHandle, releaseCount); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: waitForObject + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_waitForObject + (JNIEnv* env, jclass, jint handle) +{ + if (!wpi::WaitForObject(handle)) { + interruptedEx.Throw(env, "WaitForObject interrupted"); + } +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: waitForObjectTimeout + * Signature: (ID)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectTimeout + (JNIEnv* env, jclass, jint handle, jdouble timeout) +{ + bool timedOut; + if (!wpi::WaitForObject(handle, timeout, &timedOut) && !timedOut) { + interruptedEx.Throw(env, "WaitForObject interrupted"); + return false; + } + return timedOut; +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: waitForObjects + * Signature: ([I)[I + */ +JNIEXPORT jintArray JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_waitForObjects + (JNIEnv* env, jclass, jintArray handles) +{ + JIntArrayRef handlesArr{env, handles}; + wpi::SmallVector signaledBuf; + signaledBuf.resize(handlesArr.size()); + wpi::span handlesArr2{ + reinterpret_cast(handlesArr.array().data()), + handlesArr.size()}; + + auto signaled = wpi::WaitForObjects(handlesArr2, signaledBuf); + if (signaled.empty()) { + interruptedEx.Throw(env, "WaitForObjects interrupted"); + return nullptr; + } + return MakeJIntArray(env, signaled); +} + +/* + * Class: edu_wpi_first_util_WPIUtilJNI + * Method: waitForObjectsTimeout + * Signature: ([ID)[I + */ +JNIEXPORT jintArray JNICALL +Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectsTimeout + (JNIEnv* env, jclass, jintArray handles, jdouble timeout) +{ + JIntArrayRef handlesArr{env, handles}; + wpi::SmallVector signaledBuf; + signaledBuf.resize(handlesArr.size()); + wpi::span handlesArr2{ + reinterpret_cast(handlesArr.array().data()), + handlesArr.size()}; + + bool timedOut; + auto signaled = + wpi::WaitForObjects(handlesArr2, signaledBuf, timeout, &timedOut); + if (signaled.empty() && !timedOut) { + interruptedEx.Throw(env, "WaitForObjects interrupted"); + return nullptr; + } + return MakeJIntArray(env, signaled); +} + } // extern "C" diff --git a/wpiutil/src/main/native/include/wpi/Synchronization.h b/wpiutil/src/main/native/include/wpi/Synchronization.h new file mode 100644 index 0000000000..f93a050b7a --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/Synchronization.h @@ -0,0 +1,611 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include // NOLINT + +#ifdef __cplusplus +#include + +#include "wpi/span.h" +#endif + +/** + * Generic handle for all WPI handle-based interfaces. + * + * Handle data layout: + * - Bits 0-23: Type-specific + * - Bits 24-30: Type + * - Bit 31: Error + */ +typedef unsigned int WPI_Handle; // NOLINT + +/** An event handle. */ +typedef WPI_Handle WPI_EventHandle; // NOLINT + +/** A semaphore handle. */ +typedef WPI_Handle WPI_SemaphoreHandle; // NOLINT + +#ifdef __cplusplus + +namespace wpi { + +/** Constant representing an invalid handle. */ +constexpr unsigned int kInvalidHandle = 0; + +/** + * Standard types for handles. + * @{ + */ +constexpr int kHandleTypeEvent = 1; +constexpr int kHandleTypeSemaphore = 2; +constexpr int kHandleTypeCSBase = 3; +constexpr int kHandleTypeNTBase = 16; +constexpr int kHandleTypeHALBase = 32; +constexpr int kHandleTypeUserBase = 64; +/** @} */ + +/** + * Creates an event. Events have binary state (signaled or not signaled) and + * may be either automatically reset or manually reset. Automatic-reset events + * go to non-signaled state when a WaitForObject is woken up by the event; + * manual-reset events require ResetEvent() to be called to set the event to + * non-signaled state; if ResetEvent() is not called, any waiter on that event + * will immediately wake when called. + * + * @param manualReset true for manual reset, false for automatic reset + * @param initialState true to make the event initially in signaled state + * @return Event handle + */ +WPI_EventHandle CreateEvent(bool manualReset = false, + bool initialState = false); + +/** + * Destroys an event. Destruction wakes up any waiters. + * + * @param handle event handle + */ +void DestroyEvent(WPI_EventHandle handle); + +/** + * Sets an event to signaled state. + * + * @param handle event handle + */ +void SetEvent(WPI_EventHandle handle); + +/** + * Sets an event to non-signaled state. + * + * @param handle event handle + */ +void ResetEvent(WPI_EventHandle handle); + +/** + * Creates a semaphore. Semaphores keep an internal counter. Releasing the + * semaphore increases the count. A semaphore with a non-zero count is + * considered signaled. When a waiter wakes up it atomically decrements the + * count by 1. This is generally useful in a single-supplier, + * multiple-consumer scenario. + * + * @param initialCount initial value for the semaphore's internal counter + * @param maximumCount maximum value for the samephore's internal counter + * @return Semaphore handle + */ +WPI_SemaphoreHandle CreateSemaphore(int initialCount = 0, + int maximumCount = INT_MAX); + +/** + * Destroys a semaphore. Destruction wakes up any waiters. + * + * @param handle semaphore handle + */ +void DestroySemaphore(WPI_SemaphoreHandle handle); + +/** + * Releases N counts of a semaphore. + * + * @param handle semaphore handle + * @param releaseCount amount to add to semaphore's internal counter; + * must be positive + * @param prevCount if non-null, previous count (output parameter) + * @return True on successful release, false on failure (e.g. release count + * would exceed maximum value, or handle invalid) + */ +bool ReleaseSemaphore(WPI_SemaphoreHandle handle, int releaseCount = 1, + int* prevCount = nullptr); + +/** + * Waits for an handle to be signaled. + * + * @param handle handle to wait on + * @return True if handle was signaled, false otherwise (e.g. object was + * destroyed) + */ +bool WaitForObject(WPI_Handle handle); + +/** + * Waits for an handle to be signaled, with timeout. + * + * @param handle handle to wait on + * @param timeout timeout in seconds + * @param timedOut if non-null, set to true if timeout reached without handle + * being signaled; set to false otherwise (output) + * @return True if handle was signaled, false otherwise (e.g. object was + * destroyed or timed out) + */ +bool WaitForObject(WPI_Handle handle, double timeout, bool* timedOut); + +/** + * Waits for one or more handles to be signaled. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @return array of signaled handles (points into signaled array) + */ +wpi::span WaitForObjects(wpi::span handles, + wpi::span signaled); + +/** + * Waits for one or more handles to be signaled. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @return array of signaled handles (points into signaled array) + */ +inline wpi::span WaitForObjects( + std::initializer_list handles, wpi::span signaled) { + return WaitForObjects(wpi::span{handles.begin(), handles.size()}, signaled); +} + +/** + * Waits for one or more handles to be signaled, with timeout. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @param timeout timeout in seconds + * @param timedOut if non-null, set to true if timeout reached without any + * handle being signaled; set to false otherwise (output) + * @return array of signaled handles (points into signaled array) + */ +wpi::span WaitForObjects(wpi::span handles, + wpi::span signaled, + double timeout, bool* timedOut); +/** + * Waits for one or more handles to be signaled, with timeout. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @param timeout timeout in seconds + * @param timedOut if non-null, set to true if timeout reached without any + * handle being signaled; set to false otherwise (output) + * @return array of signaled handles (points into signaled array) + */ +inline wpi::span WaitForObjects( + std::initializer_list handles, wpi::span signaled, + double timeout, bool* timedOut) { + return WaitForObjects(wpi::span{handles.begin(), handles.size()}, signaled, + timeout, timedOut); +} + +/** + * Sets up signaling for an arbitrary handle. With this function, any handle + * can operate like an event handle. + * + * @param handle handle + * @param manualReset true for manual reset, false for automatic reset + * @param initialState true to make the handle initially in signaled state + * @return Event handle + */ +void CreateSignalObject(WPI_Handle handle, bool manualReset = false, + bool initialState = false); + +/** + * Sets a handle to signaled state. + * + * @param handle handle + */ +void SetSignalObject(WPI_Handle handle); + +/** + * Sets a handle to non-signaled state. + * + * @param handle handle + */ +void ResetSignalObject(WPI_Handle handle); + +/** + * Cleans up signaling for a handle. Destruction wakes up any waiters. + * + * @param handle handle + */ +void DestroySignalObject(WPI_Handle handle); + +/** + * An atomic signaling event for synchronization. + * + * Events have binary state (signaled or not signaled) and may be either + * automatically reset or manually reset. Automatic-reset events go to + * non-signaled state when a WaitForObject is woken up by the event; + * manual-reset events require Reset() to be called to set the event to + * non-signaled state; if Reset() is not called, any waiter on that event + * will immediately wake when called. + */ +class Event final { + public: + /** + * Constructor. + * + * @param manualReset true for manual reset, false for automatic reset + * @param initialState true to make the event initially in signaled state + */ + explicit Event(bool manualReset = false, bool initialState = false) + : m_handle{CreateEvent(manualReset, initialState)} {} + ~Event() { + if (m_handle != 0) { + DestroyEvent(m_handle); + } + } + + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + + Event(Event&& rhs) : m_handle{rhs.m_handle} { rhs.m_handle = 0; } + Event& operator=(Event&& rhs) { + if (m_handle != 0) { + DestroyEvent(m_handle); + } + m_handle = rhs.m_handle; + rhs.m_handle = 0; + return *this; + } + + /** + * Gets the event handle (e.g. for WaitForObject). + * + * @return handle + */ + explicit operator WPI_Handle() const { return m_handle; } + + /** + * Gets the event handle (e.g. for WaitForObject). + * + * @return handle + */ + WPI_EventHandle GetHandle() const { return m_handle; } + + /** + * Sets the event to signaled state. + */ + void Set() { SetEvent(m_handle); } + + /** + * Sets the event to non-signaled state. + */ + void Reset() { ResetEvent(m_handle); } + + private: + WPI_EventHandle m_handle; +}; + +/** + * A semaphore for synchronization. + * + * Semaphores keep an internal counter. Releasing the semaphore increases + * the count. A semaphore with a non-zero count is considered signaled. + * When a waiter wakes up it atomically decrements the count by 1. This + * is generally useful in a single-supplier, multiple-consumer scenario. + */ +class Semaphore final { + public: + /** + * Constructor. + * + * @param initialCount initial value for the semaphore's internal counter + * @param maximumCount maximum value for the samephore's internal counter + */ + explicit Semaphore(int initialCount = 0, int maximumCount = INT_MAX) + : m_handle{CreateSemaphore(initialCount, maximumCount)} {} + ~Semaphore() { + if (m_handle != 0) { + DestroySemaphore(m_handle); + } + } + + Semaphore(const Semaphore&) = delete; + Semaphore& operator=(const Semaphore&) = delete; + + Semaphore(Semaphore&& rhs) : m_handle{rhs.m_handle} { rhs.m_handle = 0; } + Semaphore& operator=(Semaphore&& rhs) { + if (m_handle != 0) { + DestroySemaphore(m_handle); + } + m_handle = rhs.m_handle; + rhs.m_handle = 0; + return *this; + } + + /** + * Gets the semaphore handle (e.g. for WaitForObject). + * + * @return handle + */ + explicit operator WPI_Handle() const { return m_handle; } + + /** + * Gets the semaphore handle (e.g. for WaitForObject). + * + * @return handle + */ + WPI_SemaphoreHandle GetHandle() const { return m_handle; } + + /** + * Releases N counts of the semaphore. + * + * @param releaseCount amount to add to semaphore's internal counter; + * must be positive + * @param prevCount if non-null, previous count (output parameter) + * @return True on successful release, false on failure (e.g. release count + * would exceed maximum value, or handle invalid) + */ + bool Release(int releaseCount = 1, int* prevCount = nullptr) { + return ReleaseSemaphore(m_handle, releaseCount, prevCount); + } + + private: + WPI_SemaphoreHandle m_handle; +}; + +/** + * RAII wrapper for signaling handles. + * + * Sets up signaling for an arbitrary handle. This enables any handle + * to operate like an event handle. + */ +template +class SignalObject final { + public: + /** + * Constructor. + * + * @param handle handle + * @param manualReset true for manual reset, false for automatic reset + * @param initialState true to make the handle initially in signaled state + */ + explicit SignalObject(T handle = 0, bool manualReset = false, + bool initialState = false) + : m_handle{handle} { + CreateSignalObject(handle, manualReset, initialState); + } + ~SignalObject() { + if (m_handle != 0) { + DestroySignalObject(m_handle); + } + } + + SignalObject(const SignalObject&) = delete; + SignalObject& operator=(const SignalObject&) = delete; + + SignalObject(SignalObject&& rhs) : m_handle{rhs.m_handle} { + rhs.m_handle = 0; + } + SignalObject& operator=(SignalObject&& rhs) { + if (m_handle != 0) { + DestroySemaphore(m_handle); + } + m_handle = rhs.m_handle; + rhs.m_handle = 0; + return *this; + } + + /** + * Gets the handle. + * + * @return handle + */ + /*implicit*/ operator T() const { return m_handle; } // NOLINT + + /** + * Gets the handle (e.g. for WaitForObject). + * + * @return handle + */ + T GetHandle() const { return m_handle; } + + /** + * Sets the handle to signaled state. + */ + void Set() { SetSignalObject(m_handle); } + + /** + * Sets the handle to non-signaled state. + */ + void Reset() { ResetSignalObject(m_handle); } + + private: + T m_handle; +}; + +} // namespace wpi + +extern "C" { + +#endif // __cplusplus + +/** + * Creates an event. Events have binary state (signaled or not signaled) and + * may be either automatically reset or manually reset. Automatic-reset events + * go to non-signaled state when a WaitForObject is woken up by the event; + * manual-reset events require ResetEvent() to be called to set the event to + * non-signaled state; if ResetEvent() is not called, any waiter on that event + * will immediately wake when called. + * + * @param manual_reset true for manual reset, false for automatic reset + * @param initial_state true to make the event initially in signaled state + * @return Event handle + */ +WPI_EventHandle WPI_CreateEvent(int manual_reset, int initial_state); + +/** + * Destroys an event. Destruction wakes up any waiters. + * + * @param handle event handle + */ +void WPI_DestroyEvent(WPI_EventHandle handle); + +/** + * Sets an event to signaled state. + * + * @param handle event handle + */ +void WPI_SetEvent(WPI_EventHandle handle); + +/** + * Sets an event to non-signaled state. + * + * @param handle event handle + */ +void WPI_ResetEvent(WPI_EventHandle handle); + +/** + * Creates a semaphore. Semaphores keep an internal counter. Releasing the + * semaphore increases the count. A semaphore with a non-zero count is + * considered signaled. When a waiter wakes up it atomically decrements the + * count by 1. This is generally useful in a single-supplier, + * multiple-consumer scenario. + * + * @param initial_count initial value for the semaphore's internal counter + * @param maximum_count maximum value for the samephore's internal counter + * @return Semaphore handle + */ +WPI_SemaphoreHandle WPI_CreateSemaphore(int initial_count, int maximum_count); + +/** + * Destroys a semaphore. Destruction wakes up any waiters. + * + * @param handle semaphore handle + */ +void WPI_DestroySemaphore(WPI_SemaphoreHandle handle); + +/** + * Releases N counts of a semaphore. + * + * @param handle semaphore handle + * @param release_count amount to add to semaphore's internal counter; + * must be positive + * @param prev_count if non-null, previous count (output parameter) + * @return Non-zero on successful release, zero on failure (e.g. release count + * would exceed maximum value, or handle invalid) + */ +int WPI_ReleaseSemaphore(WPI_SemaphoreHandle handle, int release_count, + int* prev_count); + +/** + * Waits for an handle to be signaled. + * + * @param handle handle to wait on + * @return Non-zero if handle was signaled, zero otherwise (e.g. object was + * destroyed) + */ +int WPI_WaitForObject(WPI_Handle handle); + +/** + * Waits for an handle to be signaled, with timeout. + * + * @param handle handle to wait on + * @param timeout timeout in seconds + * @param timed_out if non-null, set to non-zero if timeout reached without + * handle being signaled; set to zero otherwise (output) + * @return Non-zero if handle was signaled, zero otherwise (e.g. object was + * destroyed or timed out) + */ +int WPI_WaitForObjectTimeout(WPI_Handle handle, double timeout, int* timed_out); + +/** + * Waits for one or more handles to be signaled. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param handles_count length of the handles array + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @return number of signaled handles + */ +int WPI_WaitForObjects(const WPI_Handle* handles, int handles_count, + WPI_Handle* signaled); + +/** + * Waits for one or more handles to be signaled, with timeout. + * + * Invalid handles are treated as signaled; the returned array will have the + * handle error bit set for any invalid handles. + * + * @param handles array of handles to wait on + * @param handles_count length of the handles array + * @param signaled output array for storage of signaled handles; must be at + * least the size of the handles input array + * @param timeout timeout in seconds + * @param timed_out if non-null, set to non-zero if timeout reached without any + * handle being signaled; set to zero otherwise (output) + * @return number of signaled handles + */ +int WPI_WaitForObjectsTimeout(const WPI_Handle* handles, int handles_count, + WPI_Handle* signaled, double timeout, + int* timed_out); + +/** + * Sets up signaling for an arbitrary handle. With this function, any handle + * can operate like an event handle. + * + * @param handle handle + * @param manual_reset true for manual reset, false for automatic reset + * @param initial_state true to make the handle initially in signaled state + * @return Event handle + */ +void WPI_CreateSignalObject(WPI_Handle handle, int manual_reset, + int initial_state); + +/** + * Sets a handle to signaled state. + * + * @param handle handle + */ +void WPI_SetSignalObject(WPI_Handle handle); + +/** + * Sets a handle to non-signaled state. + * + * @param handle handle + */ +void WPI_ResetSignalObject(WPI_Handle handle); + +/** + * Cleans up signaling for a handle. Destruction wakes up any waiters. + * + * @param handle handle + */ +void WPI_DestroySignalObject(WPI_Handle handle); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/wpiutil/src/test/native/cpp/SynchronizationTest.cpp b/wpiutil/src/test/native/cpp/SynchronizationTest.cpp new file mode 100644 index 0000000000..a349c549e8 --- /dev/null +++ b/wpiutil/src/test/native/cpp/SynchronizationTest.cpp @@ -0,0 +1,56 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/Synchronization.h" // NOLINT(build/include_order) + +#include + +#include "gtest/gtest.h" + +TEST(EventTest, AutoReset) { + auto event = wpi::CreateEvent(false, false); + std::thread thr([&] { wpi::SetEvent(event); }); + wpi::WaitForObject(event); + thr.join(); + bool timedOut; + wpi::WaitForObject(event, 0, &timedOut); + ASSERT_EQ(timedOut, true); +} + +TEST(EventTest, ManualReset) { + auto event = wpi::CreateEvent(true, false); + int done = 0; + std::thread thr([&] { + wpi::SetEvent(event); + ++done; + }); + wpi::WaitForObject(event); + thr.join(); + ASSERT_EQ(done, 1); + bool timedOut; + wpi::WaitForObject(event, 0, &timedOut); + ASSERT_EQ(timedOut, false); +} + +TEST(EventTest, InitialSet) { + auto event = wpi::CreateEvent(false, true); + bool timedOut; + wpi::WaitForObject(event, 0, &timedOut); + ASSERT_EQ(timedOut, false); +} + +TEST(EventTest, WaitMultiple) { + auto event1 = wpi::CreateEvent(false, false); + auto event2 = wpi::CreateEvent(false, false); + std::thread thr([&] { wpi::SetEvent(event2); }); + WPI_Handle signaled[2]; + auto result1 = wpi::WaitForObjects({event1, event2}, signaled); + thr.join(); + ASSERT_EQ(result1.size(), 1u); + ASSERT_EQ(result1[0], event2); + bool timedOut; + auto result2 = wpi::WaitForObjects({event1, event2}, signaled, 0, &timedOut); + ASSERT_EQ(timedOut, true); + ASSERT_EQ(result2.size(), 0u); +}