BMessageQueue Use Cases and Implementation Details:

This document describes the BMessageQueue interface and some basics of how it is implemented. The document has the following sections:

  1. BMessageQueue Interface
  2. BMessageQueue Use Cases
  3. BMessageQueue Implementation

BMessageQueue Interface:

The BMessageQueue class is a simple class for managing a queue of BMessages. The best source of information for the BMessageQueue interface can be found here in the Be Book.

BMessageQueue Use Cases:

The following use cases cover the BMessageQueue functionality:

  1. Construction: A BMessageQueue does not take any arguments when it is constructed. After construction, the queue is empty.

  2. Destruction: When a BMessageQueue is deconstructed, all BMessages on the queue are deleted. This implies that all BMessages added to a BMessageQueue must be allocated on the heap with the new operation. The BMessageQueue is locked before performing the delete to ensure that the queue doesn't change while it is being deleted. The lock is never released from the destructor to ensure that any AddMessage() or other members blocking for the lock never succeed in getting the lock. They will fail once the BMessageQueue is completely deleted.

  3. Add Message 1: When AddMessage() is used to put a BMessage on the queue, the BMessageQueue takes ownership of the BMessage. The BMessage should be created on the heap but there is no checking to ensure that has happened. The BMessageQueue records the order in which the BMessages are added to the queue.

  4. Add Message 2: No check is performed to see if the BMessage is already part of that BMessageQueue or any other when AddMessage() is used to put a BMessage on a queue. An attempt to add a BMessage to a BMessageQueue which already has that same BMessage in it (where equality is based on the address of the BMessage) will corrupt the queue. An attempt to add a BMessage to a BMessageQueue when that BMessage is already in another BMessageQueue will corrupt the original BMessageQueue. It is up to the caller of AddMessage() to use RemoveMessage() to prevent queue corruption.

  5. Add Message 3: BMessage's can be added using AddMessage() from multiple threads at the same time with no risk of queue corruption. Similarly, RemoveMessage() or NextMessage() can be executing from another thread while an AddMessage() is started without corrupting the queue. An AddMessage() attempt will block if another thread has used the Lock() member to lock the BMessageQueue.

  6. Remove Message 1: The RemoveMessage() member takes a BMessage pointer. The BMessageQueue is searched for this BMessage pointer. If that pointer is in the queue, then it is removed from the queue. The BMessage is not deleted (note the BeBook implies it is but in fact it is not). If the BMessage pointer is not on this BMessageQueue, this call has no affect. After this call completes successfully, it is as though AddMessage() was never called for this BMessage pointer.

  7. Remove Message 2: BMessage's can be removed using RemoveMessage() from multiple threads at the same time with no risk of queue corruption. Similarly, AddMessage() or NextMessage() can be executing from another thread while a RemoveMessage() is started without corrupting the queue. A RemoveMessage() attempt will block if another thread has used the Lock() member to lock the BMessageQueue.

  8. Count Messages: The CountMessages() member function returns the number of BMessage's in the BMessageQueue. If there are no messages on the queue (an example of this situation is just after construction), the member returns 0. Note, it is possible for the count to become corrupted if the situation in Add Message 2 occurs.

  9. Is Empty: The IsEmpty() member function returns true if there are no BMessages on the BMessageQueue. If there are one or more BMessages on the BMessageQueue, it returns false.

  10. Find Message 1: The FindMessage() member function is overloaded. If the member function takes a single int32 argument, it is used to return the BMessage on the queue at a particular index as indicated by the argument. The first message is at index 0, the second at index 1 etc. If no message is at that index, NULL is returned.

  11. Find Message 2: The other FindMessage() member function takes a single uint32 argument and an optional int32 argument. The first mandatory argument specifies the "what" code for the BMessage being searched for. The second optional argument specifies what occurance of that "what" code in a BMessage on the queue should be returned. If the second argument is not provided, it is assumed to be 0. If the second argument is 0, the first BMessage that has the what code provided is returned. If the second argument is 1, the second BMessage that has the what code provided is returned, etc. If no match is found, NULL is returned.

  12. Lock 1: The Lock() member function blocks until this thread can acquire exclusive access to the BMessageQueue. Only one thread can hold the lock at any one time. A thread can acquire the Lock() multiple times but release it the same number of times. While a thread holds the lock, other threads will not be able to perform AddMessage(), RemoveMessage() or NextMessage(). Any threads attempting to do so will block until the BMessageQueue is unlocked.

  13. Lock 2: The Lock() member function returns true if the lock has successfully been acquired. It will return false if an unrecoverable error has occurred. An example of such an error would be deleting the BMessageQueue.

  14. Unlock: The Unlock() member function releases a lock on the BMessageQueue. If the thread no longer holds any locks on the BMessageQueue, other threads are free to acquire the BMessageQueue lock or call member functions like AddMessage(), RemoveMessage(), NextMessage() or delete the BMessageQueue.

  15. Next Message 1: The NextMessage() member function removes the BMessage which is the oldest on the queue. It returns that BMessage to the caller. After the call completes, the BMessage is no longer on the queue and the next oldest BMessage is at the front of the queue (ie next to be returned by NextMessage()).

  16. Next Message 2: BMessage's can be removed using NextMessage() from multiple threads at the same time with no risk of queue corruption. Similarly, AddMessage() or RemoveMessage() can be executing from another thread while a NextMessage() is started without corrupting the queue. A NextMessage() attempt will block if another thread has used the Lock() member to lock the BMessageQueue.

BMessageQueue Implementation:

Internally, the BMessageQueue uses a BLocker to ensure that member functions like AddMessage(), RemoveMessage() and NextMessage() do not corrupt the queue when used from multiple threads. The same BLocker is used to implemented the Lock() and Unlock() members.

Testing with a debugger shows that the queue is implemented using a "link" pointer in the BMessage class itself. Each BMessage is also a singly linked list which represents the queue. All the BMessageQueue needs is a pointer to the BMessage which starts the list. For performance reasons, it is worth maintaining a pointer to the BMessage at the end of the list and the count of the number of elements in the list. If these are not maintained, adding an element to the list will get slower as the number of elements grows and the cost to determine the number of elements in the list will be high.

Because the BMessageQueue uses the link pointer which is a private part of the BMessage class, the BMessageQueue must be a friend class of BMessage. Checking the headers, this is in fact the case in Be's implementation. Although friendship in classes can cause some pretty serious long term headaches if abused (and I am not convinced that this is an abuse), the OpenBeOS implementation will follow the same implementation for now.