⛏️ index : haiku.git

/*
 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <DebugLooper.h>

#include <new>

#include <AutoLocker.h>
#include <DebugMessageHandler.h>
#include <TeamDebugger.h>
#include <util/DoublyLinkedList.h>


struct BDebugLooper::Debugger {
	BTeamDebugger*			debugger;
	BDebugMessageHandler*	handler;

	Debugger(BTeamDebugger* debugger, BDebugMessageHandler* handler)
		:
		debugger(debugger),
		handler(handler)
	{
	}
};


struct BDebugLooper::Job : DoublyLinkedListLinkImpl<Job> {
	Job()
		:
		fDoneSemaphore(-1)
	{
	}

	virtual ~Job()
	{
	}

	status_t Wait(BLocker& lock)
	{
		fDoneSemaphore = create_sem(0, "debug looper job");

		lock.Unlock();

		while (acquire_sem(fDoneSemaphore) == B_INTERRUPTED) {
		}

		lock.Lock();

		delete_sem(fDoneSemaphore);
		fDoneSemaphore = -1;

		return fResult;
	}

	void Done(status_t result)
	{
		fResult = result;
		release_sem(fDoneSemaphore);
	}

	virtual status_t Do(BDebugLooper* looper) = 0;

protected:
	sem_id			fDoneSemaphore;
	status_t		fResult;
};


struct BDebugLooper::JobList : DoublyLinkedList<Job> {
};


struct BDebugLooper::AddDebuggerJob : Job {
	AddDebuggerJob(BTeamDebugger* debugger,
		BDebugMessageHandler* handler)
		:
		fDebugger(debugger),
		fHandler(handler)
	{
	}

	virtual status_t Do(BDebugLooper* looper)
	{
		Debugger* debugger = new(std::nothrow) Debugger(fDebugger, fHandler);
		if (debugger == NULL || !looper->fDebuggers.AddItem(debugger)) {
			delete debugger;
			return B_NO_MEMORY;
		}

		return B_OK;
	}

private:
	BTeamDebugger*			fDebugger;
	BDebugMessageHandler*	fHandler;
};


struct BDebugLooper::RemoveDebuggerJob : Job {
	RemoveDebuggerJob(team_id team)
		:
		fTeam(team)
	{
	}

	virtual status_t Do(BDebugLooper* looper)
	{
		for (int32 i = 0; Debugger* debugger = looper->fDebuggers.ItemAt(i);
				i++) {
			if (debugger->debugger->Team() == fTeam) {
				delete looper->fDebuggers.RemoveItemAt(i);
				return B_OK;
			}
		}

		return B_ENTRY_NOT_FOUND;
	}

private:
	team_id	fTeam;
};


// #pragma mark -


BDebugLooper::BDebugLooper()
	:
	fLock("debug looper"),
	fThread(-1),
	fOwnsThread(false),
	fTerminating(false),
	fNotified(false),
	fJobs(NULL),
	fEventSemaphore(-1)
{
}


BDebugLooper::~BDebugLooper()
{
}


status_t
BDebugLooper::Init()
{
	status_t error = fLock.InitCheck();
	if (error != B_OK)
		return error;

	AutoLocker<BLocker> locker(fLock);

	if (fThread >= 0)
		return B_BAD_VALUE;

	if (fJobs == NULL) {
		fJobs = new(std::nothrow) JobList;
		if (fJobs == NULL)
			return B_NO_MEMORY;
	}

	if (fEventSemaphore < 0) {
		fEventSemaphore = create_sem(0, "debug looper event");
		if (fEventSemaphore < 0)
			return fEventSemaphore;
	}

	return B_OK;
}


thread_id
BDebugLooper::Run(bool spawnThread)
{
	AutoLocker<BLocker> locker(fLock);

	if (fThread >= 0)
		return B_BAD_VALUE;

	fNotified = false;

	if (spawnThread) {
		fThread = spawn_thread(&_MessageLoopEntry, "debug looper",
			B_NORMAL_PRIORITY, this);
		if (fThread < 0)
			return fThread;

		fOwnsThread = true;

		resume_thread(fThread);
		return B_OK;
	}

	fThread = find_thread(NULL);
	fOwnsThread = false;

	_MessageLoop();
	return B_OK;
}


void
BDebugLooper::Quit()
{
	AutoLocker<BLocker> locker(fLock);

	fTerminating = true;
	_Notify();
}


status_t
BDebugLooper::AddTeamDebugger(BTeamDebugger* debugger,
	BDebugMessageHandler* handler)
{
	if (debugger == NULL || handler == NULL)
		return B_BAD_VALUE;

	AddDebuggerJob job(debugger, handler);
	return _DoJob(&job);
}


bool
BDebugLooper::RemoveTeamDebugger(BTeamDebugger* debugger)
{
	if (debugger == NULL)
		return false;

	RemoveDebuggerJob job(debugger->Team());
	return _DoJob(&job) == B_OK;
}


bool
BDebugLooper::RemoveTeamDebugger(team_id team)
{
	if (team < 0)
		return false;

	RemoveDebuggerJob job(team);
	return _DoJob(&job) == B_OK;
}


/*static*/ status_t
BDebugLooper::_MessageLoopEntry(void* data)
{
	return ((BDebugLooper*)data)->_MessageLoop();
}


status_t
BDebugLooper::_MessageLoop()
{
	while (true) {
		// prepare the wait info array
		int32 debuggerCount = fDebuggers.CountItems();
		object_wait_info waitInfos[debuggerCount + 1];

		for (int32 i = 0; i < debuggerCount; i++) {
			waitInfos[i].object
				= fDebuggers.ItemAt(i)->debugger->DebuggerPort();
			waitInfos[i].type = B_OBJECT_TYPE_PORT;
			waitInfos[i].events = B_EVENT_READ;
		}

		waitInfos[debuggerCount].object = fEventSemaphore;
		waitInfos[debuggerCount].type = B_OBJECT_TYPE_SEMAPHORE;
		waitInfos[debuggerCount].events = B_EVENT_ACQUIRE_SEMAPHORE;

		// wait for the next event
		wait_for_objects(waitInfos, debuggerCount + 1);

		AutoLocker<BLocker> locker(fLock);

		// handle all pending jobs
		bool handledJobs = fJobs->Head() != NULL;
		while (Job* job = fJobs->RemoveHead())
			job->Done(job->Do(this));

		// acquire notification semaphore and mark unnotified
		if ((waitInfos[debuggerCount].events & B_EVENT_ACQUIRE_SEMAPHORE) != 0)
			acquire_sem(fEventSemaphore);
		fNotified = false;

		if (fTerminating)
			return B_OK;

		// Always loop when jobs were executed, since that might add/remove
		// debuggers.
		if (handledJobs)
			continue;

		// read a pending port message
		for (int32 i = 0; i < debuggerCount; i++) {
			if ((waitInfos[i].events & B_EVENT_READ) != 0) {
				Debugger* debugger = fDebuggers.ItemAt(i);

				// read the message
				debug_debugger_message_data message;
				int32 code;
				ssize_t messageSize = read_port(
					debugger->debugger->DebuggerPort(), &code, &message,
					sizeof(message));
				if (messageSize < 0)
					continue;

				// handle the message
				bool continueThread = debugger->handler->HandleDebugMessage(
					code, message);

				// If requested, tell the thread to continue (only when there
				// is a thread and the message was synchronous).
				if (continueThread && message.origin.thread >= 0
						&& message.origin.nub_port >= 0) {
					debugger->debugger->ContinueThread(message.origin.thread);
				}

				// Handle only one message -- the hook might have added/removed
				// debuggers which makes further iteration problematic.
				break;
			}
		}
	}
}


status_t
BDebugLooper::_DoJob(Job* job)
{
	AutoLocker<BLocker> locker(fLock);

	// execute directly, if in looper thread or not running yet
	if (fThread < 0 || fThread == find_thread(NULL))
		return job->Do(this);

	// execute in the looper thread
	fJobs->Add(job);
	_Notify();

	return job->Wait(fLock);
}


void
BDebugLooper::_Notify()
{
	if (fNotified)
		return;

	fNotified = true;
	release_sem(fEventSemaphore);
}