/*
* Copyright (c) 1998-2011 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.bam.mailbox;
import com.caucho.bam.packet.Packet;
import com.caucho.util.Alarm;
import com.caucho.util.L10N;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.*;
/**
* See http://www.cs.rochester.edu/u/michael/PODC96.html
*/
public final class MailboxQueue
{
private static final L10N L = new L10N(MailboxQueue.class);
private static final Logger log
= Logger.getLogger(MailboxQueue.class.getName());
private final String _name;
private final int _discardMaxSize;
private final int _blockMaxSize;
private final long _expireTimeout;
private final AtomicReference<QueueItem> _head
= new AtomicReference<QueueItem>();
private final AtomicReference<QueueItem> _tail
= new AtomicReference<QueueItem>();
private final AtomicInteger _size = new AtomicInteger();
private final AtomicInteger _blockCount = new AtomicInteger();
private final Object _blockLock = new Object();
public MailboxQueue(String name,
int discardMaxSize,
int blockMaxSize,
long expireTimeout)
{
_name = name;
if (discardMaxSize > Integer.MAX_VALUE / 2 || discardMaxSize < 0)
discardMaxSize = Integer.MAX_VALUE / 2;
if (discardMaxSize == 0)
throw new IllegalArgumentException(L.l("discardMaxSize may not be zero"));
_discardMaxSize = discardMaxSize;
if (blockMaxSize > Integer.MAX_VALUE / 2 || blockMaxSize < 0)
blockMaxSize = Integer.MAX_VALUE / 2;
if (blockMaxSize == 0)
throw new IllegalArgumentException(L.l("blockMaxSize may not be zero"));
_blockMaxSize = blockMaxSize;
if (expireTimeout > Long.MAX_VALUE / 2 || expireTimeout < 0)
expireTimeout = Long.MAX_VALUE / 2;
if (expireTimeout == 0)
throw new IllegalArgumentException(L.l("expireTimeout may not be zero"));
_expireTimeout = expireTimeout;
_head.set(new QueueItem(null));
_tail.set(_head.get());
}
/**
* Returns the number of packets in the queue.
*/
public final int getSize()
{
return _size.get();
}
/**
* Returns true if the queue is empty
*/
public final boolean isEmpty()
{
QueueItem tail = _tail.get();
return tail.getPacket() == null;
}
public final void enqueue(Packet packet)
{
while (_discardMaxSize < _size.get()) {
if (log.isLoggable(Level.FINE)) {
log.fine(this + " dropping overflow packets size=" + _size.get()
+ " maxSize=" + _discardMaxSize);
}
dequeue();
}
if (_blockMaxSize < _size.get()) {
if (log.isLoggable(Level.FINE)) {
log.fine(this + " blocking due to overflow packets size=" + _size.get()
+ " maxSize=" + _blockMaxSize);
}
_blockCount.incrementAndGet();
try {
synchronized (_blockLock) {
if (_blockMaxSize < _size.get()) {
_blockLock.wait(1000);
}
}
} catch (Exception e) {
log.log(Level.ALL, e.toString(), e);
} finally {
_blockCount.decrementAndGet();
}
}
QueueItem item = new QueueItem(packet);
while (true) {
QueueItem tail = _tail.get();
QueueItem next = tail.getNext();
// check for consistency
if (tail != _tail.get()) {
continue;
}
if (next != null) {
// if tail has new data, move the tail pointer
_tail.compareAndSet(tail, next);
}
else if (tail.compareAndSetNext(next, item)) {
// link the tail to the new packet
// and move the _tail pointer to the new tail
_tail.compareAndSet(tail, item);
// increment the size
_size.incrementAndGet();
return;
}
}
}
/**
* Returns the first packet from the queue.
*/
public final Packet dequeue()
{
int spinCount = 4; // how many times spinning before returning null
while (true) {
QueueItem head = _head.get();
QueueItem tail = _tail.get();
QueueItem next = head.getNext();
if (head != _head.get()) { // loop if another thread has modified
continue;
}
if (head == tail) { // empty queue cases
if (next != null) { // tail has new data
_tail.compareAndSet(tail, next);
continue;
}
else if (--spinCount > 0) { // empty queue, retry spinCount times
continue;
}
else {
return null; // finally return null
}
}
if (next == null) { // empty queue, but item not removed
// XXX: impossible case?
continue;
}
// remove item from queue
if (_head.compareAndSet(head, next)) {
_size.decrementAndGet();
Packet packet = next.getPacket();
next.clearPacket();
// wake any blocked threads
if (_blockCount.get() > 0 && _size.get() < _blockMaxSize) {
synchronized (_blockLock) {
_blockLock.notifyAll();
}
}
// only return live packets
long createTime = next.getCreateTime();
long now = Alarm.getCurrentTime();
if (createTime > 0 && now <= createTime + _expireTimeout) {
return packet;
}
}
}
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + _name + "]";
}
static class QueueItem
{
private static final AtomicReferenceFieldUpdater<QueueItem,QueueItem> _casNext
= AtomicReferenceFieldUpdater.newUpdater(QueueItem.class,
QueueItem.class,
"_next");
private volatile QueueItem _next;
private Packet _packet;
private final long _createTime;
/**
* Creates a packet with a destination and a source.
*
* @param to the destination address
* @param from the source address
*/
public QueueItem(Packet packet)
{
_createTime = Alarm.getCurrentTime();
_packet = packet;
}
/**
* Creation time
*/
public final long getCreateTime()
{
return _createTime;
}
/**
* Returns the 'next' field
*/
public final QueueItem getNext()
{
return _next;
}
public final Packet getPacket()
{
return _packet;
}
public final void clearPacket()
{
_packet = null;
}
/**
* Compare and set next
*/
public final boolean compareAndSetNext(QueueItem expect, QueueItem update)
{
return _casNext.compareAndSet(this, expect, update);
}
public String toString()
{
return getClass().getSimpleName() + "[" + _packet + "]";
}
}
}