Array c_WaitableWaitHandle::getDependencyStack() { if (isFinished()) return empty_array(); Array result = Array::Create(); hphp_hash_set<c_WaitableWaitHandle*> visited; auto current_handle = this; auto session = AsioSession::Get(); while (current_handle != nullptr) { result.append(Variant{current_handle}); visited.insert(current_handle); auto context_idx = current_handle->getContextIdx(); // 1. find parent in the same context auto p = current_handle->getParentChain().firstInContext(context_idx); if (p && visited.find(p) == visited.end()) { current_handle = p; continue; } // 2. cross the context boundary auto context = session->getContext(context_idx); if (!context) { break; } current_handle = c_ResumableWaitHandle::getRunning(context->getSavedFP()); auto target_context_idx = current_handle ? current_handle->getContextIdx() : 0; while (context_idx > target_context_idx) { --context_idx; result.append(null_object); } } return result; }
void c_RescheduleWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // not in a context being exited assert(getContextIdx() <= ctx_idx); if (getContextIdx() != ctx_idx) { return; } if (UNLIKELY(getState() != STATE_SCHEDULED)) { raise_fatal_error("Invariant violation: encountered unexpected state"); } // move us to the parent context setContextIdx(getContextIdx() - 1); // reschedule if still in a context if (isInContext()) { scheduleInContext(); } // recursively move all wait handles blocked by us getParentChain().exitContext(ctx_idx); }
void c_RescheduleWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // not in a context being exited assert(getContextIdx() <= ctx_idx); if (getContextIdx() != ctx_idx) { return; } if (UNLIKELY(getState() != STATE_SCHEDULED)) { throw FatalErrorException( "Invariant violation: encountered unexpected state"); } // move us to the parent context setContextIdx(getContextIdx() - 1); // reschedule if still in a context if (isInContext()) { getContext()->schedule(this, m_queue, m_priority); } // recursively move all wait handles blocked by us for (auto pwh = getFirstParent(); pwh; pwh = pwh->getNextParent()) { pwh->exitContextBlocked(ctx_idx); } }
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; }
int c_WaitableWaitHandle::t_getcontextidx() { if (isFinished()) { return 0; } return getContextIdx(); }
c_AsyncFunctionWaitHandle* AsioContext::maybePopFast() { assertx(this == AsioSession::Get()->getCurrentContext()); while (!m_fastRunnableQueue.empty()) { auto wh = m_fastRunnableQueue.back(); m_fastRunnableQueue.pop_back(); if (wh->getState() == c_ResumableWaitHandle::STATE_READY && wh->isFastResumable()) { // We only call maybePopFast() on the current context. Since `wh' was // scheduled in this context at some point, it must still be scheduled // here now, since the only way it could leave the context is if the // context was destroyed. (Being scheduled here supercedes it having // been scheduled in earlier contexts.) assertx(wh->getContextIdx() == AsioSession::Get()->getCurrentContextIdx()); return wh; } else { // `wh' is blocked or finished in some other context. m_fastRunnableQueue.push_back(wh); return nullptr; } } return nullptr; }
void c_BlockableWaitHandle::exitContextBlocked(context_idx_t ctx_idx) { assert(getState() == STATE_BLOCKED); assert(AsioSession::Get()->getContext(ctx_idx)); // not in a context being exited assert(getContextIdx() <= ctx_idx); if (getContextIdx() != ctx_idx) { return; } // move us to the parent context setContextIdx(getContextIdx() - 1); // recursively move all wait handles blocked by us getParentChain().exitContext(ctx_idx); }
ObjectData* c_AwaitAllWaitHandle::fromFrameNoCheck( uint32_t total, uint32_t cnt, TypedValue* stk ) { assert(cnt); auto result = Alloc(cnt); auto ctx_idx = std::numeric_limits<context_idx_t>::max(); auto next = &result->m_children[cnt]; uint32_t idx = cnt; for (int64_t i = 0; i < total; i++) { auto const waitHandle = c_WaitHandle::fromCellAssert(stk[-i]); if (waitHandle->isFinished()) continue; auto const child = static_cast<c_WaitableWaitHandle*>(waitHandle); ctx_idx = std::min(ctx_idx, child->getContextIdx()); child->incRefCount(); (--next)->m_child = child; next->m_index = --idx; next->m_child->getParentChain().addParent( next->m_blockable, AsioBlockable::Kind::AwaitAllWaitHandleNode ); if (!idx) break; } assert(next == &result->m_children[0]); result->initialize(ctx_idx); return result.detach(); }
void c_SleepWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); assert(getState() == STATE_WAITING); assert(getContextIdx() == ctx_idx); // Move us to the parent context. setContextIdx(getContextIdx() - 1); // Re-register if still in a context. if (isInContext()) { registerToContext(); } // Recursively move all wait handles blocked by us. getParentChain().exitContext(ctx_idx); }
void c_AsyncFunctionWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { decRefObj(this); return; } // not in a context being exited assert(getContextIdx() <= ctx_idx); if (getContextIdx() != ctx_idx) { decRefObj(this); return; } switch (getState()) { case STATE_BLOCKED: // we were already ran due to duplicit scheduling; the context will be // updated thru exitContext() call on the non-blocked wait handle we // recursively depend on decRefObj(this); break; case STATE_READY: // Recursively move all wait handles blocked by us. getParentChain().exitContext(ctx_idx); // Move us to the parent context. setContextIdx(getContextIdx() - 1); // Reschedule if still in a context. if (isInContext()) { if (isFastResumable()) { getContext()->scheduleFast(this); } else { getContext()->schedule(this); } } else { decRefObj(this); } break; default: assert(false); } }
void c_ExternalThreadEventWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); assert(getContextIdx() == ctx_idx); assert(getState() == STATE_WAITING); // Move us to the parent context. setContextIdx(getContextIdx() - 1); // Re-register if still in a context. if (isInContext()) { registerToContext(); } // Recursively move all wait handles blocked by us. for (auto pwh = getFirstParent(); pwh; pwh = pwh->getNextParent()) { pwh->exitContextBlocked(ctx_idx); } }
void c_AsyncFunctionWaitHandle::prepareChild(c_WaitableWaitHandle* child) { assert(!child->isFinished()); // import child into the current context, throw on cross-context cycles asio::enter_context(child, getContextIdx()); // detect cycles detectCycle(child); }
void c_GenArrayWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_BLOCKED); // recursively import current child { assert(m_iterPos != ArrayData::invalid_index); TypedValue* current = m_deps->nvGetValueRef(m_iterPos); assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitableWaitHandle*>(current->m_data.pobj)); auto child_wh = static_cast<c_WaitableWaitHandle*>(current->m_data.pobj); child_wh->enterContext(ctx_idx); } // import ourselves setContextIdx(ctx_idx); // try to import other children try { for (ssize_t iter_pos = m_deps->iter_advance(m_iterPos); iter_pos != ArrayData::invalid_index; iter_pos = m_deps->iter_advance(iter_pos)) { TypedValue* current = m_deps->nvGetValueRef(iter_pos); if (IS_NULL_TYPE(current->m_type)) { continue; } assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitHandle*>(current->m_data.pobj)); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isFinished()) { continue; } assert(dynamic_cast<c_WaitableWaitHandle*>(child)); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); child_wh->enterContext(ctx_idx); } } catch (const Object& cycle_exception) { // exception will be eventually processed by onUnblocked() } }
c_WaitableWaitHandle* AsioBlockableChain::firstInContext(context_idx_t ctx_idx) { for (auto cur = m_firstParent; cur; cur = cur->getNextParent()) { auto const wh = cur->getWaitHandle(); if (!wh->isFinished() && wh->getContextIdx() == ctx_idx) { return wh; } } return nullptr; }
void c_ContinuationWaitHandle::exitContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // not in a context being exited assert(getContextIdx() <= ctx_idx); if (getContextIdx() != ctx_idx) { return; } switch (getState()) { case STATE_BLOCKED: // we were already ran due to duplicit scheduling; the context will be // updated thru exitContext() call on the non-blocked wait handle we // recursively depend on break; case STATE_SCHEDULED: // move us to the parent context setContextIdx(getContextIdx() - 1); // reschedule if still in a context if (isInContext()) { getContext()->schedule(this); } // recursively move all wait handles blocked by us for (auto pwh = getFirstParent(); pwh; pwh = pwh->getNextParent()) { pwh->exitContextBlocked(ctx_idx); } break; default: assert(false); } }
void c_AsyncGeneratorWaitHandle::prepareChild(c_WaitableWaitHandle* child) { assert(!child->isFinished()); // import child into the current context, throw on cross-context cycles child->enterContext(getContextIdx()); // detect cycles if (UNLIKELY(isDescendantOf(child))) { Object e(createCycleException(child)); throw e; } }
void c_GenVectorWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_BLOCKED); // recursively import current child { assert(m_iterPos < m_deps->size()); Cell* current = tvAssertCell(m_deps->at(m_iterPos)); assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitableWaitHandle*>(current->m_data.pobj)); auto child_wh = static_cast<c_WaitableWaitHandle*>(current->m_data.pobj); child_wh->enterContext(ctx_idx); } // import ourselves setContextIdx(ctx_idx); // try to import other children try { for (int64_t iter_pos = m_iterPos + 1; iter_pos < m_deps->size(); ++iter_pos) { Cell* current = tvAssertCell(m_deps->at(iter_pos)); assert(current->m_type == KindOfObject); assert(dynamic_cast<c_WaitHandle*>(current->m_data.pobj)); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isFinished()) { continue; } assert(dynamic_cast<c_WaitableWaitHandle*>(child)); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); child_wh->enterContext(ctx_idx); } } catch (const Object& cycle_exception) { // exception will be eventually processed by onUnblocked() } }
void c_GenMapWaitHandle::onUnblocked() { assert(getState() == STATE_BLOCKED); for (; m_deps->iter_valid(m_iterPos); m_iterPos = m_deps->iter_next(m_iterPos)) { auto* current = tvAssertCell(m_deps->iter_value(m_iterPos)); assert(current->m_type == KindOfObject); assert(current->m_data.pobj->instanceof(c_WaitHandle::classof())); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isSucceeded()) { auto k = m_deps->iter_key(m_iterPos); m_deps->set(k.asCell(), &child->getResult()); } else if (child->isFailed()) { putException(m_exception, child->getException()); } else { assert(child->instanceof(c_WaitHandle::classof())); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); try { if (isInContext()) { child_wh->enterContext(getContextIdx()); } detectCycle(child_wh); blockOn(child_wh); return; } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); } } } auto const parentChain = getFirstParent(); if (m_exception.isNull()) { setState(STATE_SUCCEEDED); tvWriteObject(m_deps.get(), &m_resultOrException); } else { setState(STATE_FAILED); tvWriteObject(m_exception.get(), &m_resultOrException); m_exception = nullptr; } m_deps = nullptr; UnblockChain(parentChain); decRefObj(this); }
c_BlockableWaitHandle* AsioBlockableChain::firstInContext(context_idx_t ctx_idx) { for (auto cur = m_firstParent; cur; cur = cur->getNextParent()) { switch (cur->getKind()) { case AsioBlockable::Kind::BlockableWaitHandle: { auto const wh = cur->getBlockableWaitHandle(); if (wh->getContextIdx() == ctx_idx) return wh; break; } case AsioBlockable::Kind::ConditionWaitHandle: // ConditionWaitHandle not supported in legacy dependency stack. break; } } return nullptr; }
void c_SetResultToRefWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_BLOCKED); m_child->enterContext(ctx_idx); setContextIdx(ctx_idx); }
void c_GenVectorWaitHandle::initialize(const Object& exception, c_Vector* deps, int64_t iter_pos, c_WaitableWaitHandle* child) { m_exception = exception; m_deps = deps; m_iterPos = iter_pos; if (isInContext()) { try { child->enterContext(getContextIdx()); } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); ++m_iterPos; onUnblocked(); return; } } blockOn(child); }
void c_GenMapWaitHandle::initialize(CObjRef exception, c_Map* deps, ssize_t iter_pos, c_WaitableWaitHandle* child) { m_exception = exception; m_deps = deps; m_iterPos = iter_pos; if (isInContext()) { try { child->enterContext(getContextIdx()); } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); m_iterPos = m_deps->iter_next(m_iterPos); onUnblocked(); return; } } blockOn(child); }
void c_RescheduleWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_SCHEDULED); setContextIdx(ctx_idx); getContext()->schedule(this, m_queue, m_priority); }
void c_AwaitAllWaitHandle::blockOnCurrent() { auto child = m_children[m_cur]; assert(!child->isFinished()); try { if (isInContext()) { child->enterContext(getContextIdx()); } if (checkCycle) { detectCycle(child); } } catch (const Object& cycle_exception) { markAsFailed(cycle_exception); return; } blockOn(child); }
void c_GenVectorWaitHandle::onUnblocked() { assert(getState() == STATE_BLOCKED); for (; m_iterPos < m_deps->size(); ++m_iterPos) { Cell* current = tvAssertCell(m_deps->at(m_iterPos)); assert(current->m_type == KindOfObject); assert(current->m_data.pobj->instanceof(c_WaitHandle::classof())); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isSucceeded()) { cellSet(child->getResult(), *current); } else if (child->isFailed()) { putException(m_exception, child->getException()); } else { assert(child->instanceof(c_WaitableWaitHandle::classof())); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); try { if (isInContext()) { child_wh->enterContext(getContextIdx()); } detectCycle(child_wh); blockOn(child_wh); return; } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); } } } if (m_exception.isNull()) { setState(STATE_SUCCEEDED); tvWriteObject(m_deps.get(), &m_resultOrException); } else { setState(STATE_FAILED); tvWriteObject(m_exception.get(), &m_resultOrException); m_exception = nullptr; } m_deps = nullptr; done(); }
void c_GenMapWaitHandle::onUnblocked() { for (; m_deps->iter_valid(m_iterPos); m_iterPos = m_deps->iter_next(m_iterPos)) { Cell* current = tvAssertCell(m_deps->iter_value(m_iterPos)); assert(current->m_type == KindOfObject); assert(current->m_data.pobj->instanceof(c_WaitHandle::classof())); auto child = static_cast<c_WaitHandle*>(current->m_data.pobj); if (child->isSucceeded()) { cellSet(child->getResult(), *current); } else if (child->isFailed()) { putException(m_exception, child->getException()); } else { assert(child->instanceof(c_WaitHandle::classof())); auto child_wh = static_cast<c_WaitableWaitHandle*>(child); try { if (isInContext()) { child_wh->enterContext(getContextIdx()); } detectCycle(child_wh); blockOn(child_wh); return; } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); } } } if (m_exception.isNull()) { setResult(make_tv<KindOfObject>(m_deps.get())); m_deps = nullptr; } else { setException(m_exception.get()); m_exception = nullptr; m_deps = nullptr; } }
void c_ExternalThreadEventWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } assert(getState() == STATE_WAITING); if (isInContext()) { getContext()->unregisterExternalThreadEvent(m_index); } setContextIdx(ctx_idx); m_index = getContext()->registerExternalThreadEvent(this); }
// throws on cycle void c_BlockableWaitHandle::blockOn(c_WaitableWaitHandle* child) { setState(STATE_BLOCKED); assert(getChild() == child); // detect complete cycles if (UNLIKELY(hasCycle(child))) { reportCycle(child); assert(false); } // make sure the child is going to do some work // throws if cross-context cycle found if (isInContext()) { child->enterContext(getContextIdx()); } // extend the linked list of parents m_nextParent = child->addParent(this); // increment ref count so that we won't deallocated before child calls back incRefCount(); }
void c_ContinuationWaitHandle::enterContext(context_idx_t ctx_idx) { assert(AsioSession::Get()->getContext(ctx_idx)); // stop before corrupting unioned data if (isFinished()) { return; } // already in the more specific context? if (LIKELY(getContextIdx() >= ctx_idx)) { return; } switch (getState()) { case STATE_BLOCKED: // enter child into new context recursively assert(dynamic_cast<c_WaitableWaitHandle*>(m_child.get())); static_cast<c_WaitableWaitHandle*>(m_child.get())->enterContext(ctx_idx); setContextIdx(ctx_idx); break; case STATE_SCHEDULED: // reschedule so that we get run setContextIdx(ctx_idx); getContext()->schedule(this); break; case STATE_RUNNING: { Object e(SystemLib::AllocInvalidOperationExceptionObject( "Detected cross-context dependency cycle. You are trying to depend " "on something that is running you serially.")); throw e; } default: assert(false); } }