forked from OSchip/llvm-project
Add the ability to catch and do the right thing with Interrupts (often control-c)
and end-of-file (often control-d). llvm-svn: 119837
This commit is contained in:
parent
601e72c88a
commit
efed613172
|
@ -149,6 +149,12 @@ public:
|
|||
void
|
||||
DispatchInput (void *baton, const void *data, size_t data_len);
|
||||
|
||||
void
|
||||
DispatchInputInterrupt ();
|
||||
|
||||
void
|
||||
DispatchInputEndOfFile ();
|
||||
|
||||
void
|
||||
PushInputReader (lldb::SBInputReader &reader);
|
||||
|
||||
|
@ -173,6 +179,9 @@ public:
|
|||
void
|
||||
SetTerminalWidth (uint32_t term_width);
|
||||
|
||||
lldb::user_id_t
|
||||
GetID ();
|
||||
|
||||
const char *
|
||||
GetPrompt() const;
|
||||
|
||||
|
|
|
@ -376,7 +376,7 @@ protected:
|
|||
/// The number of bytes to append to the cache.
|
||||
//------------------------------------------------------------------
|
||||
virtual void
|
||||
AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast);
|
||||
AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast, lldb::ConnectionStatus status);
|
||||
|
||||
//------------------------------------------------------------------
|
||||
/// Get any available bytes from our data cache. If this call
|
||||
|
|
|
@ -330,6 +330,12 @@ public:
|
|||
TargetList&
|
||||
GetTargetList ();
|
||||
|
||||
void
|
||||
DispatchInputInterrupt ();
|
||||
|
||||
void
|
||||
DispatchInputEndOfFile ();
|
||||
|
||||
void
|
||||
DispatchInput (const char *bytes, size_t bytes_len);
|
||||
|
||||
|
|
|
@ -287,6 +287,7 @@ typedef enum ReturnStatus
|
|||
typedef enum ConnectionStatus
|
||||
{
|
||||
eConnectionStatusSuccess, // Success
|
||||
eConnectionStatusEndOfFile, // End-of-file encountered
|
||||
eConnectionStatusError, // Check GetError() for details
|
||||
eConnectionStatusTimedOut, // Request timed out
|
||||
eConnectionStatusNoConnection, // No connection
|
||||
|
@ -397,6 +398,8 @@ typedef enum InputReaderAction
|
|||
eInputReaderReactivate, // reader is on top of the stack again after another reader was popped off
|
||||
eInputReaderDeactivate, // another reader was pushed on the stack
|
||||
eInputReaderGotToken, // reader got one of its tokens (granularity)
|
||||
eInputReaderInterrupt, // reader received an interrupt signal (probably from a control-c)
|
||||
eInputReaderEndOfFile, // reader received an EOF char (probably from a control-d)
|
||||
eInputReaderDone // reader was just popped off the stack and is done
|
||||
} InputReaderAction;
|
||||
|
||||
|
|
|
@ -658,6 +658,20 @@ SBDebugger::DispatchInput (void *baton, const void *data, size_t data_len)
|
|||
m_opaque_sp->DispatchInput ((const char *) data, data_len);
|
||||
}
|
||||
|
||||
void
|
||||
SBDebugger::DispatchInputInterrupt ()
|
||||
{
|
||||
if (m_opaque_sp)
|
||||
m_opaque_sp->DispatchInputInterrupt ();
|
||||
}
|
||||
|
||||
void
|
||||
SBDebugger::DispatchInputEndOfFile ()
|
||||
{
|
||||
if (m_opaque_sp)
|
||||
m_opaque_sp->DispatchInputEndOfFile ();
|
||||
}
|
||||
|
||||
void
|
||||
SBDebugger::PushInputReader (SBInputReader &reader)
|
||||
{
|
||||
|
@ -837,3 +851,11 @@ SBDebugger::GetDescription (SBStream &description)
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
lldb::user_id_t
|
||||
SBDebugger::GetID()
|
||||
{
|
||||
if (m_opaque_sp)
|
||||
return m_opaque_sp->GetID();
|
||||
return LLDB_INVALID_UID;
|
||||
}
|
||||
|
|
|
@ -468,6 +468,29 @@ CommandObjectBreakpointCommandAdd::GenerateBreakpointCommandCallback
|
|||
}
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
{
|
||||
// Finish, and cancel the breakpoint command.
|
||||
reader.SetIsDone (true);
|
||||
BreakpointOptions *bp_options = (BreakpointOptions *) baton;
|
||||
if (bp_options)
|
||||
{
|
||||
Baton *bp_options_baton = bp_options->GetBaton ();
|
||||
if (bp_options_baton)
|
||||
{
|
||||
((BreakpointOptions::CommandData *) bp_options_baton->m_data)->user_source.Clear();
|
||||
((BreakpointOptions::CommandData *) bp_options_baton->m_data)->script_source.Clear();
|
||||
}
|
||||
}
|
||||
::fprintf (out_fh, "Warning: No command attached to breakpoint.\n");
|
||||
::fflush (out_fh);
|
||||
}
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
reader.SetIsDone (true);
|
||||
break;
|
||||
|
||||
case eInputReaderDone:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -199,7 +199,18 @@ CommandObjectExpression::MultiLineExpressionCallback
|
|||
// ::fprintf (out_fh, "%3u: ", cmd_object_expr->m_expr_line_count);
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
cmd_object_expr->m_expr_lines.clear();
|
||||
reader.SetIsDone (true);
|
||||
reader.GetDebugger().GetOutputStream().Printf("%s\n", "Expression evaluation cancelled.");
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
reader.SetIsDone (true);
|
||||
break;
|
||||
|
||||
case eInputReaderDone:
|
||||
if (cmd_object_expr->m_expr_lines.size() > 0)
|
||||
{
|
||||
cmd_object_expr->EvaluateExpression (cmd_object_expr->m_expr_lines.c_str(),
|
||||
reader.GetDebugger().GetOutputStream(),
|
||||
|
|
|
@ -260,12 +260,13 @@ Communication::GetCachedBytes (void *dst, size_t dst_len)
|
|||
}
|
||||
|
||||
void
|
||||
Communication::AppendBytesToCache (const uint8_t * bytes, size_t len, bool broadcast)
|
||||
Communication::AppendBytesToCache (const uint8_t * bytes, size_t len, bool broadcast, ConnectionStatus status)
|
||||
{
|
||||
lldb_private::LogIfAnyCategoriesSet (LIBLLDB_LOG_COMMUNICATION,
|
||||
"%p Communication::AppendBytesToCache (src = %p, src_len = %zu, broadcast = %i)",
|
||||
this, bytes, len, broadcast);
|
||||
if (bytes == NULL || len == 0)
|
||||
if ((bytes == NULL || len == 0)
|
||||
&& (status != eConnectionStatusEndOfFile))
|
||||
return;
|
||||
if (m_callback)
|
||||
{
|
||||
|
@ -319,12 +320,16 @@ Communication::ReadThread (void *p)
|
|||
{
|
||||
size_t bytes_read = comm->ReadFromConnection (buf, sizeof(buf), status, &error);
|
||||
if (bytes_read > 0)
|
||||
comm->AppendBytesToCache (buf, bytes_read, true);
|
||||
comm->AppendBytesToCache (buf, bytes_read, true, status);
|
||||
else if ((bytes_read == 0)
|
||||
&& status == eConnectionStatusEndOfFile)
|
||||
comm->AppendBytesToCache (buf, bytes_read, true, status);
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case eConnectionStatusSuccess:
|
||||
case eConnectionStatusEndOfFile:
|
||||
break;
|
||||
|
||||
case eConnectionStatusNoConnection: // No connection
|
||||
|
|
|
@ -155,9 +155,19 @@ ConnectionFileDescriptor::Read (void *dst, size_t dst_len, ConnectionStatus &sta
|
|||
Error error;
|
||||
ssize_t bytes_read = ::read (m_fd, dst, dst_len);
|
||||
if (bytes_read == 0)
|
||||
{
|
||||
// 'read' did not return an error, but it didn't return any bytes either ==> End-of-File.
|
||||
// If the file descriptor is still valid, then we don't return an error; otherwise we do.
|
||||
// This allows whoever called us to act on the end-of-file, with a valid file descriptor, if they wish.
|
||||
if (fcntl (m_fd, F_GETFL, 0) >= 0)
|
||||
{
|
||||
error.Clear(); // End-of-file, but not an error. Pass along for the end-of-file handlers.
|
||||
}
|
||||
else
|
||||
{
|
||||
error.SetErrorStringWithFormat("End-of-file.\n");
|
||||
status = eConnectionStatusLostConnection;
|
||||
}
|
||||
status = eConnectionStatusEndOfFile;
|
||||
}
|
||||
else if (bytes_read < 0)
|
||||
{
|
||||
|
|
|
@ -338,19 +338,56 @@ Debugger::GetTargetList ()
|
|||
void
|
||||
Debugger::DispatchInputCallback (void *baton, const void *bytes, size_t bytes_len)
|
||||
{
|
||||
if (bytes_len > 0)
|
||||
((Debugger *)baton)->DispatchInput ((char *)bytes, bytes_len);
|
||||
else
|
||||
((Debugger *)baton)->DispatchInputEndOfFile ();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Debugger::DispatchInput (const char *bytes, size_t bytes_len)
|
||||
{
|
||||
// if (bytes == NULL || bytes_len == 0)
|
||||
// return;
|
||||
if (bytes == NULL || bytes_len == 0)
|
||||
return;
|
||||
|
||||
WriteToDefaultReader (bytes, bytes_len);
|
||||
}
|
||||
|
||||
void
|
||||
Debugger::DispatchInputInterrupt ()
|
||||
{
|
||||
m_input_reader_data.clear();
|
||||
|
||||
if (!m_input_readers.empty())
|
||||
{
|
||||
while (CheckIfTopInputReaderIsDone ()) ;
|
||||
|
||||
InputReaderSP reader_sp(m_input_readers.top());
|
||||
if (reader_sp)
|
||||
reader_sp->Notify (eInputReaderInterrupt);
|
||||
|
||||
while (CheckIfTopInputReaderIsDone ()) ;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Debugger::DispatchInputEndOfFile ()
|
||||
{
|
||||
m_input_reader_data.clear();
|
||||
|
||||
if (!m_input_readers.empty())
|
||||
{
|
||||
while (CheckIfTopInputReaderIsDone ()) ;
|
||||
|
||||
InputReaderSP reader_sp(m_input_readers.top());
|
||||
if (reader_sp)
|
||||
reader_sp->Notify (eInputReaderEndOfFile);
|
||||
|
||||
while (CheckIfTopInputReaderIsDone ()) ;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Debugger::WriteToDefaultReader (const char *bytes, size_t bytes_len)
|
||||
{
|
||||
|
|
|
@ -324,6 +324,10 @@ InputReader::Notify (InputReaderAction notification)
|
|||
m_active = false;
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
case eInputReaderEndOfFile:
|
||||
break;
|
||||
|
||||
case eInputReaderGotToken:
|
||||
return; // We don't notify the tokens here, it is done in HandleRawBytes
|
||||
}
|
||||
|
|
|
@ -860,6 +860,12 @@ CommandInterpreter::GetConfirmationInputReaderCallback (void *baton,
|
|||
}
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
case eInputReaderEndOfFile:
|
||||
*response_ptr = false; // Assume ^C or ^D means cancel the proposed action
|
||||
reader.SetIsDone (true);
|
||||
break;
|
||||
|
||||
case eInputReaderDone:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -272,11 +272,6 @@ ScriptInterpreterPython::ScriptInterpreterPython (CommandInterpreter &interprete
|
|||
PyRun_SimpleString (run_string.GetData());
|
||||
PyRun_SimpleString ("sys.stdin = new_stdin");
|
||||
|
||||
PyRun_SimpleString ("new_mode = tcgetattr(new_stdin)");
|
||||
PyRun_SimpleString ("new_mode[3] = new_mode[3] | ECHO | ICANON");
|
||||
PyRun_SimpleString ("new_mode[6][VEOF] = 255");
|
||||
PyRun_SimpleString ("tcsetattr (new_stdin, TCSANOW, new_mode)");
|
||||
|
||||
run_string.Clear();
|
||||
run_string.Printf ("lldb.debugger_unique_id = %d", interpreter.GetDebugger().GetID());
|
||||
PyRun_SimpleString (run_string.GetData());
|
||||
|
@ -302,26 +297,10 @@ ScriptInterpreterPython::ExecuteOneLine (const char *command, CommandReturnObjec
|
|||
{
|
||||
int success;
|
||||
|
||||
|
||||
// Save the current input file handle state before executing the command.
|
||||
int input_fd;
|
||||
struct termios tmp_termios;
|
||||
bool valid_termios = false;
|
||||
FILE *input_fh = m_interpreter.GetDebugger().GetInputFileHandle();
|
||||
if (input_fh != NULL)
|
||||
{
|
||||
input_fd = ::fileno (input_fh);
|
||||
valid_termios = ::tcgetattr (input_fd, &tmp_termios) == 0;
|
||||
}
|
||||
|
||||
success = PyRun_SimpleString (command);
|
||||
if (success == 0)
|
||||
return true;
|
||||
|
||||
// Restore the input file handle state after executing the command.
|
||||
if (valid_termios)
|
||||
::tcsetattr (input_fd, TCSANOW, &tmp_termios);
|
||||
|
||||
// The one-liner failed. Append the error message.
|
||||
if (result)
|
||||
result->AppendErrorWithFormat ("python failed attempting to evaluate '%s'\n", command);
|
||||
|
@ -366,12 +345,6 @@ ScriptInterpreterPython::InputReaderCallback
|
|||
|
||||
script_interpreter->m_termios_valid = ::tcgetattr (input_fd, &script_interpreter->m_termios) == 0;
|
||||
|
||||
if (script_interpreter->m_termios_valid)
|
||||
{
|
||||
struct termios tmp_termios = script_interpreter->m_termios;
|
||||
tmp_termios.c_cc[VEOF] = _POSIX_VDISABLE;
|
||||
::tcsetattr (input_fd, TCSANOW, &tmp_termios);
|
||||
}
|
||||
char error_str[1024];
|
||||
if (script_interpreter->m_embedded_python_pty.OpenFirstAvailableMaster (O_RDWR|O_NOCTTY, error_str,
|
||||
sizeof(error_str)))
|
||||
|
@ -412,6 +385,14 @@ ScriptInterpreterPython::InputReaderCallback
|
|||
case eInputReaderReactivate:
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "raise KeyboardInterrupt\n", 24);
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
::write (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor(), "quit()\n", 7);
|
||||
break;
|
||||
|
||||
case eInputReaderGotToken:
|
||||
if (script_interpreter->m_embedded_python_pty.GetMasterFileDescriptor() != -1)
|
||||
{
|
||||
|
@ -724,6 +705,17 @@ ScriptInterpreterPython::GenerateBreakpointOptionsCommandCallback
|
|||
}
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
case eInputReaderInterrupt:
|
||||
// Control-c (SIGINT) & control-d both mean finish & exit.
|
||||
reader.SetIsDone(true);
|
||||
|
||||
// Control-c (SIGINT) ALSO means cancel; do NOT create a breakpoint command.
|
||||
if (notification == eInputReaderInterrupt)
|
||||
commands_in_progress.Clear();
|
||||
|
||||
// Fall through here...
|
||||
|
||||
case eInputReaderDone:
|
||||
{
|
||||
BreakpointOptions *bp_options = (BreakpointOptions *)baton;
|
||||
|
@ -843,6 +835,10 @@ ScriptInterpreterPython::GenerateBreakpointCommandCallbackData (StringList &user
|
|||
int num_lines = user_input.GetSize ();
|
||||
StreamString sstr;
|
||||
|
||||
// Check to see if we have any data; if not, just return.
|
||||
if (user_input.GetSize() == 0)
|
||||
return false;
|
||||
|
||||
// Take what the user wrote, wrap it all up inside one big auto-generated Python function, passing in the
|
||||
// frame and breakpoint location as parameters to the function.
|
||||
|
||||
|
@ -933,10 +929,6 @@ ScriptInterpreterPython::RunEmbeddedPythonInterpreter (lldb::thread_arg_t baton)
|
|||
PyRun_SimpleString ("save_stdin = sys.stdin");
|
||||
run_string.Printf ("sys.stdin = open ('%s', 'r')", pty_slave_name);
|
||||
PyRun_SimpleString (run_string.GetData());
|
||||
PyRun_SimpleString ("new_mode = tcgetattr(sys.stdin)");
|
||||
PyRun_SimpleString ("new_mode[3] = new_mode[3] | ECHO | ICANON");
|
||||
PyRun_SimpleString ("new_mode[6][VEOF] = 255");
|
||||
PyRun_SimpleString ("tcsetattr (sys.stdin, TCSANOW, new_mode)");
|
||||
|
||||
// The following call drops into the embedded interpreter loop and stays there until the
|
||||
// user chooses to exit from the Python interpreter.
|
||||
|
|
|
@ -551,7 +551,8 @@ GDBRemoteCommunication::WaitForPacketNoLock (StringExtractorGDBRemote &response,
|
|||
}
|
||||
|
||||
void
|
||||
GDBRemoteCommunication::AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast)
|
||||
GDBRemoteCommunication::AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast,
|
||||
ConnectionStatus status)
|
||||
{
|
||||
// Put the packet data into the buffer in a thread safe fashion
|
||||
Mutex::Locker locker(m_bytes_mutex);
|
||||
|
|
|
@ -110,7 +110,7 @@ public:
|
|||
// Communication overrides
|
||||
//------------------------------------------------------------------
|
||||
virtual void
|
||||
AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast);
|
||||
AppendBytesToCache (const uint8_t *src, size_t src_len, bool broadcast, lldb::ConnectionStatus status);
|
||||
|
||||
|
||||
lldb::pid_t
|
||||
|
|
|
@ -1478,10 +1478,6 @@ Process::Halt ()
|
|||
}
|
||||
else
|
||||
{
|
||||
// Since we are eating the event, we need to update our state
|
||||
// otherwise the process state will not match reality...
|
||||
SetPublicState(state);
|
||||
|
||||
if (StateIsStoppedState (state))
|
||||
{
|
||||
// We caused the process to interrupt itself, so mark this
|
||||
|
@ -1508,7 +1504,7 @@ Process::Halt ()
|
|||
// stopped the process, intercepted the event and set the interrupted
|
||||
// bool in the event.
|
||||
if (event_sp)
|
||||
BroadcastEvent(event_sp);
|
||||
m_private_state_broadcaster.BroadcastEvent(event_sp);
|
||||
|
||||
}
|
||||
return error;
|
||||
|
@ -2175,6 +2171,14 @@ Process::ProcessInputReaderCallback (void *baton,
|
|||
}
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
process->Halt ();
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
process->AppendSTDOUT ("^D", 2);
|
||||
break;
|
||||
|
||||
case eInputReaderDone:
|
||||
break;
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ static void reset_stdin_termios ();
|
|||
static struct termios g_old_stdin_termios;
|
||||
|
||||
static char *g_debugger_name = (char *) "";
|
||||
static Driver *g_driver = NULL;
|
||||
|
||||
// In the Driver::MainLoop, we change the terminal settings. This function is
|
||||
// added as an atexit handler to make sure we clean them up.
|
||||
|
@ -98,10 +99,13 @@ Driver::Driver () :
|
|||
g_debugger_name = (char *) m_debugger.GetInstanceName();
|
||||
if (g_debugger_name == NULL)
|
||||
g_debugger_name = (char *) "";
|
||||
g_driver = this;
|
||||
}
|
||||
|
||||
Driver::~Driver ()
|
||||
{
|
||||
g_driver = NULL;
|
||||
g_debugger_name = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1091,6 +1095,23 @@ Driver::EditLineInputReaderCallback
|
|||
case eInputReaderDeactivate:
|
||||
break;
|
||||
|
||||
case eInputReaderInterrupt:
|
||||
if (driver->m_io_channel_ap.get() != NULL)
|
||||
{
|
||||
driver->m_io_channel_ap->OutWrite ("^C\n", 3);
|
||||
driver->m_io_channel_ap->RefreshPrompt();
|
||||
}
|
||||
break;
|
||||
|
||||
case eInputReaderEndOfFile:
|
||||
if (driver->m_io_channel_ap.get() != NULL)
|
||||
{
|
||||
driver->m_io_channel_ap->OutWrite ("^D\n", 3);
|
||||
driver->m_io_channel_ap->RefreshPrompt ();
|
||||
}
|
||||
write (driver->m_editline_pty.GetMasterFileDescriptor(), "quit\n", 5);
|
||||
break;
|
||||
|
||||
case eInputReaderGotToken:
|
||||
write (driver->m_editline_pty.GetMasterFileDescriptor(), bytes, bytes_len);
|
||||
break;
|
||||
|
@ -1370,6 +1391,24 @@ sigwinch_handler (int signo)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
sigint_handler (int signo)
|
||||
{
|
||||
static bool g_interrupt_sent = false;
|
||||
if (g_driver)
|
||||
{
|
||||
if (!g_interrupt_sent)
|
||||
{
|
||||
g_interrupt_sent = true;
|
||||
g_driver->GetDebugger().DispatchInputInterrupt();
|
||||
g_interrupt_sent = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
exit (signo);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char const *argv[])
|
||||
{
|
||||
|
@ -1379,6 +1418,7 @@ main (int argc, char const *argv[])
|
|||
|
||||
signal (SIGPIPE, SIG_IGN);
|
||||
signal (SIGWINCH, sigwinch_handler);
|
||||
signal (SIGINT, sigint_handler);
|
||||
|
||||
// Create a scope for driver so that the driver object will destroy itself
|
||||
// before SBDebugger::Terminate() is called.
|
||||
|
|
|
@ -448,12 +448,12 @@ IOChannel::RefreshPrompt ()
|
|||
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.
|
||||
// Compare the current time versus the last time el_gets was called. If less than 40 milliseconds
|
||||
// (40,0000 microseconds or 40,000,0000 nanoseconds) have elapsed, wait 40,0000 microseconds, to ensure el_gets had
|
||||
// time to finish writing the prompt before we start writing here.
|
||||
|
||||
if (ElapsedNanoSecondsSinceEnteringElGets() < 10000000)
|
||||
usleep (10000);
|
||||
if (ElapsedNanoSecondsSinceEnteringElGets() < (40 * 1000 * 1000))
|
||||
usleep (40 * 1000);
|
||||
|
||||
// Use the mutex to make sure OutWrite, ErrWrite and Refresh prompt do not interfere with
|
||||
// each other's output.
|
||||
|
|
Loading…
Reference in New Issue