package com.openxc.sinks;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import android.util.Log;
import com.openxc.messages.VehicleMessage;
/**
* Functionality to notify multiple clients asynchronously of new measurements.
*
* This class encapsulates the functionality to keep a thread-safe list of
* listeners that want to be notified of updates asynchronously. Subclasses need
* only to implement the {@link #propagateMessage(VehicleMessage)}
* to add the actual logic for looping over the list of receivers and send them
* new values.
*
* New measurements are queued up and propagated to receivers in a separate
* thread, to avoid blocking the original sender of the data.
*/
public abstract class AbstractQueuedCallbackSink implements VehicleDataSink {
private final static String TAG = "AbstractQueuedCallbackSink";
private NotificationThread mNotificationThread = new NotificationThread();
private Lock mNotificationsLock = new ReentrantLock();
private Condition mNotificationsChanged = mNotificationsLock.newCondition();
private CopyOnWriteArrayList<VehicleMessage> mNotifications =
new CopyOnWriteArrayList<>();
public AbstractQueuedCallbackSink() {
mNotificationThread.start();
}
@Override
public synchronized void stop() {
mNotificationThread.done();
}
@Override
public void receive(VehicleMessage message) throws DataSinkException {
try {
mNotificationsLock.lock();
mNotifications.add(message);
mNotificationsChanged.signal();
} finally {
mNotificationsLock.unlock();
}
}
/* Block until the notifications queue is cleared.
*/
public void clearQueue() {
try {
mNotificationsLock.lock();
while(!mNotifications.isEmpty()) {
mNotificationsChanged.await();
}
} catch(InterruptedException e) {
} finally {
mNotificationsLock.unlock();
}
}
abstract protected void propagateMessage(VehicleMessage message);
private class NotificationThread extends Thread {
private boolean mRunning = true;
private synchronized boolean isRunning() {
return mRunning;
}
public synchronized void done() {
Log.d(TAG, "Stopping message notifier");
mRunning = false;
// A context switch right can cause a race condition if we
// used take() instead of poll(): when mRunning is set to
// false and interrupt is called but we haven't called
// take() yet, so nobody is waiting. By using poll we can not be
// locked for more than 1s.
interrupt();
}
@Override
public void run() {
Log.d(TAG, "Starting notification thread");
while(isRunning()) {
mNotificationsLock.lock();
try {
if(mNotifications.isEmpty()) {
mNotificationsChanged.await();
}
Iterator<VehicleMessage> it = mNotifications.iterator();
CopyOnWriteArrayList<VehicleMessage> deleted =
new CopyOnWriteArrayList<>(mNotifications);
while(it.hasNext()) {
VehicleMessage message = it.next();
propagateMessage(message);
deleted.add(message);
}
mNotifications.removeAll(deleted) ;
} catch(InterruptedException e) {
Log.d(TAG, "Interrupted while waiting for a new " +
"item for notification -- likely shutting down");
break;
} finally {
mNotificationsChanged.signal();
mNotificationsLock.unlock();
}
}
Log.d(TAG, "Stopped notification thread");
}
}
}