@FunctionalInterface
public interface Executable {
/**
* Perform an execution at a given moment within the robot match.
*
* @param timeInMillis the time in the match (in milliseconds) when this execution is being called
*/
public void execute(long timeInMillis);
}
Executor
Strongback’s executor framework simplifies writing asynchronous code that runs efficiently on the RoboRIO. For the most part, the executor is used by Strongback itself, and is not something most users will need. Regardless, if your robot code has a component that has to run reliably in the background, consider running it with Strongback’s executor rather than creating a new thread.
Concepts
The executor framework involves two basic concepts. An executable component is anything that needs to be run periodically, and the executor is the thing that tracks, schedules, and runs executables at the appropriate times.
To create your own executable component, simply implement the org.strongback.Executable
interface:
Unlike Java’s standard java.lang.Runnable
or java.util.concurrent.Callable
interfaces that are designed to be run just one time, Strongback’s Executable
is designed for its execute(…)
method to be called multiple times. The execute(…)
method takes a single argument that tells the implementation the instant in time that the method is being called that some Executable
implementations may find very useful. Of course, feel free to ignore the time if you don’t need it.
In order to run your Executable
implementation, you have to create an instance and register it with Strongback’s global executor:
Executable myComponent = ...
Strongback.executor().register(myComponent);
Strongback will automatically stop your component from running when it is shutdown, but you can always explicitly unregister your component before then:
Strongback.executor().unregister(myComponent);
Why use it?
Strongback’s single executor is designed to run as reliably and regularly as possible on the JVM. This is always challenging on the JVM where garbage collection can sometimes stop the world. But the relationship between Java threads and processor cores is intentionally very loosely defined, allowing the operating system and JVM to decide when to give each thread some processing time. Ignoring other things that the operating system might be doing, if we have as many or fewer threads than cores then all of those threads can potentially run all of the time. However, if our JVM runs more threads than cores, then we’re guaranteed that some of the threads will be preempted at least some of the time.
Strongback attempts to minimize the number of threads to maximize the likelihood that all threads will always be running. We know the robot’s "main" thread is used to periodically call the autonomous, teleoperated methods of the IterativeRobot
class. So Strongback adds only one more thread that does work every n milliseconds with very little variation, and it uses a number of techniques to try to prevent preemption or thread context switches.
If we only allow ourselves 2 threads, and one of those is the "main" thread, then we have to do all of our asynchronous work on that single thread. This is why Strongback’s command framework, data and event recorders, and switch reactor all use the executor framework.
Gotchas
There are a few things to be aware of when implementing and registering your own Executable
instances.
First, by default Strongback’s executor will run on a 20 millisecond (50 Hertz) rate, which means that all of the Executable
instances must complete within 20 milliseconds or the executor will fall behind. Strongback’s executor thread monitors the JVM time, so even though it might fall behind for one or a few cycles, as long as subsequent cycles are shorter than 20 milliseconds the thread will quickly catch up and get back on its schedule. Of course, you can configure the execution period to be something larger or smaller than 20 milliseconds, so be sure that you adjust it if you find that the executor falls behind on your robot and you see error messages in Strongback’s log that look like this:
ERROR: Unable to execute all activities within 20 milliseconds!
Secondly, try to run as few things asynchronously as possible. If you’re not using the data recorder or event recorder features, be sure to configure Strongback to not record them. Only register those Executable
implementations that you really need.