Skip to content

Commit 1d93649

Browse files
committed
Issue 84 - Simplify engine and executor
This change rewrites how the `Strongback.java` class is implemented to correct a number of errors and keep the engine as simple as possible. All of the existing public methods are still available, though quite a few have been deprecated and now may no longer do anything. For example, the different timer modes and clocks only complicated how to configure Strongback, yet really didn’t offer any advantages and may have made the code worse by making the execution period less consistent. In short, robot code can continue to use these deprecated methods, but the methods may have no effect and could be removed from the robot’s codebase. Note that the deprecated methods will be removed in the next major release of Strongback for 2017. This change also corrects and simplifies how the executor thread operates. Now, Strongback measures the time required to run all executables (e.g., the switch reactor, command scheduler, data recorder, event recorder, etc.), and uses this time to determine if the executor did not complete in the specified amount of time. This should improve the accuracy of the error messages about taking too long to execute each cycle. And Strongback only uses the “busy” timer mode and the system clock, which simplified the executor implementation. Finally, Strongback now supports priorities for its executables: * High priority executables will run every cycle of the executor; * Medium priority executables will run every other cycle of the executor; and * Low priority executables will run every 4th cycle of the executor. Currently, the command scheduler (through with all commands are run) will operate at HIGH priority, while the data recorder (if used) and the switch reactor will operate at the MEDIUM priority. The event recorder still records the time at which events occurred, but events are written out at LOW priority.
1 parent 1f09032 commit 1d93649

11 files changed

Lines changed: 1209 additions & 380 deletions

File tree

build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
strongback.version=1.1.7
1+
strongback.version=1.2.0
22
#
33
# The build will download a specific version of the WPILib given by the following URL
44
# and install it into the 'libs/wpilib' folder. To use a different version of WPILib,

build.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@
8888

8989
<doctitle><![CDATA[<h1>Strongback Java Library</h1>]]></doctitle>
9090
<bottom><![CDATA[<i>Copyright &#169; ${current.year} Strongback and individual contributors. All Rights Reserved.</i>]]></bottom>
91-
<tag name="todo" scope="all" description="To do:"/>
9291
<group title="Strongback" packages="org.strongback"/>
9392
<group title="Components" packages="org.strongback.components*:org.strongback.hardware*"/>
9493
<group title="Drives" packages="org.strongback.drive"/>

strongback-tests/src/org/strongback/ExecutableTimerTest.java

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,15 @@
2020

2121
import java.util.concurrent.TimeUnit;
2222

23-
import junit.framework.AssertionFailedError;
24-
2523
import org.junit.AfterClass;
2624
import org.junit.BeforeClass;
2725
import org.junit.Test;
2826
import org.strongback.Logger.Level;
29-
import org.strongback.Strongback.Configurator.TimerMode;
27+
28+
import junit.framework.AssertionFailedError;
3029

3130
/**
3231
* This test attempts to measure the timing of the {@link Strongback#executor() Strongback Executor} instance at various periods
33-
* using the system clock and various {@link TimerMode}s. On OS X 10.10.3, the {@link TimerMode#BUSY} is clearly the most
34-
* accurate down to 1 millisecond, whereas {@link TimerMode#PARK} and {@link TimerMode#SLEEP} are relatively inaccurate and
35-
* imprecise for intervals even as high as 20ms.
3632
* <p>
3733
* The code committed into Git are small execution rates and sample sizes to minimize the time required to run the tests. As
3834
* such, the results are probably not terribly meaningful. To obtain more meaningful results on your own platform, simply adjust
@@ -44,28 +40,22 @@ public class ExecutableTimerTest {
4440

4541
@BeforeClass
4642
public static void beforeAll() {
47-
Strongback.configure().useSystemLogger(Level.ERROR).recordNoData().recordNoEvents().initialize();
48-
Strongback.start();
43+
Strongback.configure().setLogLevel(Level.ERROR).recordNoData().recordNoEvents().initialize();
4944
}
5045

5146
@AfterClass
5247
public static void afterAll() {
53-
Strongback.shutdown();
54-
}
55-
56-
@Test
57-
public void shouldMeasureAndPrintTimingHistogramUsingBusyMode() throws InterruptedException {
58-
runTimer(TimerMode.BUSY, 4, 2000);
48+
Strongback.stop();
5949
}
6050

6151
@Test
62-
public void shouldMeasureAndPrintTimingHistogramUsingParkMode() throws InterruptedException {
63-
runTimer(TimerMode.PARK, 5, 2000);
52+
public void shouldMeasureAndPrintTimingHistogramFor4MillisecondPeriod() throws InterruptedException {
53+
runTimer(4, 2000);
6454
}
6555

6656
@Test
67-
public void shouldMeasureAndPrintTimingHistogramUsingSleepMode() throws InterruptedException {
68-
runTimer(TimerMode.SLEEP, 6, 2000);
57+
public void shouldMeasureAndPrintTimingHistogramFor10MillisecondPeriod() throws InterruptedException {
58+
runTimer(10, 2000);
6959
}
7060

7161
/**
@@ -75,14 +65,14 @@ public void shouldMeasureAndPrintTimingHistogramUsingSleepMode() throws Interrup
7565
* @param millisecondExecutionPeriod the execution period in milliseconds
7666
* @param sampleTimeInMilliseconds the sample time in milliseconds
7767
*/
78-
protected void runTimer(TimerMode mode, int millisecondExecutionPeriod, int sampleTimeInMilliseconds) {
68+
protected void runTimer(int millisecondExecutionPeriod, int sampleTimeInMilliseconds) {
7969
try {
70+
Strongback.stop();
8071
Strongback.configure()
81-
.useExecutionTimerMode(mode)
82-
.useExecutionPeriod(millisecondExecutionPeriod, TimeUnit.MILLISECONDS)
83-
.initialize();
72+
.useExecutionPeriod(millisecondExecutionPeriod, TimeUnit.MILLISECONDS);
73+
Strongback.start();
8474
assertThat(ExecutableTimer.measureTimingAndPrint(Strongback.executor(),
85-
mode.toString() + " for " + millisecondExecutionPeriod + " ms",
75+
" for " + millisecondExecutionPeriod + " ms",
8676
sampleTimeInMilliseconds / millisecondExecutionPeriod)
8777
.await(10, TimeUnit.SECONDS)
8878
.isComplete());
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Strongback
3+
* Copyright 2015, Strongback and individual contributors by the @authors tag.
4+
* See the COPYRIGHT.txt in the distribution for a full listing of individual
5+
* contributors.
6+
*
7+
* Licensed under the MIT License; you may not use this file except in
8+
* compliance with the License. You may obtain a copy of the License at
9+
* http://opensource.org/licenses/MIT
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.strongback;
18+
19+
import static org.fest.assertions.Assertions.assertThat;
20+
21+
import org.junit.After;
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
import org.strongback.Logger.Level;
25+
import org.strongback.components.Clock;
26+
27+
/**
28+
* Tests that check the functionality of the {@link Strongback.Engine}.
29+
*/
30+
public class StrongbackTest {
31+
32+
private static final SystemLogger LOGGER = new SystemLogger();
33+
34+
private Clock clock;
35+
private Strongback.Engine engine;
36+
37+
@Before
38+
public void beforeEach() {
39+
LOGGER.enable(Level.INFO);
40+
clock = Clock.system();
41+
engine = new Strongback.Engine(clock, LOGGER);
42+
}
43+
44+
@After
45+
public void afterEach() {
46+
try {
47+
if (engine != null && engine.isRunning()) {
48+
engine.stop();
49+
}
50+
} finally {
51+
engine = null;
52+
}
53+
}
54+
55+
@Test
56+
public void shouldNotBeRunningWhenCreated() {
57+
assertThat(engine.isRunning()).isFalse();
58+
}
59+
60+
@Test
61+
public void shouldStartWithDefaultConfiguration() {
62+
engine.logConfiguration();
63+
assertThat(engine.isRunning()).isFalse();
64+
assertThat(engine.start()).isTrue();
65+
assertThat(engine.isRunning()).isTrue();
66+
}
67+
68+
@Test
69+
public void shouldAllowChangingExecutionPeriodWhenNotRunning() {
70+
assertThat(engine.isRunning()).isFalse();
71+
assertThat(engine.getExecutionPeriod()).isEqualTo(20);
72+
engine.setExecutionPeriod(5);
73+
assertThat(engine.getExecutionPeriod()).isEqualTo(5);
74+
assertThat(engine.start()).isTrue();
75+
engine.logConfiguration();
76+
}
77+
78+
@Test
79+
public void shouldNotAllowChangingExecutionPeriodWhenRunning() {
80+
assertThat(engine.isRunning()).isFalse();
81+
assertThat(engine.start()).isTrue();
82+
assertThat(engine.getExecutionPeriod()).isEqualTo(20);
83+
LOGGER.enable(Level.OFF);
84+
assertThat(engine.setExecutionPeriod(5)).isFalse();
85+
LOGGER.enable(Level.INFO);
86+
assertThat(engine.getExecutionPeriod()).isEqualTo(20);
87+
}
88+
89+
}

strongback/src/org/strongback/Executables.java

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,41 +16,89 @@
1616

1717
package org.strongback;
1818

19-
import java.util.Iterator;
19+
import java.util.List;
2020
import java.util.concurrent.CopyOnWriteArrayList;
2121

2222
import org.strongback.annotation.ThreadSafe;
2323

2424
/**
2525
* A simple threadsafe list of {@link Executable} instances.
26+
*
2627
* @author Randall Hauch
2728
*/
2829
@ThreadSafe
29-
final class Executables implements Executor, Iterable<Executable> {
30+
final class Executables implements Executor {
3031

31-
private final CopyOnWriteArrayList<Executable> executables = new CopyOnWriteArrayList<>();
32+
private final CopyOnWriteArrayList<Executable> highPriority = new CopyOnWriteArrayList<>();
33+
private final CopyOnWriteArrayList<Executable> mediumPriority = new CopyOnWriteArrayList<>();
34+
private final CopyOnWriteArrayList<Executable> lowPriority = new CopyOnWriteArrayList<>();
3235

3336
Executables() {
3437
}
3538

3639
@Override
37-
public boolean register(Executable r) {
38-
return executables.addIfAbsent(r);
40+
public boolean register(Executable r, Priority priority) {
41+
if (priority != null) {
42+
unregister(r);
43+
switch (priority) {
44+
case HIGH:
45+
mediumPriority.remove(r);
46+
lowPriority.remove(r);
47+
return highPriority.addIfAbsent(r);
48+
case MEDIUM:
49+
highPriority.remove(r);
50+
lowPriority.remove(r);
51+
return mediumPriority.addIfAbsent(r);
52+
case LOW:
53+
highPriority.remove(r);
54+
mediumPriority.remove(r);
55+
return lowPriority.addIfAbsent(r);
56+
}
57+
}
58+
return false;
3959
}
4060

4161
@Override
4262
public boolean unregister(Executable r) {
43-
return executables.remove(r);
63+
if (r != null) {
64+
// Remove from all
65+
boolean removed = highPriority.remove(r);
66+
removed = mediumPriority.remove(r) || removed;
67+
removed = lowPriority.remove(r) || removed;
68+
return removed;
69+
}
70+
return false;
4471
}
4572

4673
@Override
4774
public void unregisterAll() {
48-
executables.clear();
75+
highPriority.clear();
76+
mediumPriority.clear();
77+
lowPriority.clear();
4978
}
5079

51-
@Override
52-
public Iterator<Executable> iterator() {
53-
return executables.iterator();
80+
public List<Executable> lowPriorityExecutables() {
81+
return lowPriority;
82+
}
83+
84+
public List<Executable> mediumPriorityExecutables() {
85+
return mediumPriority;
86+
}
87+
88+
public List<Executable> highPriorityExecutables() {
89+
return highPriority;
90+
}
91+
92+
protected Executable[] lowPriorityExecutablesAsArrays() {
93+
return lowPriority.toArray(new Executable[0]); // will be reallocated with correct size
94+
}
95+
96+
protected Executable[] mediumPriorityExecutablesAsArrays() {
97+
return mediumPriority.toArray(new Executable[0]); // will be reallocated with correct size
98+
}
99+
100+
protected Executable[] highPriorityExecutablesAsArrays() {
101+
return highPriority.toArray(new Executable[0]); // will be reallocated with correct size
54102
}
55103

56104
}

strongback/src/org/strongback/Executor.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,45 @@
2424
@ThreadSafe
2525
public interface Executor {
2626

27+
public static enum Priority {
28+
HIGH, MEDIUM, LOW;
29+
}
30+
31+
/**
32+
* Register a high priority {@link Executable} task so that it is called repeatedly on Strongback's executor thread.
33+
*
34+
* @param task the executable task
35+
* @return {@code true} if the executable task was registered for the first time as a high priority, or {@code false} if
36+
* {@code task} was null or was already registered with this executor at high priority
37+
* @deprecated Use {@link #register(Executable, Priority)} instead
38+
*/
39+
@Deprecated
40+
default boolean register(Executable task) {
41+
return register(task, Priority.HIGH);
42+
}
43+
2744
/**
28-
* Register an {@link Executable} task to be called repeatedly.
45+
* Register an {@link Executable} task with the given priority so that it is called repeatedly on Strongback's executor
46+
* thread. If the given task is already registered with a different priority, this method reassigns it to the desired
47+
* priority; if the given task is already registered with the desired priority, this method does nothing.
48+
* <p>
49+
* This executor runs high priority tasks every cycle, medium priority tasks slightly every other cycles, and low priority
50+
* tasks every 4 cycles. All {@link Executable} tasks are called on the first cycle.
2951
*
30-
* @param r the executable task
31-
* @return true if the executable task was registered, or false if it was null or was already registered with this executor
52+
* @param task the executable task
53+
* @param priority the priority of the executable; may not be null
54+
* @return {@code true} if the executable task was registered for the first time at the given priority, or {@code false} if
55+
* {@code task} was null or was already registered with this executor at the given priority
3256
*/
33-
public boolean register(Executable r);
57+
public boolean register(Executable task, Priority priority);
3458

3559
/**
3660
* Unregister an {@link Executable} task to no longer be called.
3761
*
38-
* @param r the executable task
62+
* @param task the executable task
3963
* @return true if the executable task was unregistered, or false if it was null or not registered with this executor
4064
*/
41-
public boolean unregister(Executable r);
65+
public boolean unregister(Executable task);
4266

4367
/**
4468
* Unregister all {@link Executable} tasks.

0 commit comments

Comments
 (0)