/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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 org.apache.hadoop.hive.llap.daemon.impl;
import java.util.Comparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Bounded priority queue that evicts the last element based on priority order specified
* through comparator. Elements that are added to the queue are sorted based on the specified
* comparator. If the queue is full and if a new element is added to it, the new element is compared
* with the last element so as to claim a spot. The evicted element (or the added item) is then
* returned back. If the queue is not full, new element will be added to queue and null is returned.
*/
public class EvictingPriorityBlockingQueue<E> {
private static final Logger LOG =
LoggerFactory.getLogger(EvictingPriorityBlockingQueue.class);
private final PriorityBlockingDeque<E> deque;
private final Comparator<E> comparator;
private final int waitQueueSize;
private int currentSize = 0;
public EvictingPriorityBlockingQueue(Comparator<E> comparator, int maxSize) {
this.deque = new PriorityBlockingDeque<>(comparator);
this.waitQueueSize = maxSize;
this.comparator = comparator;
}
public synchronized E offer(E e, int additionalElementsAllowed) {
if (currentSize < waitQueueSize + additionalElementsAllowed) {
// Capacity exists.
offerToDequeueInternal(e);
currentSize++;
return null;
} else {
if (isEmpty()) {
// Empty queue. But no capacity available, due to waitQueueSize and additionalElementsAllowed
// Return the element.
return e;
}
// No capacity. Check if an element needs to be evicted.
E last = deque.peekLast();
if (comparator.compare(e, last) < 0) {
deque.removeLast();
offerToDequeueInternal(e);
return last;
}
return e;
}
}
public synchronized boolean isEmpty() {
return currentSize == 0;
}
public synchronized E peek() {
return deque.peek();
}
public synchronized E take() throws InterruptedException {
E e = deque.take();
currentSize--; // Decrement only if an element was removed.
return e;
}
public synchronized boolean remove(E e) {
boolean removed = deque.remove(e);
if (removed) {
currentSize--;
}
return removed;
}
/**
* Re-insert an element if it exists (mainly to force a re-order)
* @param e
* @return false if the element was not found. true otherwise.
*/
public synchronized boolean reinsertIfExists(E e) {
if (remove(e)) {
offerToDequeueInternal(e);
currentSize++;
return true;
} else {
return false;
}
}
private void offerToDequeueInternal(E e) {
boolean result = deque.offer(e);
if (!result) {
LOG.error(
"Failed to insert element into queue with capacity available. size={}, element={}",
size(), e);
throw new RuntimeException(
"Failed to insert element into queue with capacity available. size=" +
size());
}
}
public synchronized int size() {
return currentSize;
}
@Override
public synchronized String toString() {
StringBuilder sb = new StringBuilder();
sb.append("currentSize=").append(size()).append(", queue=")
.append(deque.toString());
return sb.toString();
}
}