Package org.jboss.errai.bus.server

Source Code of org.jboss.errai.bus.server.MessageQueueImpl

/*
* Copyright 2009 JBoss, a divison Red Hat, Inc
*
* 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 org.jboss.errai.bus.server;

import org.jboss.errai.bus.client.api.Message;
import org.jboss.errai.bus.client.framework.MarshalledMessage;
import org.jboss.errai.bus.client.framework.Payload;
import org.jboss.errai.bus.client.protocols.MessageParts;
import org.jboss.errai.bus.server.api.*;
import org.jboss.errai.bus.server.async.TimedTask;
import org.jboss.errai.bus.server.util.ServerBusUtils;

import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.System.nanoTime;

/**
* A message queue is keeps track of which messages need to be sent outbound. It keeps track of the amount of messages
* that can be stored, transmitted and those which timeout. The <tt>MessageQueue</tt> is implemented using a
* {@link java.util.concurrent.LinkedBlockingQueue} to store the messages, and a <tt>ServerMessageBus</tt> to send the
* messages.
*/
public class MessageQueueImpl implements MessageQueue {
    private static final long TIMEOUT = Boolean.getBoolean("org.jboss.errai.debugmode") ?
            secs(60) : secs(30);

    private static final int MAXIMUM_PAYLOAD_SIZE = 10;
    private static final long DEFAULT_TRANSMISSION_WINDOW = millis(25);
    private static final long MAX_TRANSMISSION_WINDOW = millis(100);

    private QueueSession session;

    private long transmissionWindow = 40;
    private volatile long lastTransmission = nanoTime();
    private long endWindow;

    private int lastQueueSize = 0;
    private boolean throttleIncoming = false;
    private boolean queueRunning = true;

    private boolean _windowPolling = false;
    private boolean windowPolling = false;

    private SessionControl sessionControl;
    private QueueActivationCallback activationCallback;
    private BlockingQueue<MarshalledMessage> queue;


    private ServerMessageBus bus;
    private volatile TimedTask task;

    private final Semaphore lock = new Semaphore(1, true);
    private volatile boolean initLock = true;
    private final Object activationLock = new Object();

    /**
     * Initializes the message queue with an initial size and a specified bus
     *
     * @param queueSize - the size of the queue
     * @param bus       - the bus that will send the messages
     * @param session   - the session associated with the queue
     */
    public MessageQueueImpl(int queueSize, ServerMessageBus bus, QueueSession session) {
        this.queue = new LinkedBlockingQueue<MarshalledMessage>(queueSize);
        this.bus = bus;
        this.session = session;
    }

    /**
     * Gets the next message to send, and returns the <tt>Payload</tt>, which contains the current messages that
     * need to be sent from the specified bus to another.
     *
     * @param wait - boolean is true if we should wait until the queue is ready. In this case, a
     *             <tt>RuntimeException</tt> will be thrown if the polling is active already. Concurrent polling is not allowed.
     * @return The <tt>Payload</tt> instance which contains the messages that need to be sent
     */
    public Payload poll(final boolean wait) {
        if (!queueRunning) {
            throw new QueueUnavailableException("queue is not available");
        }

        checkSession();

        if (lock.tryAcquire()) {
            try {
                MarshalledMessage m;

                if (wait) {
                    m = queue.poll(45, TimeUnit.SECONDS);

                } else {
                    m = queue.poll();
                }

                int payLoadSize = 0;

                Payload p = new Payload(m == null ? heartBeat : m);

                if (_windowPolling) {
                    windowPolling = true;
                    _windowPolling = false;
                } else if (windowPolling) {
                    while (!queue.isEmpty() && payLoadSize < MAXIMUM_PAYLOAD_SIZE
                            && !isWindowExceeded()) {
                        p.addMessage(queue.poll());
                        payLoadSize++;

                        try {
                            if (queue.isEmpty())
                                Thread.sleep(nanoTime() - endWindow);
                        }
                        catch (Exception e) {
                            // just resume.
                        }
                    }

                    if (!throttleIncoming && queue.size() > lastQueueSize) {
                        if (transmissionWindow < MAX_TRANSMISSION_WINDOW) {
                            transmissionWindow += millis(50);
                        } else {
                            throttleIncoming = true;
                            System.err.println("[Warning: A queue has become saturated and " +
                                    "performance is now being degraded.]");
                        }

                    } else if (queue.isEmpty()) {
                        transmissionWindow = DEFAULT_TRANSMISSION_WINDOW;
                        throttleIncoming = false;
                    }
                }

                lastQueueSize = queue.size();
                endWindow = (lastTransmission = nanoTime()) + transmissionWindow;

                return p;

            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                lock.release();
            }

        }
        return new Payload(heartBeat);
    }

    /**
     * Inserts the specified message into the queue, and returns true if it was successful
     *
     * @param message - the message to insert into the queue
     * @return true if insertion was successful
     */
    public boolean offer(final MarshalledMessage message) {
        if (!queueRunning) {
            throw new QueueUnavailableException("queue is not available");
        }

        boolean b = false;
        activity();
        try {
            b = (throttleIncoming ? queue.offer(message, 1, TimeUnit.SECONDS) : queue.offer(message));
        }
        catch (InterruptedException e) {
            // fall-through.
        }

        if (!b) {
            queue.clear();
            throw new QueueOverloadedException(null, "too many undelievered messages in queue: cannot dispatch message.");
        } else if (activationCallback != null) {
            synchronized (activationLock) {
                if (isWindowExceeded()) {
                    descheduleTask();
                    if (activationCallback != null) activationCallback.activate(this);
                } else if (task == null) {
                    scheduleActivation();
                }
            }
        }

        return b;
    }


    /**
     * Schedules the activation, by sending off the queue. All message should be processed and sent once the task is
     * processed
     */
    public void scheduleActivation() {
        synchronized (activationLock) {
            bus.getScheduler().addTask(
                    task = new TimedTask() {
                        {
                            period = -1; // only fire once.
                            nextRuntime = getEndOfWindow();
                        }

                        public void run() {

                            if (activationCallback != null)
                                activationCallback.activate(MessageQueueImpl.this);

                            task = null;
                        }

                        public boolean isFinished() {
                            return false;
                        }

                        @Override
                        public String toString() {
                            return "MessageResumer";
                        }
                    }
            );
        }
    }

    private void checkSession() {
        if (sessionControl != null && !sessionControl.isSessionValid()) {
            System.out.println("SessionExpired");
            throw new MessageQueueExpired("session has expired");
        }
    }

    /**
     * This function indicates activity on the session, so the session knows when the last time there was activity.
     * The <tt>MessageQueue</tt> relies on this to figure out whether or not to timeout
     */
    public void activity() {
        if (sessionControl != null) sessionControl.activity();
    }

    private boolean isWindowExceeded() {
        return nanoTime() > endWindow;
    }

    private long getEndOfWindow() {
        return endWindow - nanoTime();
    }

    private void descheduleTask() {
        synchronized (activationLock) {
            if (task != null) {
                task.cancel(true);
                task = null;
            }
        }
    }

    /**
     * Returns true if there is a message in the queue
     *
     * @return true if the queue is not empty
     */
    public boolean messagesWaiting() {
        return !queue.isEmpty();
    }

    /**
     * Sets the activation callback function which is called when the queue is scheduled for activation
     *
     * @param activationCallback - new activation callback function
     */
    public void setActivationCallback(QueueActivationCallback activationCallback) {
        this.activationCallback = activationCallback;
    }

    /**
     * Returns the current activation callback function
     *
     * @return the current activation callback function
     */
    public QueueActivationCallback getActivationCallback() {
        return activationCallback;
    }

    /**
     * Returns the current queue that is storing the messages
     *
     * @return the queue containing the messages to be sent
     */
    public BlockingQueue<MarshalledMessage> getQueue() {
        return queue;
    }

    public QueueSession getSession() {
        return session;
    }

    /**
     * Returns true if the queue is not running, or it has timed out
     *
     * @return true if the queue is stale
     */
    public boolean isStale() {
        return !queueRunning || (!isActive() && (nanoTime() - lastTransmission) > TIMEOUT);
    }

    /**
     * Returns true if the queue is currently active and polling
     *
     * @return true if the queue is actively polling
     */
    public boolean isActive() {
        return lock.availablePermits() == 0;
    }


    public boolean isInitialized() {
        return !initLock;
    }

    /**
     * Fakes a transmission, shows life with a heartbeat
     */
    public void heartBeat() {
        lastTransmission = nanoTime();
    }

    /**
     * Returns true if the window is polling
     *
     * @return true if the window is polling
     */
    public boolean isWindowPolling() {
        return windowPolling;
    }

    /**
     * Sets window polling
     *
     * @param windowPolling -
     */
    public void setWindowPolling(boolean windowPolling) {
        this._windowPolling = windowPolling;
    }

    public void finishInit() {
        initLock = false;
    }

    /**
     * Stops the queue, closes it on the bus and clears it completely
     */
    public void stopQueue() {
        queueRunning = false;
        queue.clear();
        bus.closeQueue(this);
    }

    private static final MarshalledMessage heartBeat = new MarshalledMessage() {
        public String getSubject() {
            return "HeartBeat";
        }

        public Object getMessage() {
            return null;
        }
    };

    private static long secs(long secs) {
        return secs * 1000000000;
    }

    private static long millis(long millis) {
        return millis * 1000000;
    }
}
TOP

Related Classes of org.jboss.errai.bus.server.MessageQueueImpl

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.