* almost rewrote BMessageQueue; simplified code, removed over-extensive documentation,

cleanup.
* made BMessageQueue::IsLocked() const - the non-const version is still provided
  for binary compatibility.
* Both BMessageQueue::FindMessage() versions are now thread safe, the queue's BLocker
  is now mutable to allow for this.
* renamed BMessage::link to fQueueLink as the "Message4" implementation uses it.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@14955 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2005-11-16 13:01:59 +00:00
parent 02ed46b0de
commit 1ba67cc8c6
5 changed files with 234 additions and 468 deletions
+1 -1
View File
@@ -399,7 +399,7 @@ static BBlockCache *sMsgCache;
void da_swap_var_sized(dyn_array *da); void da_swap_var_sized(dyn_array *da);
void da_swap_fixed_sized(dyn_array *da); void da_swap_fixed_sized(dyn_array *da);
BMessage *link; BMessage *fQueueLink;
int32 fTarget; int32 fTarget;
BMessage *fOriginal; BMessage *fOriginal;
uint32 fChangeCount; uint32 fChangeCount;
+45 -54
View File
@@ -1,65 +1,56 @@
// /*
// $Id: MessageQueue.h 1686 2002-10-26 18:59:16Z beveloper $ * Copyright 2001-2005, Haiku, Inc. All Rights Reserved.
// * Distributed under the terms of the MIT License.
// This is the BMessageQueue interface for OpenBeOS. It has been created */
// to be source and binary compatible with the BeOS version of #ifndef _MESSAGE_QUEUE_H
// BMessageQueue. #define _MESSAGE_QUEUE_H
//
#ifndef _OPENBEOS_MESSAGEQUEUE_H
#define _OPENBEOS_MESSAGEQUEUE_H
#include <Locker.h> #include <Locker.h>
#include <Message.h> /* For convenience */ #include <Message.h>
/* For convenience */
#ifdef USE_OPENBEOS_NAMESPACE
namespace OpenBeOS {
#endif
class BMessageQueue { class BMessageQueue {
public: public:
BMessageQueue(); BMessageQueue();
virtual ~BMessageQueue(); virtual ~BMessageQueue();
void AddMessage(BMessage *message);
void RemoveMessage(BMessage *message);
int32 CountMessages(void) const;
bool IsEmpty(void) const;
BMessage *FindMessage(int32 index) const;
BMessage *FindMessage(uint32 what, int32 index=0) const;
bool Lock(void);
void Unlock(void);
bool IsLocked(void);
BMessage *NextMessage(void); void AddMessage(BMessage* message);
void RemoveMessage(BMessage* message);
private: int32 CountMessages() const;
bool IsEmpty() const;
// Reserved space in the vtable for future changes to BMessageQueue BMessage* FindMessage(int32 index) const;
virtual void _ReservedMessageQueue1(void); BMessage* FindMessage(uint32 what, int32 index = 0) const;
virtual void _ReservedMessageQueue2(void);
virtual void _ReservedMessageQueue3(void); bool Lock();
void Unlock();
BMessageQueue(const BMessageQueue &); bool IsLocked() const;
BMessageQueue &operator=(const BMessageQueue &);
BMessage *NextMessage();
BMessage *fTheQueue;
BMessage *fQueueTail; private:
int32 fMessageCount; // Reserved space in the vtable for future changes to BMessageQueue
BLocker fLocker; virtual void _ReservedMessageQueue1();
virtual void _ReservedMessageQueue2();
// Reserved space for future changes to BMessageQueue virtual void _ReservedMessageQueue3();
uint32 fReservedSpace[3];
BMessageQueue(const BMessageQueue &);
BMessageQueue &operator=(const BMessageQueue &);
bool IsLocked();
// this needs to be exported for R5 compatibility and should
// be dropped as soon as possible
private:
BMessage* fHead;
BMessage* fTail;
int32 fMessageCount;
mutable BLocker fLock;
uint32 _reserved[3];
}; };
#ifdef USE_OPENBEOS_NAMESPACE #endif // _MESSAGE_QUEUE_H
}
#endif
#endif // _OPENBEOS_MESSAGEQUEUE_H
+2 -2
View File
@@ -476,7 +476,7 @@ BMessage& BMessage::operator=(const BMessage& msg)
{ {
what = msg.what; what = msg.what;
link = msg.link; fQueueLink = msg.fQueueLink;
fTarget = msg.fTarget; fTarget = msg.fTarget;
fOriginal = msg.fOriginal; fOriginal = msg.fOriginal;
fChangeCount = msg.fChangeCount; fChangeCount = msg.fChangeCount;
@@ -506,7 +506,7 @@ void BMessage::init_data()
{ {
what = 0; what = 0;
link = NULL; fQueueLink = NULL;
fTarget = B_NULL_TOKEN; fTarget = B_NULL_TOKEN;
fOriginal = NULL; fOriginal = NULL;
fChangeCount = 0; fChangeCount = 0;
+2 -2
View File
@@ -472,7 +472,7 @@ BMessage::operator=(const BMessage &msg)
{ {
what = msg.what; what = msg.what;
link = msg.link; fQueueLink = msg.fQueueLink;
fTarget = msg.fTarget; fTarget = msg.fTarget;
fOriginal = msg.fOriginal; fOriginal = msg.fOriginal;
fChangeCount = msg.fChangeCount; fChangeCount = msg.fChangeCount;
@@ -504,7 +504,7 @@ BMessage::init_data()
{ {
what = 0; what = 0;
link = NULL; fQueueLink = NULL;
fTarget = B_NULL_TOKEN; fTarget = B_NULL_TOKEN;
fOriginal = NULL; fOriginal = NULL;
fChangeCount = 0; fChangeCount = 0;
+184 -409
View File
@@ -1,501 +1,276 @@
//------------------------------------------------------------------------------ /*
// Copyright (c) 2001-2002, OpenBeOS * Copyright 2001-2005, Haiku, Inc. All Rights Reserved.
// * Distributed under the terms of the MIT License.
// Permission is hereby granted, free of charge, to any person obtaining a *
// copy of this software and associated documentation files (the "Software"), * Authors:
// to deal in the Software without restriction, including without limitation * Unknown? Eric?
// the rights to use, copy, modify, merge, publish, distribute, sublicense, * Axel Dörfler, axeld@pinc-software.de
// and/or sell copies of the Software, and to permit persons to whom the */
// Software is furnished to do so, subject to the following conditions:
// /** Queue for holding BMessages */
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// File Name: MessageQueue.cpp
// Author(s): unknown
//
// Description: Queue for holding BMessages
//
//------------------------------------------------------------------------------
#include <MessageQueue.h> #include <MessageQueue.h>
#include <Autolock.h> #include <Autolock.h>
#include <Message.h> #include <Message.h>
#ifdef USE_OPENBEOS_NAMESPACE
namespace OpenBeOS {
#endif
/*
* Method: BMessageQueue::BMessageQueue()
* Descr: This method is the only constructor for a BMessageQueue. Once the
* constructor completes, the BMessageQueue is created with no BMessages
* in it.
*
*/
BMessageQueue::BMessageQueue() BMessageQueue::BMessageQueue()
: fTheQueue(NULL), :
fQueueTail(NULL), fHead(NULL),
fTail(NULL),
fMessageCount(0), fMessageCount(0),
fLocker("BMessageQueue_fLocker") fLock("BMessageQueue Lock")
{ {
} }
/* /*!
* Method: BMessageQueue::~BMessageQueue() \brief This is the desctructor for the BMessageQueue. It iterates over
* Descr: This is the desctructor for the BMessageQueue. It iterates over any messages left on the queue and deletes them.
* any messages left on the queue and deletes them.
* The implementation is careful not to release the lock when the
* The implementation is careful not to release the lock when the BMessageQueue is deconstructed. If the lock is released, it is
* BMessageQueue is deconstructed. If the lock is released, it is possible another thread will start an AddMessage() operation before
* possible another thread will start an AddMessage() operation before the BLocker is deleted. The safe thing to do is not to unlock the
* the BLocker is deleted. The safe thing to do is not to unlock the BLocker from the destructor once it is acquired. That way, any thread
* BLocker from the destructor once it is acquired. That way, any thread waiting to do a AddMessage() will fail to acquire the lock since the
* waiting to do a AddMessage() will fail to acquire the lock since the BLocker will be deleted before they can acquire it.
* BLocker will be deleted before they can acquire it. */
*
*/
BMessageQueue::~BMessageQueue() BMessageQueue::~BMessageQueue()
{ {
if (fLocker.Lock()) { if (!Lock())
BMessage *theMessage = fTheQueue; return;
while (theMessage != NULL) {
BMessage *messageToDelete = theMessage; BMessage* message = fHead;
#ifndef USING_MESSAGE4 while (message != NULL) {
theMessage = theMessage->link; BMessage *next = message->fQueueLink;
#else
theMessage = theMessage->fQueueLink; delete message;
#endif message = next;
delete messageToDelete;
}
} }
} }
/* /*!
* Method: BMessageQueue::AddMessage() \brief This method adds a BMessage to the queue.
* Descr: This method adds a BMessage to the queue. It makes a couple of
* assumptions: It makes a couple of assumptions:
* - The BMessage was allocated on the heap with new. Since the - The BMessage was allocated on the heap with new, since the
* destructor delete's BMessages left on the queue, this must be destructor delete's BMessages left on the queue.
* true. The same assumption is made with Be's implementation. - The BMessage is not already on this or any other BMessageQueue.
* - The BMessage is not already on this or any other BMessageQueue. If it is, the queue it is already on will be corrupted.
* If it is, the queue it is already on will be corrupted. Be's */
* implementation makes this assumption also and does corrupt
* BMessageQueues where this is violated.
*
*/
void void
BMessageQueue::AddMessage(BMessage *message) BMessageQueue::AddMessage(BMessage* message)
{ {
if (message == NULL) { if (message == NULL)
return; return;
}
BAutolock _(fLock);
// The Be implementation does not seem to check that the lock acquisition if (!IsLocked())
// was successful. This will specifically cause problems when the return;
// message queue is deleted. On delete, any thread waiting for the lock
// will be notified that the lock failed. Be's implementation, because // The message passed in will be the last message on the queue so its
// they do not check proceeds with the operation, potentially corrupting // link member should be set to null.
// memory. This implementation is different, but I can't imagine that message->fQueueLink = NULL;
// Be's implementation is worth emulating.
// fMessageCount++;
BAutolock theAutoLocker(fLocker);
if (fTail == NULL) {
if (theAutoLocker.IsLocked()) { // there are no messages in the queue yet
fHead = fTail = message;
// The message passed in will be the last message on the queue so its } else {
// link member should be set to null. // just add it after the tail
#ifndef USING_MESSAGE4 fTail->fQueueLink = message;
message->link = NULL; fTail = message;
#else
message->fQueueLink = NULL;
#endif
// We now have one more BMessage on the queue.
fMessageCount++;
// If there are no BMessages on the queue.
if (fQueueTail == NULL) {
// Then this message is both the start and the end of the queue.
fTheQueue = message;
fQueueTail = message;
} else {
// If there are already messages on the queue, then the put this
// BMessage at the end. The last BMessage prior to this AddMessage()
// is fQueueTail. The BMessage at fQueueTail needs to point to the
// new last message, the one being added.
#ifndef USING_MESSAGE4
fQueueTail->link = message;
#else
fQueueTail->fQueueLink = message;
#endif
// Now update the fQueueTail to point to this new last message.
fQueueTail = message;
}
} }
} }
/* /*!
* Method: BMessageQueue::RemoveMessage() \brief This method searches the queue for a particular BMessage.
* Descr: This method searches the queue for a particular BMessage. If If it is found, it is removed from the queue.
* it is found, it is removed from the queue. */
*
*/
void void
BMessageQueue::RemoveMessage(BMessage *message) BMessageQueue::RemoveMessage(BMessage* message)
{ {
if (message == NULL) { if (message == NULL)
return; return;
}
BAutolock _(fLock);
BAutolock theAutoLocker(fLocker); if (!IsLocked())
return;
// The Be implementation does not seem to check that the lock acquisition
// was successful. This will specifically cause problems when the BMessage* last = NULL;
// message queue is deleted. On delete, any thread waiting for the lock for (BMessage* entry = fHead; entry != NULL; entry = entry->fQueueLink) {
// will be notified that the lock failed. Be's implementation, because if (entry == message) {
// they do not check proceeds with the operation, potentially corrupting // remove this one
// memory. This implementation is different, but I can't imagine that if (entry == fHead)
// Be's implementation is worth emulating. fHead = entry->fQueueLink;
// if (entry == fTail)
if (theAutoLocker.IsLocked()) { fTail = last;
// If the message to be removed is at the front of the queue.
if (fTheQueue == message) {
// We need to special case the handling of removing the first element.
// First, the new front element will be the next one.
#ifndef USING_MESSAGE4
fTheQueue = fTheQueue->link;
#else
fTheQueue = fTheQueue->fQueueLink;
#endif
// Must decrement the count of elements since the front one is being
// removed.
fMessageCount--; fMessageCount--;
// If the new front element is NULL, then that means that the queue
// is now empty. That means that fQueueTail must be set to NULL.
if (fTheQueue == NULL) {
fQueueTail = NULL;
}
// We have found the message and removed it in this case. We can
// bail out now. The autolocker will take care of releasing the
// lock for us.
return; return;
} }
last = entry;
// The message to remove is not the first one, so we need to scan the
// queue. Get a message iterator and set it to the first element.
BMessage *messageIter = fTheQueue;
// While we are not at the end of the list.
while (messageIter != NULL) {
// If the next message after this (ie second, then third etc) is
// the one we are looking for.
#ifndef USING_MESSAGE4
if (messageIter->link == message) {
#else
if (messageIter->fQueueLink == message) {
#endif
// At this point, this is what we have:
// messageIter - the BMessage in the queue just before the
// match
// messageIter->link - the BMessage which matches message
// message - the same as messageIter->link
// message->link - the element after the match
//
// The next step is to link the BMessage just before the match
// to the one just after the match. This removes the match from
// the queue.
#ifndef USING_MESSAGE4
messageIter->link = message->link;
#else
messageIter->fQueueLink = message->fQueueLink;
#endif
// One less element on the queue.
fMessageCount--;
// If there is no BMessage after the match is the
#ifndef USING_MESSAGE4
if (message->link == NULL) {
#else
if (message->fQueueLink == NULL) {
#endif
// That means that we just removed the last element from the
// queue. The new last element then must be messageIter.
fQueueTail = messageIter;
}
// We can return now because we have a match and removed it.
return;
}
// No match yet, go to the next element in the list.
#ifndef USING_MESSAGE4
messageIter = messageIter->link;
#else
messageIter = messageIter->fQueueLink;
#endif
}
} }
} }
/* /*!
* Method: BMessageQueue::CountMessages() \brief This method just returns the number of BMessages on the queue.
* Descr: This method just returns the number of BMessages on the queue. */
*/
int32 int32
BMessageQueue::CountMessages(void) const BMessageQueue::CountMessages() const
{ {
return fMessageCount; return fMessageCount;
} }
/* /*!
* Method: BMessageQueue::IsEmpty() \brief This method just returns true if there are no BMessages on the queue.
* Descr: This method just returns true if there are no BMessages on the queue. */
*/
bool bool
BMessageQueue::IsEmpty(void) const BMessageQueue::IsEmpty() const
{ {
return (fMessageCount == 0); return fMessageCount == 0;
} }
/* /*!
* Method: BMessageQueue::FindMessage() \brief This method searches the queue for the index'th BMessage.
* Descr: This method searches the queue for the index'th BMessage. The first
* BMessage is at index 0, the second at index 1 etc. The BMessage The first BMessage is at index 0, the second at index 1 etc.
* is returned if it is found. If no BMessage exists at that index The BMessage is returned if it is found. If no BMessage exists at that
* (ie the queue is not that long or the index is invalid) NULL is index (ie the queue is not that long or the index is invalid) NULL is
* returned. returned.
* */
* This method does not lock the BMessageQueue so there is risk that
* the queue could change in the course of the search. Be's
* implementation must do the same, unless they do some funky casting.
* The method is declared const which means it cannot modify the data
* members. Because it cannot modify the data members, it cannot
* acquire a lock. So unless they are casting away the const-ness
* of the this pointer, this member in Be's implementation does no
* locking either.
*/
BMessage * BMessage *
BMessageQueue::FindMessage(int32 index) const BMessageQueue::FindMessage(int32 index) const
{ {
// If the index is negative or larger than the number of messages on the BAutolock _(fLock);
// queue. if (!IsLocked())
if ((index < 0) || (index >= fMessageCount)) { return NULL;
// No match is possible, bail out now.
if (index < 0 || index >= fMessageCount)
return NULL; return NULL;
}
// Get a message iterator and initialize it to the start of the queue. for (BMessage* message = fHead; message != NULL; message = message->fQueueLink) {
BMessage *messageIter = fTheQueue;
// While this is not the end of the queue.
while (messageIter != NULL) {
// If the index reaches zero, then we have found a match. // If the index reaches zero, then we have found a match.
if (index == 0) { if (index == 0)
// Because this is a match, break out of the while loop so we can return message;
// return the message pointed to messageIter.
break;
}
// No match yet, decrement the index. We will have a match once index
// reaches zero.
index--; index--;
// Increment the messageIter to the next BMessage on the queue.
#ifndef USING_MESSAGE4
messageIter = messageIter->link;
#else
messageIter = messageIter->fQueueLink;
#endif
} }
// If no match was found, messageIter will be NULL since that is the only return NULL;
// way out of the loop. If a match was found, the messageIter will point
// to that match.
return messageIter;
} }
/* /*!
* Method: BMessageQueue::FindMessage() \brief Searches the queue for the index'th BMessage that has a
* Descr: This method searches the queue for the index'th BMessage that has a particular what code.
* particular what code. The first BMessage with that what value is at */
* index 0, the second at index 1 etc. The BMessage is returned if it
* is found. If no matching BMessage exists at that index NULL is
* returned.
*
* This method does not lock the BMessageQueue so there is risk that
* the queue could change in the course of the search. Be's
* implementation must do the same, unless they do some funky casting.
* The method is declared const which means it cannot modify the data
* members. Because it cannot modify the data members, it cannot
* acquire a lock. So unless they are casting away the const-ness
* of the this pointer, this member in Be's implementation does no
* locking either.
*/
BMessage * BMessage *
BMessageQueue::FindMessage(uint32 what, BMessageQueue::FindMessage(uint32 what, int32 index) const
int32 index) const
{ {
// If the index is negative or larger than the number of messages on the BAutolock _(fLock);
// queue. if (!IsLocked())
if ((index < 0) || (index >= fMessageCount)) {
// No match is possible, bail out now.
return NULL; return NULL;
}
if (index < 0 || index >= fMessageCount)
// Get a message iterator and initialize it to the start of the queue. return NULL;
BMessage *messageIter = fTheQueue;
for (BMessage* message = fHead; message != NULL; message = message->fQueueLink) {
// While this is not the end of the queue. if (message->what == what) {
while (messageIter != NULL) {
// If the messageIter points to a BMessage with the what code we are
// looking for.
if (messageIter->what == what) {
// If the index reaches zero, then we have found a match. // If the index reaches zero, then we have found a match.
if (index == 0) { if (index == 0)
// Because this is a match, break out of the while loop so we can return message;
// return the message pointed to messageIter.
break;
}
// No match yet, decrement the index. We will have a match once index
// reaches zero.
index--; index--;
} }
// Increment the messageIter to the next BMessage on the queue.
#ifndef USING_MESSAGE4
messageIter = messageIter->link;
#else
messageIter = messageIter->fQueueLink;
#endif
} }
// If no match was found, messageIter will be NULL since that is the only return NULL;
// way out of the loop. If a match was found, the messageIter will point
// to that match.
return messageIter;
} }
/* /*!
* Method: BMessageQueue::Lock() \brief Locks the BMessageQueue so no other thread can change
* Descr: This member just locks the BMessageQueue so no other thread can acquire or search the queue.
* the lock nor make changes to the queue through members like */
* AddMessage(), RemoveMessage(), NextMessage() or ~BMessageQueue().
*/
bool bool
BMessageQueue::Lock(void) BMessageQueue::Lock()
{ {
return fLocker.Lock(); return fLock.Lock();
} }
/* /*!
* Method: BMessageQueue::Unlock() \brief Releases the lock which was acquired by Lock().
* Descr: This member releases the lock which was acquired by Lock(). */
*/
void void
BMessageQueue::Unlock(void) BMessageQueue::Unlock()
{ {
fLocker.Unlock(); fLock.Unlock();
} }
/* /*!
* Method: BMessageQueue::IsLocked() \brief Returns whether or not the queue is locked
* Descr: This member returns whether or not the queue is locked */
*/ bool
bool BMessageQueue::IsLocked() const
BMessageQueue::IsLocked(void)
{ {
return fLocker.IsLocked(); return fLock.IsLocked();
} }
/* /*!
* Method: BMessageQueue::NextMessage() \brief Removes the first BMessage on the queue and returns
* Descr: This member removes the first BMessage on the queue and returns it to the caller. If the queue is empty, NULL is returned.
* it to the caller. If the queue is empty, NULL is returned. */
*/
BMessage * BMessage *
BMessageQueue::NextMessage(void) BMessageQueue::NextMessage()
{ {
// By default, we will assume that no BMessage is on the queue. BAutolock _(fLock);
BMessage *result = NULL; if (!IsLocked())
BAutolock theAutoLocker(fLocker); return NULL;
// The Be implementation does not seem to check that the lock acquisition // remove the head of the queue, if any, and return it
// was successful. This will specifically cause problems when the
// message queue is deleted. On delete, any thread waiting for the lock BMessage* head = fHead;
// will be notified that the lock failed. Be's implementation, because if (head == NULL)
// they do not check proceeds with the operation, potentially corrupting return NULL;
// memory. This implementation is different, but I can't imagine that
// Be's implementation is worth emulating. fMessageCount--;
// fHead = head->fQueueLink;
if (theAutoLocker.IsLocked()) {
// Store the first BMessage in the queue in result. if (fHead == NULL) {
result = fTheQueue; // If the queue is empty after removing the front element,
// we need to set the tail of the queue to NULL since the queue
// If the queue is not empty. // is now empty.
if (fTheQueue != NULL) { fTail = NULL;
// Decrement the message count since we are removing an element.
fMessageCount--;
// The new front of the list is moved forward thereby removing the
// first element from the queue.
#ifndef USING_MESSAGE4
fTheQueue = fTheQueue->link;
#else
fTheQueue = fTheQueue->fQueueLink;
#endif
// If the queue is empty after removing the front element.
if (fTheQueue == NULL) {
// We need to set the tail of the queue to NULL since the queue
// is now empty.
fQueueTail = NULL;
}
}
} }
return result;
return head;
} }
void /*!
BMessageQueue::_ReservedMessageQueue1(void) \brief This method is only here for R5 binary compatibility!
It should be dropped as soon as possible (it misses the const qualifier).
*/
bool
BMessageQueue::IsLocked()
{ {
return fLock.IsLocked();
} }
void void BMessageQueue::_ReservedMessageQueue1() {}
BMessageQueue::_ReservedMessageQueue2(void) void BMessageQueue::_ReservedMessageQueue2() {}
{ void BMessageQueue::_ReservedMessageQueue3() {}
}
void
BMessageQueue::_ReservedMessageQueue3(void)
{
}
#ifdef USE_OPENBEOS_NAMESPACE
}
#endif