* when we terminate gracefully and in the looper's thread, we no longer unlock ourselves. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@15155 a95241bf-73f2-0310-859d-f6bbb57e9c96
1572 lines
34 KiB
C++
1572 lines
34 KiB
C++
/*
|
|
* Copyright 2001-2005, Haiku.
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Authors:
|
|
* Erik Jaesler ([email protected])
|
|
* DarkWyrm ([email protected])
|
|
* Ingo Weinhold, bonefish@@users.sf.net
|
|
* Axel Dörfler, [email protected]
|
|
*/
|
|
|
|
/** BLooper class spawns a thread that runs a message loop. */
|
|
|
|
/**
|
|
@note Although I'm implementing "by the book" for now, I would like to
|
|
refactor sLooperList and all of the functions that operate on it
|
|
into their own class in the BPrivate namespace.
|
|
|
|
Also considering adding the thread priority when archiving.
|
|
*/
|
|
|
|
// debugging
|
|
//#define DBG(x) x
|
|
#define DBG(x) ;
|
|
#define PRINT(x) DBG({ printf("[%6ld] ", find_thread(NULL)); printf x; })
|
|
|
|
/*
|
|
#include <Autolock.h>
|
|
#include <Locker.h>
|
|
static BLocker sDebugPrintLocker("BLooper debug print");
|
|
#define PRINT(x) DBG({ \
|
|
BAutolock _(sDebugPrintLocker); \
|
|
debug_printf("[%6ld] ", find_thread(NULL)); \
|
|
debug_printf x; \
|
|
})
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <Autolock.h>
|
|
#include <Looper.h>
|
|
#include <Message.h>
|
|
#include <MessageFilter.h>
|
|
#include <MessageQueue.h>
|
|
#include <Messenger.h>
|
|
#include <PropertyInfo.h>
|
|
|
|
#include <LooperList.h>
|
|
#include <MessagePrivate.h>
|
|
#include <ObjectLocker.h>
|
|
#include <TokenSpace.h>
|
|
|
|
|
|
#define FILTER_LIST_BLOCK_SIZE 5
|
|
#define DATA_BLOCK_SIZE 5
|
|
|
|
// Globals ---------------------------------------------------------------------
|
|
using BPrivate::gDefaultTokens;
|
|
using BPrivate::gLooperList;
|
|
using BPrivate::BObjectLocker;
|
|
using BPrivate::BLooperList;
|
|
|
|
port_id _get_looper_port_(const BLooper* looper);
|
|
|
|
uint32 BLooper::sLooperID = (uint32)B_ERROR;
|
|
team_id BLooper::sTeamID = (team_id)B_ERROR;
|
|
|
|
enum {
|
|
BLOOPER_PROCESS_INTERNALLY = 0,
|
|
BLOOPER_HANDLER_BY_INDEX
|
|
};
|
|
|
|
static property_info gLooperPropInfo[] = {
|
|
{
|
|
"Handler",
|
|
{},
|
|
{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
|
|
NULL, BLOOPER_HANDLER_BY_INDEX,
|
|
{},
|
|
{},
|
|
{}
|
|
},
|
|
{
|
|
"Handlers",
|
|
{B_GET_PROPERTY},
|
|
{B_DIRECT_SPECIFIER},
|
|
NULL, BLOOPER_PROCESS_INTERNALLY,
|
|
{B_MESSENGER_TYPE},
|
|
{},
|
|
{}
|
|
},
|
|
{
|
|
"Handler",
|
|
{B_COUNT_PROPERTIES},
|
|
{B_DIRECT_SPECIFIER},
|
|
NULL, BLOOPER_PROCESS_INTERNALLY,
|
|
{B_INT32_TYPE},
|
|
{},
|
|
{}
|
|
},
|
|
{}
|
|
};
|
|
|
|
struct _loop_data_ {
|
|
BLooper* looper;
|
|
thread_id thread;
|
|
};
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
BLooper::BLooper(const char* name, int32 priority, int32 port_capacity)
|
|
: BHandler(name)
|
|
{
|
|
InitData(name, priority, port_capacity);
|
|
}
|
|
|
|
|
|
BLooper::~BLooper()
|
|
{
|
|
if (fRunCalled && !fTerminating) {
|
|
debugger("You can't call delete on a BLooper object "
|
|
"once it is running.");
|
|
}
|
|
|
|
Lock();
|
|
|
|
// In case the looper thread calls Quit() fLastMessage is not deleted.
|
|
if (fLastMessage) {
|
|
delete fLastMessage;
|
|
fLastMessage = NULL;
|
|
}
|
|
|
|
// Close the message port and read and reply to the remaining messages.
|
|
if (fMsgPort > 0)
|
|
close_port(fMsgPort);
|
|
|
|
BMessage *msg;
|
|
// Clear the queue so our call to IsMessageWaiting() below doesn't give
|
|
// us bogus info
|
|
while ((msg = fQueue->NextMessage()) != NULL) {
|
|
delete msg; // msg will automagically post generic reply
|
|
}
|
|
|
|
do {
|
|
delete ReadMessageFromPort(0);
|
|
// msg will automagically post generic reply
|
|
} while (IsMessageWaiting());
|
|
|
|
delete fQueue;
|
|
delete_port(fMsgPort);
|
|
|
|
// Clean up our filters
|
|
SetCommonFilterList(NULL);
|
|
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
RemoveHandler(this);
|
|
|
|
// Remove all the "child" handlers
|
|
BHandler *child;
|
|
while (CountHandlers()) {
|
|
child = HandlerAt(0);
|
|
if (child)
|
|
RemoveHandler(child);
|
|
}
|
|
|
|
Unlock();
|
|
RemoveLooper(this);
|
|
delete_sem(fLockSem);
|
|
}
|
|
|
|
|
|
BLooper::BLooper(BMessage *data)
|
|
: BHandler(data)
|
|
{
|
|
int32 portCapacity;
|
|
if (data->FindInt32("_port_cap", &portCapacity) != B_OK
|
|
|| portCapacity < 0)
|
|
portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY;
|
|
|
|
InitData(Name(), B_NORMAL_PRIORITY, portCapacity);
|
|
}
|
|
|
|
|
|
BArchivable *
|
|
BLooper::Instantiate(BMessage *data)
|
|
{
|
|
if (validate_instantiation(data, "BLooper"))
|
|
return new BLooper(data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::Archive(BMessage *data, bool deep) const
|
|
{
|
|
status_t status = BHandler::Archive(data, deep);
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
port_info info;
|
|
status = get_port_info(fMsgPort, &info);
|
|
if (status == B_OK)
|
|
status = data->AddInt32("_port_cap", info.capacity);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::PostMessage(uint32 command)
|
|
{
|
|
BMessage message(command);
|
|
return _PostMessage(&message, this, NULL);
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::PostMessage(BMessage *message)
|
|
{
|
|
return _PostMessage(message, this, NULL);
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::PostMessage(uint32 command, BHandler *handler,
|
|
BHandler *replyTo)
|
|
{
|
|
BMessage message(command);
|
|
return _PostMessage(&message, handler, replyTo);
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::PostMessage(BMessage *message, BHandler *handler,
|
|
BHandler *replyTo)
|
|
{
|
|
return _PostMessage(message, handler, replyTo);
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::DispatchMessage(BMessage *message, BHandler *handler)
|
|
{
|
|
PRINT(("BLooper::DispatchMessage(%.4s)\n", (char*)&message->what));
|
|
/** @note
|
|
Initially, DispatchMessage() was locking the looper, calling the
|
|
filtering API, determining whether to use fPreferred or not, and
|
|
deleting the message. A look at the BeBook, however, reveals that
|
|
all this function does is handle its own B_QUIT_REQUESTED messages
|
|
and pass everything else to handler->MessageReceived(). Clearly the
|
|
rest must be happening in task_looper(). This makes a lot of sense
|
|
because otherwise every derived class would have to figure out when
|
|
to use fPreferred, handle the locking and filtering and delete the
|
|
message. Even if the BeBook didn't say as much, it would make total
|
|
sense to hoist that functionality out of here and into task_looper().
|
|
*/
|
|
switch (message->what) {
|
|
case _QUIT_:
|
|
// Can't call Quit() to do this, because of the slight chance
|
|
// another thread with have us locked between now and then.
|
|
fTerminating = true;
|
|
|
|
// After returning from DispatchMessage(), the looper will be
|
|
// deleted in _task0_()
|
|
break;
|
|
|
|
case B_QUIT_REQUESTED:
|
|
if (handler == this) {
|
|
_QuitRequested(message);
|
|
break;
|
|
}
|
|
|
|
// fall through
|
|
|
|
default:
|
|
handler->MessageReceived(message);
|
|
break;
|
|
}
|
|
PRINT(("BLooper::DispatchMessage() done\n"));
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::MessageReceived(BMessage *msg)
|
|
{
|
|
// TODO: verify
|
|
// The BeBook says this "simply calls the inherited function. ...the BLooper
|
|
// implementation does nothing of importance." Which is not the same as
|
|
// saying it does nothing. Investigate.
|
|
BHandler::MessageReceived(msg);
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BLooper::CurrentMessage() const
|
|
{
|
|
return fLastMessage;
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BLooper::DetachCurrentMessage()
|
|
{
|
|
BMessage* msg = fLastMessage;
|
|
fLastMessage = NULL;
|
|
return msg;
|
|
}
|
|
|
|
|
|
BMessageQueue*
|
|
BLooper::MessageQueue() const
|
|
{
|
|
return fQueue;
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::IsMessageWaiting() const
|
|
{
|
|
AssertLocked();
|
|
|
|
if (!fQueue->IsEmpty())
|
|
return true;
|
|
|
|
/**
|
|
@note: What we're doing here differs slightly from the R5 implementation.
|
|
It appears that they probably return count != 0, which gives an
|
|
incorrect true result when port_buffer_size_etc() would block --
|
|
which indicates that the port's buffer is empty, so we should return
|
|
false. Since we don't actually care about what the error is, we
|
|
just return count > 0. This has some interesting consequences in
|
|
that we will correctly return 'false' if the port is empty
|
|
(B_WOULD_BLOCK), whereas R5 will return true. We call that a bug
|
|
where I come from. ;)
|
|
*/
|
|
int32 count;
|
|
do {
|
|
count = port_buffer_size_etc(fMsgPort, B_RELATIVE_TIMEOUT, 0);
|
|
} while (count == B_INTERRUPTED);
|
|
|
|
return count > 0;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::AddHandler(BHandler* handler)
|
|
{
|
|
if (handler == NULL)
|
|
return;
|
|
|
|
AssertLocked();
|
|
|
|
if (handler->Looper() == NULL) {
|
|
fHandlers.AddItem(handler);
|
|
handler->SetLooper(this);
|
|
if (handler != this) // avoid a cycle
|
|
handler->SetNextHandler(this);
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::RemoveHandler(BHandler* handler)
|
|
{
|
|
if (handler == NULL)
|
|
return false;
|
|
|
|
AssertLocked();
|
|
|
|
if (handler->Looper() == this && fHandlers.RemoveItem(handler)) {
|
|
if (handler == fPreferred)
|
|
fPreferred = NULL;
|
|
|
|
handler->SetNextHandler(NULL);
|
|
handler->SetLooper(NULL);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
int32
|
|
BLooper::CountHandlers() const
|
|
{
|
|
AssertLocked();
|
|
|
|
return fHandlers.CountItems();
|
|
}
|
|
|
|
|
|
BHandler*
|
|
BLooper::HandlerAt(int32 index) const
|
|
{
|
|
AssertLocked();
|
|
|
|
return (BHandler*)fHandlers.ItemAt(index);
|
|
}
|
|
|
|
|
|
int32
|
|
BLooper::IndexOf(BHandler* handler) const
|
|
{
|
|
AssertLocked();
|
|
|
|
return fHandlers.IndexOf(handler);
|
|
}
|
|
|
|
|
|
BHandler*
|
|
BLooper::PreferredHandler() const
|
|
{
|
|
return fPreferred;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::SetPreferredHandler(BHandler* handler)
|
|
{
|
|
if (handler && handler->Looper() == this && IndexOf(handler) >= 0) {
|
|
fPreferred = handler;
|
|
} else {
|
|
fPreferred = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
thread_id
|
|
BLooper::Run()
|
|
{
|
|
AssertLocked();
|
|
|
|
if (fRunCalled) {
|
|
// Not allowed to call Run() more than once
|
|
debugger("can't call BLooper::Run twice!");
|
|
return fTaskID;
|
|
}
|
|
|
|
fTaskID = spawn_thread(_task0_, Name(), fInitPriority, this);
|
|
if (fTaskID < B_OK)
|
|
return fTaskID;
|
|
|
|
if (fMsgPort < B_OK)
|
|
return fMsgPort;
|
|
|
|
fRunCalled = true;
|
|
Unlock();
|
|
|
|
status_t err = resume_thread(fTaskID);
|
|
if (err < B_OK)
|
|
return err;
|
|
|
|
return fTaskID;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::Quit()
|
|
{
|
|
PRINT(("BLooper::Quit()\n"));
|
|
|
|
if (!IsLocked()) {
|
|
printf("ERROR - you must Lock a looper before calling Quit(), "
|
|
"team=%ld, looper=%s\n", Team(), Name() ? Name() : "unnamed");
|
|
}
|
|
|
|
// Try to lock
|
|
if (!Lock()) {
|
|
// We're toast already
|
|
return;
|
|
}
|
|
|
|
PRINT((" is locked\n"));
|
|
|
|
if (!fRunCalled) {
|
|
PRINT((" Run() has not been called yet\n"));
|
|
fTerminating = true;
|
|
delete this;
|
|
} else if (find_thread(NULL) == fTaskID) {
|
|
PRINT((" We are the looper thread\n"));
|
|
fTerminating = true;
|
|
delete this;
|
|
exit_thread(0);
|
|
} else {
|
|
PRINT((" Run() has already been called and we are not the looper thread\n"));
|
|
|
|
// As with sem in _Lock(), we need to cache this here in case the looper
|
|
// disappears before we get to the wait_for_thread() below
|
|
thread_id tid = Thread();
|
|
|
|
// We need to unlock here. Otherwise the looper thread can't
|
|
// dispatch the _QUIT_ message we're going to post.
|
|
UnlockFully();
|
|
|
|
// As per the BeBook, if we've been called by a thread other than
|
|
// our own, the rest of the message queue has to get processed. So
|
|
// we put this in the queue, and when it shows up, we'll call Quit()
|
|
// from our own thread.
|
|
// A little testing with BMessageFilter shows _QUIT_ is being used here.
|
|
// I got suspicious when my test QuitRequested() wasn't getting called
|
|
// when Quit() was invoked from another thread. Makes a nice proof that
|
|
// this is how it's handled, too.
|
|
|
|
while (PostMessage(_QUIT_) == B_WOULD_BLOCK) {
|
|
// There's a slight chance that PostMessage() will return B_WOULD_BLOCK
|
|
// because the port is full, so we'll wait a bit and re-post until
|
|
// we won't block.
|
|
snooze(25000);
|
|
}
|
|
|
|
// We have to wait until the looper is done processing any remaining messages.
|
|
int32 temp;
|
|
while (wait_for_thread(tid, &temp) == B_INTERRUPTED)
|
|
;
|
|
}
|
|
|
|
PRINT(("BLooper::Quit() done\n"));
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::QuitRequested()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::Lock()
|
|
{
|
|
// Defer to global _Lock(); see notes there
|
|
return _Lock(this, -1, B_INFINITE_TIMEOUT) == B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::Unlock()
|
|
{
|
|
PRINT(("BLooper::Unlock()\n"));
|
|
// Make sure we're locked to begin with
|
|
AssertLocked();
|
|
|
|
// Decrement fOwnerCount
|
|
--fOwnerCount;
|
|
PRINT((" fOwnerCount now: %ld\n", fOwnerCount));
|
|
// Check to see if the owner still wants a lock
|
|
if (fOwnerCount == 0) {
|
|
// Set fOwner to invalid thread_id (< 0)
|
|
fOwner = -1;
|
|
|
|
// Decrement requested lock count (using fAtomicCount for this)
|
|
/* int32 atomicCount =*/ atomic_add(&fAtomicCount, -1);
|
|
PRINT((" fAtomicCount now: %ld\n", fAtomicCount));
|
|
|
|
// Check if anyone is waiting for a lock
|
|
// bonefish: Currently _Lock() always acquires the semaphore.
|
|
// if (atomicCount > 0)
|
|
{
|
|
// release the lock
|
|
release_sem(fLockSem);
|
|
}
|
|
}
|
|
PRINT(("BLooper::Unlock() done\n"));
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::IsLocked() const
|
|
{
|
|
// We have to lock the list for the call to IsLooperValid(). Has the side
|
|
// effect of not letting the looper get deleted while we're here.
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
|
|
if (!ListLock.IsLocked()) {
|
|
// If we can't lock the list, our semaphore is probably toast
|
|
return false;
|
|
}
|
|
|
|
if (!IsLooperValid(this)) {
|
|
// The looper is gone, so of course it's not locked
|
|
return false;
|
|
}
|
|
|
|
// Got this from Jeremy's BLocker implementation
|
|
return find_thread(NULL) == fOwner;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::LockWithTimeout(bigtime_t timeout)
|
|
{
|
|
return _Lock(this, -1, timeout);
|
|
}
|
|
|
|
|
|
thread_id
|
|
BLooper::Thread() const
|
|
{
|
|
return fTaskID;
|
|
}
|
|
|
|
|
|
team_id
|
|
BLooper::Team() const
|
|
{
|
|
return sTeamID;
|
|
}
|
|
|
|
|
|
BLooper*
|
|
BLooper::LooperForThread(thread_id tid)
|
|
{
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
if (ListLock.IsLocked())
|
|
return gLooperList.LooperForThread(tid);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
thread_id
|
|
BLooper::LockingThread() const
|
|
{
|
|
return fOwner;
|
|
}
|
|
|
|
|
|
int32
|
|
BLooper::CountLocks() const
|
|
{
|
|
return fOwnerCount;
|
|
}
|
|
|
|
|
|
int32
|
|
BLooper::CountLockRequests() const
|
|
{
|
|
return fAtomicCount;
|
|
}
|
|
|
|
|
|
sem_id
|
|
BLooper::Sem() const
|
|
{
|
|
return fLockSem;
|
|
}
|
|
|
|
|
|
BHandler*
|
|
BLooper::ResolveSpecifier(BMessage* msg, int32 index,
|
|
BMessage* specifier, int32 form, const char* property)
|
|
{
|
|
/**
|
|
@note When I was first dumping the results of GetSupportedSuites() from
|
|
various classes, the use of the extra_data field was quite
|
|
mysterious to me. Then I dumped BApplication and compared the
|
|
result against the BeBook's docs for scripting BApplication. A
|
|
bunch of it isn't documented, but what is tipped me to the idea
|
|
that the extra_data is being used as a quick and dirty way to tell
|
|
what scripting "command" has been sent, e.g., for easy use in a
|
|
switch statement. Would certainly be a lot faster than a bunch of
|
|
string comparisons -- which wouldn't tell the whole story anyway,
|
|
because of the same name being used for multiple properties.
|
|
*/
|
|
BPropertyInfo PropertyInfo(gLooperPropInfo);
|
|
uint32 data;
|
|
status_t err = B_OK;
|
|
const char* errMsg = "";
|
|
if (PropertyInfo.FindMatch(msg, index, specifier, form, property, &data) >= 0) {
|
|
switch (data) {
|
|
case BLOOPER_PROCESS_INTERNALLY:
|
|
return this;
|
|
|
|
case BLOOPER_HANDLER_BY_INDEX:
|
|
{
|
|
int32 index = specifier->FindInt32("index");
|
|
if (form == B_REVERSE_INDEX_SPECIFIER) {
|
|
index = CountHandlers() - index;
|
|
}
|
|
BHandler* target = HandlerAt(index);
|
|
if (target) {
|
|
// Specifier has been fully handled
|
|
msg->PopSpecifier();
|
|
return target;
|
|
} else {
|
|
err = B_BAD_INDEX;
|
|
errMsg = "handler index out of range";
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
err = B_BAD_SCRIPT_SYNTAX;
|
|
errMsg = "Didn't understand the specifier(s)";
|
|
break;
|
|
}
|
|
} else {
|
|
return BHandler::ResolveSpecifier(msg, index, specifier, form,
|
|
property);
|
|
}
|
|
|
|
BMessage Reply(B_MESSAGE_NOT_UNDERSTOOD);
|
|
Reply.AddInt32("error", err);
|
|
Reply.AddString("message", errMsg);
|
|
msg->SendReply(&Reply);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::GetSupportedSuites(BMessage* data)
|
|
{
|
|
if (data == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
status_t status = data->AddString("Suites", "suite/vnd.Be-handler");
|
|
if (status == B_OK) {
|
|
BPropertyInfo PropertyInfo(gLooperPropInfo);
|
|
status = data->AddFlat("message", &PropertyInfo);
|
|
if (status == B_OK)
|
|
status = BHandler::GetSupportedSuites(data);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::AddCommonFilter(BMessageFilter* filter)
|
|
{
|
|
if (!filter)
|
|
return;
|
|
|
|
AssertLocked();
|
|
|
|
if (filter->Looper()) {
|
|
debugger("A MessageFilter can only be used once.");
|
|
return;
|
|
}
|
|
|
|
if (!fCommonFilters)
|
|
fCommonFilters = new BList(FILTER_LIST_BLOCK_SIZE);
|
|
|
|
filter->SetLooper(this);
|
|
fCommonFilters->AddItem(filter);
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::RemoveCommonFilter(BMessageFilter* filter)
|
|
{
|
|
AssertLocked();
|
|
|
|
if (!fCommonFilters)
|
|
return false;
|
|
|
|
bool result = fCommonFilters->RemoveItem(filter);
|
|
if (result)
|
|
filter->SetLooper(NULL);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::SetCommonFilterList(BList* filters)
|
|
{
|
|
// We have a somewhat serious problem here. It is entirely possible in R5
|
|
// to assign a given list of filters to *two* BLoopers simultaneously. This
|
|
// becomes problematic when the loopers are destroyed: the last looper
|
|
// destroyed will have a problem when it tries to delete a filter list that
|
|
// has already been deleted. In R5, this results in a general protection
|
|
// fault. We fix this by checking the filter list for ownership issues.
|
|
|
|
AssertLocked();
|
|
|
|
BMessageFilter* filter;
|
|
if (filters) {
|
|
// Check for ownership issues
|
|
for (int32 i = 0; i < filters->CountItems(); ++i) {
|
|
filter = (BMessageFilter*)filters->ItemAt(i);
|
|
if (filter->Looper()) {
|
|
debugger("A MessageFilter can only be used once.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fCommonFilters) {
|
|
for (int32 i = 0; i < fCommonFilters->CountItems(); ++i) {
|
|
delete (BMessageFilter*)fCommonFilters->ItemAt(i);
|
|
}
|
|
|
|
delete fCommonFilters;
|
|
fCommonFilters = NULL;
|
|
}
|
|
|
|
// Per the BeBook, we take ownership of the list
|
|
fCommonFilters = filters;
|
|
if (fCommonFilters) {
|
|
for (int32 i = 0; i < fCommonFilters->CountItems(); ++i) {
|
|
filter = (BMessageFilter*)fCommonFilters->ItemAt(i);
|
|
filter->SetLooper(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
BList*
|
|
BLooper::CommonFilterList() const
|
|
{
|
|
return fCommonFilters;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::Perform(perform_code d, void* arg)
|
|
{
|
|
// This is sort of what we're doing for this function everywhere
|
|
return BHandler::Perform(d, arg);
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BLooper::MessageFromPort(bigtime_t timeout)
|
|
{
|
|
return ReadMessageFromPort(timeout);
|
|
}
|
|
|
|
|
|
void BLooper::_ReservedLooper1() {}
|
|
void BLooper::_ReservedLooper2() {}
|
|
void BLooper::_ReservedLooper3() {}
|
|
void BLooper::_ReservedLooper4() {}
|
|
void BLooper::_ReservedLooper5() {}
|
|
void BLooper::_ReservedLooper6() {}
|
|
|
|
|
|
BLooper::BLooper(const BLooper&)
|
|
{
|
|
// Copy construction not allowed
|
|
}
|
|
|
|
|
|
BLooper& BLooper::operator=(const BLooper& )
|
|
{
|
|
// Looper copying not allowed
|
|
return *this;
|
|
}
|
|
|
|
|
|
BLooper::BLooper(int32 priority, port_id port, const char* name)
|
|
{
|
|
// This must be a legacy constructor
|
|
fMsgPort = port;
|
|
InitData(name, priority, B_LOOPER_PORT_DEFAULT_CAPACITY);
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::_PostMessage(BMessage *msg, BHandler *handler,
|
|
BHandler *replyTo)
|
|
{
|
|
BObjectLocker<BLooperList> listLocker(gLooperList);
|
|
if (!listLocker.IsLocked())
|
|
return B_ERROR;
|
|
|
|
if (!IsLooperValid(this))
|
|
return B_BAD_VALUE;
|
|
|
|
// Does handler belong to this looper?
|
|
if (handler && handler->Looper() != this)
|
|
return B_MISMATCHED_VALUES;
|
|
|
|
status_t status;
|
|
BMessenger messenger(handler, this, &status);
|
|
if (status == B_OK)
|
|
status = messenger.SendMessage(msg, replyTo, 0);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::_Lock(BLooper* loop, port_id port, bigtime_t timeout)
|
|
{
|
|
PRINT(("BLooper::_Lock(%p, %lx)\n", loop, port));
|
|
/**
|
|
@note The assumption I'm under here is that since we can get the port of
|
|
the BLooper directly from the BLooper itself, the port parameter is
|
|
for identifying BLoopers by port_id when a pointer to the BLooper in
|
|
question is not available. So this function has two modes:
|
|
o When loop != NULL, use it directly
|
|
o When loop == NULL and port is valid, use the port_id to get
|
|
the looper
|
|
I scoured the docs to find out what constitutes a valid port_id to
|
|
no avail. Since create_port uses the standard error values in its
|
|
returned port_id, I'll assume that anything less than zero is a safe
|
|
bet as an *invalid* port_id. I'm guessing that, like thread and
|
|
semaphore ids, anything >= zero is valid. So, the short version of
|
|
this reads: if you don't want to find by port_id, make port = -1.
|
|
|
|
Another assumption I'm making is that Lock() and LockWithTimeout()
|
|
are covers for this function. If it turns out that we don't really
|
|
need this function, I may refactor this code into LockWithTimeout()
|
|
and have Lock() call it instead. This function could then be
|
|
removed.
|
|
*/
|
|
|
|
// Check params (loop, port)
|
|
if (!loop && port < 0)
|
|
{
|
|
PRINT(("BLooper::_Lock() done 1\n"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
// forward declared so I can use BAutolock on sLooperListLock
|
|
thread_id curThread;
|
|
sem_id sem;
|
|
|
|
/**
|
|
@note We lock the looper list at the start of the lock operation to
|
|
prevent the looper getting removed from the list while we're
|
|
doing list operations. Also ensures that the looper doesn't
|
|
get deleted here (since ~BLooper() has to lock the list as
|
|
well to remove itself).
|
|
*/
|
|
{
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
if (!ListLock.IsLocked())
|
|
{
|
|
// If we can't lock, the semaphore is probably
|
|
// gone, which leaves us in no-man's land
|
|
PRINT(("BLooper::_Lock() done 2\n"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
// Look up looper by port_id, if necessary
|
|
if (!loop)
|
|
{
|
|
loop = LooperForPort(port);
|
|
if (!loop)
|
|
{
|
|
PRINT(("BLooper::_Lock() done 3\n"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check looper validity
|
|
if (!IsLooperValid(loop))
|
|
{
|
|
PRINT(("BLooper::_Lock() done 4\n"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
}
|
|
|
|
// Is the looper trying to lock itself?
|
|
// Check for nested lock attempt
|
|
curThread = find_thread(NULL);
|
|
if (curThread == loop->fOwner)
|
|
{
|
|
// Bump fOwnerCount
|
|
++loop->fOwnerCount;
|
|
PRINT(("BLooper::_Lock() done 5: fOwnerCount: %ld\n", loop->fOwnerCount));
|
|
return B_OK;
|
|
}
|
|
|
|
// Something external to the looper is attempting to lock
|
|
// Cache the semaphore
|
|
sem = loop->fLockSem;
|
|
|
|
// Validate the semaphore
|
|
if (sem < 0)
|
|
{
|
|
PRINT(("BLooper::_Lock() done 6\n"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
|
|
// Bump the requested lock count (using fAtomicCount for this)
|
|
atomic_add(&loop->fAtomicCount, 1);
|
|
|
|
// sLooperListLock automatically released here
|
|
}
|
|
|
|
/**
|
|
@note We have to operate with the looper list unlocked during semaphore
|
|
acquisition so that the rest of the application doesn't have to
|
|
wait for this lock to happen. This is why we cached fLockSem
|
|
earlier -- with the list unlocked, the looper might get deleted
|
|
right out from under us. This is also why we use a raw semaphore
|
|
instead of the easier-to-deal-with BLocker; you can't cache a
|
|
BLocker.
|
|
*/
|
|
// acquire the lock
|
|
status_t err;
|
|
do
|
|
{
|
|
err = acquire_sem_etc(sem, 1, B_RELATIVE_TIMEOUT, timeout);
|
|
} while (err == B_INTERRUPTED);
|
|
|
|
if (!err)
|
|
{
|
|
// Assign current thread to fOwner
|
|
loop->fOwner = curThread;
|
|
// Reset fOwnerCount to 1
|
|
loop->fOwnerCount = 1;
|
|
}
|
|
|
|
PRINT(("BLooper::_Lock() done: %lx\n", err));
|
|
return err;
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::_LockComplete(BLooper *looper, int32 old, thread_id this_tid,
|
|
sem_id sem, bigtime_t timeout)
|
|
{
|
|
// What is this for? Hope I'm not missing something conceptually here ...
|
|
return B_ERROR;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::InitData()
|
|
{
|
|
fOwner = B_ERROR;
|
|
fRunCalled = false;
|
|
fQueue = new BMessageQueue();
|
|
fCommonFilters = NULL;
|
|
fLastMessage = NULL;
|
|
fPreferred = NULL;
|
|
fTaskID = B_ERROR;
|
|
fTerminating = false;
|
|
fMsgPort = -1;
|
|
|
|
if (sTeamID == -1) {
|
|
thread_info info;
|
|
get_thread_info(find_thread(NULL), &info);
|
|
sTeamID = info.team;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::InitData(const char *name, int32 priority, int32 portCapacity)
|
|
{
|
|
InitData();
|
|
|
|
if (name == NULL)
|
|
name = "anonymous looper";
|
|
|
|
fLockSem = create_sem(1, name);
|
|
|
|
if (portCapacity <= 0)
|
|
portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY;
|
|
|
|
fMsgPort = create_port(portCapacity, name);
|
|
|
|
fInitPriority = priority;
|
|
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
AddLooper(this);
|
|
AddHandler(this);
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::AddMessage(BMessage* msg)
|
|
{
|
|
_AddMessagePriv(msg);
|
|
|
|
// ToDo: if called from a different thread, we need to wake up the looper
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::_AddMessagePriv(BMessage* msg)
|
|
{
|
|
// ToDo: if no target token is specified, set to preferred handler
|
|
// Others may want to peek into our message queue, so the preferred
|
|
// handler must be set correctly already if no token was given
|
|
|
|
fQueue->AddMessage(msg);
|
|
}
|
|
|
|
|
|
status_t
|
|
BLooper::_task0_(void *arg)
|
|
{
|
|
BLooper *looper = (BLooper *)arg;
|
|
|
|
PRINT(("LOOPER: _task0_()\n"));
|
|
|
|
if (looper->Lock()) {
|
|
PRINT(("LOOPER: looper locked\n"));
|
|
looper->task_looper();
|
|
|
|
delete looper;
|
|
}
|
|
|
|
PRINT(("LOOPER: _task0_() done: thread %ld\n", find_thread(NULL)));
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void *
|
|
BLooper::ReadRawFromPort(int32 *msgCode, bigtime_t timeout)
|
|
{
|
|
PRINT(("BLooper::ReadRawFromPort()\n"));
|
|
int8 *msgBuffer = NULL;
|
|
ssize_t bufferSize;
|
|
|
|
do {
|
|
bufferSize = port_buffer_size_etc(fMsgPort, B_RELATIVE_TIMEOUT, timeout);
|
|
} while (bufferSize == B_INTERRUPTED);
|
|
|
|
if (bufferSize < B_OK) {
|
|
PRINT(("BLooper::ReadRawFromPort(): failed: %ld\n", bufferSize));
|
|
return NULL;
|
|
}
|
|
|
|
if (bufferSize > 0)
|
|
msgBuffer = new int8[bufferSize];
|
|
|
|
// we don't want to wait again here, since that can only mean
|
|
// that someone else has read our message and our bufferSize
|
|
// is now probably wrong
|
|
PRINT(("read_port()...\n"));
|
|
bufferSize = read_port_etc(fMsgPort, msgCode, msgBuffer, bufferSize,
|
|
B_RELATIVE_TIMEOUT, 0);
|
|
if (bufferSize < B_OK) {
|
|
delete[] msgBuffer;
|
|
return NULL;
|
|
}
|
|
|
|
PRINT(("BLooper::ReadRawFromPort() read: %.4s, %p\n", (char *)msgCode, msgBuffer));
|
|
return msgBuffer;
|
|
}
|
|
|
|
|
|
BMessage *
|
|
BLooper::ReadMessageFromPort(bigtime_t tout)
|
|
{
|
|
PRINT(("BLooper::ReadMessageFromPort()\n"));
|
|
int32 msgcode;
|
|
BMessage* bmsg;
|
|
|
|
void* msgbuffer = ReadRawFromPort(&msgcode, tout);
|
|
if (!msgbuffer)
|
|
return NULL;
|
|
|
|
bmsg = ConvertToMessage(msgbuffer, msgcode);
|
|
|
|
delete[] (int8*)msgbuffer;
|
|
|
|
PRINT(("BLooper::ReadMessageFromPort() done: %p\n", bmsg));
|
|
return bmsg;
|
|
}
|
|
|
|
|
|
BMessage*
|
|
BLooper::ConvertToMessage(void* raw, int32 code)
|
|
{
|
|
PRINT(("BLooper::ConvertToMessage()\n"));
|
|
BMessage* bmsg = new BMessage(code);
|
|
|
|
if (raw != NULL) {
|
|
if (bmsg->Unflatten((const char*)raw) != B_OK) {
|
|
PRINT(("BLooper::ConvertToMessage(): unflattening message failed\n"));
|
|
delete bmsg;
|
|
bmsg = NULL;
|
|
}
|
|
}
|
|
|
|
PRINT(("BLooper::ConvertToMessage(): %p\n", bmsg));
|
|
return bmsg;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::task_looper()
|
|
{
|
|
PRINT(("BLooper::task_looper()\n"));
|
|
// Check that looper is locked (should be)
|
|
AssertLocked();
|
|
// Unlock the looper
|
|
Unlock();
|
|
|
|
if (IsLocked())
|
|
debugger("looper must not be locked!");
|
|
|
|
// loop: As long as we are not terminating.
|
|
while (!fTerminating) {
|
|
PRINT(("LOOPER: outer loop\n"));
|
|
// TODO: timeout determination algo
|
|
// Read from message port (how do we determine what the timeout is?)
|
|
PRINT(("LOOPER: MessageFromPort()...\n"));
|
|
BMessage *msg = MessageFromPort();
|
|
PRINT(("LOOPER: ...done\n"));
|
|
|
|
// Did we get a message?
|
|
if (msg)
|
|
_AddMessagePriv(msg);
|
|
|
|
// Get message count from port
|
|
int32 msgCount = port_count(fMsgPort);
|
|
for (int32 i = 0; i < msgCount; ++i) {
|
|
// Read 'count' messages from port (so we will not block)
|
|
// We use zero as our timeout since we know there is stuff there
|
|
msg = MessageFromPort(0);
|
|
if (msg)
|
|
_AddMessagePriv(msg);
|
|
}
|
|
|
|
// loop: As long as there are messages in the queue and the port is
|
|
// empty... and we are not terminating, of course.
|
|
bool dispatchNextMessage = true;
|
|
while (!fTerminating && dispatchNextMessage) {
|
|
PRINT(("LOOPER: inner loop\n"));
|
|
// Get next message from queue (assign to fLastMessage)
|
|
fLastMessage = fQueue->NextMessage();
|
|
|
|
Lock();
|
|
|
|
if (!fLastMessage) {
|
|
// No more messages: Unlock the looper and terminate the
|
|
// dispatch loop.
|
|
dispatchNextMessage = false;
|
|
} else {
|
|
PRINT(("LOOPER: fLastMessage: 0x%lx: %.4s\n", fLastMessage->what,
|
|
(char*)&fLastMessage->what));
|
|
DBG(fLastMessage->PrintToStream());
|
|
|
|
// Get the target handler
|
|
BHandler *handler = NULL;
|
|
BMessage::Private messagePrivate(fLastMessage);
|
|
bool usePreferred = messagePrivate.UsePreferredTarget();
|
|
|
|
if (usePreferred) {
|
|
PRINT(("LOOPER: use preferred target\n"));
|
|
handler = fPreferred;
|
|
if (handler == NULL)
|
|
handler = this;
|
|
} else {
|
|
gDefaultTokens.GetToken(messagePrivate.GetTarget(),
|
|
B_HANDLER_TOKEN, (void **)&handler);
|
|
|
|
// if this handler doesn't belong to us, we drop the message
|
|
if (handler != NULL && handler->Looper() != this)
|
|
handler = NULL;
|
|
|
|
PRINT(("LOOPER: use %ld, handler: %p, this: %p\n",
|
|
messagePrivate.GetTarget(), handler, this));
|
|
}
|
|
|
|
// Is this a scripting message? (BMessage::HasSpecifiers())
|
|
if (handler != NULL && fLastMessage->HasSpecifiers()) {
|
|
int32 index = 0;
|
|
// Make sure the current specifier is kosher
|
|
if (fLastMessage->GetCurrentSpecifier(&index) == B_OK)
|
|
handler = resolve_specifier(handler, fLastMessage);
|
|
}
|
|
|
|
if (handler) {
|
|
// Do filtering
|
|
handler = _TopLevelFilter(fLastMessage, handler);
|
|
PRINT(("LOOPER: _TopLevelFilter(): %p\n", handler));
|
|
if (handler && handler->Looper() == this)
|
|
DispatchMessage(fLastMessage, handler);
|
|
}
|
|
}
|
|
|
|
if (fTerminating) {
|
|
// we leave the looper locked when we quit
|
|
return;
|
|
}
|
|
|
|
// Unlock the looper
|
|
Unlock();
|
|
|
|
// Delete the current message (fLastMessage)
|
|
if (fLastMessage) {
|
|
delete fLastMessage;
|
|
fLastMessage = NULL;
|
|
}
|
|
|
|
// Are any messages on the port?
|
|
if (port_count(fMsgPort) > 0) {
|
|
// Do outer loop
|
|
dispatchNextMessage = false;
|
|
}
|
|
}
|
|
}
|
|
PRINT(("BLooper::task_looper() done\n"));
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::_QuitRequested(BMessage *msg)
|
|
{
|
|
bool isQuitting = QuitRequested();
|
|
|
|
// We send a reply to the sender, when they're waiting for a reply or
|
|
// if the request message contains a boolean "_shutdown_" field with value
|
|
// true. In the latter case the message came from the registrar, asking
|
|
// the application to shut down.
|
|
bool shutdown;
|
|
if (msg->IsSourceWaiting()
|
|
|| (msg->FindBool("_shutdown_", &shutdown) == B_OK && shutdown)) {
|
|
BMessage ReplyMsg(B_REPLY);
|
|
ReplyMsg.AddBool("result", isQuitting);
|
|
ReplyMsg.AddInt32("thread", fTaskID);
|
|
msg->SendReply(&ReplyMsg);
|
|
}
|
|
|
|
if (isQuitting)
|
|
Quit();
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::AssertLocked() const
|
|
{
|
|
if (!IsLocked()) {
|
|
debugger("looper must be locked before proceeding\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
BHandler *
|
|
BLooper::_TopLevelFilter(BMessage* msg, BHandler* target)
|
|
{
|
|
if (msg) {
|
|
// Apply the common filters first
|
|
target = _ApplyFilters(CommonFilterList(), msg, target);
|
|
if (target) {
|
|
if (target->Looper() != this) {
|
|
debugger("Targeted handler does not belong to the looper.");
|
|
target = NULL;
|
|
} else {
|
|
// Now apply handler-specific filters
|
|
target = _HandlerFilter(msg, target);
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
|
|
BHandler *
|
|
BLooper::_HandlerFilter(BMessage* msg, BHandler* target)
|
|
{
|
|
// Keep running filters until our handler is NULL, or until the filtering
|
|
// handler returns itself as the designated handler
|
|
BHandler* previousTarget = NULL;
|
|
while (target != NULL && target != previousTarget) {
|
|
previousTarget = target;
|
|
|
|
target = _ApplyFilters(target->FilterList(), msg, target);
|
|
if (target != NULL && target->Looper() != this) {
|
|
debugger("Targeted handler does not belong to the looper.");
|
|
target = NULL;
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
|
|
BHandler *
|
|
BLooper::_ApplyFilters(BList* list, BMessage* msg, BHandler* target)
|
|
{
|
|
// This is where the action is!
|
|
// Check the parameters
|
|
if (!list || !msg)
|
|
return target;
|
|
|
|
// For each filter in the provided list
|
|
BMessageFilter* filter = NULL;
|
|
for (int32 i = 0; i < list->CountItems(); ++i) {
|
|
filter = (BMessageFilter*)list->ItemAt(i);
|
|
|
|
// Check command conditions
|
|
if (filter->FiltersAnyCommand() || (filter->Command() == msg->what)) {
|
|
// Check delivery conditions
|
|
message_delivery delivery = filter->MessageDelivery();
|
|
bool dropped = msg->WasDropped();
|
|
if (delivery == B_ANY_DELIVERY
|
|
|| (delivery == B_DROPPED_DELIVERY && dropped)
|
|
|| (delivery == B_PROGRAMMED_DELIVERY && !dropped)) {
|
|
// Check source conditions
|
|
message_source source = filter->MessageSource();
|
|
bool remote = msg->IsSourceRemote();
|
|
if (source == B_ANY_SOURCE
|
|
|| (source == B_REMOTE_SOURCE && remote)
|
|
|| (source == B_LOCAL_SOURCE && !remote)) {
|
|
// Are we using an "external" function?
|
|
filter_result result;
|
|
filter_hook func = filter->FilterFunction();
|
|
if (func)
|
|
result = func(msg, &target, filter);
|
|
else
|
|
result = filter->Filter(msg, &target);
|
|
|
|
// Is further processing allowed?
|
|
if (result == B_SKIP_MESSAGE) {
|
|
// No; time to bail out
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::check_lock()
|
|
{
|
|
// This is a cheap variant of AssertLocked()
|
|
// It is used in situations where it's clear that the looper is valid,
|
|
// ie. from handlers
|
|
if (fOwner == -1 || fOwner != find_thread(NULL))
|
|
debugger("Looper must be locked.");
|
|
}
|
|
|
|
|
|
BHandler *
|
|
BLooper::resolve_specifier(BHandler* target, BMessage* msg)
|
|
{
|
|
// Check params
|
|
if (!target || !msg)
|
|
return NULL;
|
|
|
|
int32 index;
|
|
BMessage specifier;
|
|
int32 form;
|
|
const char* property;
|
|
status_t err = B_OK;
|
|
BHandler* newTarget = target;
|
|
// Loop to deal with nested specifiers
|
|
// (e.g., the 3rd button on the 4th view)
|
|
do {
|
|
err = msg->GetCurrentSpecifier(&index, &specifier, &form, &property);
|
|
if (err) {
|
|
BMessage reply(B_REPLY);
|
|
reply.AddInt32("error", err);
|
|
msg->SendReply(&reply);
|
|
return NULL;
|
|
}
|
|
// Current target gets what was the new target
|
|
target = newTarget;
|
|
newTarget = target->ResolveSpecifier(msg, index, &specifier, form,
|
|
property);
|
|
// Check that new target is owned by looper;
|
|
// use IndexOf() to avoid dereferencing newTarget
|
|
// (possible race condition with object destruction
|
|
// by another looper)
|
|
if (!newTarget || IndexOf(newTarget) < 0)
|
|
return NULL;
|
|
|
|
// Get current specifier index (may change in ResolveSpecifier())
|
|
msg->GetCurrentSpecifier(&index);
|
|
} while (newTarget && newTarget != target && index >= 0);
|
|
|
|
return newTarget;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::UnlockFully()
|
|
{
|
|
AssertLocked();
|
|
|
|
/**
|
|
@note What we're doing here is completely undoing the current owner's lock
|
|
on the looper. This is actually pretty easy, since the owner only
|
|
has a single aquisition on the semaphore; every subsequent "lock"
|
|
is just an increment to the owner count. The whole thing is quite
|
|
similar to Unlock(), except that we clear the ownership variables,
|
|
rather than merely decrementing them.
|
|
*/
|
|
// Clear the owner count
|
|
fOwnerCount = 0;
|
|
// Nobody owns the lock now
|
|
fOwner = -1;
|
|
// There is now one less thread holding a lock on this looper
|
|
// bonefish: Currently _Lock() always acquires the semaphore.
|
|
/* long atomicCount = */atomic_add(&fAtomicCount, -1);
|
|
// if (atomicCount > 0)
|
|
{
|
|
release_sem(fLockSem);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::AddLooper(BLooper *looper)
|
|
{
|
|
if (gLooperList.IsLocked())
|
|
gLooperList.AddLooper(looper);
|
|
}
|
|
|
|
|
|
bool
|
|
BLooper::IsLooperValid(const BLooper *looper)
|
|
{
|
|
if (gLooperList.IsLocked())
|
|
return gLooperList.IsLooperValid(looper);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::RemoveLooper(BLooper *looper)
|
|
{
|
|
if (gLooperList.IsLocked())
|
|
gLooperList.RemoveLooper(looper);
|
|
}
|
|
|
|
|
|
void
|
|
BLooper::GetLooperList(BList* list)
|
|
{
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|
if (ListLock.IsLocked())
|
|
gLooperList.GetLooperList(list);
|
|
}
|
|
|
|
|
|
BLooper *
|
|
BLooper::LooperForName(const char* name)
|
|
{
|
|
if (gLooperList.IsLocked())
|
|
return gLooperList.LooperForName(name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
BLooper *
|
|
BLooper::LooperForPort(port_id port)
|
|
{
|
|
if (gLooperList.IsLocked())
|
|
return gLooperList.LooperForPort(port);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
port_id
|
|
_get_looper_port_(const BLooper *looper)
|
|
{
|
|
return looper->fMsgPort;
|
|
}
|
|
|