Commit 8b7a288e authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Debugger message handler can be called from V8 thread.

The message handler function set through the debugger API is normally called in a different thread than the V8 thread where execution is stopped due to debugger event. This change adds an option to the API for specifying that the message handler should be called directly from the V8 thread. For an application like Chrome where thread dispatching is already in place this makes more sense.

Add an option to the message handler debugger API to process messages in the thread where V8 is running instead of posting it to a queue for processing on a additional thread.
Review URL: http://codereview.chromium.org/42643

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1627 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 8e34544b
......@@ -131,7 +131,8 @@ class EXPORT Debug {
static void DebugBreak();
// Message based interface. The message protocol is JSON.
static void SetMessageHandler(DebugMessageHandler handler, void* data = NULL);
static void SetMessageHandler(DebugMessageHandler handler, void* data = NULL,
bool message_handler_thread = true);
static void SendCommand(const uint16_t* command, int length);
// Dispatch interface.
......
......@@ -3074,9 +3074,10 @@ void Debug::DebugBreak() {
}
void Debug::SetMessageHandler(v8::DebugMessageHandler handler, void* data) {
void Debug::SetMessageHandler(v8::DebugMessageHandler handler, void* data,
bool message_handler_thread) {
EnsureInitialized("v8::Debug::SetMessageHandler");
i::Debugger::SetMessageHandler(handler, data);
i::Debugger::SetMessageHandler(handler, data, message_handler_thread);
}
......
......@@ -1380,6 +1380,10 @@ void* Debugger::message_handler_data_ = NULL;
v8::DebugHostDispatchHandler Debugger::host_dispatch_handler_ = NULL;
void* Debugger::host_dispatch_handler_data_ = NULL;
DebuggerAgent* Debugger::agent_ = NULL;
LockingMessageQueue Debugger::command_queue_(kQueueInitialSize);
LockingMessageQueue Debugger::message_queue_(kQueueInitialSize);
Semaphore* Debugger::command_received_ = OS::CreateSemaphore(0);
Semaphore* Debugger::message_received_ = OS::CreateSemaphore(0);
Handle<Object> Debugger::MakeJSObject(Vector<const char> constructor_name,
......@@ -1666,9 +1670,9 @@ void Debugger::ProcessDebugEvent(v8::DebugEvent event,
if (caught_exception) {
return;
}
// First notify the builtin debugger.
if (message_thread_ != NULL) {
message_thread_->DebugEvent(event, exec_state, event_data, auto_continue);
// First notify the message handler if any.
if (message_handler_ != NULL) {
NotifyMessageHandler(event, exec_state, event_data, auto_continue);
}
// Notify registered debug event listener. This can be either a C or a
// JavaScript function.
......@@ -1706,240 +1710,7 @@ void Debugger::ProcessDebugEvent(v8::DebugEvent event,
}
void Debugger::SetEventListener(Handle<Object> callback,
Handle<Object> data) {
HandleScope scope;
// Clear the global handles for the event listener and the event listener data
// object.
if (!event_listener_.is_null()) {
GlobalHandles::Destroy(
reinterpret_cast<Object**>(event_listener_.location()));
event_listener_ = Handle<Object>();
}
if (!event_listener_data_.is_null()) {
GlobalHandles::Destroy(
reinterpret_cast<Object**>(event_listener_data_.location()));
event_listener_data_ = Handle<Object>();
}
// If there is a new debug event listener register it together with its data
// object.
if (!callback->IsUndefined() && !callback->IsNull()) {
event_listener_ = Handle<Object>::cast(GlobalHandles::Create(*callback));
if (data.is_null()) {
data = Factory::undefined_value();
}
event_listener_data_ = Handle<Object>::cast(GlobalHandles::Create(*data));
}
UpdateActiveDebugger();
}
void Debugger::TearDown() {
if (message_thread_ != NULL) {
message_thread_->Stop();
delete message_thread_;
message_thread_ = NULL;
}
}
void Debugger::SetMessageHandler(v8::DebugMessageHandler handler, void* data) {
message_handler_ = handler;
message_handler_data_ = data;
if (!message_thread_) {
message_thread_ = new DebugMessageThread();
message_thread_->Start();
}
UpdateActiveDebugger();
}
void Debugger::SetHostDispatchHandler(v8::DebugHostDispatchHandler handler,
void* data) {
host_dispatch_handler_ = handler;
host_dispatch_handler_data_ = data;
}
// Posts an output message from the debugger to the debug_message_handler
// callback. This callback is part of the public API. Messages are
// kept internally as Vector<uint16_t> strings, which are allocated in various
// places and deallocated by the calling function sometime after this call.
void Debugger::SendMessage(Vector< uint16_t> message) {
if (message_handler_ != NULL) {
message_handler_(message.start(), message.length(), message_handler_data_);
}
}
void Debugger::ProcessCommand(Vector<const uint16_t> command) {
if (message_thread_ != NULL) {
message_thread_->ProcessCommand(
Vector<uint16_t>(const_cast<uint16_t *>(command.start()),
command.length()));
}
}
bool Debugger::HasCommands() {
if (message_thread_ != NULL) {
return message_thread_->HasCommands();
} else {
return false;
}
}
void Debugger::ProcessHostDispatch(void* dispatch) {
if (message_thread_ != NULL) {
message_thread_->ProcessHostDispatch(dispatch);
}
}
void Debugger::UpdateActiveDebugger() {
set_debugger_active((message_thread_ != NULL &&
message_handler_ != NULL) ||
!event_listener_.is_null());
if (!debugger_active() && message_thread_) {
message_thread_->OnDebuggerInactive();
}
if (!debugger_active()) {
Debug::Unload();
}
}
Handle<Object> Debugger::Call(Handle<JSFunction> fun,
Handle<Object> data,
bool* pending_exception) {
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter() || !debugger.HasJavaScriptFrames()) {
return Factory::undefined_value();
}
// Create the execution state.
bool caught_exception = false;
Handle<Object> exec_state = MakeExecutionState(&caught_exception);
if (caught_exception) {
return Factory::undefined_value();
}
static const int kArgc = 2;
Object** argv[kArgc] = { exec_state.location(), data.location() };
Handle<Object> result = Execution::Call(fun, Factory::undefined_value(),
kArgc, argv, pending_exception);
return result;
}
bool Debugger::StartAgent(const char* name, int port) {
if (Socket::Setup()) {
agent_ = new DebuggerAgent(name, port);
agent_->Start();
return true;
}
return false;
}
void Debugger::StopAgent() {
if (agent_ != NULL) {
agent_->Shutdown();
agent_->Join();
delete agent_;
agent_ = NULL;
}
}
DebugMessageThread::DebugMessageThread()
: host_running_(true),
command_queue_(kQueueInitialSize),
message_queue_(kQueueInitialSize),
keep_running_(true) {
command_received_ = OS::CreateSemaphore(0);
message_received_ = OS::CreateSemaphore(0);
}
// Should only be done after the thread is done running.
DebugMessageThread::~DebugMessageThread() {
delete command_received_;
delete message_received_;
}
void DebugMessageThread::Stop() {
keep_running_ = false;
SendMessage(Vector<uint16_t>(NULL, 0));
Join();
}
// Puts an event coming from V8 on the queue. Creates
// a copy of the JSON formatted event string managed by the V8.
// Called by the V8 thread.
// The new copy of the event string is destroyed in Run().
void DebugMessageThread::SendMessage(Vector<uint16_t> message) {
Vector<uint16_t> message_copy = message.Clone();
Logger::DebugTag("Put message on event message_queue.");
message_queue_.Put(message_copy);
message_received_->Signal();
}
bool DebugMessageThread::SetEventJSONFromEvent(Handle<Object> event_data) {
v8::HandleScope scope;
// Call toJSONProtocol on the debug event object.
v8::Local<v8::Object> api_event_data =
v8::Utils::ToLocal(Handle<JSObject>::cast(event_data));
v8::Local<v8::String> fun_name = v8::String::New("toJSONProtocol");
v8::Local<v8::Function> fun =
v8::Function::Cast(*api_event_data->Get(fun_name));
v8::TryCatch try_catch;
v8::Local<v8::Value> json_event = *fun->Call(api_event_data, 0, NULL);
v8::Local<v8::String> json_event_string;
if (!try_catch.HasCaught()) {
if (!json_event->IsUndefined()) {
json_event_string = json_event->ToString();
if (FLAG_trace_debug_json) {
PrintLn(json_event_string);
}
v8::String::Value val(json_event_string);
Vector<uint16_t> str(reinterpret_cast<uint16_t*>(*val),
json_event_string->Length());
SendMessage(str);
} else {
SendMessage(Vector<uint16_t>::empty());
}
} else {
PrintLn(try_catch.Exception());
return false;
}
return true;
}
void DebugMessageThread::Run() {
// Sends debug events to an installed debugger message callback.
while (keep_running_) {
// Wait and Get are paired so that semaphore count equals queue length.
message_received_->Wait();
Logger::DebugTag("Get message from event message_queue.");
Vector<uint16_t> message = message_queue_.Get();
if (message.length() > 0) {
Debugger::SendMessage(message);
}
}
}
// This method is called by the V8 thread whenever a debug event occurs in
// the VM.
void DebugMessageThread::DebugEvent(v8::DebugEvent event,
void Debugger::NotifyMessageHandler(v8::DebugEvent event,
Handle<Object> exec_state,
Handle<Object> event_data,
bool auto_continue) {
......@@ -1987,7 +1758,7 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
// Notify the debugger that a debug event has occurred unless auto continue is
// active in which case no event is send.
if (!auto_continue) {
bool success = SetEventJSONFromEvent(event_data);
bool success = SendEventMessage(event_data);
if (!success) {
// If failed to notify debugger just continue running.
return;
......@@ -1995,7 +1766,6 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
}
// Process requests from the debugger.
host_running_ = false;
while (true) {
// Wait for new command in the queue.
command_received_->Wait();
......@@ -2007,9 +1777,7 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
// Get the command from the queue.
Vector<uint16_t> command = command_queue_.Get();
Logger::DebugTag("Got request from command queue, in interactive loop.");
ASSERT(!host_running_);
if (!Debugger::debugger_active()) {
host_running_ = true;
return;
}
......@@ -2023,7 +1791,7 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
continue;
}
// Invoke the JavaScript to process the debug request.
// Invoke JavaScript to process the debug request.
v8::Local<v8::String> fun_name;
v8::Local<v8::Function> fun;
v8::Local<v8::Value> request;
......@@ -2072,9 +1840,6 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
Vector<uint16_t> str(reinterpret_cast<uint16_t*>(*val),
response->Length());
// Set host_running_ correctly for nested debugger evaluations.
host_running_ = running;
// Return the result.
SendMessage(str);
......@@ -2088,26 +1853,142 @@ void DebugMessageThread::DebugEvent(v8::DebugEvent event,
}
void Debugger::SetEventListener(Handle<Object> callback,
Handle<Object> data) {
HandleScope scope;
// Clear the global handles for the event listener and the event listener data
// object.
if (!event_listener_.is_null()) {
GlobalHandles::Destroy(
reinterpret_cast<Object**>(event_listener_.location()));
event_listener_ = Handle<Object>();
}
if (!event_listener_data_.is_null()) {
GlobalHandles::Destroy(
reinterpret_cast<Object**>(event_listener_data_.location()));
event_listener_data_ = Handle<Object>();
}
// If there is a new debug event listener register it together with its data
// object.
if (!callback->IsUndefined() && !callback->IsNull()) {
event_listener_ = Handle<Object>::cast(GlobalHandles::Create(*callback));
if (data.is_null()) {
data = Factory::undefined_value();
}
event_listener_data_ = Handle<Object>::cast(GlobalHandles::Create(*data));
}
UpdateActiveDebugger();
}
void Debugger::SetMessageHandler(v8::DebugMessageHandler handler, void* data,
bool message_handler_thread) {
message_handler_ = handler;
message_handler_data_ = data;
if (!message_thread_ && message_handler_thread) {
message_thread_ = new DebugMessageThread();
message_thread_->Start();
}
UpdateActiveDebugger();
}
void Debugger::SetHostDispatchHandler(v8::DebugHostDispatchHandler handler,
void* data) {
host_dispatch_handler_ = handler;
host_dispatch_handler_data_ = data;
}
// Calls the registered debug message handler. This callback is part of the
// public API. Messages are kept internally as Vector<uint16_t> strings, which
// are allocated in various places and deallocated by the calling function
// sometime after this call.
void Debugger::InvokeMessageHandler(Vector<uint16_t> message) {
if (message_handler_ != NULL) {
message_handler_(message.start(), message.length(), message_handler_data_);
}
}
void Debugger::SendMessage(Vector<uint16_t> message) {
if (message_thread_ == NULL) {
// If there is no message thread just invoke the message handler from the
// V8 thread.
InvokeMessageHandler(message);
} else {
// Put a copy of the message coming from V8 on the queue. The new copy of
// the event string is destroyed by the message thread.
Vector<uint16_t> message_copy = message.Clone();
Logger::DebugTag("Put message on event message_queue.");
message_queue_.Put(message_copy);
message_received_->Signal();
}
}
bool Debugger::SendEventMessage(Handle<Object> event_data) {
v8::HandleScope scope;
// Call toJSONProtocol on the debug event object.
v8::Local<v8::Object> api_event_data =
v8::Utils::ToLocal(Handle<JSObject>::cast(event_data));
v8::Local<v8::String> fun_name = v8::String::New("toJSONProtocol");
v8::Local<v8::Function> fun =
v8::Function::Cast(*api_event_data->Get(fun_name));
v8::TryCatch try_catch;
v8::Local<v8::Value> json_event = *fun->Call(api_event_data, 0, NULL);
v8::Local<v8::String> json_event_string;
if (!try_catch.HasCaught()) {
if (!json_event->IsUndefined()) {
json_event_string = json_event->ToString();
if (FLAG_trace_debug_json) {
PrintLn(json_event_string);
}
v8::String::Value val(json_event_string);
Vector<uint16_t> str(reinterpret_cast<uint16_t*>(*val),
json_event_string->Length());
SendMessage(str);
} else {
SendMessage(Vector<uint16_t>::empty());
}
} else {
PrintLn(try_catch.Exception());
return false;
}
return true;
}
// Puts a command coming from the public API on the queue. Creates
// a copy of the command string managed by the debugger. Up to this
// point, the command data was managed by the API client. Called
// by the API client thread. This is where the API client hands off
// processing of the command to the DebugMessageThread thread.
// The new copy of the command is destroyed in HandleCommand().
void DebugMessageThread::ProcessCommand(Vector<uint16_t> command) {
Vector<uint16_t> command_copy = command.Clone();
void Debugger::ProcessCommand(Vector<const uint16_t> command) {
// Make a copy of the command. Need to cast away const for Clone to work.
Vector<uint16_t> command_copy =
Vector<uint16_t>(const_cast<uint16_t*>(command.start()),
command.length()).Clone();
Logger::DebugTag("Put command on command_queue.");
command_queue_.Put(command_copy);
command_received_->Signal();
if (!Debug::InDebugger()) {
StackGuard::DebugCommand();
}
}
bool Debugger::HasCommands() {
return !command_queue_.IsEmpty();
}
void Debugger::ProcessHostDispatch(void* dispatch) {
// Puts a host dispatch comming from the public API on the queue.
void DebugMessageThread::ProcessHostDispatch(void* dispatch) {
uint16_t hack[3];
hack[0] = 0;
hack[1] = reinterpret_cast<uint32_t>(dispatch) >> 16;
......@@ -2118,15 +1999,96 @@ void DebugMessageThread::ProcessHostDispatch(void* dispatch) {
}
void DebugMessageThread::OnDebuggerInactive() {
// Send an empty command to the debugger if in a break to make JavaScript run
// again if the debugger is closed.
if (!host_running_) {
ProcessCommand(Vector<uint16_t>::empty());
void Debugger::UpdateActiveDebugger() {
set_debugger_active((message_thread_ != NULL &&
message_handler_ != NULL) ||
!event_listener_.is_null());
if (!debugger_active() && message_thread_) {
// Send an empty command to the debugger if in a break to make JavaScript
// run again if the debugger is closed.
ProcessCommand(Vector<const uint16_t>::empty());
}
if (!debugger_active()) {
Debug::Unload();
}
}
Handle<Object> Debugger::Call(Handle<JSFunction> fun,
Handle<Object> data,
bool* pending_exception) {
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter() || !debugger.HasJavaScriptFrames()) {
return Factory::undefined_value();
}
// Create the execution state.
bool caught_exception = false;
Handle<Object> exec_state = MakeExecutionState(&caught_exception);
if (caught_exception) {
return Factory::undefined_value();
}
static const int kArgc = 2;
Object** argv[kArgc] = { exec_state.location(), data.location() };
Handle<Object> result = Execution::Call(fun, Factory::undefined_value(),
kArgc, argv, pending_exception);
return result;
}
bool Debugger::StartAgent(const char* name, int port) {
if (Socket::Setup()) {
agent_ = new DebuggerAgent(name, port);
agent_->Start();
return true;
}
return false;
}
void Debugger::StopAgent() {
if (agent_ != NULL) {
agent_->Shutdown();
agent_->Join();
delete agent_;
agent_ = NULL;
}
}
void Debugger::TearDown() {
if (message_thread_ != NULL) {
message_thread_->Stop();
delete message_thread_;
message_thread_ = NULL;
}
}
void DebugMessageThread::Run() {
// Sends debug events to an installed debugger message callback.
while (keep_running_) {
// Wait and Get are paired so that semaphore count equals queue length.
Debugger::message_received_->Wait();
Logger::DebugTag("Get message from event message_queue.");
Vector<uint16_t> message = Debugger::message_queue_.Get();
if (message.length() > 0) {
Debugger::InvokeMessageHandler(message);
}
}
}
void DebugMessageThread::Stop() {
keep_running_ = false;
Debugger::SendMessage(Vector<uint16_t>(NULL, 0));
Join();
}
MessageQueue::MessageQueue(int size) : start_(0), end_(0), size_(size) {
messages_ = NewArray<Vector<uint16_t> >(size);
}
......
......@@ -396,6 +396,46 @@ class Debug {
};
// A Queue of Vector<uint16_t> objects. A thread-safe version is
// LockingMessageQueue, based on this class.
class MessageQueue BASE_EMBEDDED {
public:
explicit MessageQueue(int size);
~MessageQueue();
bool IsEmpty() const { return start_ == end_; }
Vector<uint16_t> Get();
void Put(const Vector<uint16_t>& message);
void Clear() { start_ = end_ = 0; } // Queue is empty after Clear().
private:
// Doubles the size of the message queue, and copies the messages.
void Expand();
Vector<uint16_t>* messages_;
int start_;
int end_;
int size_; // The size of the queue buffer. Queue can hold size-1 messages.
};
// LockingMessageQueue is a thread-safe circular buffer of Vector<uint16_t>
// messages. The message data is not managed by LockingMessageQueue.
// Pointers to the data are passed in and out. Implemented by adding a
// Mutex to MessageQueue. Includes logging of all puts and gets.
class LockingMessageQueue BASE_EMBEDDED {
public:
explicit LockingMessageQueue(int size);
~LockingMessageQueue();
bool IsEmpty() const;
Vector<uint16_t> Get();
void Put(const Vector<uint16_t>& message);
void Clear();
private:
MessageQueue queue_;
Mutex* lock_;
DISALLOW_COPY_AND_ASSIGN(LockingMessageQueue);
};
class DebugMessageThread;
class Debugger {
......@@ -427,14 +467,33 @@ class Debugger {
static void ProcessDebugEvent(v8::DebugEvent event,
Handle<Object> event_data,
bool auto_continue);
static void NotifyMessageHandler(v8::DebugEvent event,
Handle<Object> exec_state,
Handle<Object> event_data,
bool auto_continue);
static void SetEventListener(Handle<Object> callback, Handle<Object> data);
static void SetMessageHandler(v8::DebugMessageHandler handler, void* data);
static void SetMessageHandler(v8::DebugMessageHandler handler, void* data,
bool message_handler_thread);
static void TearDown();
static void SetHostDispatchHandler(v8::DebugHostDispatchHandler handler,
void* data);
// Invoke the message handler function.
static void InvokeMessageHandler(Vector< uint16_t> message);
// Send a message to the message handler eiher through the message thread or
// directly.
static void SendMessage(Vector<uint16_t> message);
// Send the JSON message for a debug event.
static bool SendEventMessage(Handle<Object> event_data);
// Add a debugger command to the command queue.
static void ProcessCommand(Vector<const uint16_t> command);
// Check whether there are commands in the command queue.
static bool HasCommands();
static void ProcessHostDispatch(void* dispatch);
static void UpdateActiveDebugger();
static Handle<Object> Call(Handle<JSFunction> fun,
......@@ -477,95 +536,30 @@ class Debugger {
static DebuggerAgent* agent_;
friend class DebugMessageThread;
};
// A Queue of Vector<uint16_t> objects. A thread-safe version is
// LockingMessageQueue, based on this class.
class MessageQueue BASE_EMBEDDED {
public:
explicit MessageQueue(int size);
~MessageQueue();
bool IsEmpty() const { return start_ == end_; }
Vector<uint16_t> Get();
void Put(const Vector<uint16_t>& message);
void Clear() { start_ = end_ = 0; } // Queue is empty after Clear().
private:
// Doubles the size of the message queue, and copies the messages.
void Expand();
Vector<uint16_t>* messages_;
int start_;
int end_;
int size_; // The size of the queue buffer. Queue can hold size-1 messages.
};
static const int kQueueInitialSize = 4;
static LockingMessageQueue command_queue_;
static LockingMessageQueue message_queue_;
static Semaphore* command_received_; // Signaled for each command received.
static Semaphore* message_received_; // Signalled for each message send.
// LockingMessageQueue is a thread-safe circular buffer of Vector<uint16_t>
// messages. The message data is not managed by LockingMessageQueue.
// Pointers to the data are passed in and out. Implemented by adding a
// Mutex to MessageQueue. Includes logging of all puts and gets.
class LockingMessageQueue BASE_EMBEDDED {
public:
explicit LockingMessageQueue(int size);
~LockingMessageQueue();
bool IsEmpty() const;
Vector<uint16_t> Get();
void Put(const Vector<uint16_t>& message);
void Clear();
private:
MessageQueue queue_;
Mutex* lock_;
DISALLOW_COPY_AND_ASSIGN(LockingMessageQueue);
friend class DebugMessageThread;
};
/* This class is the data for a running thread that serializes
* event messages and command processing for the debugger.
* All uncommented methods are called only from this message thread.
*/
// Thread to read messages from the message queue and invoke the debug message
// handler in another thread as the V8 thread. This thread is started if the
// registration of the debug message handler requested to be called in a thread
// seperate from the V8 thread.
class DebugMessageThread: public Thread {
public:
DebugMessageThread(); // Called from API thread.
virtual ~DebugMessageThread();
// Called by V8 thread. Reports events from V8 VM.
// Also handles command processing in stopped state of V8,
// when host_running_ is false.
void DebugEvent(v8::DebugEvent,
Handle<Object> exec_state,
Handle<Object> event_data,
bool auto_continue);
// Puts event on the output queue. Called by V8.
// This is where V8 hands off
// processing of the event to the DebugMessageThread thread,
// which forwards it to the debug_message_handler set by the API.
void SendMessage(Vector<uint16_t> event_json);
// Formats an event into JSON, and calls SendMessage.
bool SetEventJSONFromEvent(Handle<Object> event_data);
// Puts a command coming from the public API on the queue. Called
// by the API client thread. This is where the API client hands off
// processing of the command to the DebugMessageThread thread.
void ProcessCommand(Vector<uint16_t> command);
void ProcessHostDispatch(void* dispatch);
void OnDebuggerInactive();
DebugMessageThread() : keep_running_(true) {}
virtual ~DebugMessageThread() {}
// Main function of DebugMessageThread thread.
void Run();
// Check whether there are commands in the queue.
bool HasCommands() { return !command_queue_.IsEmpty(); }
void Stop();
bool host_running_; // Is the debugging host running or stopped?
Semaphore* command_received_; // Non-zero when command queue is non-empty.
Semaphore* message_received_; // Exactly equal to message queue length.
private:
bool TwoByteEqualsAscii(Vector<uint16_t> two_byte, const char* ascii);
static const int kQueueInitialSize = 4;
LockingMessageQueue command_queue_;
LockingMessageQueue message_queue_;
bool keep_running_;
DISALLOW_COPY_AND_ASSIGN(DebugMessageThread);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment