Files
SingularityViewer/indra/aistatemachine/aistatemachinethread.cpp
Aleric Inglewood 8d028e6948 Fix for AIStateMachineThread (LLThreadSafeRefCount: deleting non-zero reference)
This fixes
https://code.google.com/p/singularity-viewer/issues/detail?id=736

The problem was that we need to keep the 'user' derived THREAD_IMPL
alive in the thread, and therefore used an LLPointer<THREAD_IMPL>
(as base class of AIStateMachineThread<THREAD_IMPL>), and therefore
THREAD_IMPL, derived from AIThreadImpl had to be derived from
LLThreadSafeRefCount. However, AIStateMachineThread<THREAD_IMPL> also
needed to be a statemachine of itself and is derived from
AIStateMachineThreadBase derived from AIStateMachine which is ALSO
derived from LLThreadSafeRefCount - that in this case wasn't really
needed. An attempt to deactive it by calling ref() from the constructor
of AIStateMachineThreadBase failed on the fact that LLThreadSafeRefCount
insists that its ref count mRef is exactly zero when it is being
deleted.

The chosen solution is to remove the ref count from AIThreadImpl and use
the LLThreadSafeRefCount base class of AIStateMachineThreadBase. The
result is that not only THREAD_IMPL, but also the state machine object
is kept alive, but that doesn't seem like a problem.

Thus, instead of passing a AIThreadImpl* to
AIStateMachineThreadBase::Thread, we now pass a
AIStateMachineThreadBase* to it to keep the whole of the
AIStateMachineThread<THREAD_IMPL> object alive, which has a THREAD_IMPL
as member now. This member then can be accessed through a virtual
function impl(). Another result of this change is that the 'user' (the
class derived from AIThreadImpl, THREAD_IMPL) now has to deal with the
LLPointer, and use LLPointer<AIStateMachineThread<THREAD_IMPL> >
instead of just AIStateMachineThread<THREAD_IMPL> and also allocate
this object himself. The access from there then changes into a -> to
access the state machine (as opposed to a .) and ->thread_impl() to
access the THREAD_IMPL object (as opposed to a ->).
2013-04-20 04:54:25 +02:00

194 lines
6.3 KiB
C++

/**
* @file aistatemachinethread.cpp
* @brief Implementation of AIStateMachineThread
*
* Copyright (c) 2013, Aleric Inglewood.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 23/01/2013
* Initial version, written by Aleric Inglewood @ SL
*/
#include "linden_common.h"
#include "aistatemachinethread.h"
class AIStateMachineThreadBase::Thread : public LLThread {
private:
LLPointer<AIStateMachineThreadBase> mImpl;
bool mNeedCleanup;
public:
Thread(AIStateMachineThreadBase* impl) :
#ifdef LL_DEBUG
LLThread(impl->impl().getName()),
#else
LLThread("AIStateMachineThreadBase::Thread"),
#endif
mImpl(impl) { }
protected:
/*virtual*/ void run(void)
{
mNeedCleanup = mImpl->impl().thread_done(mImpl->impl().run());
}
/*virtual*/ void terminated(void)
{
mStatus = STOPPED;
if (mNeedCleanup)
{
// The state machine that started us has disappeared! Clean up ourselves.
// This is OK now because in our case nobody is watching us and having set
// the status to STOPPED didn't change anything really, although it will
// prevent LLThread::shutdown from blocking a whole minute and then calling
// apr_thread_exit to never return! Normally, the LLThread might be deleted
// immediately (by the main thread) after setting mStatus to STOPPED.
Thread::completed(this);
}
}
public:
// TODO: Implement a thread pool. For now, just create a new one every time.
static Thread* allocate(AIStateMachineThreadBase* impl) { return new Thread(impl); }
static void completed(Thread* threadp) { delete threadp; }
};
// MAIN THREAD
char const* AIStateMachineThreadBase::state_str_impl(state_type run_state) const
{
switch(run_state)
{
AI_CASE_RETURN(start_thread);
AI_CASE_RETURN(wait_stopped);
}
return "UNKNOWN STATE";
}
void AIStateMachineThreadBase::initialize_impl(void)
{
mThread = NULL;
mAbort = false;
set_state(start_thread);
}
void AIStateMachineThreadBase::multiplex_impl(state_type run_state)
{
switch(run_state)
{
case start_thread:
mThread = Thread::allocate(this);
// Set next state.
set_state(wait_stopped);
idle(); // Wait till the thread returns.
mThread->start();
break;
case wait_stopped:
if (!mThread->isStopped())
break;
// We're done!
//
// We can only get here when AIThreadImpl::done called cont(), (very
// shortly after which it will be STOPPED), which means we need to do
// the clean up of the thread.
// Also, the thread has really stopped now, so it's safe to delete it.
//
// Clean up the thread.
Thread::completed(mThread);
mThread = NULL; // Stop abort_impl() from doing anything.
if (mAbort)
abort();
else
finish();
break;
}
}
void AIStateMachineThreadBase::abort_impl(void)
{
if (mThread)
{
// If this AIStateMachineThreadBase still exists then the AIStateMachineThread<THREAD_IMPL>
// that is derived from it still exists and therefore its member THREAD_IMPL also still exists
// and therefore impl() is valid.
bool need_cleanup = impl().state_machine_done(mThread); // Signal the fact that we aborted.
if (need_cleanup)
{
// This is an unlikely race condition. We have been aborted by our parent,
// but at the same time the thread finished!
// We can only get here when AIThreadImpl::thread_done already returned
// (VERY shortly after the thread will stop (if not already)).
// Just go into a tight loop while waiting for that, when it is safe
// to clean up the thread.
while (!mThread->isStopped())
{
mThread->yield();
}
Thread::completed(mThread);
}
else
{
// The thread is still happily running (and will clean up itself).
// Lets make sure we're not flooded with this situation.
llwarns << "Thread state machine aborted while the thread is still running. That is a waste of CPU and should be avoided." << llendl;
}
}
}
bool AIThreadImpl::state_machine_done(LLThread* threadp)
{
StateMachineThread_wat state_machine_thread_w(mStateMachineThread);
AIStateMachineThreadBase* state_machine_thread = *state_machine_thread_w;
bool need_cleanup = !state_machine_thread;
if (!need_cleanup)
{
// If state_machine_thread is non-NULL, then AIThreadImpl::thread_done wasnt called yet
// (or at least didn't return yet) which means the thread is still running.
// Try telling the thread that it can stop.
threadp->setQuitting();
// Finally, mark that we are NOT going to do the cleanup by setting mStateMachineThread to NULL.
*state_machine_thread_w = NULL;
}
return need_cleanup;
}
// AIStateMachineThread THREAD
bool AIThreadImpl::thread_done(bool result)
{
StateMachineThread_wat state_machine_thread_w(mStateMachineThread);
AIStateMachineThreadBase* state_machine_thread = *state_machine_thread_w;
bool need_cleanup = !state_machine_thread;
if (!need_cleanup)
{
// If state_machine_thread is non-NULL then AIThreadImpl::abort_impl wasn't called,
// which means the state machine still exists. In fact, it should be in the waiting() state.
// It can also happen that the state machine is being aborted right now.
llassert(state_machine_thread->waiting_or_aborting());
state_machine_thread->schedule_abort(!result);
// Note that if the state machine is not running (being aborted, ie - hanging in abort_impl
// waiting for the lock on mStateMachineThread) then this is simply ignored.
state_machine_thread->cont();
// Finally, mark that we are NOT going to do the cleanup by setting mStateMachineThread to NULL.
*state_machine_thread_w = NULL;
}
return need_cleanup;
}