edu.brown.cs.systems.retro.throttling.throttlingqueues.ThrottlingLockingQueue.java Source code

Java tutorial

Introduction

Here is the source code for edu.brown.cs.systems.retro.throttling.throttlingqueues.ThrottlingLockingQueue.java

Source

package edu.brown.cs.systems.retro.throttling.throttlingqueues;

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

import java.util.AbstractQueue;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.NotImplementedException;

import edu.brown.cs.systems.resourcethrottling.ThrottlingMessages.ThrottlingPointSpecification;
import edu.brown.cs.systems.retro.Retro;
import edu.brown.cs.systems.retro.aggregation.LocalResources;
import edu.brown.cs.systems.retro.aggregation.aggregators.ThrottlingPointAggregator;
import edu.brown.cs.systems.retro.throttling.ClassChecker;
import edu.brown.cs.systems.retro.throttling.ThrottlingQueue;

/**
 * An unbounded {@linkplain BlockingQueue blocking queue} of <tt>Delayed</tt>
 * elements, in which an element can only be taken when its delay has expired.
 * The <em>head</em> of the queue is that <tt>Delayed</tt> element whose delay
 * expired furthest in the past. If no delay has expired there is no head and
 * <tt>poll</tt> will return <tt>null</tt>. Expiration occurs when an element's
 * <tt>getDelay(TimeUnit.NANOSECONDS)</tt> method returns a value less than or
 * equal to zero. Even though unexpired elements cannot be removed using
 * <tt>take</tt> or <tt>poll</tt>, they are otherwise treated as normal
 * elements. For example, the <tt>size</tt> method returns the count of both
 * expired and unexpired elements. This queue does not permit null elements.
 *
 * <p>
 * This class and its iterator implement all of the <em>optional</em> methods of
 * the {@link Collection} and {@link Iterator} interfaces.
 *
 * <p>
 * This class is a member of the <a href="{@docRoot}
 * /../technotes/guides/collections/index.html"> Java Collections Framework</a>.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <E>
 *            the type of elements held in this collection
 */

public class ThrottlingLockingQueue<E> extends AbstractQueue<E> implements ThrottlingQueue<E> {

    private final ClassChecker hadoop_ipc_class_checker_hack = new ClassChecker();

    public volatile int size = 0;
    private final long window;
    private final ThrottlingPointAggregator taggregator;

    public ThrottlingLockingQueue(String queueid) {
        this(queueid, 1000000000); // default 1 second history (1 billion
                                   // nanoseconds)
    }

    /**
     * @param historySize
     *            up to historySizeNanos nanoseconds grace period
     */
    public ThrottlingLockingQueue(String queueid, long historySizeNanos) {
        this.window = historySizeNanos;
        this.taggregator = LocalResources.getThrottlingPointAggregator(queueid);
    }

    private long now() {
        return System.nanoTime();
    }

    private class TenantQueue {

        public final int tenantId;

        public volatile long delay_ns = 0;
        public volatile long next = 0;

        public final Queue<Item> elements = new ArrayDeque<Item>();

        public class Item implements Comparable<Item> {
            public final E element;
            public final long enqueue;
            public long available;
            public final TenantQueue q = TenantQueue.this;

            public Item(E element) {
                this.element = element;
                this.enqueue = System.nanoTime();
            }

            @Override
            public int compareTo(Item other) {
                return Long.compare(this.enqueue, other.enqueue);
            }

            @Override
            public boolean equals(Object other) {
                return other == null ? false : other.equals(element);
            }
        }

        public TenantQueue(int tenantId) {
            this.tenantId = tenantId;
        }

        public boolean offer(E e, long t) {
            elements.add(new Item(e));
            size++;
            if (elements.size() == 1)
                next = Math.max(t - window, next);
            return true;
        }

        public E peek() {
            Item peeked = elements.peek();
            return peeked == null ? null : peeked.element;
        }

        public Item poll(long t) {
            Item polled = elements.poll();
            if (polled == null)
                return null;
            size--;
            polled.available = next; // record when the element was actually
                                     // available for deq
            next = Math.max(t - window, next + delay_ns);
            return polled;
        }

        public void clear() {
            size -= elements.size();
            elements.clear();
        }

        public void setRate(double rate_per_second) {
            long new_delay_ns = (long) Math.floor(1000 * 1000 * 1000 / rate_per_second);
            next = next - delay_ns + new_delay_ns;
            delay_ns = new_delay_ns;
        }

        public void clearRate() {
            next -= delay_ns;
            this.delay_ns = 0;
        }

        /**
         * returns when the queue will next be available to pull from, or if it
         * can be pulled from now, when the available element was enqueued
         */
        public long available(long t) {
            return (elements.size() == 0) ? Long.MAX_VALUE : ((next <= t) ? elements.peek().enqueue : next);
        }

        public Object[] toArray() {
            return elements.toArray();
        }

        public boolean remove(Object o) {
            return elements.remove(o);
        }

    }

    private transient final ReentrantLock lock = new ReentrantLock();
    private final Map<Integer, TenantQueue> qs = new HashMap<Integer, TenantQueue>();

    public int tenant() {
        return Retro.getTenant();
    }

    private TenantQueue queue(int tenantId) {
        TenantQueue q = qs.get(tenantId);
        if (q == null)
            qs.put(tenantId, q = new TenantQueue(tenantId));
        return q;
    }

    private TenantQueue nextQueue(long now) {
        long available = Long.MAX_VALUE;
        TenantQueue next = null;

        for (TenantQueue q : qs.values()) {
            long qavailable = q.available(now);
            if (qavailable < available) {
                available = qavailable;
                next = q;
            }
        }

        return next;
    }

    private E peekNext(long now) {
        TenantQueue nextQueue = nextQueue(now);
        return nextQueue == null ? null : nextQueue.peek();
    }

    private boolean hasNext() {
        return size > 0;
    }

    private E done(TenantQueue.Item item, long t) {
        if (item == null)
            return null;

        Retro.setTenant(item.q.tenantId);

        taggregator.throttled(item.q.tenantId, 0L); // TODO track latency
        if (hadoop_ipc_class_checker_hack.isHadoopIPCCall(item.element))
            hadoop_ipc_class_checker_hack.setCallEnqueue(item.element, Math.max(item.enqueue, item.available));
        if (hadoop_ipc_class_checker_hack.isHBaseCallRunner(item.element))
            hadoop_ipc_class_checker_hack.setCallRunnerEnqueue(item.element,
                    Math.max(item.enqueue, item.available));

        return item.element;
    }

    /**
     * Thread designated to wait for the element at the head of the queue. This
     * variant of the Leader-Follower pattern
     * (http://www.cs.wustl.edu/~schmidt/POSA/POSA2/) serves to minimize
     * unnecessary timed waiting. When a thread becomes the leader, it waits
     * only for the next delay to elapse, but other threads await indefinitely.
     * The leader thread must signal some other thread before returning from
     * take() or poll(...), unless some other thread becomes leader in the
     * interim. Whenever the head of the queue is replaced with an element with
     * an earlier expiration time, the leader field is invalidated by being
     * reset to null, and some waiting thread, but not necessarily the current
     * leader, is signalled. So waiting threads must be prepared to acquire and
     * lose leadership while waiting.
     */
    private Thread leader = null;

    /**
     * Condition signalled when a newer element becomes available at the head of
     * the queue or a new thread may need to become leader.
     */
    private final Condition available = lock.newCondition();

    /**
     * Inserts the specified element into this delay queue.
     *
     * @param e
     *            the element to add
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     * @throws NullPointerException
     *             if the specified element is null
     */
    public boolean add(E e) {
        return offer(e);
    }

    /**
     * Inserts the specified element into this delay queue.
     *
     * @param e
     *            the element to add
     * @return <tt>true</tt>
     * @throws NullPointerException
     *             if the specified element is null
     */
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        int tenant = tenant();
        lock.lock();
        try {
            long t = now();
            queue(tenant).offer(e, t);
            if (peekNext(t) == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
            taggregator.throttling(tenant);
        }
    }

    /**
     * Inserts the specified element into this delay queue. As the queue is
     * unbounded this method will never block.
     *
     * @param e
     *            the element to add
     * @throws NullPointerException
     *             {@inheritDoc}
     */
    public void put(E e) {
        offer(e);
    }

    /**
     * Inserts the specified element into this delay queue. As the queue is
     * unbounded this method will never block.
     *
     * @param e
     *            the element to add
     * @param timeout
     *            This parameter is ignored as the method never blocks
     * @param unit
     *            This parameter is ignored as the method never blocks
     * @return <tt>true</tt>
     * @throws NullPointerException
     *             {@inheritDoc}
     */
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e);
    }

    /**
     * Retrieves and removes the head of this queue, or returns <tt>null</tt> if
     * this queue has no elements with an expired delay.
     *
     * @return the head of this queue, or <tt>null</tt> if this queue has no
     *         elements with an expired delay
     */
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        long t = now();
        TenantQueue.Item item = null;
        try {
            TenantQueue q = nextQueue(t);
            if (q == null || q.next > t)
                return null;
            else {
                item = q.poll(t);
                return item == null ? null : item.element;
            }
        } finally {
            lock.unlock();
            done(item, t);
        }
    }

    /**
     * Retrieves and removes the head of this queue, waiting if necessary until
     * an element with an expired delay is available on this queue.
     *
     * @return the head of this queue
     * @throws InterruptedException
     *             {@inheritDoc}
     */
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        long t = now();
        TenantQueue.Item item = null;
        try {
            for (;;) {
                TenantQueue q = nextQueue(t);
                if (q == null)
                    available.await();
                else {
                    long delay = q.next - t;
                    if (delay <= 0) {
                        item = q.poll(t);
                        return item == null ? null : item.element;
                    } else if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
                t = System.nanoTime();
            }
        } finally {
            if (leader == null && hasNext())
                available.signal();
            lock.unlock();
            done(item, t);
        }
    }

    /**
     * Retrieves and removes the head of this queue, waiting if necessary until
     * an element with an expired delay is available on this queue, or the
     * specified wait time expires.
     *
     * @return the head of this queue, or <tt>null</tt> if the specified waiting
     *         time elapses before an element with an expired delay becomes
     *         available
     * @throws InterruptedException
     *             {@inheritDoc}
     */
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        long t = now();
        TenantQueue.Item item = null;
        try {
            for (;;) {
                TenantQueue q = nextQueue(t);
                if (q == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = q.next - t;
                    if (delay <= 0) {
                        item = q.poll(t);
                        return item == null ? null : item.element;
                    }
                    if (nanos <= 0)
                        return null;
                    if (nanos < delay || leader != null)
                        nanos = available.awaitNanos(nanos);
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            long timeLeft = available.awaitNanos(delay);
                            nanos -= delay - timeLeft;
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
                t = System.nanoTime();
            }
        } finally {
            if (leader == null && hasNext())
                available.signal();
            lock.unlock();
            done(item, t);
        }
    }

    /**
     * Retrieves, but does not remove, the head of this queue, or returns
     * <tt>null</tt> if this queue is empty. Unlike <tt>poll</tt>, if no expired
     * elements are available in the queue, this method returns the element that
     * will expire next, if one exists.
     *
     * @return the head of this queue, or <tt>null</tt> if this queue is empty.
     */
    public E peek() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return peekNext(now());
        } finally {
            lock.unlock();
        }
    }

    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return size;
        } finally {
            lock.unlock();
        }
    }

    /**
     * @throws UnsupportedOperationException
     *             {@inheritDoc}
     * @throws ClassCastException
     *             {@inheritDoc}
     * @throws NullPointerException
     *             {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    public int drainTo(Collection<? super E> c) {
        // if (c == null)
        // throw new NullPointerException();
        // if (c == this)
        // throw new IllegalArgumentException();
        // final ReentrantLock lock = this.lock;
        // lock.lock();
        // TenantQueue.Item item = null;
        // try {
        // int n = 0;
        // long t = now();
        // for (;;) {
        // TenantQueue q = nextQueue(t);
        // if (q == null || q.next > t)
        // break;
        // c.add(q.poll(t));
        // ++n;
        // }
        // return n;
        // } finally {
        // lock.unlock();
        // }
        throw new NotImplementedException("");
    }

    /**
     * @throws UnsupportedOperationException
     *             {@inheritDoc}
     * @throws ClassCastException
     *             {@inheritDoc}
     * @throws NullPointerException
     *             {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    public int drainTo(Collection<? super E> c, int maxElements) {
        // if (c == null)
        // throw new NullPointerException();
        // if (c == this)
        // throw new IllegalArgumentException();
        // if (maxElements <= 0)
        // return 0;
        // final ReentrantLock lock = this.lock;
        // lock.lock();
        // TenantQueue.Item item = null;
        // try {
        // int n = 0;
        // long t = now();
        // while (n < maxElements) {
        // TenantQueue q = nextQueue(t);
        // if (q == null || q.next > t)
        // break;
        // c.add(q.poll(t));
        // ++n;
        // }
        // return n;
        // } finally {
        // lock.unlock();
        // }
        throw new NotImplementedException("");
    }

    /**
     * Atomically removes all of the elements from this delay queue. The queue
     * will be empty after this call returns. Elements with an unexpired delay
     * are not waited for; they are simply discarded from the queue.
     */
    public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            for (TenantQueue q : qs.values()) {
                q.clear();
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Always returns <tt>Integer.MAX_VALUE</tt> because a <tt>DelayQueue</tt>
     * is not capacity constrained.
     *
     * @return <tt>Integer.MAX_VALUE</tt>
     */
    public int remainingCapacity() {
        return Integer.MAX_VALUE;
    }

    /**
     * Returns an array containing all of the elements in this queue. The
     * returned array elements are in no particular order.
     *
     * <p>
     * The returned array will be "safe" in that no references to it are
     * maintained by this queue. (In other words, this method must allocate a
     * new array). The caller is thus free to modify the returned array.
     *
     * <p>
     * This method acts as bridge between array-based and collection-based APIs.
     *
     * @return an array containing all of the elements in this queue
     */
    public Object[] toArray() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] arr = new Object[size];
            int i = 0;
            for (TenantQueue q : qs.values()) {
                Object[] qarr = q.toArray();
                System.arraycopy(qarr, 0, arr, i, qarr.length);
                i += qarr.length;
            }
            return arr;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns an array containing all of the elements in this queue; the
     * runtime type of the returned array is that of the specified array. The
     * returned array elements are in no particular order. If the queue fits in
     * the specified array, it is returned therein. Otherwise, a new array is
     * allocated with the runtime type of the specified array and the size of
     * this queue.
     *
     * <p>
     * If this queue fits in the specified array with room to spare (i.e., the
     * array has more elements than this queue), the element in the array
     * immediately following the end of the queue is set to <tt>null</tt>.
     *
     * <p>
     * Like the {@link #toArray()} method, this method acts as bridge between
     * array-based and collection-based APIs. Further, this method allows
     * precise control over the runtime type of the output array, and may, under
     * certain circumstances, be used to save allocation costs.
     *
     * <p>
     * The following code can be used to dump a delay queue into a newly
     * allocated array of <tt>Delayed</tt>:
     *
     * <pre>
     * Delayed[] a = q.toArray(new Delayed[0]);
     * </pre>
     *
     * Note that <tt>toArray(new Object[0])</tt> is identical in function to
     * <tt>toArray()</tt>.
     *
     * @param a
     *            the array into which the elements of the queue are to be
     *            stored, if it is big enough; otherwise, a new array of the
     *            same runtime type is allocated for this purpose
     * @return an array containing all of the elements in this queue
     * @throws ArrayStoreException
     *             if the runtime type of the specified array is not a supertype
     *             of the runtime type of every element in this queue
     * @throws NullPointerException
     *             if the specified array is null
     */
    public <T> T[] toArray(T[] a) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (T[]) toArray(a);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Removes a single instance of the specified element from this queue, if it
     * is present, whether or not it has expired.
     */
    public boolean remove(Object o) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            for (TenantQueue q : qs.values()) {
                if (q.remove(o))
                    return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns an iterator over all the elements (both expired and unexpired) in
     * this queue. The iterator does not return the elements in any particular
     * order.
     *
     * <p>
     * The returned iterator is a "weakly consistent" iterator that will never
     * throw {@link java.util.ConcurrentModificationException
     * ConcurrentModificationException}, and guarantees to traverse elements as
     * they existed upon construction of the iterator, and may (but is not
     * guaranteed to) reflect any modifications subsequent to construction.
     *
     * @return an iterator over the elements in this queue
     */
    public Iterator<E> iterator() {
        return new Itr(toArray());
    }

    /**
     * Snapshot iterator that works off copy of underlying q array.
     */
    private class Itr implements Iterator<E> {
        final Object[] array; // Array of all elements
        int cursor; // index of next element to return;
        int lastRet; // index of last element, or -1 if no such

        Itr(Object[] array) {
            lastRet = -1;
            this.array = array;
        }

        public boolean hasNext() {
            return cursor < array.length;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (cursor >= array.length)
                throw new NoSuchElementException();
            lastRet = cursor;
            return (E) array[cursor++];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            Object x = array[lastRet];
            lastRet = -1;
            lock.lock();
            try {
                ThrottlingLockingQueue.this.remove(x);
            } finally {
                lock.unlock();
            }
        }
    }

    public void setRate(int tenantId, double rate) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            queue(tenantId).setRate(rate);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void update(ThrottlingPointSpecification spec) {
        // Pull out the tenant IDs for tenants that currently are rate limited
        HashSet<Integer> remainingTenants = new HashSet<Integer>(qs.keySet());

        // Update the limits as specified.
        for (int i = 0; i < spec.getTenantIDCount(); i++) {
            int tenantId = spec.getTenantID(i);
            setRate(tenantId, spec.getThrottlingRate(i));
            remainingTenants.remove(tenantId);
        }

        // If a tenant does not have a rate specified, then it is no longer rate
        // limited
        for (Integer tenantId : remainingTenants) {
            clearRate(tenantId);
        }
    }

    public void clearRate(int tenantId) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            queue(tenantId).clearRate();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void clearRates() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            for (TenantQueue q : qs.values()) {
                q.clearRate();
            }
        } finally {
            lock.unlock();
        }
    }

}