package com.roboclub.robobuggy.ros;
import java.util.ArrayDeque;
import java.util.Deque;
import com.roboclub.robobuggy.ros.internal.MessageServer;
/**
* @author Matt Sebek
*
* @version 0.5
*
* CHANGELOG: NONE
*
* DESCRIPTION: Note that we add elements to the head of the queue, and
* take elements from the tail of the queue.
*/
public class Subscriber {
private MessageServer ms;
private MessageListener callback;
private String topicName;
private int maxLocalInboxLength;
private long messagesReceived = 0;
private long messagesDropped = 0;
private Deque<Message> local_inbox = new ArrayDeque<Message>();
private volatile boolean shouldShutdown = false;
private Thread worker;
// Note that callbacks will run in a different thread
public Subscriber(String ownerName, String topic, int maxMessagesToKeep, MessageListener messageListener) {
this.ms = MessageServer.getMaster();
this.callback = messageListener;
this.topicName = topic;
this.maxLocalInboxLength = maxMessagesToKeep;
// Register this thread as a subscriber to "topic" on the master queue
ms.addListener(topic, this);
worker = new Thread(new WorkerThread(), "Subscriber=" + ownerName + ":" + topic);
worker.start();
}
// Note that callbacks will run in a different thread
public Subscriber(String ownerName, String topic, MessageListener messageListener) {
this(ownerName, topic, Integer.MAX_VALUE, messageListener);
}
public void putMessage(Message m) {
synchronized (local_inbox) {
local_inbox.push(m);
local_inbox.notify();
}
}
class WorkerThread implements Runnable {
@Override
public void run() {
Message m;
while (true) {
// Grab the lock and check 2 cases:
// 1. if there is a message in the queue, process this message
// immediately.
// 2. if not, drop the lock and wait for someone to notify us
// that work has been added to the queue.
// if we are woken spuriously, go back to sleep.
synchronized (local_inbox) {
while (local_inbox.size() == 0) {
try {
local_inbox.wait();
} catch (InterruptedException ie) {
if(!shouldShutdown) {
System.out.println("Interrupted but not shutting down? Impossible.");
continue;
}
break;
}
}
if(shouldShutdown) {
return;
}
// Note that we compare with size()-1; if maxLocalInboxLength is 1, we
// want to leave one message in the inbox to poll outside of the loop.
while((local_inbox.size()-1) > maxLocalInboxLength) {
// Remove items from local_inbox until the length is equal to max.
local_inbox.pollLast();
messagesDropped++;
}
m = local_inbox.pollLast();
}
if (m == null) {
System.out.println("If length is zero, we cannot be null... Very bad.");
}
// N. B. Do not hold local_inbox lock over user callback.
// If the callback acquires a lock, we may acquire locks out of
// order,
// This can lead to us having A and needing B, and them having B
// and needing A.
messagesReceived++;
callback.actionPerformed(topicName, m);
}
}
}
// Stop receiving callbacks from Master.
// Used for cleaning up this node after a test completes; subscribers will not be cleaned up
// automatically because MessageServer holds a reference to the Subscriber.
public void close() {
ms.removeListener(this.topicName, this);
this.ms = null;
this.shouldShutdown = true;
worker.interrupt();
try {
worker.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// Note that these methods allow the subscriber callback to call back
// into the class while the callback is running. Since no locks are held
// over the callback, it is safe to acquire a lock here.
public int getMessageQueueLength() {
synchronized (local_inbox) {
return local_inbox.size();
}
}
// TODO: do we need to hold this lock if we instead make this variable
// volatile? Will it work on the same core? on different cores?
public long getNumberMessagesDropped() {
synchronized (local_inbox) {
return messagesDropped;
}
}
}