1644 lines
43 KiB
C++
1644 lines
43 KiB
C++
//------------------------------------------------------------------------------
|
|||
|
|
// Copyright (c) 2001-2002, OpenBeOS
|
||
|
|
//
|
||
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||
|
|
// copy of this software and associated documentation files (the "Software"),
|
||
|
|
// to deal in the Software without restriction, including without limitation
|
||
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
|
|
// 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:
|
||
|
|
//
|
||
|
|
// 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: Looper.cpp
|
||
|
|
// Author(s): Erik Jaesler ([email protected])
|
||
|
|
// DarkWyrm ([email protected])
|
||
|
|
// Description: 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 OUT printf
|
||
|
|
|
||
|
|
// Standard Includes -----------------------------------------------------------
|
||
|
|
#include <stdio.h>
|
||
|
|
|
||
|
|
// System Includes -------------------------------------------------------------
|
||
|
|
#include <Autolock.h>
|
||
|
|
#include <Looper.h>
|
||
|
|
#include <Message.h>
|
||
|
|
#include <MessageFilter.h>
|
||
|
|
#include <MessageQueue.h>
|
||
|
|
#include <Messenger.h>
|
||
|
|
#include <PropertyInfo.h>
|
||
|
|
|
||
|
|
// Project Includes ------------------------------------------------------------
|
||
|
|
|
||
|
|
// Local Includes --------------------------------------------------------------
|
||
#include <LooperList.h>
|
|||
|
|
#include <ObjectLocker.h>
|
||
|
|
#include <TokenSpace.h>
|
||
|
|||
|
|
// Local Defines ---------------------------------------------------------------
|
||
|
|
#define FILTER_LIST_BLOCK_SIZE 5
|
||
|
|
#define DATA_BLOCK_SIZE 5
|
||
|
|
|
||
|
|
// Globals ---------------------------------------------------------------------
|
||
|
|
using BPrivate::gDefaultTokens;
|
||
using BPrivate::gLooperList;
|
|||
|
|
using BPrivate::BObjectLocker;
|
||
|
|
using BPrivate::BLooperList;
|
||
|
|||
|
|
typedef bool (*find_loop_pred)(_loop_data_* data, void* data);
|
||
|
|
_loop_data_* find_loop_data(_loop_data_* begin, _loop_data_* end,
|
||
|
|
find_loop_pred, void* data);
|
||
|
|
bool looper_by_port_pred(_loop_data_* looper, void* data);
|
||
|
|
bool looper_by_tid_pred(_loop_data_* looper, void* data);
|
||
|
|
bool looper_by_name_pred(_loop_data_* looper, void* data);
|
||
|
|
bool looper_pred(_loop_data_* looper, void* data);
|
||
|
|
bool empty_slot_pred(_loop_data_* looper, void* data);
|
||
|
|
bool copy_list_pred(_loop_data_* looper, void* data);
|
||
|
|
|
||
|
|
port_id _get_looper_port_(const BLooper* looper);
|
||
|
|
bool _use_preferred_target_(BMessage* msg) { return msg->fPreferred; }
|
||
|
|
int32 _get_message_target_(BMessage* msg) { return msg->fTarget; }
|
||
|
|
|
||
|
|
uint32 BLooper::sLooperID = B_ERROR;
|
||
|
|
uint32 BLooper::sLooperListSize = 0;
|
||
|
|
uint32 BLooper::sLooperCount = 0;
|
||
|
|
_loop_data_* BLooper::sLooperList = NULL;
|
||
|
|
BLocker BLooper::sLooperListLock;
|
||
|
|
team_id BLooper::sTeamID = B_ERROR;
|
||
|
|
|
||
|
|
static property_info gLooperPropInfo[] =
|
||
|
|
{
|
||
{
|
|||
"Handler",
|
|||
{},
|
|||
|
|
{B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER},
|
||
|
|
// TODO: what is the extra_data for?
|
||
|
|
NULL, 1,
|
||
|
|
{},
|
||
|
|
{},
|
||
|
|
{}
|
||
|
|
},
|
||
|
|
{
|
||
"Handlers",
|
|||
{B_GET_PROPERTY},
|
|||
|
|
{B_DIRECT_SPECIFIER},
|
||
|
|
NULL, 0,
|
||
|
|
{B_MESSENGER_TYPE},
|
||
|
|
{},
|
||
|
|
{}
|
||
|
|
},
|
||
|
|
{
|
||
"Handler",
|
|||
{B_COUNT_PROPERTIES},
|
|||
|
|
{B_DIRECT_SPECIFIER},
|
||
|
|
NULL, 0,
|
||
|
|
{B_INT32_TYPE},
|
||
|
|
{},
|
||
|
|
{}
|
||
|
|
},
|
||
|
|
{}
|
||
};
|
|||
|
|
|
||
|
|
struct _loop_data_
|
||
|
|
{
|
||
|
|
BLooper* looper;
|
||
|
|
thread_id thread;
|
||
|
|
};
|
||
|
|
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
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();
|
|||
kill_thread(fTaskID);
|
|||
|
|
delete fQueue;
|
||
|
|
delete_sem(fLockSem);
|
||
|
|
delete_port(fMsgPort);
|
||
|
|
|
||
|
|
// Clean up our filters
|
||
|
|
SetCommonFilterList(NULL);
|
||
|
|
|
||
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|||
|
|
#if 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
#endif
|
|||
RemoveHandler(this);
|
|||
|
|
RemoveLooper(this);
|
||
|
|||
|
|
UnlockFully();
|
||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BLooper::BLooper(BMessage* data)
|
||
|
|
: BHandler(data)
|
||
|
|
{
|
||
|
|
int32 portCap;
|
||
|
|
if (data->FindInt32("_port_cap", &portCap) != B_OK)
|
||
|
|
{
|
||
|
|
portCap = B_LOOPER_PORT_DEFAULT_CAPACITY;
|
||
|
|
}
|
||
|
|
|
||
|
|
InitData(Name(), B_NORMAL_PRIORITY, portCap);
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
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 err = BHandler::Archive(data, deep);
|
||
|
|
if (!err)
|
||
|
|
{
|
||
|
|
port_info info;
|
||
|
|
err = get_port_info(fMsgPort, &info);
|
||
|
|
if (!err)
|
||
|
|
{
|
||
|
|
err = data->AddInt32("_port_cap", info.capacity);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
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* reply_to)
|
||
|
|
{
|
||
|
|
BMessage Message(command);
|
||
|
|
return _PostMessage(&Message, handler, reply_to);
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
status_t BLooper::PostMessage(BMessage* message, BHandler* handler,
|
||
|
|
BHandler* reply_to)
|
||
|
|
{
|
||
|
|
return _PostMessage(message, handler, reply_to);
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::DispatchMessage(BMessage* message, BHandler* handler)
|
||
|
|
{
|
||
|
|
DBG(OUT("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;
|
||
|
|
delete this;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case B_QUIT_REQUESTED:
|
||
|
|
{
|
||
|
|
if (handler == this)
|
||
|
|
{
|
||
|
|
do_quit_requested(message);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// fall through
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
default:
|
||
|
|
{
|
||
|
|
handler->MessageReceived(message);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
DBG(OUT("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()
|
||
|
|
{
|
||
|
|
Lock();
|
||
|
|
BMessage* msg = fLastMessage;
|
||
|
|
fLastMessage = NULL;
|
||
|
|
fQueue->RemoveMessage(msg);
|
||
|
|
Unlock();
|
||
|
|
return msg;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BMessageQueue* BLooper::MessageQueue() const
|
||
|
|
{
|
||
|
|
return fQueue;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool BLooper::IsMessageWaiting() const
|
||
|
|
{
|
||
|
|
if (!IsLocked())
|
||
|
|
{
|
||
debugger("The Looper must be locked before calling IsMsgWaiting");
|
|||
|
|
return false;
|
||
}
|
|||
|
|
|
||
|
|
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_TIMEOUT, 0);
|
||
} while (count == B_INTERRUPTED);
|
|||
|
|||
|
|
return count > 0;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::AddHandler(BHandler* handler)
|
||
|
|
{
|
||
if (!handler)
|
|||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!IsLocked())
|
||
|
|
{
|
||
|
|
debugger("Looper must be locked before calling AddHandler.");
|
||
|
|
}
|
||
|
|||
|
|
if (handler->Looper() == NULL)
|
||
|
|
{
|
||
|
|
fHandlers.AddItem(handler);
|
||
|
|
handler->SetLooper(this);
|
||
if (handler != this) // avoid a cycle
|
|||
|
|
handler->SetNextHandler(this);
|
||
BList* Filters = handler->FilterList();
|
|||
|
|
if (Filters)
|
||
|
|
{
|
||
|
|
for (int32 i = 0; i < Filters->CountItems(); ++i)
|
||
|
|
{
|
||
|
|
((BMessageFilter*)Filters->ItemAt(i))->SetLooper(this);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool BLooper::RemoveHandler(BHandler* handler)
|
||
|
|
{
|
||
// R5 implementation didn't bother to check its params, thus NULL handlers
|
|||
|
|
// will seg fault. Bad form, you know.
|
||
|
|
if (!handler)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Correction; testing shows the looper *does* need to be locked for this;
|
||
|
|
// it just doesn't use AssertLocked() for that.
|
||
|
|
if (!IsLocked())
|
||
|
|
{
|
||
|
|
debugger("Looper must be locked before calling RemoveHandler.");
|
||
|
|
}
|
||
|
|||
|
|
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
|
||
|
|
{
|
||
if (!IsLocked())
|
|||
|
|
{
|
||
|
|
debugger("Looper must be locked before calling HandlerAt.");
|
||
|
|
}
|
||
|
|||
|
|
return (BHandler*)fHandlers.ItemAt(index);
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
int32 BLooper::IndexOf(BHandler* handler) const
|
||
|
|
{
|
||
if (!IsLocked())
|
|||
|
|
{
|
||
|
|
debugger("Looper must be locked before calling IndexOf.");
|
||
|
|
}
|
||
|
|||
|
|
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!");
|
|||
}
|
|||
|
|
|
||
|
|
fTaskID = spawn_thread(_task0_, Name(), fInitPriority, this);
|
||
|
|
|
||
|
|
if (fTaskID == B_NO_MORE_THREADS || fTaskID == B_NO_MEMORY)
|
||
|
|
{
|
||
|
|
return fTaskID;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (fMsgPort == B_NO_MORE_PORTS || fMsgPort == B_BAD_VALUE)
|
||
|
|
{
|
||
|
|
return fMsgPort;
|
||
|
|
}
|
||
|
|
|
||
|
|
fRunCalled = true;
|
||
|
|
Unlock();
|
||
|
|
status_t err = resume_thread(fTaskID);
|
||
|
|
if (err)
|
||
|
|
{
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
return fTaskID;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::Quit()
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::Quit()\n"));
|
||
|
|
if (!IsLocked())
|
||
|
|
{
|
||
|
|
const char* name = Name();
|
||
|
|
if (!name)
|
||
|
|
{
|
||
|
|
name = "no-name";
|
||
|
|
}
|
||
|
|
printf("ERROR - you must Lock a looper before calling Quit(), "
|
||
"team=%ld, looper=%s", Team(), name);
|
|||
}
|
|||
|
|||
|
|
// Try to lock
|
||
|
|
if (!Lock())
|
||
|
|
{
|
||
|
|
// We're toast already
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
DBG(OUT(" is locked\n"));
|
|||
|
|
|
||
|
|
if (!fRunCalled || find_thread(NULL) == fTaskID)
|
||
|
|
{
|
||
|
|
DBG(OUT(" Run() has not been called yet or we are the looper thread\n"));
|
||
|
|
fTerminating = true;
|
||
|
|
delete this;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
DBG(OUT(" 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();
|
||
|
|
|
||
|
|
// bonefish: We need to unlock here. Otherwise the looper thread can't
|
||
|
|
// dispatch the _QUIT_ message we're going to post.
|
||
|
|
do {
|
||
|
|
Unlock();
|
||
|
|
} while (IsLocked());
|
||
|
|
|
||
|
|
// 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.
|
||
|
|
status_t err;
|
||
|
|
DBG(OUT(" PostMessage(_QUIT_)...\n"));
|
||
|
|
// err = PostMessage(_QUIT_);
|
||
|
|
|
||
|
|
BMessage message(_QUIT_);
|
||
|
|
message.AddInt32("testfield", 42);
|
||
|
|
err = PostMessage(&message);
|
||
|
|
DBG(OUT(" ... done: %lx\n", err));
|
||
|
|
|
||
|
|
// There's a possibility that PostMessage() will return B_WILL_BLOCK
|
||
|
|
// because the port is full, so we'll wait a bit and re-post until
|
||
|
|
// we won't block.
|
||
|
|
while (err == B_WOULD_BLOCK)
|
||
|
|
{
|
||
|
|
// TODO: test this value; it may be too short
|
||
|
|
snooze(10000);
|
||
|
|
err = PostMessage(_QUIT_);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Also as per the BeBook, we have to wait until the looper is done
|
||
|
|
// processing any remaining messages.
|
||
|
|
int32 temp;
|
||
|
|
do
|
||
|
|
{
|
||
|
|
DBG(OUT(" wait_for_thread()...\n"));
|
||
|
|
err = wait_for_thread(tid, &temp);
|
||
|
|
} while (err == B_INTERRUPTED);
|
||
|
|
}
|
||
|
|
DBG(OUT("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()
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::Unlock()\n"));
|
||
|
|
// Make sure we're locked to begin with
|
||
|
|
AssertLocked();
|
||
|
|
|
||
|
|
// Decrement fOwnerCount
|
||
|
|
--fOwnerCount;
|
||
|
|
DBG(OUT(" 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);
|
|||
DBG(OUT(" 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
DBG(OUT("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 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
#endif
|
|||
|
|||
|
|
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);
|
||
|
|
}
|
||
|
|
#if 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
|
|
if (ListLock.IsLocked())
|
||
|
|
{
|
||
|
|
_loop_data_* result = find_loop_data(sLooperList,
|
||
|
|
sLooperList + sLooperCount,
|
||
|
|
looper_by_tid_pred, (void*)tid);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
return result->looper;
|
||
|
|
}
|
||
|
|
}
|
||
#endif
|
|||
|
|||
|
|
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.
|
||
|
|
*/
|
||
// Straight from the BeBook
|
|||
|
|
BPropertyInfo PropertyInfo(gLooperPropInfo);
|
||
uint32 data;
|
|||
|
|
if (PropertyInfo.FindMatch(msg, index, specifier, form, property, &data) >= 0)
|
||
{
|
|||
|
|
return this;
|
||
|
|
}
|
||
else
|
|||
|
|
{
|
||
|
|
return BHandler::ResolveSpecifier(msg, index, specifier, form,
|
||
|
|
property);
|
||
|
|
}
|
||
|
|||
|
|
BMessage Reply(B_MESSAGE_NOT_UNDERSTOOD);
|
||
|
|
Reply.AddInt32("error", B_BAD_SCRIPT_SYNTAX);
|
||
|
|
Reply.AddString("message", "Didn't understand the specifier(s)");
|
||
|
|
msg->SendReply(&Reply);
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
status_t BLooper::GetSupportedSuites(BMessage* data)
|
||
|
|
{
|
||
status_t err;
|
|||
|
|
if (!data)
|
||
|
|
{
|
||
|
|
err = B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!err)
|
||
|
|
{
|
||
|
|
err = data->AddString("Suites", "suite/vnd.Be-handler");
|
||
|
|
if (!err)
|
||
|
|
{
|
||
|
|
BPropertyInfo PropertyInfo(gLooperPropInfo);
|
||
|
|
err = data->AddFlat("message", &PropertyInfo);
|
||
|
|
if (!err)
|
||
|
|
{
|
||
|
|
err = BHandler::GetSupportedSuites(data);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return err;
|
||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::AddCommonFilter(BMessageFilter* filter)
|
||
|
|
{
|
||
if (!filter)
|
|||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Locked())
|
||
|
|
{
|
||
|
|
debugger("Owning Looper must be locked before calling AddCommonFilter");
|
||
|
|
}
|
||
|
|
|
||
if (!fCommonFilters)
|
|||
|
|
{
|
||
|
|
fCommonFilters = new BList(FILTER_LIST_BLOCK_SIZE);
|
||
|
|
}
|
||
|
|
filter->SetLooper(this);
|
||
|
|
fCommonFilters->AddItem(filter);
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool BLooper::RemoveCommonFilter(BMessageFilter* filter)
|
||
|
|
{
|
||
|
|
AssertLocked();
|
||
|
|
bool result = fCommonFilters->RemoveItem(filter);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
filter->SetLooper(NULL);
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::SetCommonFilterList(BList* filters)
|
||
|
|
{
|
||
|
|
if (fCommonFilters)
|
||
|
|
{
|
||
|
|
for (int32 i = 0; i < fCommonFilters->CountItems(); ++i)
|
||
|
|
{
|
||
|
|
delete fCommonFilters->ItemAt(i);
|
||
|
|
}
|
||
|
|
fCommonFilters->MakeEmpty();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Per the BeBook, we take ownership of the list
|
||
|
|
fCommonFilters = filters;
|
||
|
|
if (fCommonFilters)
|
||
|
|
{
|
||
|
|
for (int32 i = 0; i < fCommonFilters->CountItems(); ++i)
|
||
|
|
{
|
||
|
|
((BMessageFilter*)fCommonFilters->ItemAt(i))->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 B_ERROR;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
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* reply_to)
|
||
|
|
{
|
||
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|||
|
|
#if 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
#endif
|
|||
if (!ListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
return B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!IsLooperValid(this))
|
||
|
|
{
|
||
|
|
return B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Does handler belong to this looper?
|
||
|
|
if (handler && handler->Looper() != this)
|
||
|
|
{
|
||
|
|
return B_MISMATCHED_VALUES;
|
||
|
|
}
|
||
|
|
|
||
|
|
status_t err = B_OK;
|
||
|
|
|
||
|
|
BMessenger Messenger(handler, this, &err);
|
||
|
|
|
||
|
|
if (!err)
|
||
|
|
{
|
||
err = Messenger.SendMessage(msg, reply_to, 0);
|
|||
}
|
|||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
status_t BLooper::_Lock(BLooper* loop, port_id port, bigtime_t timeout)
|
||
|
|
{
|
||
|
|
DBG(OUT("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)
|
||
|
|
{
|
||
|
|
DBG(OUT("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 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
#endif
|
|||
if (!ListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
// If we can't lock, the semaphore is probably
|
||
|
|
// gone, which leaves us in no-man's land
|
||
|
|
DBG(OUT("BLooper::_Lock() done 2\n"));
|
||
|
|
return B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Look up looper by port_id, if necessary
|
||
|
|
if (!loop)
|
||
|
|
{
|
||
|
|
loop = LooperForPort(port);
|
||
|
|
if (!loop)
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::_Lock() done 3\n"));
|
||
|
|
return B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Check looper validity
|
||
|
|
if (!IsLooperValid(loop))
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::_Lock() done 4\n"));
|
||
|
|
return B_BAD_VALUE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for nested lock attempt
|
||
|
|
curThread = find_thread(NULL);
|
||
|
|
if (curThread == loop->fOwner)
|
||
|
|
{
|
||
|
|
// Bump fOwnerCount
|
||
|
|
++loop->fOwnerCount;
|
||
|
|
DBG(OUT("BLooper::_Lock() done 5: fOwnerCount: %ld\n", loop->fOwnerCount));
|
||
|
|
return B_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cache the semaphore
|
||
|
|
sem = loop->fLockSem;
|
||
|
|
|
||
|
|
// Validate the semaphore
|
||
|
|
if (sem < 0)
|
||
|
|
{
|
||
|
|
DBG(OUT("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;
|
||
|
|
}
|
||
|
|
|
||
|
|
DBG(OUT("BLooper::_Lock() done: %lx\n", err));
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
status_t BLooper::_LockComplete(BLooper* loop, 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;
|
||
|
|
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 port_capacity)
|
||
|
|
{
|
||
InitData();
|
|||
|
|
|
||
fLockSem = create_sem(1, name);
|
|||
|
|
|
||
if (port_capacity <= 0)
|
|||
{
|
|||
port_capacity = B_LOOPER_PORT_DEFAULT_CAPACITY;
|
|||
}
|
|||
|
|
|
||
fMsgPort = create_port(port_capacity, name ? name : "LooperPort");
|
|||
|
|
|
||
|
|
fInitPriority = priority;
|
||
|
|||
|
|
BObjectLocker<BLooperList> ListLock(gLooperList);
|
||
|
|
#if 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
#endif
|
|||
AddLooper(this);
|
|||
|
|
AddHandler(this);
|
||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::AddMessage(BMessage* msg)
|
||
|
|
{
|
||
// NOTE: Why is this here?
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::_AddMessagePriv(BMessage* msg)
|
||
|
|
{
|
||
// NOTE: No, really; why the hell is this here??
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
status_t BLooper::_task0_(void* arg)
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: _task0_()\n"));
|
||
|
|
BLooper* obj = (BLooper*)arg;
|
||
|
|
|
||
|
|
DBG(OUT("LOOPER: locking looper...\n"));
|
||
|
|
if (obj->Lock())
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: looper locked\n"));
|
||
|
|
obj->task_looper();
|
||
|
|
obj->fTerminating = true;
|
||
|
|
delete obj;
|
||
|
|
}
|
||
|
|
|
||
|
|
return B_OK;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void* BLooper::ReadRawFromPort(int32* msgcode, bigtime_t tout)
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::ReadRawFromPort()\n"));
|
||
|
|
int8* msgbuffer = NULL;
|
||
|
|
ssize_t buffersize;
|
||
|
|
ssize_t bytesread;
|
||
|
|
|
||
|
|
if (tout == B_INFINITE_TIMEOUT)
|
||
|
|
{
|
||
|
|
buffersize = port_buffer_size(fMsgPort);
|
||
|
|
DBG(OUT("BLooper::ReadRawFromPort(): buffersize: %ld\n", buffersize));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
buffersize = port_buffer_size_etc(fMsgPort, 0, tout);
|
||
|
|
if (buffersize == B_TIMED_OUT || buffersize == B_BAD_PORT_ID ||
|
||
|
|
buffersize == B_WOULD_BLOCK)
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::ReadRawFromPort() done 1\n"));
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (buffersize > 0)
|
||
|
|
msgbuffer = new int8[buffersize];
|
||
|
|
|
||
|
|
if (tout == B_INFINITE_TIMEOUT)
|
||
|
|
{
|
||
|
|
DBG(OUT("read_port()...\n"));
|
||
|
|
bytesread = read_port(fMsgPort, msgcode, msgbuffer, buffersize);
|
||
|
|
DBG(OUT("read_port() done: %ld\n", bytesread));
|
||
|
|
DBG(OUT("BLooper::ReadRawFromPort() read: %.4s\n", (char*)msgcode));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
bytesread = read_port_etc(fMsgPort, msgcode, msgbuffer, buffersize,
|
||
|
|
B_TIMEOUT, tout);
|
||
|
|
}
|
||
|
|
|
||
|
|
DBG(OUT("BLooper::ReadRawFromPort() done: %p\n", msgbuffer));
|
||
|
|
return msgbuffer;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BMessage* BLooper::ReadMessageFromPort(bigtime_t tout)
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::ReadMessageFromPort()\n"));
|
||
|
|
int32 msgcode;
|
||
|
|
BMessage* bmsg;
|
||
|
|
|
||
|
|
void* msgbuffer = ReadRawFromPort(&msgcode, tout);
|
||
|
|
|
||
|
|
bmsg = ConvertToMessage(msgbuffer, msgcode);
|
||
|
|
|
||
|
|
if (msgbuffer)
|
||
|
|
{
|
||
|
|
delete[] msgbuffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
DBG(OUT("BLooper::ReadMessageFromPort() done: %p\n", bmsg));
|
||
|
|
return bmsg;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BMessage* BLooper::ConvertToMessage(void* raw, int32 code)
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::ConvertToMessage()\n"));
|
||
|
|
BMessage* bmsg = new BMessage(code);
|
||
|
|
|
||
|
|
if (raw != NULL)
|
||
|
|
{
|
||
if (bmsg->Unflatten((const char*)raw) != B_OK)
|
|||
|
|
{
|
||
DBG(OUT("BLooper::ConvertToMessage(): unflattening message failed\n"));
|
|||
|
|
delete bmsg;
|
||
|
|
bmsg = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
DBG(OUT("BLooper::ConvertToMessage(): %p\n", bmsg));
|
||
|
|
return bmsg;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::task_looper()
|
||
|
|
{
|
||
|
|
DBG(OUT("BLooper::task_looper()\n"));
|
||
|
|
// Check that looper is locked (should be)
|
||
|
|
AssertLocked();
|
||
|
|
// Unlock the looper
|
||
|
|
Unlock();
|
||
|
|
|
||
|
|
// loop: As long as we are not terminating.
|
||
|
|
while (!fTerminating)
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: outer loop\n"));
|
||
// TODO: timeout determination algo
|
|||
// Read from message port (how do we determine what the timeout is?)
|
|||
|
|
DBG(OUT("LOOPER: MessageFromPort()...\n"));
|
||
|
|
BMessage* msg = MessageFromPort();
|
||
|
|
DBG(OUT("LOOPER: ...done\n"));
|
||
|
|
|
||
|
|
// Did we get a message?
|
||
|
|
if (msg)
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: got message\n"));
|
||
|
|
// Add to queue
|
||
|
|
fQueue->AddMessage(msg);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
DBG(OUT("LOOPER: got no message\n"));
|
||
|
|
|
||
|
|
// 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);
|
||
|
|
// Add messages to queue
|
||
if (msg)
|
|||
|
|
{
|
||
|
|
fQueue->AddMessage(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)
|
|||
|
|
{
|
||
DBG(OUT("LOOPER: inner loop\n"));
|
|||
|
|
// Get next message from queue (assign to fLastMessage)
|
||
|
|
fLastMessage = fQueue->NextMessage();
|
||
|
|||
|
|
// Lock the looper
|
||
|
|
Lock();
|
||
|
|
if (!fLastMessage)
|
||
|
|
{
|
||
// No more messages: Unlock the looper and terminate the
|
|||
|
|
// dispatch loop.
|
||
|
|
dispatchNextMessage = false;
|
||
}
|
|||
|
|
else
|
||
|
|
{
|
||
DBG(OUT("LOOPER: fLastMessage: 0x%lx: %.4s\n", fLastMessage->what,
|
|||
|
|
(char*)&fLastMessage->what));
|
||
|
|
DBG(fLastMessage->PrintToStream());
|
||
|
|
// Get the target handler
|
||
|
|
// Use BMessage friend functions to determine if we are using the
|
||
|
|
// preferred handler, or if a target has been specified
|
||
|
|
BHandler* handler;
|
||
|
|
if (_use_preferred_target_(fLastMessage))
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: use preferred target\n"));
|
||
|
|
handler = fPreferred;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: don't use preferred target\n"));
|
||
|
|
/**
|
||
|
|
@note Here is where all the token stuff starts to
|
||
|
|
make sense. How, exactly, do we determine
|
||
|
|
what the target BHandler is? If we look at
|
||
|
|
BMessage, we see an int32 field, fTarget.
|
||
|
|
Amazingly, we happen to have a global mapping
|
||
|
|
of BHandler pointers to int32s!
|
||
|
|
*/
|
||
|
|
DBG(OUT("LOOPER: use: %ld\n", _get_message_target_(fLastMessage)));
|
||
|
|
gDefaultTokens.GetToken(_get_message_target_(fLastMessage),
|
||
|
|
B_HANDLER_TOKEN,
|
||
|
|
(void**)&handler);
|
||
|
|
DBG(OUT("LOOPER: handler: %p, this: %p\n", handler, this));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!handler)
|
||
|
|
{
|
||
|
|
DBG(OUT("LOOPER: no target handler, use this\n"));
|
||
|
|
handler = this;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Is this a scripting message? (BMessage::HasSpecifiers())
|
||
|
|
if (fLastMessage->HasSpecifiers())
|
||
|
|
{
|
||
|
|
int32 index = 0;
|
||
|
|
// Make sure the current specifier is kosher
|
||
|
|
if (fLastMessage->GetCurrentSpecifier(&index) == B_OK)
|
||
|
|
{
|
||
|
|
handler = resolve_specifier(handler, fLastMessage);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
DBG(OUT("LOOPER: no scripting message\n"));
|
||
|
|
|
||
|
|
if (handler)
|
||
|
|
{
|
||
|
|
// Do filtering
|
||
|
|
handler = top_level_filter(fLastMessage, handler);
|
||
|
|
DBG(OUT("LOOPER: top_level_filter(): %p\n", handler));
|
||
|
|
if (handler && handler->Looper() == this)
|
||
|
|
{
|
||
|
|
DispatchMessage(fLastMessage, handler);
|
||
|
|
}
|
||
|
|
}
|
||
}
|
|||
|
|||
// 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;
|
||
}
|
|||
|
|
}
|
||
|
|
}
|
||
|
|
DBG(OUT("BLooper::task_looper() done\n"));
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::do_quit_requested(BMessage* msg)
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
@note I couldn't figure out why do_quit_requested() was necessary; why not
|
||
|
|
just call Quit()? Then, while writing the PostMessage() code, I
|
||
|
|
realized that the sender of the B_QUIT_REQUESTED message just might
|
||
|
|
be waiting for a reply. A quick test, and yes, we get a reply
|
||
|
|
which consists of:
|
||
|
|
what: B_REPLY
|
||
|
|
"result" (bool) return of QuitRequested()
|
||
|
|
"thread" (int32) the looper's thread id
|
||
|
|
|
||
|
|
While Quit() could use fLastMessage, it makes more sense that
|
||
|
|
do_quit_requested() would handle it since it gets passed the
|
||
|
|
message.
|
||
|
|
*/
|
||
|
|
|
||
|
|
bool isQuitting = QuitRequested();
|
||
|
|
|
||
|
|
if (msg->IsSourceWaiting())
|
||
|
|
{
|
||
|
|
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::top_level_filter(BMessage* msg, BHandler* t)
|
||
|
|
{
|
||
|
|
// TODO: implement
|
||
|
|
// return the supplied handler for now
|
||
|
|
return t;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BHandler* BLooper::handler_only_filter(BMessage* msg, BHandler* t)
|
||
|
|
{
|
||
|
|
// TODO: implement
|
||
return NULL;
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BHandler* BLooper::apply_filters(BList* list, BMessage* msg, BHandler* target)
|
||
|
|
{
|
||
|
|
// TODO: implement
|
||
return NULL;
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::check_lock()
|
||
|
|
{
|
||
// NOTE: any use for this?
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BHandler* BLooper::resolve_specifier(BHandler* target, BMessage* msg)
|
||
|
|
{
|
||
|
|
// TODO: implement
|
||
return NULL;
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::UnlockFully()
|
||
|
|
{
|
||
|
|
// TODO: implement
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::AddLooper(BLooper* loop)
|
||
|
|
{
|
||
if (gLooperList.IsLocked())
|
|||
|
|
{
|
||
|
|
gLooperList.AddLooper(loop);
|
||
|
|
}
|
||
|
|
#if 0
|
||
if (sLooperListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
#if defined(CHECK_ADD_LOOPER)
|
||
|
|
// First see if it's already been added
|
||
|
|
if (!IsLooperValid(loop))
|
||
|
|
#endif
|
||
|
|
{
|
||
|
|
_loop_data_* result = find_loop_data(sLooperList,
|
||
|
|
sLooperList + sLooperCount,
|
||
|
|
empty_slot_pred, NULL);
|
||
|
|
|
||
|
|
uint32& looperCount = sLooperCount; // hokey debugging aids
|
||
|
|
uint32& looperListSize = sLooperListSize;
|
||
|
|
if (!result)
|
||
|
|
{
|
||
|
|
// No empty slots; time to expand
|
||
|
|
if (looperCount == looperListSize)
|
||
|
|
{
|
||
|
|
// Allocate the expanded list
|
||
|
|
_loop_data_* temp =
|
||
|
|
new _loop_data_[looperListSize + DATA_BLOCK_SIZE];
|
||
|
|
if (!temp)
|
||
|
|
{
|
||
|
|
// Not good
|
||
|
|
debugger("unable to allocate looper list");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Transfer the existing data
|
||
|
|
memcpy(temp, sLooperList,
|
||
|
|
sizeof (_loop_data_*) * looperListSize);
|
||
|
|
delete[] sLooperList;
|
||
|
|
sLooperList = temp;
|
||
|
|
looperListSize += DATA_BLOCK_SIZE;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Whether we expanded or not, the "new" one will be at the end
|
||
|
|
DBG(OUT("BLooper::AddLooper(): looper added at %ld\n", looperCount));
|
||
|
|
result = &sLooperList[looperCount];
|
||
|
|
}
|
||
|
|
|
||
|
|
result->looper = loop;
|
||
|
|
result->thread = loop->fTaskID;
|
||
|
|
++looperCount;
|
||
|
|||
|
|
// Moved this here from InitData() because it occured to me that the
|
||
|
|
// looper could potentially get removed from the list between now
|
||
|
|
// and when we locked it in InitData(). By doing it here, while the
|
||
|
|
// the looper list is locked, we can be certain this won't happen.
|
||
|
|
loop->Lock();
|
||
}
|
|||
|
|
}
|
||
else
|
|||
|
|
{
|
||
|
|
debugger("sLooperList is not locked!");
|
||
|
|
}
|
||
#endif
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool BLooper::IsLooperValid(const BLooper* l)
|
||
|
|
{
|
||
if (gLooperList.IsLocked())
|
|||
|
|
{
|
||
|
|
return gLooperList.IsLooperValid(l);
|
||
|
|
}
|
||
|
|
#if 0
|
||
if (sLooperListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
return find_loop_data(sLooperList, sLooperList + sLooperCount,
|
||
|
|
looper_pred, (void*)l);
|
||
|
|
}
|
||
#endif
|
|||
|
|||
|
|
return false;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::RemoveLooper(BLooper* l)
|
||
|
|
{
|
||
if (gLooperList.IsLocked())
|
|||
|
|
{
|
||
|
|
gLooperList.RemoveLooper(l);
|
||
|
|
}
|
||
|
|
#if 0
|
||
if (sLooperListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
_loop_data_* result = find_loop_data(sLooperList,
|
||
|
|
sLooperList + sLooperCount,
|
||
|
|
looper_pred, l);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
result->looper = NULL;
|
||
|
|
--sLooperCount;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nothing left? Clean up; the app is probably exiting anyway
|
||
|
|
if (sLooperCount == 0)
|
||
|
|
{
|
||
|
|
delete[] sLooperList;
|
||
|
|
sLooperList = NULL;
|
||
|
|
sLooperListSize = 0;
|
||
|
|
}
|
||
|
|
}
|
||
#endif
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
void BLooper::GetLooperList(BList* list)
|
||
|
|
{
|
||
BObjectLocker<BLooperList> ListLock(gLooperList);
|
|||
|
|
if (ListLock.IsLocked())
|
||
|
|
{
|
||
|
|
gLooperList.GetLooperList(list);
|
||
|
|
}
|
||
|
|
#if 0
|
||
BAutolock ListLock(sLooperListLock);
|
|||
|
|
if (ListLock.IsLocked())
|
||
|
|
{
|
||
|
|
find_loop_data(sLooperList, sLooperList + sLooperCount,
|
||
|
|
copy_list_pred, (void*)list);
|
||
|
|
}
|
||
#endif
|
|||
}
|
|||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BLooper* BLooper::LooperForName(const char* name)
|
||
|
|
{
|
||
if (gLooperList.IsLocked())
|
|||
|
|
{
|
||
|
|
return gLooperList.LooperForName(name);
|
||
|
|
}
|
||
|
|
#if 0
|
||
if (sLooperListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
_loop_data_* result = find_loop_data(sLooperList,
|
||
|
|
sLooperList + sLooperCount,
|
||
|
|
looper_by_name_pred, (void*)name);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
return result->looper;
|
||
|
|
}
|
||
|
|
}
|
||
#endif
|
|||
|
|||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
BLooper* BLooper::LooperForPort(port_id port)
|
||
|
|
{
|
||
if (gLooperList.IsLocked())
|
|||
|
|
{
|
||
|
|
return gLooperList.LooperForPort(port);
|
||
|
|
}
|
||
|
|
#if 0
|
||
if (sLooperListLock.IsLocked())
|
|||
|
|
{
|
||
|
|
_loop_data_* result = find_loop_data(sLooperList,
|
||
|
|
sLooperList + sLooperCount,
|
||
|
|
looper_by_port_pred, (void*)port);
|
||
|
|
if (result)
|
||
|
|
{
|
||
|
|
return result->looper;
|
||
|
|
}
|
||
|
|
}
|
||
#endif
|
|||
|
|||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
_loop_data_* find_loop_data(_loop_data_* begin, _loop_data_* end,
|
||
|
|
find_loop_pred predicate, void* data)
|
||
|
|
{
|
||
|
|
while (begin && begin != end)
|
||
|
|
{
|
||
|
|
if (begin->looper)
|
||
|
|
{
|
||
|
|
if (predicate(begin, data))
|
||
|
|
{
|
||
|
|
return begin;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
++begin;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool looper_by_port_pred(_loop_data_* looper, void *data)
|
||
|
|
{
|
||
|
|
return _get_looper_port_(looper->looper) == (port_id)data;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool looper_by_tid_pred(_loop_data_* looper, void *data)
|
||
|
|
{
|
||
|
|
return looper->thread == (thread_id)data;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool looper_by_name_pred(_loop_data_* looper, void *data)
|
||
|
|
{
|
||
|
|
return strcmp(looper->looper->Name(), (const char*)data) == 0;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool looper_pred(_loop_data_* looper, void *data)
|
||
|
|
{
|
||
|
|
return looper->looper == (BLooper*)data;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool empty_slot_pred(_loop_data_* looper, void*)
|
||
|
|
{
|
||
|
|
return looper->looper == NULL;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
bool copy_list_pred(_loop_data_ *looper, void* data)
|
||
|
|
{
|
||
|
|
BList* List = (BList*)data;
|
||
|
|
if (List && looper->looper)
|
||
|
|
{
|
||
|
|
List->AddItem(looper->looper);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ride this train to the end
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
port_id _get_looper_port_(const BLooper* looper)
|
||
|
|
{
|
||
|
|
return looper->fMsgPort;
|
||
|
|
}
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
|
||
|
|
/*
|
||
|
|
* $Log $
|
||
|
|
*
|
||
|
|
* $Id $
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|