gtest: tweaked test runner to fix an extra comma, added more tdd-based thread coordinator behavior.

Starting to flesh out the thread state coordinator class that will be used
by Linux/llgs.

llvm-svn: 218537
This commit is contained in:
Todd Fiala
2014-09-26 19:08:00 +00:00
parent 2dd3129b0a
commit 80bef312b5
4 changed files with 377 additions and 24 deletions

View File

@@ -41,7 +41,11 @@ def line_combine_printer(file, previous_data, new_line_subn_result):
return ("", 0)
if len(accumulated_line) > 0:
new_line = accumulated_line + ", " + incoming_line
if accumulated_line[-2:] != ": ":
# Need to add a comma
new_line = accumulated_line + ", " + incoming_line
else:
new_line = accumulated_line + incoming_line
else:
new_line = incoming_line

View File

@@ -22,7 +22,7 @@ TEST(ThreadStateCoordinatorTest, StopCoordinatorWorksNoPriorEvents)
coordinator.StopCoordinator ();
ASSERT_EQ(coordinator.ProcessNextEvent (), false);
ASSERT_EQ(false, coordinator.ProcessNextEvent ());
}
TEST(ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenNoPendingStops)
@@ -31,19 +31,82 @@ TEST(ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenNoPendingStops)
const lldb::tid_t TRIGGERING_TID = 4105;
bool call_after_fired = false;
lldb::tid_t reported_firing_tid = 0;
// Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped.
coordinator.CallAfterThreadsStop (TRIGGERING_TID,
EMPTY_THREAD_ID_SET,
[](lldb::tid_t tid) {},
[&](lldb::tid_t tid) { call_after_fired = true; });
[&](lldb::tid_t tid) {
call_after_fired = true;
reported_firing_tid = tid;
});
// Notification trigger shouldn't go off yet.
ASSERT_EQ (call_after_fired, false);
ASSERT_EQ (false, call_after_fired);
// Process next event. This will pick up the call after threads stop event.
ASSERT_EQ(coordinator.ProcessNextEvent (), true);
ASSERT_EQ (true, coordinator.ProcessNextEvent ());
// Now the trigger should have fired.
ASSERT_EQ(call_after_fired, true);
// Now the trigger should have fired, since there were no threads that needed to first stop.
ASSERT_EQ (true, call_after_fired);
// And the firing tid should be the one we indicated.
ASSERT_EQ (TRIGGERING_TID, reported_firing_tid);
}
TEST(ThreadStateCoordinatorTest, CallAfterThreadsStopFiresWhenOnePendingStop)
{
ThreadStateCoordinator coordinator(NOPLogger);
const lldb::tid_t TRIGGERING_TID = 4105;
const lldb::tid_t PENDING_STOP_TID = 3;
ThreadStateCoordinator::ThreadIDSet pending_stop_tids { PENDING_STOP_TID };
bool call_after_fired = false;
lldb::tid_t reported_firing_tid = 0;
bool request_thread_stop_called = false;
lldb::tid_t request_thread_stop_tid = 0;
// Notify we have a trigger that needs to be fired when all threads in the wait tid set have stopped.
coordinator.CallAfterThreadsStop (TRIGGERING_TID,
pending_stop_tids,
[&](lldb::tid_t tid) {
request_thread_stop_called = true;
request_thread_stop_tid = tid;
},
[&](lldb::tid_t tid) {
call_after_fired = true;
reported_firing_tid = tid;
});
// Neither trigger should have gone off yet.
ASSERT_EQ (false, call_after_fired);
ASSERT_EQ (false, request_thread_stop_called);
// Process next event.
ASSERT_EQ (true, coordinator.ProcessNextEvent ());
// Now the request thread stop should have been called for the pending stop.
ASSERT_EQ (true, request_thread_stop_called);
ASSERT_EQ (PENDING_STOP_TID, request_thread_stop_tid);
// But we still shouldn't have the deferred signal call go off yet. Need to wait for the stop to be reported.
ASSERT_EQ (false, call_after_fired);
// Now report the that the pending stop occurred.
coordinator.NotifyThreadStop (PENDING_STOP_TID);
// Shouldn't take effect until after next processing step.
ASSERT_EQ (false, call_after_fired);
// Process next event.
ASSERT_EQ (true, coordinator.ProcessNextEvent ());
// Deferred signal notification should have fired now.
ASSERT_EQ (true, call_after_fired);
ASSERT_EQ (TRIGGERING_TID, reported_firing_tid);
}

View File

@@ -7,14 +7,214 @@
//
//===----------------------------------------------------------------------===//
#if !defined (__STDC_FORMAT_MACROS)
#define __STDC_FORMAT_MACROS 1
#endif
#include <inttypes.h>
#include "ThreadStateCoordinator.h"
#include <memory>
using namespace lldb_private;
ThreadStateCoordinator::ThreadStateCoordinator (const LogFunc &log_func) :
m_done_b (false),
m_log_func (log_func)
//===----------------------------------------------------------------------===//
class ThreadStateCoordinator::EventBase : public std::enable_shared_from_this<ThreadStateCoordinator::EventBase>
{
public:
EventBase ()
{
}
virtual
~EventBase ()
{
}
// Return false if the coordinator should terminate running.
virtual bool
ProcessEvent (ThreadStateCoordinator &coordinator) = 0;
};
//===----------------------------------------------------------------------===//
class ThreadStateCoordinator::EventStopCoordinator : public ThreadStateCoordinator::EventBase
{
public:
EventStopCoordinator ():
EventBase ()
{
}
~EventStopCoordinator () override
{
}
bool
ProcessEvent(ThreadStateCoordinator &coordinator) override
{
return false;
}
};
//===----------------------------------------------------------------------===//
class ThreadStateCoordinator::EventCallAfterThreadsStop : public ThreadStateCoordinator::EventBase
{
public:
EventCallAfterThreadsStop (lldb::tid_t triggering_tid,
const ThreadIDSet &wait_for_stop_tids,
const ThreadIDFunc &request_thread_stop_func,
const ThreadIDFunc &call_after_func):
EventBase (),
m_triggering_tid (triggering_tid),
m_wait_for_stop_tids (wait_for_stop_tids),
m_request_thread_stop_func (request_thread_stop_func),
m_call_after_func (call_after_func)
{
}
~EventCallAfterThreadsStop () override
{
}
lldb::tid_t GetTriggeringTID () const
{
return m_triggering_tid;
}
ThreadIDSet &
GetRemainingWaitTIDs ()
{
return m_wait_for_stop_tids;
}
bool
ProcessEvent(ThreadStateCoordinator &coordinator) override
{
// Request a stop for all the thread stops that are needed.
// In the future, we can keep track of which stops we're
// still waiting for, and can not re-issue for already
// requested stops.
for (auto tid : m_wait_for_stop_tids)
m_request_thread_stop_func (tid);
if (m_wait_for_stop_tids.empty ())
{
// We're not waiting for any threads. Fire off the deferred signal delivery event.
NotifyNow ();
}
else
{
// The real meat of this class: wait until all
// the thread stops (or thread deaths) come in
// before firing off that the triggering signal
// arrived.
coordinator.SetPendingNotification (shared_from_this ());
}
return true;
}
void
NotifyNow ()
{
m_call_after_func (m_triggering_tid);
}
private:
const lldb::tid_t m_triggering_tid;
ThreadIDSet m_wait_for_stop_tids;
ThreadIDFunc m_request_thread_stop_func;
ThreadIDFunc m_call_after_func;
};
//===----------------------------------------------------------------------===//
class ThreadStateCoordinator::EventThreadStopped : public ThreadStateCoordinator::EventBase
{
public:
EventThreadStopped (lldb::tid_t tid):
EventBase (),
m_tid (tid)
{
}
~EventThreadStopped () override
{
}
bool
ProcessEvent(ThreadStateCoordinator &coordinator) override
{
coordinator.ThreadDidStop (m_tid);
return true;
}
private:
const lldb::tid_t m_tid;
};
//===----------------------------------------------------------------------===//
ThreadStateCoordinator::ThreadStateCoordinator (const LogFunc &log_func) :
m_log_func (log_func),
m_event_queue (),
m_queue_condition (),
m_queue_mutex (),
m_tid_stop_map ()
{
}
void
ThreadStateCoordinator::EnqueueEvent (EventBaseSP event_sp)
{
std::lock_guard<std::mutex> lock (m_queue_mutex);
m_event_queue.push (event_sp);
m_queue_condition.notify_one ();
}
ThreadStateCoordinator::EventBaseSP
ThreadStateCoordinator::DequeueEventWithWait ()
{
// Wait for an event to be present.
std::unique_lock<std::mutex> lock (m_queue_mutex);
m_queue_condition.wait (lock,
[this] { return !m_event_queue.empty (); });
// Grab the event and pop it off the queue.
EventBaseSP event_sp = m_event_queue.front ();
m_event_queue.pop ();
return event_sp;
}
void
ThreadStateCoordinator::SetPendingNotification (const EventBaseSP &event_sp)
{
assert (event_sp && "null event_sp");
if (!event_sp)
return;
const EventCallAfterThreadsStop *new_call_after_event = static_cast<EventCallAfterThreadsStop*> (event_sp.get ());
if (m_pending_notification_sp)
{
const EventCallAfterThreadsStop *prev_call_after_event = static_cast<EventCallAfterThreadsStop*> (m_pending_notification_sp.get ());
// Yikes - we've already got a pending signal notification in progress.
// Log this info. We lose the pending notification here.
Log ("ThreadStateCoordinator::%s dropping existing pending signal notification for tid %" PRIu64 ", to be replaced with signal for tid %" PRIu64,
__FUNCTION__,
prev_call_after_event->GetTriggeringTID (),
new_call_after_event->GetTriggeringTID ());
}
m_pending_notification_sp = event_sp;
}
void
@@ -23,16 +223,63 @@ ThreadStateCoordinator::CallAfterThreadsStop (const lldb::tid_t triggering_tid,
const ThreadIDFunc &request_thread_stop_func,
const ThreadIDFunc &call_after_func)
{
EnqueueEvent (EventBaseSP (new EventCallAfterThreadsStop (triggering_tid,
wait_for_stop_tids,
request_thread_stop_func,
call_after_func)));
}
void
ThreadStateCoordinator::ThreadDidStop (lldb::tid_t tid)
{
// Update the global list of known thread states. This one is definitely stopped.
m_tid_stop_map[tid] = true;
// If we have a pending notification, remove this from the set.
if (m_pending_notification_sp)
{
EventCallAfterThreadsStop *call_after_event = static_cast<EventCallAfterThreadsStop*> (m_pending_notification_sp.get ());
auto remaining_stop_tids = call_after_event->GetRemainingWaitTIDs ();
// Remove this tid if it was in it.
remaining_stop_tids.erase (tid);
if (remaining_stop_tids.empty ())
{
// Fire the pending notification now.
call_after_event->NotifyNow ();
// Clear the pending notification now.
m_pending_notification_sp.reset ();
}
}
}
void
ThreadStateCoordinator::Log (const char *format, ...)
{
va_list args;
va_start (args, format);
m_log_func (format, args);
va_end (args);
}
void
ThreadStateCoordinator::NotifyThreadStop (lldb::tid_t tid)
{
EnqueueEvent (EventBaseSP (new EventThreadStopped (tid)));
}
void
ThreadStateCoordinator::StopCoordinator ()
{
m_done_b = true;
EnqueueEvent (EventBaseSP (new EventStopCoordinator ()));
}
bool
ThreadStateCoordinator::ProcessNextEvent ()
{
return !m_done_b;
return DequeueEventWithWait()->ProcessEvent (*this);
}

View File

@@ -10,7 +10,11 @@
#ifndef lldb_ThreadStateCoordinator_h
#define lldb_ThreadStateCoordinator_h
#include <condition_variable>
#include <functional>
#include <mutex>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include "lldb/lldb-types.h"
@@ -24,6 +28,9 @@ namespace lldb_private
// Typedefs.
typedef std::unordered_set<lldb::tid_t> ThreadIDSet;
// Protocols.
// Callback definitions.
typedef std::function<void (lldb::tid_t tid)> ThreadIDFunc;
typedef std::function<void (const char *format, va_list args)> LogFunc;
@@ -34,7 +41,7 @@ namespace lldb_private
// The main purpose of the class: triggering an action after
// a given set of threads stop.
void
CallAfterThreadsStop (const lldb::tid_t triggering_tid,
CallAfterThreadsStop (lldb::tid_t triggering_tid,
const ThreadIDSet &wait_for_stop_tids,
const ThreadIDFunc &request_thread_stop_func,
const ThreadIDFunc &call_after_func);
@@ -59,26 +66,58 @@ namespace lldb_private
// Process the next event, returning false when the coordinator is all done.
// This call is synchronous and blocks when there are no events pending.
// Expected usage is to run this in a separate thread until the function
// returns false.
// returns false. Always call this from the same thread. The processing
// logic assumes the execution of this is implicitly serialized.
bool
ProcessNextEvent ();
private:
enum EventType
{
eInvalid,
eEventTypeCallAfterThreadsStop,
eEventTypeThreadStopped,
eEventTypeThreadResumed,
eEventTypeThreadCreated,
eEventTypeThreadDied,
};
// Typedefs.
class EventBase;
bool m_done_b;
class EventCallAfterThreadsStop;
class EventStopCoordinator;
class EventThreadStopped;
typedef std::shared_ptr<EventBase> EventBaseSP;
typedef std::queue<EventBaseSP> QueueType;
typedef std::unordered_map<lldb::tid_t, bool> TIDBoolMap;
// Private member functions.
void
EnqueueEvent (EventBaseSP event_sp);
EventBaseSP
DequeueEventWithWait ();
void
SetPendingNotification (const EventBaseSP &event_sp);
void
ThreadDidStop (lldb::tid_t tid);
void
Log (const char *format, ...);
// Member variables.
LogFunc m_log_func;
QueueType m_event_queue;
// For now we do simple read/write lock strategy with efficient wait-for-data.
// We can replace with entirely non-blocking queue later but we still want the
// reader to sleep when nothing is available - this will be a bursty but infrequent
// event mechanism.
std::condition_variable m_queue_condition;
std::mutex m_queue_mutex;
EventBaseSP m_pending_notification_sp;
// Maps known TIDs to stop (true) or not-stopped (false) state.
TIDBoolMap m_tid_stop_map;
};
}