diff --git a/src/apps/debugger/Jamfile b/src/apps/debugger/Jamfile index 0c3650d437..1c312e606a 100644 --- a/src/apps/debugger/Jamfile +++ b/src/apps/debugger/Jamfile @@ -23,11 +23,13 @@ Application Debugger : # ElfFile.cpp Image.cpp ImageInfo.cpp + Jobs.cpp Team.cpp TeamDebugger.cpp TeamDebugModel.cpp Thread.cpp ThreadInfo.cpp + Worker.cpp # arch Architecture.cpp diff --git a/src/apps/debugger/Jobs.cpp b/src/apps/debugger/Jobs.cpp new file mode 100644 index 0000000000..c1381adcdc --- /dev/null +++ b/src/apps/debugger/Jobs.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "Jobs.h" + +#include + +#include "CpuState.h" +#include "DebuggerInterface.h" +#include "Team.h" +#include "Thread.h" + + +GetCpuStateJob::GetCpuStateJob(DebuggerInterface* debuggerInterface, + Thread* thread) + : + fDebuggerInterface(debuggerInterface), + fThread(thread) +{ + fThread->AddReference(); +} + + +GetCpuStateJob::~GetCpuStateJob() +{ + fThread->RemoveReference(); +} + + +JobKey +GetCpuStateJob::Key() const +{ + return JobKey(fThread, JOB_TYPE_GET_CPU_STATE); +} + + +status_t +GetCpuStateJob::Do() +{ + CpuState* state; + status_t error = fDebuggerInterface->GetCpuState(fThread->ID(), state); + if (error != B_OK) + return error; + Reference reference(state); + + AutoLocker locker(fThread->GetTeam()); + + if (fThread->State() == THREAD_STATE_STOPPED) + fThread->SetCpuState(state); + + return B_OK; +} diff --git a/src/apps/debugger/Jobs.h b/src/apps/debugger/Jobs.h new file mode 100644 index 0000000000..b494d74c79 --- /dev/null +++ b/src/apps/debugger/Jobs.h @@ -0,0 +1,37 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef JOBS_H +#define JOBS_H + +#include "Worker.h" + + +class DebuggerInterface; +class Thread; + + +// job types +enum { + JOB_TYPE_GET_CPU_STATE +}; + + +class GetCpuStateJob : public Job { +public: + GetCpuStateJob( + DebuggerInterface* debuggerInterface, + Thread* thread); + virtual ~GetCpuStateJob(); + + virtual JobKey Key() const; + virtual status_t Do(); + +private: + DebuggerInterface* fDebuggerInterface; + Thread* fThread; +}; + + +#endif // JOBS_H diff --git a/src/apps/debugger/TeamDebugger.cpp b/src/apps/debugger/TeamDebugger.cpp index 1b5c37b77c..8defc9bb39 100644 --- a/src/apps/debugger/TeamDebugger.cpp +++ b/src/apps/debugger/TeamDebugger.cpp @@ -17,6 +17,7 @@ #include "CpuState.h" #include "DebuggerInterface.h" +#include "Jobs.h" #include "MessageCodes.h" #include "Team.h" #include "TeamDebugModel.h" @@ -29,6 +30,7 @@ TeamDebugger::TeamDebugger() fDebugModel(NULL), fTeamID(-1), fDebuggerInterface(NULL), + fWorker(NULL), fDebugEventListener(-1), fTeamWindow(NULL), fTerminating(false) @@ -43,15 +45,17 @@ TeamDebugger::~TeamDebugger() fTerminating = true; fDebuggerInterface->Close(); + fWorker->ShutDown(); locker.Unlock(); if (fDebugEventListener >= 0) wait_for_thread(fDebugEventListener, NULL); + delete fDebuggerInterface; + delete fWorker; delete fDebugModel; delete fTeam; - delete fDebuggerInterface; } @@ -86,6 +90,15 @@ TeamDebugger::Init(team_id teamID, thread_id threadID, bool stopInMain) if (error != B_OK) return error; + // create our worker + fWorker = new(std::nothrow) Worker; + if (fWorker == NULL) + return B_NO_MEMORY; + + error = fWorker->Init(); + if (error != B_OK) + return error; + // create debugger interface fDebuggerInterface = new(std::nothrow) DebuggerInterface(fTeamID); if (fDebuggerInterface == NULL) @@ -207,6 +220,27 @@ TeamDebugger::TeamWindowQuitRequested(TeamWindow* window) } +void +TeamDebugger::JobDone(Job* job) +{ +printf("TeamDebugger::JobDone(%p)\n", job); +} + + +void +TeamDebugger::JobFailed(Job* job) +{ +printf("TeamDebugger::JobFailed(%p)\n", job); +} + + +void +TeamDebugger::JobAborted(Job* job) +{ +printf("TeamDebugger::JobAborted(%p)\n", job); +} + + /*static*/ status_t TeamDebugger::_DebugEventListenerEntry(void* data) { @@ -335,14 +369,7 @@ TeamDebugger::_HandleThreadStopped(thread_id threadID, CpuState* cpuState) if (thread == NULL) return false; - // update the thread state - thread->SetState(THREAD_STATE_STOPPED); - - if (cpuState != NULL) { - thread->SetCpuState(cpuState); - } else { - // TODO: Trigger updating the CPU state! - } + _SetThreadState(thread, THREAD_STATE_STOPPED, cpuState); return true; } @@ -435,18 +462,42 @@ TeamDebugger::_HandleImageDeleted(ImageDeletedEvent* event) void TeamDebugger::_UpdateThreadState(::Thread* thread) { - CpuState* state = NULL; - status_t error = fDebuggerInterface->GetCpuState(thread->ID(), state); + CpuState* cpuState = NULL; + status_t error = fDebuggerInterface->GetCpuState(thread->ID(), cpuState); uint32 newState = THREAD_STATE_UNKNOWN; if (error == B_OK) { newState = THREAD_STATE_STOPPED; - state->RemoveReference(); + cpuState->RemoveReference(); } else if (error == B_BAD_THREAD_STATE) newState = THREAD_STATE_RUNNING; - thread->SetState(newState); - thread->SetCpuState(state); + _SetThreadState(thread, newState, cpuState); +} + + +void +TeamDebugger::_SetThreadState(::Thread* thread, uint32 state, + CpuState* cpuState) +{ + // update the thread state + uint32 oldState = thread->State(); + thread->SetState(state); + + // cancel jobs for this thread + if (oldState == THREAD_STATE_STOPPED) + fWorker->AbortJob(JobKey(thread, JOB_TYPE_GET_CPU_STATE)); + + if (state == THREAD_STATE_STOPPED) { + if (cpuState != NULL) { + thread->SetCpuState(cpuState); + } else { + // trigger updating the CPU state + fWorker->ScheduleJob(new(std::nothrow) GetCpuStateJob( + fDebuggerInterface, thread), + this); + } + } } @@ -469,7 +520,7 @@ TeamDebugger::_HandleThreadAction(thread_id threadID, uint32 action) // When continuing the thread update thread state before actually issuing // the command, since we need to unlock. if (action != MSG_THREAD_STOP) - thread->SetState(THREAD_STATE_RUNNING); + _SetThreadState(thread, THREAD_STATE_RUNNING, NULL); locker.Unlock(); diff --git a/src/apps/debugger/TeamDebugger.h b/src/apps/debugger/TeamDebugger.h index c758d92195..cac5f0934a 100644 --- a/src/apps/debugger/TeamDebugger.h +++ b/src/apps/debugger/TeamDebugger.h @@ -13,6 +13,7 @@ #include "DebugEvent.h" #include "TeamWindow.h" +#include "Worker.h" class DebuggerInterface; @@ -21,7 +22,7 @@ class TeamDebugModel; class TeamDebugger : public DoublyLinkedListLinkImpl, - private BLooper, private TeamWindow::Listener { + private BLooper, private TeamWindow::Listener, private JobListener { public: TeamDebugger(); ~TeamDebugger(); @@ -39,6 +40,11 @@ private: thread_id threadID, uint32 action); virtual bool TeamWindowQuitRequested(TeamWindow* window); + // JobListener + virtual void JobDone(Job* job); + virtual void JobFailed(Job* job); + virtual void JobAborted(Job* job); + private: static status_t _DebugEventListenerEntry(void* data); status_t _DebugEventListener(); @@ -70,6 +76,8 @@ private: ImageDeletedEvent* event); void _UpdateThreadState(::Thread* thread); + void _SetThreadState(::Thread* thread, uint32 state, + CpuState* cpuState); void _HandleThreadAction(thread_id threadID, uint32 action); @@ -80,6 +88,7 @@ private: team_id fTeamID; port_id fNubPort; DebuggerInterface* fDebuggerInterface; + Worker* fWorker; thread_id fDebugEventListener; TeamWindow* fTeamWindow; volatile bool fTerminating; diff --git a/src/apps/debugger/Worker.cpp b/src/apps/debugger/Worker.cpp new file mode 100644 index 0000000000..584ce648a6 --- /dev/null +++ b/src/apps/debugger/Worker.cpp @@ -0,0 +1,443 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "Worker.h" + +#include +#include + + +// #pragma mark - JobListener + + +JobListener::~JobListener() +{ +} + + +void +JobListener::JobDone(Job* job) +{ +} + + +void +JobListener::JobFailed(Job* job) +{ +} + + +void +JobListener::JobAborted(Job* job) +{ +} + + +// #pragma mark - Job + + +Job::Job() + : + fWorker(NULL), + fState(JOB_STATE_UNSCHEDULED), + fDependency(NULL), + fWaitStatus(JOB_DEPENDENCY_NOT_FOUND), + fListeners(10) +{ +} + + +Job::~Job() +{ +} + + +job_wait_status +Job::WaitFor(const JobKey& key) +{ + return fWorker->WaitForJob(this, key); +} + + +void +Job::SetWorker(Worker* worker) +{ + fWorker = worker; +} + + +void +Job::SetState(job_state state) +{ + fState = state; +} + + +void +Job::SetDependency(Job* job) +{ + fDependency = job; +} + + +void +Job::SetWaitStatus(job_wait_status status) +{ + fWaitStatus = status; +} + + +status_t +Job::AddListener(JobListener* listener) +{ + return fListeners.AddItem(listener) ? B_OK : B_NO_MEMORY; +} + + +void +Job::RemoveListener(JobListener* listener) +{ + fListeners.RemoveItem(listener); +} + + +void +Job::NotifyListeners() +{ + int32 count = fListeners.CountItems(); + for (int32 i = count - 1; i >= 0; i--) { + JobListener* listener = fListeners.ItemAt(i); + switch (fState) { + case JOB_STATE_SUCCEEDED: + listener->JobDone(this); + break; + case JOB_STATE_FAILED: + listener->JobFailed(this); + break; + case JOB_STATE_ABORTED: + default: + listener->JobAborted(this); + break; + } + } +} + + +// #pragma mark - Worker + + +Worker::Worker() + : + fLock("worker"), + fWorkerThread(-1), + fTerminating(false) +{ +} + + +Worker::~Worker() +{ + ShutDown(); + + if (fWorkerThread >= 0) + wait_for_thread(fWorkerThread, NULL); +} + + +status_t +Worker::Init() +{ + // check lock + status_t error = fLock.InitCheck(); + if (error != B_OK) + return error; + + // init jobs table + error = fJobs.Init(); + if (error != B_OK) + return error; + + // create semaphore for the worker + fWorkToDoSem = create_sem(0, "work to do"); + if (fWorkToDoSem < 0) + return fWorkToDoSem; + + // spawn worker thread + fWorkerThread = spawn_thread(_WorkerLoopEntry, "worker", B_NORMAL_PRIORITY, + this); + if (fWorkerThread < 0) + return fWorkerThread; + + resume_thread(fWorkerThread); + + return B_OK; +} + + +void +Worker::ShutDown() +{ + AutoLocker locker(this); + + if (fTerminating) + return; + + fTerminating = true; + + // abort all jobs + Job* job = fJobs.Clear(true); + while (job != NULL) { + Job* nextJob = static_cast*>(job)->fNext; + _AbortJob(job, false); + job = nextJob; + + } + + // let the work thread terminate + delete_sem(fWorkToDoSem); + fWorkToDoSem = -1; +} + + +status_t +Worker::ScheduleJob(Job* job, JobListener* listener) +{ + if (job == NULL) + return B_NO_MEMORY; + + ObjectDeleter jobDeleter(job); + AutoLocker locker(this); + + if (fTerminating) + return B_ERROR; + + if (listener != NULL) { + status_t error = job->AddListener(listener); + if (error != B_OK) + return error; + } + + bool notify = fUnscheduledJobs.IsEmpty() && fAbortedJobs.IsEmpty(); + + job->SetWorker(this); + job->SetState(JOB_STATE_UNSCHEDULED); + fJobs.Insert(job); + fUnscheduledJobs.Add(job); + jobDeleter.Detach(); + + if (notify) + release_sem(fWorkToDoSem); + + return B_OK; +} + + +void +Worker::AbortJob(const JobKey& key) +{ + AutoLocker locker(this); + + Job* job = fJobs.Lookup(key); + if (job == NULL) + return; + + _AbortJob(job, true); +} + + +status_t +Worker::AddListener(const JobKey& key, JobListener* listener) +{ + AutoLocker locker(this); + + Job* job = fJobs.Lookup(key); + if (job == NULL) + return B_ENTRY_NOT_FOUND; + + return job->AddListener(listener); +} + + +void +Worker::RemoveListener(const JobKey& key, JobListener* listener) +{ + AutoLocker locker(this); + + if (Job* job = fJobs.Lookup(key)) + job->RemoveListener(listener); +} + + +job_wait_status +Worker::WaitForJob(Job* waitingJob, const JobKey& key) +{ + AutoLocker locker(this); + + // don't wait when the game is over anyway + if (fTerminating || waitingJob->State() == JOB_STATE_ABORTED) + return JOB_DEPENDENCY_ABORTED; + + Job* job = fJobs.Lookup(key); + if (job == NULL) + return JOB_DEPENDENCY_NOT_FOUND; + + waitingJob->SetWaitStatus(JOB_DEPENDENCY_ACTIVE); + waitingJob->SetDependency(job); + job->DependentJobs().Add(waitingJob); + + // TODO: Continuations would be nice. For the time being we have to use + // recursion. Disadvantages are that we'll use more stack and that aborting + // a job waiting for a dependency won't abort the job before the dependency + // is done. + locker.Unlock(); + _ProcessJobs(job); + locker.Lock(); + + // ignore the actual wait status when the game is over anyway + if (fTerminating || waitingJob->State() == JOB_STATE_ABORTED) + return JOB_DEPENDENCY_ABORTED; + + return waitingJob->WaitStatus(); +} + + +/*static*/ status_t +Worker::_WorkerLoopEntry(void* data) +{ + return ((Worker*)data)->_WorkerLoop(); +} + + +status_t +Worker::_WorkerLoop() +{ + _ProcessJobs(NULL); + + // clean up aborted jobs + AutoLocker locker(this); + while (Job* job = fAbortedJobs.RemoveHead()) + _FinishJob(job); + + return B_OK; +} + + +void +Worker::_ProcessJobs(Job* finalJob) +{ + while (true) { + AutoLocker locker(this); + + // wait for next job + if (fUnscheduledJobs.IsEmpty() && fAbortedJobs.IsEmpty()) { + locker.Unlock(); + + status_t error = acquire_sem(fWorkToDoSem); + if (error != B_OK) { + if (error == B_INTERRUPTED) + continue; + break; + } + + locker.Lock(); + } + + // clean up aborted jobs + while (Job* job = fAbortedJobs.RemoveHead()) { + _FinishJob(job); + + if (job == finalJob) + break; + } + + // process the next job + if (Job* job = fUnscheduledJobs.RemoveHead()) { + job->SetState(JOB_STATE_ACTIVE); + + locker.Unlock(); + status_t error = job->Do(); + locker.Lock(); + + if (job->State() == JOB_STATE_ACTIVE) { + job->SetState( + error == B_OK ? JOB_STATE_SUCCEEDED : JOB_STATE_FAILED); + } + + _FinishJob(job); + + if (job == finalJob) + break; + } + } +} + + +void +Worker::_AbortJob(Job* job, bool removeFromTable) +{ + switch (job->State()) { + case JOB_STATE_ABORTED: + return; + + case JOB_STATE_UNSCHEDULED: + fUnscheduledJobs.Remove(job); + fAbortedJobs.Add(job); + break; + + case JOB_STATE_WAITING: + job->Dependency()->DependentJobs().Remove(job); + job->SetDependency(NULL); + break; + + case JOB_STATE_ACTIVE: + case JOB_STATE_FAILED: + case JOB_STATE_SUCCEEDED: + default: + break; + } + + job->SetState(JOB_STATE_ABORTED); + if (removeFromTable) + fJobs.Remove(job); +} + + +void +Worker::_FinishJob(Job* job) +{ + // wake up dependent jobs + if (!job->DependentJobs().IsEmpty()) { + job_wait_status waitStatus; + switch (job->State()) { + case JOB_STATE_ABORTED: + waitStatus = JOB_DEPENDENCY_ABORTED; + break; + case JOB_STATE_FAILED: + waitStatus = JOB_DEPENDENCY_FAILED; + break; + case JOB_STATE_SUCCEEDED: + waitStatus = JOB_DEPENDENCY_SUCCEEDED; + break; + + case JOB_STATE_UNSCHEDULED: + case JOB_STATE_WAITING: + case JOB_STATE_ACTIVE: + default: + // should never happen + waitStatus = JOB_DEPENDENCY_NOT_FOUND; + break; + } + + while (Job* dependentJob = job->DependentJobs().RemoveHead()) { + dependentJob->SetDependency(NULL); + dependentJob->SetWaitStatus(waitStatus); + } + } + + if (job->State() != JOB_STATE_ABORTED) + fJobs.Remove(job); + job->NotifyListeners(); + delete job; +} diff --git a/src/apps/debugger/Worker.h b/src/apps/debugger/Worker.h new file mode 100644 index 0000000000..5a8069df20 --- /dev/null +++ b/src/apps/debugger/Worker.h @@ -0,0 +1,206 @@ +/* + * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef WORKER_H +#define WORKER_H + +#include + +#include +#include +#include + + +class Job; +class Worker; + + +enum job_state { + JOB_STATE_UNSCHEDULED, + JOB_STATE_WAITING, + JOB_STATE_ACTIVE, + JOB_STATE_ABORTED, + JOB_STATE_FAILED, + JOB_STATE_SUCCEEDED +}; + +enum job_wait_status { + JOB_DEPENDENCY_NOT_FOUND, + JOB_DEPENDENCY_SUCCEEDED, + JOB_DEPENDENCY_FAILED, + JOB_DEPENDENCY_ABORTED, + JOB_DEPENDENCY_ACTIVE + // internal only +}; + + +struct JobKey { + void* object; + uint32 type; + + JobKey(void* object, uint32 type) + : + object(object), + type(type) + { + } + + JobKey(const JobKey& other) + : + object(other.object), + type(other.type) + { + } + + JobKey& operator=(const JobKey& other) + { + object = other.object; + type = other.type; + return *this; + } + + bool operator==(const JobKey& other) const + { + return object == other.object && type == other.type; + } + + size_t HashValue() const + { + return (size_t)(addr_t)object ^ (size_t)type; + } +}; + + +class JobListener { +public: + virtual ~JobListener(); + + virtual void JobDone(Job* job); + virtual void JobFailed(Job* job); + virtual void JobAborted(Job* job); +}; + + +typedef DoublyLinkedList JobList; + + +class Job : public DoublyLinkedListLinkImpl, public HashTableLink { +public: + Job(); + virtual ~Job(); + + virtual JobKey Key() const = 0; + virtual status_t Do() = 0; + + Worker* GetWorker() const { return fWorker; } + job_state State() const { return fState; } + +protected: + job_wait_status WaitFor(const JobKey& key); + +private: + friend class Worker; + +private: + void SetWorker(Worker* worker); + void SetState(job_state state); + + Job* Dependency() const { return fDependency; } + void SetDependency(Job* job); + + JobList& DependentJobs() { return fDependentJobs; } + + job_wait_status WaitStatus() const { return fWaitStatus; } + void SetWaitStatus(job_wait_status status); + + status_t AddListener(JobListener* listener); + void RemoveListener(JobListener* listener); + void NotifyListeners(); + +private: + typedef BObjectList ListenerList; + +private: + Worker* fWorker; + job_state fState; + Job* fDependency; + JobList fDependentJobs; + job_wait_status fWaitStatus; + ListenerList fListeners; +}; + + +class Worker { +public: + Worker(); + ~Worker(); + + status_t Init(); + void ShutDown(); + + bool Lock() { return fLock.Lock(); } + void Unlock() { fLock.Unlock(); } + + status_t ScheduleJob(Job* job, + JobListener* listener = NULL); + // always takes over ownership + void AbortJob(const JobKey& key); + + status_t AddListener(const JobKey& key, + JobListener* listener); + void RemoveListener(const JobKey& key, + JobListener* listener); + +private: + friend class Job; + + struct JobHashDefinition { + typedef JobKey KeyType; + typedef Job ValueType; + + size_t HashKey(const JobKey& key) const + { + return key.HashValue(); + } + + size_t Hash(Job* value) const + { + return HashKey(value->Key()); + } + + bool Compare(const JobKey& key, Job *value) const + { + return value->Key() == key; + } + + HashTableLink* GetLink(Job* value) const + { + return value; + } + }; + + typedef OpenHashTable JobTable; + +private: + job_wait_status WaitForJob(Job* waitingJob, const JobKey& key); + + static status_t _WorkerLoopEntry(void* data); + status_t _WorkerLoop(); + + void _ProcessJobs(Job* finalJob); + void _AbortJob(Job* job, bool removeFromTable); + void _FinishJob(Job* job); + +private: + BLocker fLock; + JobTable fJobs; + JobList fUnscheduledJobs; + JobList fAbortedJobs; + sem_id fWorkToDoSem; + thread_id fWorkerThread; + volatile bool fTerminating; +}; + + +#endif // WORKER_H