mirror of
https://github.com/intel/llvm.git
synced 2026-01-25 19:44:38 +08:00
Fix various timing/threading problems in IOChannel & Driver that
were causing the prompt to be stomped on, mangled or omitted occasionally. llvm-svn: 115059
This commit is contained in:
@@ -625,24 +625,34 @@ Driver::ParseArgs (int argc, const char *argv[], FILE *out_fh, bool &exit)
|
||||
return error;
|
||||
}
|
||||
|
||||
void
|
||||
size_t
|
||||
Driver::GetProcessSTDOUT ()
|
||||
{
|
||||
// The process has stuff waiting for stdout; get it and write it out to the appropriate place.
|
||||
char stdio_buffer[1024];
|
||||
size_t len;
|
||||
size_t total_bytes = 0;
|
||||
while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDOUT (stdio_buffer, sizeof (stdio_buffer))) > 0)
|
||||
{
|
||||
m_io_channel_ap->OutWrite (stdio_buffer, len);
|
||||
total_bytes += len;
|
||||
}
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
void
|
||||
size_t
|
||||
Driver::GetProcessSTDERR ()
|
||||
{
|
||||
// The process has stuff waiting for stderr; get it and write it out to the appropriate place.
|
||||
char stdio_buffer[1024];
|
||||
size_t len;
|
||||
size_t total_bytes = 0;
|
||||
while ((len = m_debugger.GetSelectedTarget().GetProcess().GetSTDERR (stdio_buffer, sizeof (stdio_buffer))) > 0)
|
||||
{
|
||||
m_io_channel_ap->ErrWrite (stdio_buffer, len);
|
||||
total_bytes += len;
|
||||
}
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -721,20 +731,23 @@ Driver::HandleProcessEvent (const SBEvent &event)
|
||||
{
|
||||
// The process has stdout available, get it and write it out to the
|
||||
// appropriate place.
|
||||
GetProcessSTDOUT ();
|
||||
if (GetProcessSTDOUT ())
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
}
|
||||
else if (event_type & SBProcess::eBroadcastBitSTDERR)
|
||||
{
|
||||
// The process has stderr available, get it and write it out to the
|
||||
// appropriate place.
|
||||
GetProcessSTDERR ();
|
||||
if (GetProcessSTDERR ())
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
}
|
||||
else if (event_type & SBProcess::eBroadcastBitStateChanged)
|
||||
{
|
||||
// Drain all stout and stderr so we don't see any output come after
|
||||
// we print our prompts
|
||||
GetProcessSTDOUT ();
|
||||
GetProcessSTDERR ();
|
||||
if (GetProcessSTDOUT ()
|
||||
|| GetProcessSTDERR ())
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
|
||||
// Something changed in the process; get the event and report the process's current status and location to
|
||||
// the user.
|
||||
@@ -766,8 +779,13 @@ Driver::HandleProcessEvent (const SBEvent &event)
|
||||
break;
|
||||
|
||||
case eStateExited:
|
||||
m_debugger.HandleCommand("process status");
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
{
|
||||
SBCommandReturnObject result;
|
||||
m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false);
|
||||
m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize());
|
||||
m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize());
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
}
|
||||
break;
|
||||
|
||||
case eStateStopped:
|
||||
@@ -781,12 +799,16 @@ Driver::HandleProcessEvent (const SBEvent &event)
|
||||
int message_len = ::snprintf (message, sizeof(message), "Process %d stopped and was programmatically restarted.\n",
|
||||
process.GetProcessID());
|
||||
m_io_channel_ap->OutWrite(message, message_len);
|
||||
m_io_channel_ap->RefreshPrompt ();
|
||||
}
|
||||
else
|
||||
{
|
||||
SBCommandReturnObject result;
|
||||
UpdateSelectedThread ();
|
||||
m_debugger.HandleCommand("process status");
|
||||
m_io_channel_ap->RefreshPrompt();
|
||||
m_debugger.GetCommandInterpreter().HandleCommand("process status", result, false);
|
||||
m_io_channel_ap->ErrWrite (result.GetError(), result.GetErrorSize());
|
||||
m_io_channel_ap->OutWrite (result.GetOutput(), result.GetOutputSize());
|
||||
m_io_channel_ap->RefreshPrompt ();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -133,10 +133,10 @@ private:
|
||||
void
|
||||
ResetOptionValues ();
|
||||
|
||||
void
|
||||
size_t
|
||||
GetProcessSTDOUT ();
|
||||
|
||||
void
|
||||
size_t
|
||||
GetProcessSTDERR ();
|
||||
|
||||
void
|
||||
|
||||
@@ -29,6 +29,10 @@ typedef std::map<EditLine *, std::string> PromptMap;
|
||||
const char *g_default_prompt = "(lldb) ";
|
||||
PromptMap g_prompt_map;
|
||||
|
||||
#define NSEC_PER_USEC 1000ull
|
||||
#define USEC_PER_SEC 1000000ull
|
||||
#define NSEC_PER_SEC 1000000000ull
|
||||
|
||||
static const char*
|
||||
el_prompt(EditLine *el)
|
||||
{
|
||||
@@ -156,6 +160,8 @@ IOChannel::IOChannel
|
||||
Driver *driver
|
||||
) :
|
||||
SBBroadcaster ("IOChannel"),
|
||||
m_output_mutex (),
|
||||
m_enter_elgets_time (),
|
||||
m_driver (driver),
|
||||
m_read_thread (LLDB_INVALID_HOST_THREAD),
|
||||
m_read_thread_should_exit (false),
|
||||
@@ -187,6 +193,25 @@ IOChannel::IOChannel
|
||||
::history (m_history, &m_history_event, H_SETUNIQUE, 1);
|
||||
// Load history
|
||||
HistorySaveLoad (false);
|
||||
|
||||
// Set up mutex to make sure OutErr, OutWrite and RefreshPrompt do not interfere
|
||||
// with each other when writing.
|
||||
|
||||
int error;
|
||||
::pthread_mutexattr_t attr;
|
||||
error = ::pthread_mutexattr_init (&attr);
|
||||
assert (error == 0);
|
||||
error = ::pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
assert (error == 0);
|
||||
error = ::pthread_mutex_init (&m_output_mutex, &attr);
|
||||
assert (error == 0);
|
||||
error = ::pthread_mutexattr_destroy (&attr);
|
||||
assert (error == 0);
|
||||
|
||||
// Initialize time that ::el_gets was last called.
|
||||
|
||||
m_enter_elgets_time.tv_sec = 0;
|
||||
m_enter_elgets_time.tv_usec = 0;
|
||||
}
|
||||
|
||||
IOChannel::~IOChannel ()
|
||||
@@ -205,6 +230,8 @@ IOChannel::~IOChannel ()
|
||||
::el_end (m_edit_line);
|
||||
m_edit_line = NULL;
|
||||
}
|
||||
|
||||
::pthread_mutex_destroy (&m_output_mutex);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -231,7 +258,23 @@ IOChannel::LibeditGetInput (std::string &new_line)
|
||||
if (m_edit_line != NULL)
|
||||
{
|
||||
int line_len = 0;
|
||||
|
||||
// Set boolean indicating whether or not el_gets is trying to get input (i.e. whether or not to attempt
|
||||
// to refresh the prompt after writing data).
|
||||
SetGettingCommand (true);
|
||||
|
||||
// Get the current time just before calling el_gets; this is used by OutWrite, ErrWrite, and RefreshPrompt
|
||||
// to make sure they have given el_gets enough time to write the prompt before they attempt to write
|
||||
// anything.
|
||||
|
||||
::gettimeofday (&m_enter_elgets_time, NULL);
|
||||
|
||||
// Call el_gets to prompt the user and read the user's input.
|
||||
const char *line = ::el_gets (m_edit_line, &line_len);
|
||||
|
||||
// Re-set the boolean indicating whether or not el_gets is trying to get input.
|
||||
SetGettingCommand (false);
|
||||
|
||||
if (line)
|
||||
{
|
||||
// strip any newlines off the end of the string...
|
||||
@@ -399,6 +442,23 @@ IOChannel::Stop ()
|
||||
void
|
||||
IOChannel::RefreshPrompt ()
|
||||
{
|
||||
// If we are not in the middle of getting input from the user, there is no need to
|
||||
// refresh the prompt.
|
||||
|
||||
if (! IsGettingCommand())
|
||||
return;
|
||||
|
||||
// Compare the current time versus the last time el_gets was called. If less than
|
||||
// 10000 microseconds (10000000 nanoseconds) have elapsed, wait 10000 microseconds, to ensure el_gets had time
|
||||
// to finish writing the prompt before we start writing here.
|
||||
|
||||
if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000)
|
||||
usleep (10000);
|
||||
|
||||
// Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with
|
||||
// each other's output.
|
||||
|
||||
IOLocker locker (m_output_mutex);
|
||||
::el_set (m_edit_line, EL_REFRESH);
|
||||
}
|
||||
|
||||
@@ -407,7 +467,20 @@ IOChannel::OutWrite (const char *buffer, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return;
|
||||
::fwrite (buffer, 1, len, m_out_file);
|
||||
|
||||
// Compare the current time versus the last time el_gets was called. If less than
|
||||
// 10000 microseconds (10000000 nanoseconds) have elapsed, wait 10000 microseconds, to ensure el_gets had time
|
||||
// to finish writing the prompt before we start writing here.
|
||||
|
||||
if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000)
|
||||
usleep (10000);
|
||||
|
||||
{
|
||||
// Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with
|
||||
// each other's output.
|
||||
IOLocker locker (m_output_mutex);
|
||||
::fwrite (buffer, 1, len, m_out_file);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -415,7 +488,20 @@ IOChannel::ErrWrite (const char *buffer, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
return;
|
||||
::fwrite (buffer, 1, len, m_err_file);
|
||||
|
||||
// Compare the current time versus the last time el_gets was called. If less than
|
||||
// 10000 microseconds (10000000 nanoseconds) have elapsed, wait 10000 microseconds, to ensure el_gets had time
|
||||
// to finish writing the prompt before we start writing here.
|
||||
|
||||
if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000)
|
||||
usleep (10000);
|
||||
|
||||
{
|
||||
// Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with
|
||||
// each other's output.
|
||||
IOLocker locker (m_output_mutex);
|
||||
::fwrite (buffer, 1, len, m_err_file);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -452,3 +538,48 @@ IOChannel::CommandQueueIsEmpty () const
|
||||
{
|
||||
return m_command_queue.empty();
|
||||
}
|
||||
|
||||
bool
|
||||
IOChannel::IsGettingCommand () const
|
||||
{
|
||||
return m_getting_command;
|
||||
}
|
||||
|
||||
void
|
||||
IOChannel::SetGettingCommand (bool new_value)
|
||||
{
|
||||
m_getting_command = new_value;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IOChannel::Nanoseconds (const struct timeval &time_val) const
|
||||
{
|
||||
uint64_t nanoseconds = time_val.tv_sec * NSEC_PER_SEC + time_val.tv_usec * NSEC_PER_USEC;
|
||||
|
||||
return nanoseconds;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
IOChannel::ElapsedNanoSecondsSinceEnteringElGets ()
|
||||
{
|
||||
if (! IsGettingCommand())
|
||||
return 0;
|
||||
|
||||
struct timeval current_time;
|
||||
::gettimeofday (¤t_time, NULL);
|
||||
return (Nanoseconds (current_time) - Nanoseconds (m_enter_elgets_time));
|
||||
}
|
||||
|
||||
IOLocker::IOLocker (pthread_mutex_t &mutex) :
|
||||
m_mutex_ptr (&mutex)
|
||||
{
|
||||
if (m_mutex_ptr)
|
||||
::pthread_mutex_lock (m_mutex_ptr);
|
||||
|
||||
}
|
||||
|
||||
IOLocker::~IOLocker ()
|
||||
{
|
||||
if (m_mutex_ptr)
|
||||
::pthread_mutex_unlock (m_mutex_ptr);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#include <editline/readline.h>
|
||||
#include <histedit.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "Driver.h"
|
||||
|
||||
@@ -89,11 +91,25 @@ public:
|
||||
static unsigned char
|
||||
ElCompletionFn (EditLine *e, int ch);
|
||||
|
||||
protected:
|
||||
|
||||
bool
|
||||
IsGettingCommand () const;
|
||||
|
||||
void
|
||||
SetGettingCommand (bool new_value);
|
||||
|
||||
uint64_t
|
||||
Nanoseconds (const struct timeval &time_val) const;
|
||||
|
||||
uint64_t
|
||||
ElapsedNanoSecondsSinceEnteringElGets ();
|
||||
|
||||
private:
|
||||
|
||||
pthread_mutex_t m_output_mutex;
|
||||
struct timeval m_enter_elgets_time;
|
||||
|
||||
Driver *m_driver;
|
||||
lldb::thread_t m_read_thread;
|
||||
bool m_read_thread_should_exit;
|
||||
@@ -114,4 +130,22 @@ private:
|
||||
HandleCompletion (EditLine *e, int ch);
|
||||
};
|
||||
|
||||
class IOLocker
|
||||
{
|
||||
public:
|
||||
|
||||
IOLocker (pthread_mutex_t &mutex);
|
||||
|
||||
~IOLocker ();
|
||||
|
||||
protected:
|
||||
|
||||
pthread_mutex_t *m_mutex_ptr;
|
||||
|
||||
private:
|
||||
|
||||
IOLocker (const IOLocker&);
|
||||
const IOLocker& operator= (const IOLocker&);
|
||||
};
|
||||
|
||||
#endif // lldb_IOChannel_h_
|
||||
|
||||
Reference in New Issue
Block a user