Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

MessageQueue.h

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #ifndef INCLUDED_MessageQueue_h_
00003 #define INCLUDED_MessageQueue_h_
00004 
00005 #ifdef PLATFORM_APERIOS
00006 #  warning MessageQueue is not Aperios compatable
00007 #else
00008 
00009 #include "ListMemBuf.h"
00010 #include "RCRegion.h"
00011 #include "LockScope.h"
00012 #include <exception>
00013 
00014 //! Defines the interface for sending new shared memory regions between processes
00015 /*! This base class holds all of the template-independent code to allow general
00016  *  operations on MessageQueues.  The templated version of MessageQueue provides
00017  *  concrete implementation, which is what you would instantiate.
00018  *  
00019  *  Each message entails its own shared memory region, as compared to
00020  *  SharedQueue, where a single large buffer is maintained, and all messages are
00021  *  copied into the common buffer.  This class is better for large regions since
00022  *  it can avoid copying data around. */
00023 class MessageQueueBase {
00024 public:
00025 
00026   //!constructor
00027   MessageQueueBase()
00028     : lock(), overflowPolicy(THROW_BAD_ALLOC), isClosed(false), reportDroppings(false), numMessages(0),
00029       numReceivers(0), messagesRead()
00030   {}
00031   //!destructor
00032   virtual ~MessageQueueBase() {}
00033   
00034   
00035   //!An interface to allow you to receive callbacks when a message has been read from a MessageQueue, subscribed via an external calss which is monitoring the queue's MessageQueueBase::pollStatus() (e.g. LoadFileThread)
00036   class StatusListener {
00037   public:
00038     //! destructor -- does nothing
00039     virtual ~StatusListener() {}
00040     
00041     //! Called after a message has been read by all receivers, and thus has been removed from the queue
00042     /*! In order to allow this to happen, a thread must repeatedly call
00043      *  MessageQueueBase::pollStatus() in order to check if other processes have
00044      *  read their messages since the last pollStatus call.
00045      *
00046      *  Don't assume that because you receive this callback there is space in
00047      *  the queue -- an earlier listener may have already added a message, or
00048      *  the queue might have been already waiting to send a message if
00049      *  #overflowPolicy is #WAIT
00050      *
00051      *  @param which The MessageQueueBase which has had message(s) read
00052      *  @param howmany The number of message which have been cleared */
00053     virtual void messagesRead(MessageQueueBase& which, unsigned int howmany)=0;
00054   };
00055 
00056   //! Checks to see how many messages have been processed (read by all receivers and removed from queue) since the last call to pollStatus()
00057   virtual unsigned int pollStatus() {
00058     AutoLock autolock(lock);
00059     unsigned int read=messagesRead;
00060     messagesRead=0;
00061     return read;
00062   }
00063 
00064   
00065   //! The storage type for message entry indicies
00066   /*! This index is to be used with accessor functions, but may be recycled for
00067    *  a new message after all receivers have read the previous message.  If you
00068    *  wish to have a unique message identifier, see getMessageSN() */
00069   typedef unsigned short index_t;
00070   
00071   
00072   //!< add one to the receiver reference count
00073   virtual void addReceiver()=0;
00074   //!< remove one from the receiver reference count
00075   virtual void removeReceiver()=0;
00076   //! return the receiver reference count
00077   virtual unsigned int getNumReceivers() const { return numReceivers; }
00078 
00079 
00080   //! post a message into the queue -- a shared reference is added, the caller retains control current reference
00081   /*! Thus, if you are sending a region and do not intend to use it again, call
00082    *  RCRegion::RemoveReference() after sending to free the sender's memory.
00083    *  Otherwise, you can continue to access the region, even as the receiver
00084    *  accesses it as well.  If both sides retain references, you can use the
00085    *  region as a shared memory area for future communication.  (beware of race
00086    *  conditions!) */
00087   virtual void sendMessage(RCRegion * rcr)=0;
00088   //! request access to a particular message, increments read counter -- do not call more than once per receiver!
00089   /*! The message is marked read and will be popped from the queue if all
00090    *  receivers have read the message as well.  The caller inherits a reference
00091    *  to the returned region -- call RemoveReference when you are done with
00092    *  it */
00093   virtual RCRegion * readMessage(index_t msg)=0;
00094   //! request access to a particular message, does not mark message -- call as often as you like
00095   /*! The caller inherits a reference to the returned region -- call
00096    *  RemoveReference when you are done with it */
00097   virtual RCRegion * peekMessage(index_t msg)=0;
00098   //! increments read counter -- do not call more than once per receiver!
00099   virtual void markRead(index_t msg)=0;
00100   //! do not allow any new messages to be posted
00101   virtual void close() { AutoLock autolock(lock); isClosed=true; }
00102 
00103   virtual void setReportDroppings(bool report) { reportDroppings=report; }
00104   virtual bool getReportDroppings() const { return reportDroppings; }
00105   
00106   
00107   //! Each message gets a unique, monotonically increasing serial number; this function returns that number (#serialNumber)
00108   virtual unsigned int getMessageSN(index_t msg)=0;
00109   
00110   
00111   //! a typedef to make it easier to obtain a lock on the queue for the extent of a scope
00112   typedef LockScope<ProcessID::NumProcesses> AutoLock;
00113   //! returns a lock on the queue for the scope of the returned storage
00114   AutoLock getLock() const { return AutoLock(lock); }
00115 
00116   
00117   virtual index_t oldest() const=0;          //!< return oldest message still in the queue (may or may not have been read by this process)
00118   virtual index_t newer(index_t it) const=0; //!< return the next message in the queue (may or may not have been read by this process)
00119   virtual index_t older(index_t it) const=0; //!< return the previous message in the queue (may or may not have been read by this process)
00120   virtual index_t newest() const=0;          //!< return most recent message added to the queue (may or may not have been read by this process)
00121   virtual bool isEnd(index_t it) const=0;    //!< returns true if @a it is the one-past-the-end of the queue
00122   
00123   //! an enumerations of policies for dealing with overflow, pass to setOverflowPolicy()
00124   enum OverflowPolicy_t {
00125     DROP_OLDEST,     //!< the oldest unread message is dropped
00126     DROP_NEWEST,     //!< the most recently added message is dropped (i.e. the overflowing message is ignored)
00127     WAIT,            //!< the adding process/thread polls until space is available
00128     THROW_BAD_ALLOC  //!< throw a std::bad_alloc exception (falls through to abort() if you don't catch it)
00129   };
00130   //! allows you to pick how to handle running out of space in the queue, see OverflowPolicy_t
00131   void setOverflowPolicy(OverflowPolicy_t op) { overflowPolicy=op; }
00132   //! returns the current overflow policy, see OverflowPolicy_t
00133   OverflowPolicy_t getOverflowPolicy() const { return overflowPolicy; }
00134   
00135 protected:
00136   //! data storage needed for each message
00137   struct entry {
00138     entry() : id(), sn(), numRead(0) {} //!< constructor
00139     entry(unsigned int serialNumber, RCRegion* r)
00140       : id(r->ID()), sn(serialNumber), numRead(0) {} //!< constructor, pass message info
00141     RCRegion::Identifier id; //! the identifier for the shared memory region so that other regions can attach it
00142     unsigned int sn; //!< serial number for this message (not the same as its index in the queue -- indicies are reused, this id is unique to this message
00143     unsigned int numRead; //!< a count of the number of receivers which have read this message
00144   };
00145   mutable MutexLock<ProcessID::NumProcesses> lock; //!< a lock to grant serial access to the queue
00146   OverflowPolicy_t overflowPolicy; //!< the choice of how to handle message overflow -- see OverflowPolicy_t
00147   bool isClosed; //!< if true, new messages will be rejected
00148   bool reportDroppings; //!< if true, output will be sent on cerr when overflow occurs
00149   unsigned int numMessages; //!< number of messages which have been sent (serial number of next message)
00150   unsigned int numReceivers; //!< how many receivers to expect
00151   unsigned int messagesRead; //!< number of messages which have been read and removed from queue since last call to pollStatus()
00152 };
00153 
00154 //! An implementation of MessageQueueBase, which provides mechanisms for sending shared memory regions between processes
00155 /*! @see MessageQueueBase */
00156 template<unsigned int MAX_UNREAD>
00157 class MessageQueue : public MessageQueueBase {
00158 public:
00159   //! total number of messages which can be backed up in the queue
00160   static const unsigned int CAPACITY=MAX_UNREAD;
00161   
00162   //! constructor
00163   MessageQueue() : MessageQueueBase(), mq() {}
00164   
00165   virtual ~MessageQueue() {
00166     //lock shouldn't be necessary -- refcount should ensure the containing
00167     //region isn't deleted until only one process has access anyway
00168     //AutoLock autolock(lock);
00169     while(!mq.empty()) {
00170       RCRegion * rcr = RCRegion::attach(mq.front().id);
00171       rcr->RemoveSharedReference();
00172       rcr->RemoveReference();
00173       mq.pop_front();
00174     }
00175   }
00176   
00177   virtual void addReceiver() {
00178     AutoLock autolock(lock);
00179     numReceivers++;
00180   }
00181   
00182   virtual void removeReceiver() {
00183     AutoLock autolock(lock);
00184     numReceivers--;
00185     for(index_t it=mq.begin(); it!=mq.end(); it=mq.next(it)) {
00186       if(mq[it].numRead==numReceivers) {
00187         //all *remaining* processes have gotten a look, remove the neutral MessageQueue reference
00188         RCRegion * rcr = RCRegion::attach(mq[it].id);
00189         rcr->RemoveSharedReference();
00190         rcr->RemoveReference();
00191         it=mq.prev(it);
00192         mq.erase(mq.next(it));
00193         messagesRead++;
00194       }
00195     }
00196   }
00197   
00198   virtual void sendMessage(RCRegion * rcr) {
00199     AutoLock autolock(lock);
00200     if(numReceivers==0) {
00201       //if(reportDroppings)
00202       //std::cerr << "Warning: MessageQueue dropping " << rcr->ID().key << " because there are no receivers" << std::endl;
00203       return;
00204     }
00205     if(isClosed) {
00206       if(reportDroppings)
00207         std::cerr << "Warning: MessageQueue dropping " << rcr->ID().key << " because queue is closed" << std::endl;
00208       return;
00209     }
00210     rcr->AddSharedReference();
00211     if(mq.size()==mq.getMaxCapacity()) {
00212       switch(overflowPolicy) {
00213         case DROP_OLDEST: {
00214           if(reportDroppings)
00215             std::cerr << "WARNING: MessageQueue full, dropping oldest unread message (" << mq.front().id.key << ")" << std::endl;
00216           RCRegion * eldest = RCRegion::attach(mq.front().id);
00217           eldest->RemoveSharedReference();
00218           mq.pop_front();
00219           eldest->RemoveReference();
00220         } break;
00221         case DROP_NEWEST:
00222           if(reportDroppings)
00223             std::cerr << "WARNING: MessageQueue full, dropping newest unread message (" << rcr->ID().key << ")" << std::endl;
00224           rcr->RemoveSharedReference();
00225           return;
00226         case WAIT:
00227           if(reportDroppings)
00228             std::cerr << "WARNING: MessageQueue full, waiting for readers to catch up" << std::endl;
00229           while(mq.size()==mq.getMaxCapacity()) {
00230             //have to release locks so readers can get access
00231             unsigned int ll=lock.get_lock_level();
00232             lock.releaseAll();
00233             usleep(MutexLockBase::usleep_granularity*15);
00234             for(unsigned int i=0; i<ll; i++)
00235               lock.lock(ProcessID::getID());
00236           }
00237           break;
00238         case THROW_BAD_ALLOC:
00239           if(reportDroppings)
00240             std::cerr << "WARNING: MessageQueue full, throwing bad_alloc exception" << std::endl;
00241           rcr->RemoveSharedReference();
00242           throw std::bad_alloc();
00243           break;
00244       }
00245     }
00246     if(mq.push_back(entry(numMessages++,rcr))==mq.end()) {
00247       //our overflow policy should've prevented this
00248       std::cerr << "ERROR: MessageQueue unable to add message; buggy overflow policy?" << std::endl;
00249       exit(EXIT_FAILURE);
00250     }
00251   }
00252   
00253   virtual RCRegion * readMessage(index_t msg) {
00254     AutoLock autolock(lock);
00255     RCRegion * rcr = RCRegion::attach(mq[msg].id);
00256     mq[msg].numRead++;
00257     if(mq[msg].numRead==numReceivers) {
00258       //all processes have gotten a look, remove the neutral MessageQueue reference
00259       rcr->RemoveSharedReference();
00260       mq.erase(msg);
00261       messagesRead++;
00262     }
00263     return rcr;
00264   }
00265   
00266   virtual RCRegion * peekMessage(index_t msg) {
00267     //AutoLock autolock(lock); //I don't think a lock is necessary here
00268     return RCRegion::attach(mq[msg].id);
00269   }
00270   
00271   virtual void markRead(index_t msg) {
00272     AutoLock autolock(lock);
00273     mq[msg].numRead++;
00274     if(mq[msg].numRead==numReceivers) {
00275       //all processes have gotten a look, remove the neutral MessageQueue reference
00276       RCRegion * rcr = RCRegion::attach(mq[msg].id);
00277       rcr->RemoveSharedReference();
00278       rcr->RemoveReference();
00279       mq.erase(msg);
00280       messagesRead++;
00281     }
00282   }
00283 
00284   virtual unsigned int getMessageSN(index_t msg) { /*AutoLock autolock(lock);*/ return mq[msg].sn; }
00285   
00286   virtual index_t oldest() const { AutoLock autolock(lock); return mq.begin(); }
00287   virtual index_t newer(index_t it) const { AutoLock autolock(lock); return mq.next(it); }
00288   virtual index_t older(index_t it) const { AutoLock autolock(lock); return mq.prev(it); }
00289   virtual index_t newest() const { AutoLock autolock(lock); return mq.prev(mq.end()); }
00290   virtual bool isEnd(index_t it) const { AutoLock autolock(lock); return it==mq.end() || it>=mq_t::MAX_ENTRIES; }
00291   
00292 protected:
00293   //! shorthand for the type of data storage of message entries
00294   typedef ListMemBuf<entry,MAX_UNREAD,index_t> mq_t;
00295   
00296   //! the data storage of message entries
00297   mq_t mq;
00298 };
00299 
00300 /*! @file
00301  * @brief Defines MessageQueue, which provides mechanisms for sending shared memory regions between processes
00302  * @author ejt (Creator)
00303  *
00304  * $Author: ejt $
00305  * $Name: tekkotsu-2_4_1 $
00306  * $Revision: 1.12 $
00307  * $State: Exp $
00308  * $Date: 2005/08/04 21:32:16 $
00309  */
00310 
00311 #endif //APERIOS check
00312 
00313 #endif //INCLUDED

Tekkotsu v2.4.1
Generated Tue Aug 16 16:32:47 2005 by Doxygen 1.4.4