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);
+}