Skip to content

Commit

Permalink
Added async stack inspection and traversal to hphpd
Browse files Browse the repository at this point in the history
Debugger commands 'up','down' and 'variable' now operate on the async
stack as well as the regular stack. If used in conjuction with 'where async'
the awaitable dependency stack is traversed instead of the regular stack
by the said commands.

Reviewed By: @mikemag

Differential Revision: D1034548
  • Loading branch information
Cristian Hancila authored and sgolemon committed Nov 5, 2013
1 parent 537a2ba commit 0cd1401
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 12 deletions.
7 changes: 6 additions & 1 deletion hphp/runtime/debugger/cmd/cmd_down.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ void CmdDown::onClient(DebuggerClient &client) {
if (client.argCount() > 1) {
help(client);
} else {
CmdWhere().fetchStackTrace(client);
if (client.isStackTraceAsync()) {
CmdWhere(KindOfWhereAsync).fetchStackTrace(client);
} else {
CmdWhere().fetchStackTrace(client);
}

client.moveToFrame(client.getFrame() - CmdUp::ParseNumber(client));
}
}
Expand Down
7 changes: 6 additions & 1 deletion hphp/runtime/debugger/cmd/cmd_up.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ void CmdUp::onClient(DebuggerClient &client) {
if (client.argCount() > 1) {
help(client);
} else {
CmdWhere().fetchStackTrace(client);
if (client.isStackTraceAsync()) {
CmdWhere(KindOfWhereAsync).fetchStackTrace(client);
} else {
CmdWhere().fetchStackTrace(client);
}

client.moveToFrame(client.getFrame() + ParseNumber(client));
}
}
Expand Down
78 changes: 74 additions & 4 deletions hphp/runtime/debugger/cmd/cmd_variable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/

#include "hphp/runtime/debugger/cmd/cmd_variable.h"

#include "hphp/runtime/base/hphp-system.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/debugger/cmd/cmd_where.h"
#include "hphp/runtime/ext/ext_asio.h"
#include "hphp/runtime/ext/ext_continuation.h"

namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -79,7 +83,8 @@ void CmdVariable::help(DebuggerClient &client) {
}

void CmdVariable::PrintVariable(DebuggerClient &client, const String& varName) {
CmdVariable cmd;
CmdVariable cmd(client.isStackTraceAsync()
? KindOfVariableAsync : KindOfVariable);
auto charCount = client.getDebuggerClientShortPrintCharCount();
cmd.m_frame = client.getFrame();
CmdVariablePtr rcmd = client.xend<CmdVariable>(&cmd);
Expand Down Expand Up @@ -141,7 +146,8 @@ void CmdVariable::PrintVariables(DebuggerClient &client, CArrRef variables,
if (version == 2) {
// Using the new protocol, so variables contain only names.
// Fetch the value separately.
CmdVariable cmd;
CmdVariable cmd(client.isStackTraceAsync()
? KindOfVariableAsync : KindOfVariable);
cmd.m_frame = frame;
cmd.m_variables = null_array;
cmd.m_varName = name;
Expand Down Expand Up @@ -219,7 +225,12 @@ void CmdVariable::onClient(DebuggerClient &client) {
return;
}

if (client.isStackTraceAsync()) {
m_type = KindOfVariableAsync;
}

m_frame = client.getFrame();

CmdVariablePtr cmd = client.xend<CmdVariable>(this);
if (cmd->m_variables.empty()) {
client.info("(no variable was defined)");
Expand All @@ -237,14 +248,72 @@ Array CmdVariable::GetGlobalVariables() {
return ret;
}

static c_WaitableWaitHandle *objToWaitableWaitHandle(Object o) {
assert(o->instanceof(c_WaitableWaitHandle::classof()));
return static_cast<c_WaitableWaitHandle*>(o.get());
}

static c_AsyncFunctionWaitHandle *objToContinuationWaitHandle(Object o) {
return dynamic_cast<c_AsyncFunctionWaitHandle*>(o.get());
}

static
c_AsyncFunctionWaitHandle *getWaitHandleAtAsyncStackPosition(int position) {
auto top = f_asio_get_running();

if (top.isNull()) {
return nullptr;
}

if (position == 0) {
return objToContinuationWaitHandle(top);
}

Array depStack =
objToWaitableWaitHandle(top)->t_getdependencystack();

return objToContinuationWaitHandle(depStack[position].toObject());
}

static Array getVariables(const ActRec *fp) {
if (fp->hasVarEnv()) {
return fp->m_varEnv->getDefinedVariables();
} else {
const Func *func = fp->m_func;
auto numLocals = func->numNamedLocals();
ArrayInit ret(numLocals);
for (Id id = 0; id < numLocals; ++id) {
TypedValue* ptv = frame_local(fp, id);
if (ptv->m_type == KindOfUninit) {
continue;
}
Variant name(func->localVarName(id));
ret.add(name, tvAsVariant(ptv));
}
return ret.toArray();
}
}

bool CmdVariable::onServer(DebuggerProxy &proxy) {
if (m_frame < 0) {
if (m_type == KindOfVariableAsync) {
//we only do variable inspection on continuation wait handles
auto frame = getWaitHandleAtAsyncStackPosition(m_frame);

if (frame != nullptr) {
auto fp = frame->getActRec();
if (fp != nullptr) {
m_variables = getVariables(fp);
}
}
}
else if (m_frame < 0) {
m_variables = g_vmContext->m_globalVarEnv->getDefinedVariables();
m_global = true;
} else {
m_variables = g_vmContext->getLocalDefinedVariables(m_frame);
m_global = g_vmContext->getVarEnv(m_frame) == g_vmContext->m_globalVarEnv;
}

if (m_global) {
m_variables.remove(s_GLOBALS);
}
Expand Down Expand Up @@ -280,6 +349,7 @@ bool CmdVariable::onServer(DebuggerProxy &proxy) {
m_variables = ret.toArray();
}
}

return proxy.sendToClient(this);
}

Expand Down
3 changes: 2 additions & 1 deletion hphp/runtime/debugger/cmd/cmd_variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class CmdVariable : public DebuggerCommand {
int frame, const String& text, int version);

public:
CmdVariable() : DebuggerCommand(KindOfVariable) {

explicit CmdVariable(Type type = KindOfVariable) : DebuggerCommand(type) {
m_frame = 0;
m_version = 1;
m_global = false;
Expand Down
2 changes: 1 addition & 1 deletion hphp/runtime/debugger/cmd/cmd_where.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ void addContinuationLocation(Array& frameData,
// id, as well as the pseudo-frames for context breaks at explicit
// joins. Later we'll add more, like file and line, hopefully function
// args, wait handle status, etc.
Array createAsyncStacktrace() {
static Array createAsyncStacktrace() {
Array trace;
auto currentWaitHandle = f_asio_get_running();
if (currentWaitHandle.isNull()) return trace;
Expand Down
13 changes: 9 additions & 4 deletions hphp/runtime/debugger/debugger_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2025,15 +2025,20 @@ void DebuggerClient::addWatch(const char *fmt, const std::string &php) {
void DebuggerClient::setStackTrace(CArrRef stacktrace, bool isAsync) {
TRACE(2, "DebuggerClient::setStackTrace\n");
m_stacktrace = stacktrace;
//when we set a new stack we need to reset the frame position
//if we go from regular to async or vice-versa since the lengths
//aren't necessairly the same
if (m_stacktraceAsync != isAsync) {
m_frame = 0;
const char* direction = isAsync ? "sync->async" : "async->sync";
info("switching stack contexts (%s) resetting stack state", direction);
}
m_stacktraceAsync = isAsync;
}

void DebuggerClient::moveToFrame(int index, bool display /* = true */) {
TRACE(2, "DebuggerClient::moveToFrame\n");
if (m_stacktraceAsync && display) {
error("Unable to move along the async stack at this time, sorry!");
return;
}

m_frame = index;
if (m_frame >= m_stacktrace.size()) {
m_frame = m_stacktrace.size() - 1;
Expand Down
2 changes: 2 additions & 0 deletions hphp/runtime/debugger/debugger_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
case KindOfThread : cmd = DebuggerCommandPtr(new CmdThread ()); break;
case KindOfUp : cmd = DebuggerCommandPtr(new CmdUp ()); break;
case KindOfVariable : cmd = DebuggerCommandPtr(new CmdVariable ()); break;
case KindOfVariableAsync :
cmd = DebuggerCommandPtr(new CmdVariable (KindOfVariableAsync)); break;
case KindOfWhere : cmd = DebuggerCommandPtr(new CmdWhere ()); break;
case KindOfWhereAsync:
cmd = DebuggerCommandPtr(new CmdWhere(KindOfWhereAsync)); break;
Expand Down
1 change: 1 addition & 0 deletions hphp/runtime/debugger/debugger_command.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class DebuggerCommand {
KindOfThread = 20,
KindOfUp = 21,
KindOfVariable = 22,
KindOfVariableAsync = 222,
KindOfWhere = 23,
KindOfWhereAsync = 223,
KindOfExtended = 24,
Expand Down
4 changes: 4 additions & 0 deletions hphp/runtime/ext/asio/async_function_wait_handle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,9 @@ int c_AsyncFunctionWaitHandle::getLineNumber() {
return unit->getLineNumber(m_continuation->getNextExecutionOffset());
}

const ActRec* c_AsyncFunctionWaitHandle::getActRec() {
return m_continuation->m_arPtr;
}

///////////////////////////////////////////////////////////////////////////////
}
1 change: 1 addition & 0 deletions hphp/runtime/ext/ext_asio.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ class c_AsyncFunctionWaitHandle : public c_BlockableWaitHandle {
bool isRunning() { return getState() == STATE_RUNNING; }
String getFileName();
int getLineNumber();
const ActRec* getActRec();

protected:
void onUnblocked();
Expand Down
11 changes: 11 additions & 0 deletions hphp/test/quick/debugger/async_stack.php.expectf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ w
at %s/async_stack.php:50
wa
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
(no async stacktrace to display)
c
In genBar
Expand All @@ -47,6 +48,7 @@ Breakpoint 4 reached at genBar() on line 24 of %s/async_stack.php
25 return $a + 2;

w
switching stack contexts (async->sync) resetting stack state
#0 ()
at %s/async_stack.php:24
#1 genBar ()
Expand All @@ -57,6 +59,7 @@ w
at %s/async_stack.php:50
wa
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
#0 genBar [%d]
#1 <gen-array> [%d]
#2 genFoo [%d]
Expand All @@ -70,6 +73,7 @@ Breakpoint 4 reached at genBar() on line 24 of %s/async_stack.php
25 return $a + 2;

w
switching stack contexts (async->sync) resetting stack state
#0 ()
at %s/async_stack.php:24
#1 genBar ()
Expand All @@ -80,6 +84,7 @@ w
at %s/async_stack.php:50
wa
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
#0 genBar [%d]
#1 <gen-array> [%d]
#2 genFoo [%d]
Expand All @@ -93,6 +98,7 @@ Breakpoint 3 reached at gen2() on line 15 of %s/async_stack.php
16 error_log('Finished in gen2');

w
switching stack contexts (async->sync) resetting stack state
#0 ()
at %s/async_stack.php:15
#1 gen2 ()
Expand All @@ -103,6 +109,7 @@ w
at %s/async_stack.php:50
wa
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
#0 gen2 [%d]
#1 <gen-array> [%d]
#2 genFoo [%d]
Expand All @@ -117,6 +124,7 @@ Breakpoint 1 reached at gen1() on line 8 of %s/async_stack.php
9 return $a + 1;

w
switching stack contexts (async->sync) resetting stack state
#0 ()
at %s/async_stack.php:8
#1 gen1 (45)
Expand All @@ -129,6 +137,7 @@ w
at %s/async_stack.php:50
w a
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
#0 gen2 [%d]
#1 <gen-array> [%d]
#2 genFoo [%d]
Expand All @@ -141,6 +150,7 @@ Breakpoint 1 reached at gen1() on line 8 of %s/async_stack.php
9 return $a + 1;

w
switching stack contexts (async->sync) resetting stack state
#0 ()
at %s/async_stack.php:8
#1 gen1 ()
Expand All @@ -155,6 +165,7 @@ w
at %s/async_stack.php:50
wa
Fetching async stacktrace...
switching stack contexts (sync->async) resetting stack state
#0 gen1 [%d]
#1 <<join>>
#2 gen2 [%d]
Expand Down
39 changes: 39 additions & 0 deletions hphp/test/quick/debugger/async_stack_traversal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?hh
// Copyright 2004-present Facebook. All Rights Reserved.

async function gen1($a) {
$v1 = "v1-gen1";
await RescheduleWaitHandle::Create(1, 1);
return $a + 1;
}

async function gen2($a) {
$v1 = "v1-gen2";
await RescheduleWaitHandle::Create(1, 1);
$x = gen1($a)->join();
return $x;
}

async function genBar($a) {
$v1 = "v1-genBar";
var_dump($a);
return $a + 2;
}

async function genFoo($a) {
$v1 = "v1-genFoo";
$a++;
await GenArrayWaitHandle::Create(
array(
genBar($a),
gen2($a + 2),
gen2($a + 3)
)
);
}

function main($a) {
genFoo($a)->join();
}

main(42);
Loading

0 comments on commit 0cd1401

Please sign in to comment.