Package org.voltdb

Source Code of org.voltdb.RateLimitedClientNotifier$Node

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.voltcore.network.Connection;
import org.voltcore.utils.CoreUtils;
import org.voltcore.utils.DeferredSerialization;

import com.google_voltpatches.common.base.Preconditions;
import com.google_voltpatches.common.base.Predicate;
import com.google_voltpatches.common.base.Supplier;
import com.google_voltpatches.common.base.Throwables;
import com.google_voltpatches.common.cache.Cache;
import com.google_voltpatches.common.cache.CacheBuilder;
import com.google_voltpatches.common.util.concurrent.ListeningExecutorService;
import com.google_voltpatches.common.util.concurrent.RateLimiter;

/**
* A helper class for gradually emitting notifications to client connections as well as coalescing updates
* to individual connections by deduplicating suppliers that have already been queued.
*
* It is required that a supplier instance for a given type of event will be equal by identity as that
* will be used to identify when the same event is signaled multiple times. Internally the supplier
* is free to return the most recent value for that type of event (such as cluster topology)
*
* With a few adapters this could be a generic event coalescing and rate limiting mechanism, but I
* don't see a reason to over engineer it until we actually have a second use case
*
*/
public class RateLimitedClientNotifier {
    private final ListeningExecutorService m_es =
            CoreUtils.getCachedSingleThreadExecutor("RateLimitedClientNotifier", 60 * 1000);

    private final ConcurrentMap<Connection, Object> m_clientsPendingNotification
                                = new ConcurrentHashMap<Connection, Object>(2048, .75f, 128);
    private final LinkedBlockingQueue<Runnable> m_submissionQueue = new LinkedBlockingQueue<Runnable>();

    static double NOTIFICATION_RATE = Long.getLong("CLIENT_NOTIFICATION_RATE", 1000).doubleValue();
    static long WARMUP_MS = Long.getLong("CLIENT_NOTIFICATION_WARMUP_MS", 5000);
    private RateLimiter m_limiter;

    //Cache nodes use to build linked lists of notifications
    //This avoids having to create and promote large numbers of objects and then GC them later
    static final Cache<Node, Node> m_cachedNodes =
                                            CacheBuilder.newBuilder()
                                                    .maximumSize(10000).concurrencyLevel(1).build();

    /*
     * Linked list node saves allocating a dedicated list object and allows for object pooling
     */
    public static class Node implements Callable<Node> {
        private final Supplier<DeferredSerialization> notification;
        private final Node next;

        public Node(Supplier<DeferredSerialization> notification, Node next) {
            Preconditions.checkNotNull(notification);
            this.notification = notification;
            this.next = next;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) return true;
            if (o == null) return false;
            if (!(o instanceof Node)) return false;
            Node other = (Node)o;
            if (other.notification == notification) {
                if (other.next == next) return true;
                if (other.next != null && next != null) {
                    return next.equals(other.next);
                }
            }
            return false;
        }

        @Override
        public int hashCode() {
            if (next == null) return notification.hashCode();
            final int prime = 31;
            int result = 1;
            Node head = this;
            do {
                result = result * prime + head.notification.hashCode();
            } while ((head = head.next) != null);
            return result;
        }

        @Override
        public Node call() {
            return this;
        }
    }

    private final Runnable m_loop = new Runnable() {
        @Override
        public void run() {
            try {
                RateLimitedClientNotifier.this.run();
            } catch (Throwable t) {
                VoltDB.crashLocalVoltDB("Unexpected exception in client notifier", true, t);
            }
        }
    };

    private Iterator<Map.Entry<Connection, Object>> m_iter;

    private void run() throws Exception {
        while (true) {
            if (m_es.isShutdown()) return;
            if (m_clientsPendingNotification.isEmpty()) {
                //Block until submissions create further work
                runSubmissions(true);
                //Create a fresh limiter each time so there isn't a burst of permits
                m_limiter = RateLimiter.create(NOTIFICATION_RATE, WARMUP_MS, TimeUnit.MILLISECONDS);
            } else {
                //Non-blocking poll for changes
                runSubmissions(false);
            }

            //Regenerate the iterator every time we are done sweeping the map
            if (m_iter == null) m_iter = m_clientsPendingNotification.entrySet().iterator();

            if (m_iter.hasNext()) {
                //The limiter has no dependencies so this will never pause for long waiting to acquire
                m_limiter.acquire();
                //Poll and remove, there are not other threads modifying the map concurrently
                final Map.Entry<Connection, Object> entry = m_iter.next();
                m_iter.remove();
                dispatchNotifications(entry.getKey(), entry.getValue());
            } else {
                m_iter = null;
            }
        }
    }

    private void dispatchNotifications(Connection key, Object value) {
        //This is a scalar notification
        if (value instanceof Supplier) {
            @SuppressWarnings("unchecked")
            final Supplier<DeferredSerialization> s = (Supplier<DeferredSerialization>)value;
            key.writeStream().enqueue(s.get());
        } else {
            //Notification is a linked list containing multiple events
            Node head = (Node)value;
            do {
                key.writeStream().enqueue(head.notification.get());
            } while ((head = head.next) != null);
        }

    }

    //Check for tasks that generate new notifications, optionally blocking
    //if there is no other work to do
    private void runSubmissions(boolean block) throws InterruptedException {
        if (block) {
            Runnable r = m_submissionQueue.take();
            do {
                r.run();
            } while ((r = m_submissionQueue.poll()) != null);
        } else {
            Runnable r = null;
            while ((r = m_submissionQueue.poll()) != null) {
                r.run();
            }
        }
    }

    //Start here instead of in constructor to avoid leaking this
    public void start() {
        m_es.execute(m_loop);
    }

    //Queue a notification to a collection of connections
    //The collection will be filtered to exclude non VoltPort connections
    public void queueNotification(
            final Collection<ClientInterfaceHandleManager> connections,
            final Supplier<DeferredSerialization> notification,
            final Predicate<ClientInterfaceHandleManager> wantsNotificationPredicate) {
        m_submissionQueue.offer(new Runnable() {
            @Override
            public void run() {
                for (ClientInterfaceHandleManager cihm : connections) {
                    if (!wantsNotificationPredicate.apply(cihm)) continue;
                    final Connection c = cihm.connection;

                    /*
                     * To avoid extra allocations and promotion we initially store a single event
                     * as just the event. Once we have two or more events we create a linked list
                     * and walk the list to dedupe events by identity
                     */
                    Object pendingNotifications = m_clientsPendingNotification.get(c);
                    try {
                        if (pendingNotifications == null) {
                            m_clientsPendingNotification.put(c, notification);
                        } else if (pendingNotifications instanceof Supplier) {
                            //Identity duplicate check
                            if (pendingNotifications == notification) return;
                            //Convert to a two node linked list
                            @SuppressWarnings("unchecked")
                            Node n1 = new Node((Supplier<DeferredSerialization>)pendingNotifications, null);
                            n1 = m_cachedNodes.get(n1, n1);
                            Node n2 = new Node(notification, n1);
                            n2 = m_cachedNodes.get(n2, n2);
                            m_clientsPendingNotification.put(c,  n2);
                        } else {
                            //Walk the list and check if the notification is a duplicate
                            Node head = (Node)pendingNotifications;
                            boolean dup = false;
                            while (head != null) {
                                if (head.notification == notification) {
                                    dup = true;
                                    break;
                                }
                                head = head.next;
                            }
                            //If it's a dupe, no new work
                            if (dup) continue;
                            //Otherwise replace the head of the list which is the value in the map
                            Node replacement = new Node(notification, (Node)pendingNotifications);
                            replacement = m_cachedNodes.get(replacement, replacement);
                            m_clientsPendingNotification.put(c, replacement);
                        }
                    } catch (ExecutionException e) {
                        VoltDB.crashLocalVoltDB(
                                "Unexpected exception pushing client notifications",
                                true,
                                Throwables.getRootCause(e));
                    }
                }
            }
        });
    }

    public void removeConnection(Connection c) {
        /*
         * It's a concurrent map so this is safe
         * If there is a race with the notifier thread it will be fine
         * Failing to remove it completely will cause the notification to be delivered
         * to the network and dropped. Technically we don't even have to remove it
         * we could just let the notification thread eventually remove it.
         */
        m_clientsPendingNotification.remove(c);
    }

    public void shutdown() throws InterruptedException {
        m_es.shutdown();
        m_submissionQueue.add(new Runnable() {
            @Override
            public void run() {
                return;
            }
        });
        m_es.awaitTermination(356, TimeUnit.DAYS);
    }
}
TOP

Related Classes of org.voltdb.RateLimitedClientNotifier$Node

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.