com.caucho.jms.queue.AbstractMemoryQueue Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.jms.queue;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.caucho.env.thread.ThreadPool;
import com.caucho.util.Alarm;
import com.caucho.util.CurrentTime;
/**
* Provides abstract implementation for a memory queue.
*
*/
@SuppressWarnings("serial")
public abstract class AbstractMemoryQueue>
extends AbstractQueue
{
private static final Logger log
= Logger.getLogger(AbstractMemoryQueue.class.getName());
private int _queueSizeMax = Integer.MAX_VALUE / 2;
private final Object _queueLock = new Object();
private ArrayList> _callbackList
= new ArrayList>();
private ArrayList> _listenList
= new ArrayList>();
private QE []_head = (QE []) new QueueEntry[10];
private QE []_tail = (QE []) new QueueEntry[10];
private ThreadPool _threadPool = ThreadPool.getThreadPool();
private final AtomicLong _readSequenceGenerator = new AtomicLong();
private final AtomicInteger _queueSize = new AtomicInteger();
private final AtomicBoolean _isQueueThrottle = new AtomicBoolean();
// stats
private AtomicInteger _receiverCount = new AtomicInteger();
private AtomicInteger _listenerCount = new AtomicInteger();
//
// configuration
//
public void setQueueSizeMax(int max)
{
if (max <= 0 || Integer.MAX_VALUE / 2 < max)
_queueSizeMax = Integer.MAX_VALUE / 2;
else
_queueSizeMax = max;
}
public int getQueueSizeMax()
{
return _queueSizeMax;
}
//
// Abstract/stub methods to be implemented by the Queue
//
/**
* Sends a message to the queue
*/
@Override
public void send(String msgId,
E payload,
int priority,
long expireTime,
String publisherId)
throws MessageException
{
QE entry = writeEntry(msgId, payload, priority, expireTime);
addQueueEntry(entry, expireTime);
}
//
// send implementation
//
abstract protected QE writeEntry(String msg,
E payload,
int priority,
long expires);
protected void addQueueEntry(QE entry, long expires)
{
addEntry(entry, expires);
dispatchMessage();
}
//
// receive implementation
//
/**
* Primary message receiving, registers a callback for any new
* message.
*/
@Override
public QE receiveEntry(long expireTime, boolean isAutoAck)
throws MessageException
{
return receiveEntry(expireTime, isAutoAck, null);
}
@Override
public QE receiveEntry(long expireTime,
boolean isAutoAck,
QueueEntrySelector selector)
throws MessageException
{
if (CurrentTime.isTest())
expireTime += (CurrentTime.getCurrentTimeActual()
- CurrentTime.getCurrentTime());
_receiverCount.incrementAndGet();
try {
QE entry = null;
synchronized (_queueLock) {
if (_callbackList.size() == 0) {
entry = readEntry(selector);
}
}
if (entry != null) {
readPayload(entry);
if (isAutoAck)
acknowledge(entry.getMsgId());
return entry;
}
if (expireTime <= CurrentTime.getCurrentTimeActual()) {
return null;
}
ReceiveEntryCallback callback = new ReceiveEntryCallback(isAutoAck);
return (QE) callback.waitForEntry(expireTime);
} finally {
_receiverCount.decrementAndGet();
}
}
public void receive(long expireTime,
boolean isAutoAck,
QueueEntrySelector selector,
MessageCallback callback)
throws MessageException
{
ListenEntryCallback entryCallback
= new ListenEntryCallback(callback, isAutoAck);
listen(entryCallback);
}
@Override
public void addMessageCallback(MessageCallback callback,
boolean isAutoAck)
{
_listenerCount.incrementAndGet();
ListenEntryCallback entryCallback
= new ListenEntryCallback(callback, isAutoAck);
synchronized (_listenList) {
_listenList.add(entryCallback);
}
listen(entryCallback);
}
@Override
public void removeMessageCallback(MessageCallback callback)
{
ListenEntryCallback listenerCallback = null;
synchronized (_listenList) {
for (int i = _listenList.size() - 1; i >= 0; i--) {
EntryCallback cb = _listenList.get(i);
if (cb.getMessageCallback() == callback) {
listenerCallback = (ListenEntryCallback) cb;
_callbackList.remove(cb);
break;
}
}
}
if (listenerCallback != null) {
listenerCallback.close();
}
synchronized (_queueLock) {
if (listenerCallback != null)
_callbackList.remove(listenerCallback);
}
if (listenerCallback != null) {
listenerCallback.close();
_listenerCount.decrementAndGet();
}
}
//
// abstract receive stubs
//
protected void acknowledge(QE entry)
{
}
protected void readPayload(QE entry)
{
}
/**
* Acknowledges the receipt of a message
*/
@Override
public void acknowledge(String msgId)
{
QE entry = removeEntry(msgId);
if (entry != null)
acknowledge(entry);
}
protected boolean listen(EntryCallback callback)
throws MessageException
{
QE entry = null;
synchronized (_queueLock) {
if (_callbackList.size() > 0 || (entry = readEntry()) == null) {
_callbackList.add(callback);
return false;
}
}
readPayload(entry);
if (callback.entryReceived(entry)) {
acknowledge(entry.getMsgId());
}
return true;
}
protected void dispatchMessage()
{
while (true) {
QE entry = null;
EntryCallback callback = null;
synchronized (_queueLock) {
if (_callbackList.size() == 0 || (entry = readEntry()) == null) {
return;
}
callback = _callbackList.remove(0);
}
readPayload(entry);
if (callback.entryReceived(entry)) {
acknowledge(entry.getMsgId());
}
}
}
//
// Queue statistics JMX
//
/**
* Returns the queue size
*/
@Override
public int getQueueSize()
{
int count = 0;
for (int i = 0; i < _head.length; i++) {
for (QueueEntry entry = _head[i];
entry != null;
entry = entry._next) {
count++;
}
}
return count;
}
/**
* Returns true if a message is available.
*/
@Override
public boolean hasMessage()
{
return getQueueSize() > 0;
}
@Override
public int getConsumerCount()
{
return _listenerCount.get();
}
@Override
public int getReceiverCount()
{
return _receiverCount.get();
}
//
// queue management
//
/**
* Add an entry to the queue
*/
private QE addEntry(QE entry, long expires)
{
int priority = entry.getPriority();
synchronized (_queueLock) {
if (_tail[priority] != null)
_tail[priority]._next = entry;
else
_head[priority] = entry;
_tail[priority] = entry;
}
int size = _queueSize.incrementAndGet();
if (_queueSizeMax < size) {
long timeout = 100;
waitForQueueThrottle(timeout);
}
return entry;
}
private void waitForQueueThrottle(long timeout)
{
_isQueueThrottle.set(true);
synchronized (_isQueueThrottle) {
try {
if (_isQueueThrottle.get()) {
// long timeout = expires - Alarm.getCurrentTimeActual();
if (timeout > 1000)
timeout = 1000;
if (timeout > 0) {
_isQueueThrottle.wait(timeout);
}
}
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
}
}
}
private void wakeQueueThrottle()
{
int size = _queueSize.get();
if (size <= _queueSizeMax) {
if (_isQueueThrottle.compareAndSet(true, false)) {
synchronized (_isQueueThrottle) {
_isQueueThrottle.notifyAll();
}
}
}
}
/**
* Returns the next entry from the queue
*/
protected QE readEntry()
{
QE entry = readEntry(null);
return entry;
}
/**
* Returns the next entry from the queue
*/
protected QE readEntry(QueueEntrySelector selector)
{
for (int i = _head.length - 1; i >= 0; i--) {
for (QE entry = _head[i];
entry != null;
entry = (QE) entry._next) {
if (! entry.isLease()) {
continue;
}
if (entry.isRead()) {
continue;
}
readPayload((QE) entry);
if ((selector != null) && (! selector.isMatch(entry))) {
continue;
}
entry.setReadSequence(_readSequenceGenerator.incrementAndGet());
return entry;
}
}
return null;
}
/**
*
* @param selector
* @return Entries present in the Queue.
*/
public ArrayList getBrowserList()
{
ArrayList entries = new ArrayList();
for (int i = _head.length - 1; i >= 0; i--) {
for (QE entry = _head[i];
entry != null;
entry = (QE) entry._next) {
if (! entry.isLease()) {
continue;
}
if (entry.isRead()) {
continue;
}
readPayload(entry);
entries.add(entry);
}
}
return entries.size() > 0 ? entries : null;
}
/**
* Removes message.
*/
protected QE removeEntry(String msgId)
{
synchronized (_queueLock) {
for (int i = _head.length - 1; i >= 0; i--) {
QE prev = null;
QE entry = _head[i];
while (entry != null) {
QE next = (QE) entry._next;
if (msgId.equals(entry.getMsgId())) {
if (prev != null)
prev._next = entry._next;
else
_head[i] = (QE) entry._next;
if (_tail[i] == entry)
_tail[i] = prev;
_queueSize.decrementAndGet();
if (_isQueueThrottle.get()) {
wakeQueueThrottle();
}
return entry;
}
prev = entry;
entry = next;
}
}
}
return null;
}
/**
* Rolls back the receipt of a message
*/
@Override
public void rollback(String msgId)
{
synchronized (_queueLock) {
for (int i = _head.length - 1; i >= 0; i--) {
for (QueueEntry entry = _head[i];
entry != null;
entry = entry._next) {
if (msgId.equals(entry.getMsgId())) {
if (entry.isRead()) {
entry.setReadSequence(0);
/*
MessageImpl msg = (MessageImpl) getPayload(entry);
if (msg != null)
msg.setJMSRedelivered(true);
*/
}
return;
}
}
}
}
}
public ArrayList getMessageIds()
{
ArrayList browserList = new ArrayList();
synchronized (_queueLock) {
for (int i = 0; i < _head.length; i++) {
for (QueueEntry entry = _head[i];
entry != null;
entry = entry._next) {
browserList.add(entry.getMsgId());
}
}
}
return browserList;
}
/**
* Synchronous timeout receive
*/
class ReceiveEntryCallback implements EntryCallback {
private boolean _isAutoAck;
private Thread _thread;
private volatile QueueEntry _entry;
ReceiveEntryCallback(boolean isAutoAck)
{
_isAutoAck = isAutoAck;
_thread = Thread.currentThread();
}
public MessageCallback getMessageCallback()
{
return null;
}
public boolean entryReceived(QueueEntry entry)
{
_entry = entry;
LockSupport.unpark(_thread);
return _isAutoAck;
}
public QueueEntry waitForEntry(long expireTime)
{
listen(this);
while (_entry == null
&& (CurrentTime.getCurrentTimeActual() < expireTime)) {
LockSupport.parkUntil(expireTime);
}
if (_entry == null) {
synchronized (_queueLock) {
_callbackList.remove(this);
}
}
return _entry;
}
}
/**
* Async listen receive
*/
class ListenEntryCallback implements EntryCallback, Runnable {
private MessageCallback _callback;
private ClassLoader _classLoader;
private boolean _isClosed;
private volatile QueueEntry _entry;
ListenEntryCallback(MessageCallback callback, boolean isAutoAck)
{
_callback = callback;
_classLoader = Thread.currentThread().getContextClassLoader();
}
@Override
public MessageCallback getMessageCallback()
{
return _callback;
}
@Override
public boolean entryReceived(QueueEntry entry)
{
_entry = entry;
_threadPool.schedule(this);
return false;
}
@Override
public void run()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
boolean isValid = false;
long readSequence = _entry.getReadSequence();
try {
thread.setContextClassLoader(_classLoader);
_callback.messageReceived(_entry.getMsgId(), _entry.getPayload());
isValid = true;
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
isValid = true;
} catch (Throwable t) {
log.log(Level.SEVERE, t.toString(), t);
} finally {
thread.setContextClassLoader(oldLoader);
if (readSequence == _entry.getReadSequence()){
acknowledge(_entry.getMsgId());
}
}
if (! _isClosed && isValid) {
listen(this);
}
}
public void close()
{
_isClosed = true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy