From 87e34967ef32b8a38f2cd6b7090d2c793a5e131a Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Tue, 3 Aug 2021 00:05:47 -0700 Subject: [PATCH] [wpiutil] Add synchronization primitives These enable more consistent use of synchronization across the native libraries. Users can create Event and Semaphore primitives, but in addition, libraries can set up any handle as an Event-type signal. --- .../java/edu/wpi/first/util/WPIUtilJNI.java | 59 ++ .../edu/wpi/first/util/concurrent/Event.java | 70 ++ .../wpi/first/util/concurrent/Semaphore.java | 80 +++ .../src/main/native/cpp/Synchronization.cpp | 368 +++++++++++ .../src/main/native/cpp/jni/WPIUtilJNI.cpp | 181 +++++- .../main/native/include/wpi/Synchronization.h | 611 ++++++++++++++++++ .../test/native/cpp/SynchronizationTest.cpp | 56 ++ 7 files changed, 1424 insertions(+), 1 deletion(-) create mode 100644 wpiutil/src/main/java/edu/wpi/first/util/concurrent/Event.java create mode 100644 wpiutil/src/main/java/edu/wpi/first/util/concurrent/Semaphore.java create mode 100644 wpiutil/src/main/native/cpp/Synchronization.cpp create mode 100644 wpiutil/src/main/native/include/wpi/Synchronization.h create mode 100644 wpiutil/src/test/native/cpp/SynchronizationTest.cpp 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); +}