Package org.jboss.errai.bus.server

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

/*
* Copyright 2010 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.HasEncoded;
import org.jboss.errai.bus.client.api.Message;
import org.jboss.errai.bus.client.api.base.QueueStopMessage;
import org.jboss.errai.bus.server.api.*;
import org.jboss.errai.bus.server.async.TimedTask;
import org.jboss.errai.bus.server.io.JSONStreamEncoder;
import org.mvel2.util.StringAppender;

import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

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 HEARTBEAT_PERIOD = secs(30);
  private static final long TIMEOUT = Boolean.getBoolean("org.jboss.errai.debugmode") ?
          secs(360) : 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 final 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<Message> queue;


  private final ServerMessageBus bus;
  private volatile TimedTask task;

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


  public static class OutputStreamCapture extends OutputStream {
    private OutputStream wrap;
    private StringAppender buf = new StringAppender();


    public OutputStreamCapture(OutputStream wrap) {
      this.wrap = wrap;
    }

    @Override
    public void write(int b) throws IOException {
      buf.append((char) b);
      wrap.write(b);
    }

    @Override
    public String toString() {
      return buf.toString();
    }
  }

  /**
   * 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(final int queueSize, final ServerMessageBus bus, final QueueSession session) {
    this.queue = new LinkedBlockingQueue<Message>(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.
   * @param outstream - output stream to write the polling results to.
   */
  public void poll(final boolean wait, final OutputStream outstream) throws IOException {
    if (!queueRunning) {
      JSONStreamEncoder.encode(new QueueStopMessage().getParts(), outstream);
      return;
    }

    Message m = null;

    checkSession();

    if (lock.tryAcquire()) {
      outstream.write('[');

      int payLoadSize = 0;
      try {

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

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

        if (m instanceof HasEncoded) {
          outstream.write(((HasEncoded) m).getEncoded().getBytes());
        }
        else if (m instanceof QueueStopMessage) {
          JSONStreamEncoder.encode(m.getParts(), outstream);
          queueRunning = false;
          bus.closeQueue(this);
        }
        else if (m != null) {
          JSONStreamEncoder.encode(m.getParts(), outstream);
        }

        if (_windowPolling) {
          windowPolling = true;
          _windowPolling = false;
        }
        else if (windowPolling) {
          while (!queue.isEmpty() && payLoadSize < MAXIMUM_PAYLOAD_SIZE
                  && !isWindowExceeded()) {
            outstream.write(',');
            if ((m = queue.poll()) instanceof HasEncoded) {
              outstream.write(((HasEncoded) m).getEncoded().getBytes());
            }
            else {
              JSONStreamEncoder.encode(m.getParts(), outstream);
            }
            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;

        if (m == null && isHeartbeatNeeded()) {
          outstream.write(heartBeatBytes);
        }

        outstream.write(']');
        return;

      }
      catch (InterruptedException e) {
        e.printStackTrace();
      }
      finally {
        lock.release();
      }
      if (m == null && isHeartbeatNeeded()) {
        outstream.write(heartBeatBytes);
        outstream.write(']');
      }

    }
    else if (isHeartbeatNeeded()) {
      outstream.write('[');
      outstream.write(heartBeatBytes);
      outstream.write(']');
    }
  }

  private static final byte[] heartBeatBytes = "{ToSubject:\"ClientBus\", CommandType:\"Heartbeat\"}".getBytes();

  /**
   * 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 Message 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()) {
      throw new MessageQueueExpired("session has expired");
    }
  }

  public void setSessionControl(SessionControl sessionControl) {
    this.sessionControl = sessionControl;
  }

  /**
   * 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 boolean isHeartbeatNeeded() {
    return (nanoTime() - lastTransmission) > HEARTBEAT_PERIOD;
  }

  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) {
    synchronized (activationLock) {
      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<Message> 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() {
    queue.offer(new QueueStopMessage());
  }

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

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

  @Override
  public Object getActivationLock() {
    return activationLock;
  }
}
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.