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); }
void tearDownEagerAsyncFrame(ActRec*& fp, Stack& stack, PC& pc, ObjectData* e) { auto const func = fp->func(); auto const prevFp = fp->sfp(); auto const soff = fp->m_soff; assert(!fp->resumed()); assert(func->isAsyncFunction()); assert(*reinterpret_cast<const Op*>(pc) != OpRetC); FTRACE(1, "tearDownAsyncFrame: {} ({})\n fp {} prevFp {}\n", func->fullName()->data(), func->unit()->filepath()->data(), implicit_cast<void*>(fp), implicit_cast<void*>(prevFp)); try { frame_free_locals_unwind(fp, func->numLocals()); } catch (...) {} stack.ndiscard(func->numSlotsInFrame()); stack.ret(); assert(stack.topTV() == &fp->m_r); tvWriteObject(c_StaticWaitHandle::CreateFailed(e), &fp->m_r); e->decRefCount(); if (UNLIKELY(!prevFp)) { pc = 0; return; } assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) || prevFp->resumed()); auto const prevOff = soff + prevFp->func()->base(); pc = prevFp->func()->unit()->at(prevOff); fp = prevFp; }
/** * Mark the wait handle as failed due to unexpected abrupt interrupt. */ void c_AsyncFunctionWaitHandle::failCpp() { assert(isRunning()); auto const exception = AsioSession::Get()->getAbruptInterruptException(); auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception, &m_resultOrException); parentChain.unblock(); }
c_StaticExceptionWaitHandle* c_StaticExceptionWaitHandle::Create(ObjectData* exception) { assert(exception->instanceof(SystemLib::s_ExceptionClass)); auto wait_handle = NEWOBJ(c_StaticExceptionWaitHandle)(); tvWriteObject(exception, &wait_handle->m_resultOrException); return wait_handle; }
void c_ExternalThreadEventWaitHandle::process() { assert(getState() == STATE_WAITING); if (isInContext()) { unregisterFromContext(); } // clean up once event is processed auto exit_guard = folly::makeGuard([&] { destroyEvent(); }); Cell result; try { m_event->unserialize(result); } catch (const Object& exception) { assert(exception->instanceof(SystemLib::s_ExceptionClass)); auto const parentChain = getFirstParent(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventFailCallback())) { session->onExternalThreadEventFail(this, exception); } return; } catch (...) { auto const parentChain = getFirstParent(); setState(STATE_FAILED); tvWriteObject(AsioSession::Get()->getAbruptInterruptException(), &m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); throw; } assert(cellIsPlausible(result)); auto const parentChain = getFirstParent(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventSuccessCallback())) { session->onExternalThreadEventSuccess(this, tvAsCVarRef(&result)); } }
void c_AsyncGeneratorWaitHandle::failCpp() { auto const exception = AsioSession::Get()->getAbruptInterruptException(); auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception, &m_resultOrException); parentChain.unblock(); decRefObj(m_generator); decRefObj(this); }
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()) { auto result = child->getResult(); m_deps->set(m_iterPos, &result); } 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 { detectCycle(child_wh); child_wh->getParentChain() .addParent(m_blockable, AsioBlockable::Kind::GenVectorWaitHandle); return; } catch (const Object& cycle_exception) { putException(m_exception, cycle_exception.get()); } } } auto parentChain = getParentChain(); 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; parentChain.unblock(); decRefObj(this); }
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_AsyncFunctionWaitHandle::markAsFailed(const Object& exception) { AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnAsyncFunctionFailCallback())) { session->onAsyncFunctionFail(this, exception); } setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); done(); }
void c_AwaitAllWaitHandle::markAsFailed(const Object& exception) { auto child = &m_children[m_cur]; while (m_cur-- >= 0) { decRefObj(*(child--)); } auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); decRefObj(this); }
Object c_StaticExceptionWaitHandle::ti_create(const char* cls, CObjRef exception) { if (!exception.instanceof("Exception")) { STATIC_METHOD_INJECTION_BUILTIN(StaticExceptionWaitHandle, StaticExceptionWaitHandle::create); Object e(SystemLib::AllocInvalidArgumentExceptionObject( "Expected exception to be an instance of Exception")); throw e; } p_StaticExceptionWaitHandle wh = NEWOBJ(c_StaticExceptionWaitHandle)(); tvWriteObject(exception.get(), &wh->m_resultOrException); return wh; }
void c_AsyncFunctionWaitHandle::fail(ObjectData* exception) { AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnAsyncFunctionFailCallback())) { session->onAsyncFunctionFail(this, exception); } auto const parentChain = getFirstParent(); setState(STATE_FAILED); tvWriteObject(exception, &m_resultOrException); UnblockChain(parentChain); decRefObj(this); }
void c_AwaitAllWaitHandle::markAsFailed(const Object& exception) { for (uint32_t idx = 0; idx < m_cap; idx++) { auto const child = m_children[idx].m_child; if (!child->isFinished()) { // Remove the current AAWH from the parent chain of all children. child->getParentChain().removeFromChain(&m_children[idx].m_blockable); } } auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); decRefObj(this); }
void c_WaitableWaitHandle::setException(ObjectData* exception) { assert(exception); assert(exception->instanceof(SystemLib::s_ExceptionClass)); setState(STATE_FAILED); tvWriteObject(exception, &m_resultOrException); // unref creator if (m_creator) { decRefObj(m_creator); m_creator = nullptr; } // unblock parents while (m_firstParent) { m_firstParent = m_firstParent->unblock(); } }
bool c_SleepWaitHandle::cancel(const Object& exception) { if (getState() != STATE_WAITING) { return false; // already finished } if (isInContext()) { unregisterFromContext(); } auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); // this is technically a lie, since sleep failed auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnSleepSuccess())) { session->onSleepSuccess(this); } return true; }
bool c_ExternalThreadEventWaitHandle::cancel(const Object& exception) { if (getState() != STATE_WAITING) { return false; // already finished } if (!m_event->cancel()) { return false; } // canceled; the processing thread will take care of cleanup if (isInContext()) { unregisterFromContext(); } // clean up once we finish canceling event auto exit_guard = folly::makeGuard([&] { // unregister Sweepable m_sweepable.unregister(); m_privData.reset(); // drop ownership by pending event (see initialize()) decRefObj(this); }); auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventFail())) { session->onExternalThreadEventFail(this, exception, 0); } return true; }
TypedValue* zend_wrap_func(ActRec* ar) { // Sync the translator state. // We need to do this before a native function calls into a C library // compiled with -fomit-frame-pointer with the intention of having it call // back. Normal HHVM extensions have the luxury of only when such a thing // will be attempted, but we have no way to know in advance. VMRegAnchor _; TSRMLS_FETCH(); zend_ext_func native_func = reinterpret_cast<zend_ext_func>(ar->func()->nativeFuncPtr()); // Using Variant so exceptions will decref them Variant return_value_var(Variant::NullInit{}); auto const return_value = return_value_var.asTypedValue(); tvBox(return_value); Variant this_ptr_var(Variant::NullInit{}); auto const this_ptr = this_ptr_var.asTypedValue(); tvBox(this_ptr); if (ar->func()->cls() && ar->hasThis()) { tvWriteObject( ar->getThis(), this_ptr->m_data.pref->tv() ); } auto *return_value_ptr = &return_value->m_data.pref; // Clear any stored exception zend_clear_exception(TSRMLS_C); // Invoke the PHP extension function/method ZendExecutionStack::pushHHVMStack((void*)ar); try { native_func( ar->numArgs(), return_value->m_data.pref, return_value_ptr, this_ptr_var.isNull() ? nullptr : this_ptr->m_data.pref, 1 TSRMLS_CC ); } catch (...) { zend_wrap_func_cleanup(); throw; } zend_wrap_func_cleanup(); // If an exception was caught, rethrow it ZendExceptionStore& exceptionStore = ZendExceptionStore::getInstance(); if (!exceptionStore.empty()) { exceptionStore.rethrow(); } // Take care of freeing the args, tearing down the ActRec, and moving the // return value to the right place. Note that frame_free_locals expects to // be able to free return_value in the event of an exception, so we have to // take it out of our Variant /before/ calling that. TypedValue return_value_copy = *return_value; return_value->m_type = KindOfNull; // clear the Variant's copy frame_free_locals_inl(ar, ar->func()->numLocals(), &return_value_copy); memcpy(&ar->m_r, &return_value_copy, sizeof(TypedValue)); if (ar->func()->isReturnRef()) { if (!ar->m_r.m_data.pref->isReferenced()) { tvUnbox(&ar->m_r); } } else { tvUnbox(&ar->m_r); } return &ar->m_r; }
void c_ExternalThreadEventWaitHandle::process() { assertx(getState() == STATE_WAITING); if (isInContext()) { unregisterFromContext(); } // Store the finish time of the underlying IO operation // So we can pass it in the finish callbacks // clean up once event is processed auto exit_guard = folly::makeGuard([&] { destroyEvent(); }); Cell result; try { try { m_event->unserialize(result); } catch (ExtendedException& exception) { exception.recomputeBacktraceFromWH(this); throw exception; } } catch (const Object& exception) { assertx(exception->instanceof(SystemLib::s_ThrowableClass)); throwable_recompute_backtrace_from_wh(exception.get(), this); auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventFail())) { session->onExternalThreadEventFail( this, exception, std::chrono::duration_cast<std::chrono::nanoseconds>( m_event->getFinishTime().time_since_epoch() ).count() ); } return; } catch (...) { auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(AsioSession::Get()->getAbruptInterruptException(), &m_resultOrException); parentChain.unblock(); throw; } assertx(cellIsPlausible(result)); auto parentChain = getParentChain(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); parentChain.unblock(); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventSuccess())) { session->onExternalThreadEventSuccess( this, tvAsCVarRef(&result), std::chrono::duration_cast<std::chrono::nanoseconds>( m_event->getFinishTime().time_since_epoch() ).count() ); } }
p_StaticExceptionWaitHandle c_StaticExceptionWaitHandle::Create(ObjectData* exception) { p_StaticExceptionWaitHandle wh = NEWOBJ(c_StaticExceptionWaitHandle)(); tvWriteObject(exception, &wh->m_resultOrException); return wh; }
TypedValue* zend_wrap_func( ActRec* ar, zend_ext_func builtin_func, int numParams, bool isReturnRef) { TSRMLS_FETCH(); // Prepare the arguments and return value before they are // exposed to the PHP extension zPrepArgs(ar); // Using Variant so exceptions will decref them Variant return_value_var(Variant::NullInit{}); auto const return_value = return_value_var.asTypedValue(); tvBox(return_value); Variant this_ptr_var(Variant::NullInit{}); auto const this_ptr = this_ptr_var.asTypedValue(); tvBox(this_ptr); if (ar->hasThis()) { tvWriteObject( ar->getThis(), this_ptr->m_data.pref->tv() ); } auto *return_value_ptr = &return_value->m_data.pref; // Clear any stored exception ZendExceptionStore& exceptionStore = ZendExceptionStore::getInstance(); exceptionStore.clear(); // Invoke the PHP extension function/method ZendExecutionStack::pushHHVMStack(); try { builtin_func( ar->numArgs(), return_value->m_data.pref, return_value_ptr, this_ptr_var.isNull() ? nullptr : this_ptr->m_data.pref, 1 TSRMLS_CC ); } catch (...) { ZendExecutionStack::popHHVMStack(); throw; } ZendExecutionStack::popHHVMStack(); // If an exception was caught, rethrow it if (!exceptionStore.empty()) { exceptionStore.rethrow(); } // Take care of freeing the args, tearing down the ActRec, // and moving the return value to the right place frame_free_locals_inl(ar, numParams); memcpy(&ar->m_r, return_value, sizeof(TypedValue)); return_value->m_type = KindOfNull; if (isReturnRef) { if (!ar->m_r.m_data.pref->isReferenced()) { tvUnbox(&ar->m_r); } } else { tvUnbox(&ar->m_r); } return &ar->m_r; }
UnwindAction tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, const Fault& fault) { auto const func = fp->func(); auto const curOp = *reinterpret_cast<const Op*>(pc); auto const prevFp = fp->sfp(); auto const soff = fp->m_soff; FTRACE(1, "tearDownFrame: {} ({})\n fp {} prevFp {}\n", func->fullName()->data(), func->unit()->filepath()->data(), implicit_cast<void*>(fp), implicit_cast<void*>(prevFp)); // When throwing from a constructor, we normally want to avoid running the // destructor on an object that hasn't been fully constructed yet. But if // we're unwinding through the constructor's RetC, the constructor has // logically finished and we're unwinding for some internal reason (timeout // or user profiler, most likely). More importantly, fp->m_this may have // already been destructed and/or overwritten due to sharing space with // fp->m_r. if (fp->isFromFPushCtor() && fp->hasThis() && curOp != OpRetC) { fp->getThis()->setNoDestruct(); } /* * It is possible that locals have already been decref'd. * * Here's why: * * - If a destructor for any of these things throws a php * exception, it's swallowed at the dtor boundary and we keep * running php. * * - If the destructor for any of these things throws a fatal, * it's swallowed, and we set surprise flags to throw a fatal * from now on. * * - If the second case happened and we have to run another * destructor, its enter hook will throw, but it will be * swallowed again. * * - Finally, the exit hook for the returning function can * throw, but this happens last so everything is destructed. * * - When that happens, exit hook sets localsDecRefd flag. */ if (!fp->localsDecRefd()) { try { // Note that we must convert locals and the $this to // uninit/zero during unwind. This is because a backtrace // from another destructing object during this unwind may try // to read them. frame_free_locals_unwind(fp, func->numLocals(), fault); } catch (...) {} } auto action = UnwindAction::Propagate; if (LIKELY(!fp->resumed())) { if (UNLIKELY(func->isAsyncFunction()) && fault.m_faultType == Fault::Type::UserException) { // If in an eagerly executed async function, wrap the user exception // into a failed StaticWaitHandle and return it to the caller. auto const e = fault.m_userException; stack.ndiscard(func->numSlotsInFrame()); stack.ret(); assert(stack.topTV() == &fp->m_r); tvWriteObject(c_StaticWaitHandle::CreateFailed(e), &fp->m_r); e->decRefCount(); action = UnwindAction::ResumeVM; } else { // Free ActRec. stack.ndiscard(func->numSlotsInFrame()); stack.discardAR(); } } else if (func->isAsyncFunction()) { auto const waitHandle = frame_afwh(fp); if (fault.m_faultType == Fault::Type::UserException) { // Handle exception thrown by async function. waitHandle->fail(fault.m_userException); action = UnwindAction::ResumeVM; } else { // Fail the async function and let the C++ exception propagate. waitHandle->fail(AsioSession::Get()->getAbruptInterruptException()); } } else if (func->isAsyncGenerator()) { // Do nothing. AsyncGeneratorWaitHandle will handle the exception. } else if (func->isNonAsyncGenerator()) { // Mark the generator as finished. frame_generator(fp)->finish(); } else { not_reached(); } /* * At the final ActRec in this nesting level. */ if (UNLIKELY(!prevFp)) { pc = nullptr; fp = nullptr; return action; } assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) || prevFp->resumed()); auto const prevOff = soff + prevFp->func()->base(); pc = prevFp->func()->unit()->at(prevOff); fp = prevFp; return action; }