/*
* Copyright 2014, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.client.socket;
import com.betfair.cougar.core.api.ev.Subscription;
import com.betfair.cougar.netutil.nio.HeapDelta;
import com.betfair.cougar.netutil.nio.connected.InitialUpdate;
import com.betfair.platform.virtualheap.Heap;
import org.apache.mina.common.IoSession;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HeapState {
private final Heap heap;
private final BlockingQueue<QueuedHeapDelta> queue = new PriorityBlockingQueue<QueuedHeapDelta>(10, HEAP_DELTA_COMPARATOR);
private final AtomicLong lastUpdateId = new AtomicLong(-1);
private final Lock heapUpdateLock = new ReentrantLock();
private final ConcurrentMap<String, ClientSubscription> subscriptions = new ConcurrentHashMap<String, ClientSubscription>();
private volatile boolean seenInitialUpdate;
public HeapState(Heap heap) {
this.heap = heap;
}
public Subscription addSubscription(ClientConnectedObjectManager ccom, IoSession session, long heapId, String subscriptionId) {
ClientSubscription sub = new ClientSubscription(ccom, session, heapId, subscriptionId);
ClientSubscription existing = subscriptions.putIfAbsent(subscriptionId, sub);
if (existing != null) {
return null;
}
return sub;
}
public Map<String, ClientSubscription> getSubscriptions() {
return subscriptions;
}
public void terminateSubscription(String subscriptionId, Subscription.CloseReason reason) {
ClientSubscription sub = subscriptions.remove(subscriptionId);
if (sub != null) {
if (reason != Subscription.CloseReason.REQUESTED_BY_SUBSCRIBER) {
// we don't want to call close() here as that will end up calling back into CCOM and get a deadlock ;)
sub.onConnectionClosed(reason);
}
}
}
public void terminateAllSubscriptions(Subscription.CloseReason reason) {
List<String> keys = new ArrayList<String>(subscriptions.keySet());
for (String s : keys) {
terminateSubscription(s, reason);
}
}
public int getSubscriptionCount() {
return subscriptions.size();
}
public long getLastDeltaId() {
return lastUpdateId.get();
}
public void queueUpdate(HeapDelta payload) {
queue.add(new QueuedHeapDelta(payload));
}
public String getHeapUri() {
return heap.getUri();
}
public HeapDelta peekNextDelta() {
QueuedHeapDelta next = queue.peek();
return next != null && isNextUpdate(next.delta) ? next.delta : null;
}
private boolean isNextUpdate(HeapDelta delta) {
return delta.getUpdateId() == getNextUpdateId() || isInitialUpdate(delta);
}
private boolean isInitialUpdate(HeapDelta delta) {
if (seenInitialUpdate) {
return false;
}
if (delta.getUpdates().isEmpty()) {
return false;
}
return delta.getUpdates().get(0) instanceof InitialUpdate;
}
public boolean haveSeenInitialUpdate() {
return seenInitialUpdate;
}
public long getNextUpdateId() {
return lastUpdateId.get() + 1;
}
public Lock getHeapUpdateLock() {
return heapUpdateLock;
}
public Heap getHeap() {
return heap;
}
public HeapDelta popNextDelta() {
HeapDelta nextDelta = queue.remove().delta; // We only call this if we know the queue has something
seenInitialUpdate |= isInitialUpdate(nextDelta);
lastUpdateId.set(nextDelta.getUpdateId());
return nextDelta;
}
public QueueHealth checkDeltaQueueHealth(int maxQueueSize, long maxWaitTime) {
// if the delta queue for a heap grows too long then we've lost a message and need to abort/disconnect
if (queue.size() > maxQueueSize) {
return QueueHealth.QUEUE_TOO_LONG;
}
QueuedHeapDelta first = queue.peek();
if (first != null
&& first.delta.getUpdateId() > getNextUpdateId()
&& first.queueTime + maxWaitTime < System.currentTimeMillis()) {
// get the time the next update was queued, and check against that timeout..
return QueueHealth.WAITED_TOO_LONG;
}
return QueueHealth.HEALTHY;
}
public static enum QueueHealth {
HEALTHY,
QUEUE_TOO_LONG,
WAITED_TOO_LONG
}
private static final Comparator<QueuedHeapDelta> HEAP_DELTA_COMPARATOR = new Comparator<QueuedHeapDelta>() {
@Override
public int compare(QueuedHeapDelta o1, QueuedHeapDelta o2) {
long a = o1.delta.getUpdateId();
long b = o2.delta.getUpdateId();
if (a > b) {
return 1;
} else if (b > a) {
return -1;
} else {
return 0;
}
}
};
private static final class QueuedHeapDelta {
private final long queueTime = System.currentTimeMillis();
private final HeapDelta delta;
private QueuedHeapDelta(HeapDelta delta) {
this.delta = delta;
}
}
}