Files
allwpilib/wpilibj/src/main/java/org/wpilib/system/Notifier.java

233 lines
7.3 KiB
Java
Raw Normal View History

// 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.
2025-11-07 19:55:43 -05:00
package org.wpilib.system;
2025-11-07 19:55:43 -05:00
import static org.wpilib.units.Units.Seconds;
import static org.wpilib.util.ErrorMessages.requireNonNullParam;
2025-11-07 19:57:21 -05:00
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import org.wpilib.driverstation.DriverStation;
2025-11-07 19:55:43 -05:00
import org.wpilib.hardware.hal.NotifierJNI;
import org.wpilib.units.measure.Frequency;
import org.wpilib.units.measure.Time;
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
import org.wpilib.util.WPIUtilJNI;
2019-07-15 21:20:11 -04:00
/**
* Notifiers run a user-provided callback function on a separate thread.
*
* <p>If startSingle() is used, the callback will run once. If startPeriodic() is used, the callback
* will run repeatedly with the given period until stop() is called.
*/
public class Notifier implements AutoCloseable {
// The thread waiting on the HAL alarm.
private Thread m_thread;
// The lock held while updating process information.
private final ReentrantLock m_processLock = new ReentrantLock();
// HAL handle passed to the JNI bindings (atomic for proper destruction).
private final AtomicInteger m_notifier = new AtomicInteger();
// The user-provided callback.
private Runnable m_callback;
@Override
public void close() {
int handle = m_notifier.getAndSet(0);
if (handle == 0) {
return;
}
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
NotifierJNI.destroyNotifier(handle);
// Join the thread to ensure the callback has exited.
if (m_thread.isAlive()) {
try {
m_thread.interrupt();
m_thread.join();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
m_thread = null;
}
/**
* Create a Notifier with the given callback.
*
* <p>Configure when the callback runs with startSingle() or startPeriodic().
*
* @param callback The callback to run.
*/
public Notifier(Runnable callback) {
requireNonNullParam(callback, "callback", "Notifier");
2019-07-15 21:20:11 -04:00
m_callback = callback;
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
m_notifier.set(NotifierJNI.createNotifier());
m_thread =
new Thread(
() -> {
while (!Thread.interrupted()) {
int notifier = m_notifier.get();
if (notifier == 0) {
break;
}
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
try {
WPIUtilJNI.waitForObject(notifier);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
Runnable threadHandler;
m_processLock.lock();
try {
threadHandler = m_callback;
} finally {
m_processLock.unlock();
}
// Call callback
if (threadHandler != null) {
threadHandler.run();
}
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
// Acknowledge the alarm
NotifierJNI.acknowledgeNotifierAlarm(notifier, false, 0, 0, false);
}
});
m_thread.setName("Notifier");
m_thread.setDaemon(true);
m_thread.setUncaughtExceptionHandler(
(thread, error) -> {
Throwable cause = error.getCause();
if (cause != null) {
error = cause;
}
DriverStation.reportError(
"Unhandled exception in Notifier thread: " + error, error.getStackTrace());
DriverStation.reportError(
"The Runnable for this Notifier (or methods called by it) should have handled "
+ "the exception above.\n"
+ " The above stacktrace can help determine where the error occurred.\n"
+ " See https://wpilib.org/stacktrace for more information.",
false);
});
m_thread.start();
}
2019-11-09 11:41:58 -08:00
/**
* Sets the name of the notifier. Used for debugging purposes only.
2019-11-09 11:41:58 -08:00
*
* @param name Name
*/
public void setName(String name) {
m_thread.setName(name);
NotifierJNI.setNotifierName(m_notifier.get(), name);
}
/**
* Change the callback function.
*
* @param callback The callback function.
*/
public void setCallback(Runnable callback) {
m_processLock.lock();
try {
m_callback = callback;
} finally {
m_processLock.unlock();
}
}
/**
* Run the callback once after the given delay.
*
* @param delay Time in seconds to wait before the callback is called.
*/
public void startSingle(double delay) {
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
NotifierJNI.setNotifierAlarm(m_notifier.get(), (long) (delay * 1e6), 0, false);
}
/**
* Run the callback once after the given delay.
*
* @param delay Time to wait before the callback is called.
*/
public void startSingle(Time delay) {
startSingle(delay.in(Seconds));
}
/**
* Run the callback periodically with the given period.
*
* <p>The user-provided callback should be written so that it completes before the next time it's
* scheduled to run.
*
2025-06-30 20:21:11 -07:00
* @param period Period in seconds after which to call the callback starting one period after the
* call to this method.
*/
public void startPeriodic(double period) {
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
long periodMicroS = (long) (period * 1e6);
NotifierJNI.setNotifierAlarm(m_notifier.get(), periodMicroS, periodMicroS, false);
}
/**
* Run the callback periodically with the given period.
*
* <p>The user-provided callback should be written so that it completes before the next time it's
* scheduled to run.
*
* @param period Period after which to call the callback starting one period after the call to
* this method.
*/
public void startPeriodic(Time period) {
startPeriodic(period.in(Seconds));
}
/**
* Run the callback periodically with the given frequency.
*
* <p>The user-provided callback should be written so that it completes before the next time it's
* scheduled to run.
*
* @param frequency Frequency at which to call the callback, starting one period after the call to
* this method.
*/
public void startPeriodic(Frequency frequency) {
startPeriodic(frequency.asPeriod());
}
/**
* Stop further callback invocations.
*
* <p>No further periodic callbacks will occur. Single invocations will also be cancelled if they
* haven't yet occurred.
*
* <p>If a callback invocation is in progress, this function will block until the callback is
* complete.
*/
public void stop() {
[hal] Revamp notifiers (#8424) This changes the HAL notifier interface to: - Use wpiutil signal objects. This means waiting is done through the `WPI_WaitObject` API instead of a dedicated function and allows for higher level code to simultaneously wait on notifiers and other events. - Interval timers are supported at the HAL layer - Handlers are now required to acknowledge notifications. This is invisible to users unless they're directly using the HAL API. - For interval timers, an overrun count is maintained to detect if the handler didn't acknowledge The underlying implementation still uses condition variables for the actual waiting. In basic testing using this approach seemed to be lower jitter than timerfd. Currently, the simulation and systemcore implementations are nearly identical except for a few additional sim hook bits. This could be refactored, but keeping them separate may make sense to keep the systemcore implementation easy to read and reason about, or if we ever choose to use a different underlying timer implementation on systemcore. The simulation side API is unchanged in form but does change in function--waiting for notifiers now only waits for currently running (or newly signaled) notifiers to acknowledge. To avoid a race condition in sim stepTiming, users of the low level API must make any alarm updates (especially for one-shot alarms) prior to acknowledging the previous alarm. The only current use of the interval timer feature is the `Notifier` class. The `TimedRobot` implementation still uses a single notifier and its own interval timing logic to ensure consistent callback order. Using separate notifiers for each user-level interval would substantially increase complexity. `Watchdog` also doesn't use the interval timer, as it's looking for an amount of time since the last `set` call rather than a recurring interval time. To reduce flicker, the sim GUI uses a fade out when a timeout goes from set to unset. This fixes tsan for wpilib and commands, and also fixes some spurious test failures.
2025-11-29 11:00:18 -08:00
NotifierJNI.cancelNotifierAlarm(m_notifier.get());
}
/**
* Sets the HAL notifier thread priority.
*
* <p>The HAL notifier thread is responsible for managing the FPGA's notifier interrupt and waking
* up user's Notifiers when it's their time to run. Giving the HAL notifier thread real-time
* priority helps ensure the user's real-time Notifiers, if any, are notified to run in a timely
* manner.
*
* @param realTime Set to true to set a real-time priority, false for standard priority.
* @param priority Priority to set the thread to. For real-time, this is 1-99 with 99 being
* highest. For non-real-time, this is forced to 0. See "man 7 sched" for more details.
* @return True on success.
*/
public static boolean setHALThreadPriority(boolean realTime, int priority) {
return NotifierJNI.setHALThreadPriority(realTime, priority);
}
}