Array c_WaitableWaitHandle::t_getdependencystack() { Array result = Array::Create(); if (isFinished()) return result; hphp_hash_set<int64_t> visited; auto wait_handle = this; auto session = AsioSession::Get(); while (wait_handle != nullptr) { result.append(wait_handle); visited.insert(wait_handle->t_getid()); auto context_idx = wait_handle->getContextIdx(); // 1. find parent in the same context auto p = wait_handle->getParentChain().firstInContext(context_idx); if (p && visited.find(p->t_getid()) == visited.end()) { wait_handle = p; continue; } // 2. cross the context boundary auto context = session->getContext(context_idx); if (!context) { break; } wait_handle = c_ResumableWaitHandle::getRunning(context->getSavedFP()); auto target_context_idx = wait_handle ? wait_handle->getContextIdx() : 0; while (context_idx > target_context_idx) { --context_idx; result.append(null_object); } } return result; }
Array c_WaitableWaitHandle::t_getdependencystack() { Array result = Array::Create(); if (isFinished()) return result; hphp_hash_set<int64_t> visited; auto wait_handle = this; while (wait_handle != nullptr) { result.append(wait_handle); visited.insert(wait_handle->t_getid()); auto context_idx = wait_handle->getContextIdx(); // 1. find parent in the same context auto p = wait_handle->getFirstParent(); while (p) { if ((p->getContextIdx() == context_idx) && visited.find(p->t_getid()) == visited.end()) { wait_handle = p; break; } p = p->getNextParent(); } if (p) continue; // 2. cross the context boundary result.append(null_object); wait_handle = (context_idx > 1) ? AsioSession::Get()->getContext(context_idx - 1)->getCurrent() : nullptr; } return result; }
// Form a trace of the async stack starting with the currently running // generator, if any. For now we just toss in the function name and // 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. static Array createAsyncStacktrace() { Array trace; auto currentWaitHandle = HHVM_FN(asio_get_running)(); if (currentWaitHandle.isNull()) return trace; Array depStack = objToWaitableWaitHandle(currentWaitHandle)->t_getdependencystack(); for (ArrayIter iter(depStack); iter; ++iter) { if (iter.secondRef().isNull()) { trace.append(Array(staticEmptyArray())); } else { auto wh = objToWaitableWaitHandle(iter.secondRef().toObject()); auto parents = wh->t_getparents(); Array ancestors; for (ArrayIter piter(parents); piter; ++piter) { // Note: the parent list contains no nulls. auto parent = objToWaitableWaitHandle(piter.secondRef().toObject()); ancestors.append(parent->t_getname()); } Array frameData; frameData.set(s_function, wh->t_getname(), true); frameData.set(s_id, wh->t_getid(), true); frameData.set(s_ancestors, ancestors, true); // Async function wait handles may have a source location to add. if (wh->getKind() == c_WaitHandle::Kind::AsyncFunction) { auto afwh = static_cast<c_AsyncFunctionWaitHandle*>(wh); addAsyncFunctionLocation(frameData, *afwh); } trace.append(frameData); } } return trace; }
// always throws void c_BlockableWaitHandle::reportCycle(c_WaitableWaitHandle* start) { assert(getState() == STATE_BLOCKED); assert(getChild() == start); smart::vector<std::string> exception_msg_items; exception_msg_items.push_back("Encountered dependency cycle.\n"); exception_msg_items.push_back("Existing stack:\n"); assert(dynamic_cast<c_BlockableWaitHandle*>(start)); auto current = static_cast<c_BlockableWaitHandle*>(start); assert(current->getState() == STATE_BLOCKED); do { exception_msg_items.push_back(folly::stringPrintf( " %s (%" PRId64 ")\n", current->getName()->data(), current->t_getid())); auto next = current->getChild(); assert(dynamic_cast<c_BlockableWaitHandle*>(next)); current = static_cast<c_BlockableWaitHandle*>(next); assert(current->getState() == STATE_BLOCKED); } while (current != start); exception_msg_items.push_back("Trying to introduce dependency on:\n"); exception_msg_items.push_back(folly::stringPrintf( " %s (%" PRId64 ") (dupe)\n", start->getName()->data(), start->t_getid())); Object e(SystemLib::AllocInvalidOperationExceptionObject( folly::join("", exception_msg_items))); throw e; }
ObjectData* c_BlockableWaitHandle::createCycleException(c_WaitableWaitHandle* child) const { assert(isDescendantOf(child)); smart::vector<std::string> exception_msg_items; exception_msg_items.push_back("Encountered dependency cycle.\n"); exception_msg_items.push_back("Existing stack:\n"); exception_msg_items.push_back(folly::stringPrintf( " %s (%" PRId64 ")\n", child->getName().data(), child->t_getid())); assert(child->instanceof(c_BlockableWaitHandle::classof())); auto current = static_cast<c_BlockableWaitHandle*>(child); while (current != this) { assert(current->getState() == STATE_BLOCKED); assert(current->getChild()->instanceof(c_BlockableWaitHandle::classof())); current = static_cast<c_BlockableWaitHandle*>(current->getChild()); exception_msg_items.push_back(folly::stringPrintf( " %s (%" PRId64 ")\n", current->getName().data(), current->t_getid())); } exception_msg_items.push_back("Trying to introduce dependency on:\n"); exception_msg_items.push_back(folly::stringPrintf( " %s (%" PRId64 ") (dupe)\n", child->getName().data(), child->t_getid())); return SystemLib::AllocInvalidOperationExceptionObject( folly::join("", exception_msg_items)); }
// Form a trace of the async stack starting with the currently running // generator, if any. For now we just toss in the function name and // 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() { Array trace; auto currentWaitHandle = f_asio_get_running(); if (currentWaitHandle.isNull()) return trace; Array depStack = objToWaitableWaitHandle(currentWaitHandle)->t_getdependencystack(); for (ArrayIter iter(depStack); iter; ++iter) { if (iter.secondRef().isNull()) { trace.append(ArrayInit(0).toVariant()); } else { auto wh = objToWaitableWaitHandle(iter.secondRef().toObject()); auto parents = wh->t_getparents(); Array ancestors; for (ArrayIter piter(parents); piter; ++piter) { // Note: the parent list contains no nulls. auto parent = objToWaitableWaitHandle(piter.secondRef().toObject()); ancestors.append(parent->t_getname()); } Array frameData; frameData.set(s_function, wh->t_getname(), true); frameData.set(s_id, wh->t_getid(), true); frameData.set(s_ancestors, ancestors, true); // Continuation wait handles may have a source location to add. auto contWh = dynamic_cast<c_AsyncFunctionWaitHandle*>(wh); if (contWh != nullptr) addContinuationLocation(frameData, *contWh); trace.append(frameData); } } return trace; }