Skip to content

Commit 178781d

Browse files
Add AutoEjectFIFO service
1 parent 0ecdc68 commit 178781d

File tree

4 files changed

+284
-7
lines changed

4 files changed

+284
-7
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package org.myrobotlab.service;
2+
3+
import org.myrobotlab.framework.Service;
4+
import org.myrobotlab.service.config.ServiceConfig;
5+
6+
import java.util.List;
7+
import java.util.concurrent.BlockingDeque;
8+
import java.util.concurrent.LinkedBlockingDeque;
9+
import java.util.concurrent.locks.ReadWriteLock;
10+
import java.util.concurrent.locks.ReentrantReadWriteLock;
11+
12+
/**
13+
* A simple service that acts as a circular FIFO queue.
14+
* This queue can store a number of items, but once
15+
* its max capacity is reached, any attempt to add more
16+
* items ejects the oldest element, i.e. the head.
17+
* <p></p>
18+
* This queue is not typed, i.e. it can store any type
19+
* of object, with the downside that no type checking is
20+
* performed. This is to allow the fifo to be used
21+
* in any situation, since we don't currently have a way
22+
* to create generic services.
23+
*
24+
* @author AutonomicPerfectionist
25+
*/
26+
public class AutoEjectFIFO extends Service<ServiceConfig> {
27+
public static final int DEFAULT_MAX_SIZE = 50;
28+
29+
30+
/**
31+
* Lock used to protect the fifo queue,
32+
* used instead of synchronized block to allow
33+
* multiple simultaneous readers so long as there
34+
* is no writer writing to the queue.
35+
*/
36+
private final ReadWriteLock lock = new ReentrantReadWriteLock();
37+
38+
/**
39+
* The actual queue, whose initial maximum size is set to
40+
* {@link #DEFAULT_MAX_SIZE}.
41+
*/
42+
private BlockingDeque<Object> fifo = new LinkedBlockingDeque<>(DEFAULT_MAX_SIZE);
43+
44+
45+
/**
46+
* Constructor of service, reservedkey typically is a services name and inId
47+
* will be its process id
48+
*
49+
* @param reservedKey the service name
50+
* @param inId process id
51+
*/
52+
public AutoEjectFIFO(String reservedKey, String inId) {
53+
super(reservedKey, inId);
54+
}
55+
56+
57+
/**
58+
* Sets the size at which the FIFO will begin evicting
59+
* elements. If smaller than the current number of items,
60+
* then elements will be silently evicted.
61+
* @param size The new max size
62+
*/
63+
public void setMaxSize(int size) {
64+
lock.writeLock().lock();
65+
BlockingDeque<Object> newFifo = new LinkedBlockingDeque<>(size);
66+
newFifo.addAll(fifo);
67+
fifo = newFifo;
68+
lock.writeLock().unlock();
69+
}
70+
71+
/**
72+
* Add a new element to the FIFO, if
73+
* it's full then this will trigger an
74+
* eviction
75+
* @param item The new item to be added to the tail
76+
*/
77+
public void add(Object item) {
78+
lock.writeLock().lock();
79+
try {
80+
if (!fifo.offer(item)) {
81+
Object head = fifo.removeFirst();
82+
invoke("publishEviction", head);
83+
fifo.add(item);
84+
}
85+
invoke("publishItemAdded", item);
86+
} catch (Exception e) {
87+
error(e);
88+
} finally {
89+
lock.writeLock().unlock();
90+
}
91+
92+
93+
}
94+
95+
public void clear() {
96+
lock.writeLock().lock();
97+
fifo.clear();
98+
lock.writeLock().unlock();
99+
invoke("publishClear");
100+
}
101+
102+
public List<Object> getAll() {
103+
lock.readLock().lock();
104+
List<Object> ret = List.copyOf(fifo);
105+
lock.readLock().unlock();
106+
invoke("publishAll", ret);
107+
108+
return ret;
109+
}
110+
111+
public Object getHead() {
112+
lock.readLock().lock();
113+
Object head = fifo.peek();
114+
lock.readLock().unlock();
115+
invoke("publishHead", head);
116+
return head;
117+
118+
}
119+
120+
public Object getTail() {
121+
lock.readLock().lock();
122+
Object tail = fifo.peekLast();
123+
lock.readLock().unlock();
124+
invoke("publishTail", tail);
125+
return tail;
126+
}
127+
128+
public Object publishItemAdded(Object item) {
129+
return item;
130+
}
131+
132+
public void publishClear() {
133+
// Do nothing
134+
}
135+
136+
public List<Object> publishAll(List<Object> items) {
137+
return items;
138+
}
139+
140+
public Object publishHead(Object head) {
141+
return head;
142+
}
143+
144+
public Object publishTail(Object tail) {
145+
return tail;
146+
}
147+
148+
public Object publishEviction(Object evicted) {
149+
return evicted;
150+
}
151+
152+
153+
154+
}

src/main/java/org/myrobotlab/service/TestCatcher.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public Ball() {
108108

109109
public BlockingQueue<String> strings = new LinkedBlockingDeque<>();
110110

111+
public BlockingQueue<Object> objects = new LinkedBlockingQueue<>();
112+
111113
/**
112114
* awesome override to simulate remote services - e.g. in
113115
* Serial.addByteListener
@@ -168,6 +170,9 @@ public void clear() {
168170
pinSet.clear();
169171
methodsCalled.clear();
170172
longs.clear();
173+
integers.clear();
174+
strings.clear();
175+
objects.clear();
171176
}
172177

173178
public Message getMsg(long timeout) throws InterruptedException {
@@ -264,21 +269,25 @@ public void checkMsg(long timeout, String method, Object... checkParms) throws I
264269
throw new IOException(String.format("expected null parameters - got non-null"));
265270
}
266271

272+
// Never reached since msg.data.length is accessed above and would throw NPE
273+
// Probably don't need this if we can assume that msg.data is always non-null
274+
// and may just be empty
267275
if (checkParms != null && msg.data == null) {
268276
log.error("{}", msg);
269-
throw new IOException(String.format("expected non null parameters - got null"));
277+
throw new IOException("expected non null parameters - got null");
270278
}
271279

272280
if (!method.equals(msg.method)) {
273281
log.error("{}", msg);
274282
throw new IOException(String.format("unlike methods - expected %s got %s", method, msg.method));
275283
}
276-
277-
for (int i = 0; i < checkParms.length; ++i) {
278-
Object expected = checkParms[i];
279-
Object got = msg.data[i];
280-
if (!expected.equals(got)) {
281-
throw new IOException(String.format("unlike methods - expected %s got %s", method, msg.method));
284+
if (checkParms != null) {
285+
for (int i = 0; i < checkParms.length; ++i) {
286+
Object expected = checkParms[i];
287+
Object got = msg.data[i];
288+
if (!expected.equals(got)) {
289+
throw new IOException(String.format("unlike methods - expected %s got %s", method, msg.method));
290+
}
282291
}
283292
}
284293

@@ -331,6 +340,12 @@ public double onDouble(double data) {
331340
return data;
332341
}
333342

343+
public Object onObject(Object data) {
344+
log.info("onObject {}", data);
345+
objects.add(data);
346+
return data;
347+
}
348+
334349
public int waitForThis(int data, long sleep) {
335350
sleep(sleep);
336351
log.info("waitForThis {}", data);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.myrobotlab.service.meta;
2+
3+
import org.myrobotlab.service.meta.abstracts.MetaData;
4+
5+
public class AutoEjectFIFOMeta extends MetaData {
6+
public AutoEjectFIFOMeta() {
7+
addDescription("A simple sized FIFO that will auto-eject the oldest element when it reaches the given max size.");
8+
}
9+
10+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.myrobotlab.service;
2+
3+
import org.junit.After;
4+
import org.junit.Before;
5+
import org.junit.Test;
6+
import org.myrobotlab.test.AbstractTest;
7+
8+
import java.io.IOException;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
import static org.junit.Assert.assertArrayEquals;
13+
import static org.junit.Assert.assertEquals;
14+
15+
public class AutoEjectFIFOTest extends AbstractTest {
16+
17+
private AutoEjectFIFO fifo;
18+
private TestCatcher catcher;
19+
20+
21+
@Before
22+
public void createService() throws Exception {
23+
fifo = (AutoEjectFIFO) Runtime.start("fifo", "AutoEjectFIFO");
24+
catcher = (TestCatcher) Runtime.start("catcher", "TestCatcher");
25+
catcher.clear();
26+
fifo.clear();
27+
}
28+
29+
@After
30+
public void releaseService() {
31+
Runtime.release(fifo.getFullName());
32+
Runtime.release(catcher.getFullName());
33+
}
34+
35+
@Test
36+
public void testAdd10() throws IOException, InterruptedException {
37+
catcher.subscribe(fifo.getFullName(), "publishItemAdded", "onInteger");
38+
sleep(50);
39+
List<Integer> ints = new ArrayList<>();
40+
for (int i = 0; i < 10; i ++) {
41+
fifo.add(i);
42+
ints.add(i);
43+
}
44+
catcher.waitForMsgs(10, 2000);
45+
assertEquals(10, catcher.integers.size());
46+
assertEquals(0, fifo.getHead());
47+
// Last element was 9 since we added 0-9, not 1-10
48+
assertEquals(9, fifo.getTail());
49+
assertArrayEquals(ints.toArray(), fifo.getAll().toArray());
50+
}
51+
52+
@Test
53+
public void testAddMax() throws IOException, InterruptedException {
54+
catcher.subscribe(fifo.getFullName(), "publishItemAdded", "onInteger");
55+
sleep(50);
56+
for (int i = 0; i < AutoEjectFIFO.DEFAULT_MAX_SIZE; i ++) {
57+
fifo.add(i);
58+
}
59+
catcher.waitForMsgs(AutoEjectFIFO.DEFAULT_MAX_SIZE, 2000);
60+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE, catcher.integers.size());
61+
assertEquals(0, fifo.getHead());
62+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE - 1, fifo.getTail());
63+
}
64+
65+
@Test
66+
public void testAddMaxPlusOne() throws IOException, InterruptedException {
67+
catcher.subscribe(fifo.getFullName(), "publishItemAdded", "onInteger");
68+
catcher.subscribe(fifo.getFullName(), "publishEviction", "onObject");
69+
sleep(50);
70+
for (int i = 0; i < AutoEjectFIFO.DEFAULT_MAX_SIZE + 1; i ++) {
71+
fifo.add(i);
72+
}
73+
catcher.waitForMsgs(AutoEjectFIFO.DEFAULT_MAX_SIZE + 2, 2000);
74+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE + 1, catcher.integers.size());
75+
assertEquals(1, catcher.objects.size());
76+
77+
assertEquals(1, fifo.getHead());
78+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE, fifo.getTail());
79+
}
80+
81+
@Test
82+
public void testAddMaxPlusTwo() throws IOException, InterruptedException {
83+
catcher.subscribe(fifo.getFullName(), "publishItemAdded", "onInteger");
84+
catcher.subscribe(fifo.getFullName(), "publishEviction", "onObject");
85+
sleep(50);
86+
for (int i = 0; i < AutoEjectFIFO.DEFAULT_MAX_SIZE + 2; i ++) {
87+
fifo.add(i);
88+
}
89+
90+
// Two more adds plus 2 evictions
91+
catcher.waitForMsgs(AutoEjectFIFO.DEFAULT_MAX_SIZE + 4, 2000);
92+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE + 2, catcher.integers.size());
93+
assertEquals(2, catcher.objects.size());
94+
95+
assertEquals(2, fifo.getHead());
96+
assertEquals(AutoEjectFIFO.DEFAULT_MAX_SIZE + 1, fifo.getTail());
97+
}
98+
}

0 commit comments

Comments
 (0)