package org.jgroups.util; import org.jgroups.logging.Log; import org.jgroups.logging.LogFactory; import org.jgroups.TimeoutException; import java.util.*; /** * Elements are added at the tail and removed from the head. Class is thread-safe in that * 1 producer and 1 consumer may add/remove elements concurrently. The class is not * explicitely designed for multiple producers or consumers. Implemented as a linked * list, so that removal of an element at the head does not cause a right-shift of the * remaining elements (as in a Vector-based implementation). * @author Bela Ban */ public class Queue { /*head and the tail of the list so that we can easily add and remove objects*/ private Element head=null, tail=null; /*flag to determine the state of the queue*/ private volatile boolean closed=false; /*current size of the queue*/ private volatile int size=0; /* Lock object for synchronization. Is notified when element is added */ private final Object mutex=new Object(); /** Lock object for syncing on removes. It is notified when an object is removed */ // Object remove_mutex=new Object(); /*the number of end markers that have been added*/ private int num_markers=0; /** * if the queue closes during the runtime * an endMarker object is added to the end of the queue to indicate that * the queue will close automatically when the end marker is encountered * This allows for a "soft" close. * @see Queue#close */ private static final Object endMarker=new Object(); protected static final Log log=LogFactory.getLog(Queue.class); /** * the class Element indicates an object in the queue. * This element allows for the linked list algorithm by always holding a * reference to the next element in the list. * if Element.next is null, then this element is the tail of the list. */ static class Element { /*the actual value stored in the queue*/ Object obj=null; /*pointer to the next item in the (queue) linked list*/ Element next=null; /** * creates an Element object holding its value * @param o - the object to be stored in the queue position */ Element(Object o) { obj=o; } /** * prints out the value of the object */ public String toString() { return obj != null? obj.toString() : "null"; } } /** * creates an empty queue */ public Queue() { } /** * Returns the first element. Returns null if no elements are available. */ public Object getFirst() { synchronized(mutex) { return head != null? head.obj : null; } } /** * Returns the last element. Returns null if no elements are available. */ public Object getLast() { synchronized(mutex) { return tail != null? tail.obj : null; } } /** * returns true if the Queue has been closed * however, this method will return false if the queue has been closed * using the close(true) method and the last element has yet not been received. * @return true if the queue has been closed */ public boolean closed() { synchronized(mutex) { return closed; } } /** * adds an object to the tail of this queue * If the queue has been closed with close(true) no exception will be * thrown if the queue has not been flushed yet. * @param obj - the object to be added to the queue * @exception QueueClosedException exception if closed() returns true */ public void add(Object obj) throws QueueClosedException { if(obj == null) { if(log.isErrorEnabled()) log.error("argument must not be null"); return; } /*lock the queue from other threads*/ synchronized(mutex) { if(closed) throw new QueueClosedException(); if(this.num_markers > 0) throw new QueueClosedException("queue has been closed. You can not add more elements. " + "Waiting for removal of remaining elements."); addInternal(obj); /*wake up all the threads that are waiting for the lock to be released*/ mutex.notifyAll(); } } public void addAll(Collection c) throws QueueClosedException { if(c == null) { if(log.isErrorEnabled()) log.error("argument must not be null"); return; } /*lock the queue from other threads*/ synchronized(mutex) { if(closed) throw new QueueClosedException(); if(this.num_markers > 0) throw new QueueClosedException("queue has been closed. You can not add more elements. " + "Waiting for removal of remaining elements."); Object obj; for(Iterator it=c.iterator(); it.hasNext();) { obj=it.next(); if(obj != null) addInternal(obj); } /*wake up all the threads that are waiting for the lock to be released*/ mutex.notifyAll(); } } public void addAll(List<Object> list) throws QueueClosedException { if(list == null) { if(log.isErrorEnabled()) log.error("argument must not be null"); return; } /*lock the queue from other threads*/ synchronized(mutex) { if(closed) throw new QueueClosedException(); if(this.num_markers > 0) throw new QueueClosedException("queue has been closed. You can not add more elements. " + "Waiting for removal of remaining elements."); for(Object obj: list) { if(obj != null) addInternal(obj); } /*wake up all the threads that are waiting for the lock to be released*/ mutex.notifyAll(); } } /** * Removes 1 element from head or <B>blocks</B> * until next element has been added or until queue has been closed * @return the first element to be taken of the queue */ public Object remove() throws QueueClosedException { Object retval; synchronized(mutex) { /*wait as long as the queue is empty. return when an element is present or queue is closed*/ while(size == 0) { if(closed) throw new QueueClosedException(); try { mutex.wait(); } catch(InterruptedException ex) { } } if(closed) throw new QueueClosedException(); /*remove the head from the queue, if we make it to this point, retval should not be null !*/ retval=removeInternal(); if(retval == null) if(log.isErrorEnabled()) log.error("element was null, should never be the case"); } /* * we ran into an Endmarker, which means that the queue was closed before * through close(true) */ // if(retval == endMarker) { // close(false); // mark queue as closed // throw new QueueClosedException(); // } return retval; } /** * Removes 1 element from the head. * If the queue is empty the operation will wait for timeout ms. * if no object is added during the timeout time, a Timout exception is thrown * (bela Aug 2009) Note that the semantics of remove(long timeout) are weird - the method waits until an element has * been added, but doesn't do so in a loop ! So if we have 10 threads waiting on an empty queue, and 1 thread * adds an element, all 10 threads will return (but only 1 will have the element), therefore 9 will throw * a TimeoutException ! If I change this to the 'correct' semantics, however (e.g. the method removeWait() below), * GMS.ViewHandler doesn't work correctly anymore. I won't change this now, as Queue will get removed anyway in 3.0. * @param timeout - the number of milli seconds this operation will wait before it times out * @return the first object in the queue */ public Object remove(long timeout) throws QueueClosedException, TimeoutException { Object retval; synchronized(mutex) { if(closed) throw new QueueClosedException(); /*if the queue size is zero, we want to wait until a new object is added*/ if(size == 0) { try { /*release the mutex lock and wait no more than timeout ms*/ mutex.wait(timeout); } catch(InterruptedException ex) { } } /*we either timed out, or got notified by the mutex lock object*/ if(closed) throw new QueueClosedException(); /*get the next value*/ retval=removeInternal(); /*null result means we timed out*/ if(retval == null) throw new TimeoutException("timeout=" + timeout + "ms"); /*if we reached an end marker we are going to close the queue*/ // if(retval == endMarker) { // close(false); // throw new QueueClosedException(); // } /*at this point we actually did receive a value from the queue, return it*/ return retval; } } public Object removeWait(long timeout) throws QueueClosedException, TimeoutException { synchronized(mutex) { if(closed) throw new QueueClosedException(); final long end_time=System.currentTimeMillis() + timeout; long wait_time, current_time; /*if the queue size is zero, we want to wait until a new object is added*/ while(size == 0 && (current_time=System.currentTimeMillis()) < end_time) { if(closed) throw new QueueClosedException(); try { /*release the mutex lock and wait no more than timeout ms*/ wait_time=end_time - current_time; // guarnteed to be > 0 mutex.wait(wait_time); } catch(InterruptedException ex) { } } /*we either timed out, or got notified by the mutex lock object*/ if(closed) throw new QueueClosedException(); /*get the next value*/ Object retval=removeInternal(); /*null result means we timed out*/ if(retval == null) throw new TimeoutException("timeout=" + timeout + "ms"); return retval; } } /** * removes a specific object from the queue. * the object is matched up using the Object.equals method. * @param obj the actual object to be removed from the queue */ public void removeElement(Object obj) throws QueueClosedException { Element el, tmp_el; if(obj == null) { if(log.isErrorEnabled()) log.error("argument must not be null"); return; } synchronized(mutex) { if(closed) /*check to see if the queue is closed*/ throw new QueueClosedException(); el=head; /*the queue is empty*/ if(el == null) return; /*check to see if the head element is the one to be removed*/ if(el.obj.equals(obj)) { /*the head element matched we will remove it*/ head=el.next; el.next=null; el.obj=null; /*check if we only had one object left *at this time the queue becomes empty *this will set the tail=head=null */ if(size == 1) tail=head; // null decrementSize(); return; } /*look through the other elements*/ while(el.next != null) { if(el.next.obj.equals(obj)) { tmp_el=el.next; if(tmp_el == tail) // if it is the last element, move tail one to the left (bela Sept 20 2002) tail=el; el.next.obj=null; el.next=el.next.next; // point to the el past the next one. can be null. tmp_el.next=null; tmp_el.obj=null; decrementSize(); break; } el=el.next; } } } /** * returns the first object on the queue, without removing it. * If the queue is empty this object blocks until the first queue object has * been added * @return the first object on the queue */ public Object peek() throws QueueClosedException { Object retval; synchronized(mutex) { while(size == 0) { if(closed) throw new QueueClosedException(); try { mutex.wait(); } catch(InterruptedException ex) { } } if(closed) throw new QueueClosedException(); retval=(head != null)? head.obj : null; } if(retval == endMarker) { close(false); // mark queue as closed throw new QueueClosedException(); } return retval; } /** * returns the first object on the queue, without removing it. * If the queue is empty this object blocks until the first queue object has * been added or the operation times out * @param timeout how long in milli seconds will this operation wait for an object to be added to the queue * before it times out * @return the first object on the queue */ public Object peek(long timeout) throws QueueClosedException, TimeoutException { Object retval; synchronized(mutex) { if(size == 0) { if(closed) throw new QueueClosedException(); try { mutex.wait(timeout); } catch(InterruptedException ex) { } } if(closed) throw new QueueClosedException(); retval=head != null? head.obj : null; if(retval == null) throw new TimeoutException("timeout=" + timeout + "ms"); if(retval == endMarker) { close(false); throw new QueueClosedException(); } return retval; } } /** Removes all elements from the queue. This method can succeed even when the queue is closed */ public void clear() { synchronized(mutex) { head=tail=null; size=0; num_markers=0; mutex.notifyAll(); } } /** Marks the queues as closed. When an <code>add</code> or <code>remove</code> operation is attempted on a closed queue, an exception is thrown. @param flush_entries When true, a end-of-entries marker is added to the end of the queue. Entries may be added and removed, but when the end-of-entries marker is encountered, the queue is marked as closed. This allows to flush pending messages before closing the queue. */ public void close(boolean flush_entries) { synchronized(mutex) { if(flush_entries && size > 0) { try { add(endMarker); // add an end-of-entries marker to the end of the queue num_markers++; } catch(QueueClosedException closed_ex) { } return; } closed=true; mutex.notifyAll(); } } /** Waits until the queue has been closed. Returns immediately if already closed * @param timeout Number of milliseconds to wait. A value <= 0 means to wait forever */ public void waitUntilClosed(long timeout) { synchronized(mutex) { if(closed) return; try { mutex.wait(timeout); } catch(InterruptedException e) { } } } /** * resets the queue. * This operation removes all the objects in the queue and marks the queue open */ public void reset() { synchronized(mutex) { num_markers=0; if(!closed) close(false); size=0; head=null; tail=null; closed=false; mutex.notifyAll(); } } /** * Returns all the elements of the queue * @return A copy of the queue */ public LinkedList values() { LinkedList retval=new LinkedList(); synchronized(mutex) { Element el=head; while(el != null) { retval.add(el.obj); el=el.next; } } return retval; } /** * returns the number of objects that are currently in the queue */ public int size() { synchronized(mutex) { return size - num_markers; } } /** * prints the size of the queue */ public String toString() { return "Queue (" + size() + ") elements"; } /* ------------------------------------- Private Methods ----------------------------------- */ private final void addInternal(Object obj) { /*create a new linked list element*/ Element el=new Element(obj); /*check the first element*/ if(head == null) { /*the object added is the first element*/ /*set the head to be this object*/ head=el; /*set the tail to be this object*/ tail=head; /*set the size to be one, since the queue was empty*/ size=1; } else { /*add the object to the end of the linked list*/ tail.next=el; /*set the tail to point to the last element*/ tail=el; /*increase the size*/ size++; } } /** * Removes the first element. Returns null if no elements in queue. * Always called with mutex locked (we don't have to lock mutex ourselves) */ private Object removeInternal() { Element retval; Object obj; /*if the head is null, the queue is empty*/ if(head == null) return null; retval=head; // head must be non-null now head=head.next; if(head == null) tail=null; decrementSize(); if(head != null && head.obj == endMarker) { closed=true; mutex.notifyAll(); } retval.next=null; obj=retval.obj; retval.obj=null; return obj; } /** Doesn't need to be synchronized; is always called from synchronized methods */ final private void decrementSize() { size--; if(size < 0) size=0; } /* ---------------------------------- End of Private Methods -------------------------------- */ }