// Wait for an event (either XEvent, or an internal Message) bool sleepUntilEvent (const int timeoutMs) { if (! isEmpty()) return true; if (display != 0) { ScopedXLock xlock; if (XPending (display)) return true; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = timeoutMs * 1000; int fd0 = getWaitHandle(); int fdmax = fd0; fd_set readset; FD_ZERO (&readset); FD_SET (fd0, &readset); if (display != 0) { ScopedXLock xlock; int fd1 = XConnectionNumber (display); FD_SET (fd1, &readset); fdmax = jmax (fd0, fd1); } const int ret = select (fdmax + 1, &readset, 0, 0, &tv); return (ret > 0); // ret <= 0 if error or timeout }
Object File::await(uint16_t events, double timeout) { if (isClosed()) { Cell closedResult; closedResult.m_type = KindOfInt64; closedResult.m_data.num = FileAwait::CLOSED; return c_StaticWaitHandle::CreateSucceeded(closedResult); } if (fd() < 0) { throw Object(SystemLib::AllocExceptionObject( "Unable to await on stream, invalid file descriptor")); } events = events & FileEventHandler::READ_WRITE; if (!events) { throw Object(SystemLib::AllocExceptionObject( "Must await for reading, writing, or both.")); } auto ev = new FileAwait(fd(), events, timeout); try { return ev->getWaitHandle(); } catch (...) { assert(false); ev->abandon(); throw; } }
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; }
Array AsioBlockableChain::toArray() { Array result = Array::Create(); for (auto cur = m_firstParent; cur; cur = cur->getNextParent()) { auto const wh = cur->getWaitHandle(); if (!wh->isFinished()) { result.append(wh); } } return result; }
Object MCRouter::issue(mcr::mcrouter_msg_t& msg) { auto ev = new MCRouterResult(this, msg); try { return Object{ev->getWaitHandle()}; } catch (...) { assert(false); ev->abandon(); throw; } }
c_ResumableWaitHandle* c_ResumableWaitHandle::getRunning(ActRec* fp) { for (; fp; fp = g_context->getPrevVMState(fp)) { if (fp->resumed() && fp->func()->isAsync()) { if (fp->func()->isGenerator()) { // async generator auto generator = frame_async_generator(fp); if (!generator->isEagerlyExecuted()) { return generator->getWaitHandle(); } } else { // async function return frame_afwh(fp); } } } return nullptr; }
/** * Discard the current frame, assuming that a PHP exception given in * phpException argument, or C++ exception (phpException == nullptr) * is being thrown. Returns an exception to propagate, or nulltpr * if the VM execution should be resumed. */ ObjectData* tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, ObjectData* phpException) { auto const func = fp->func(); auto const curOp = peek_op(pc); auto const prevFp = fp->sfp(); auto const soff = fp->m_soff; ITRACE(1, "tearDownFrame: {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); ITRACE(1, " fp {} prevFp {}\n", 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 (curOp != OpRetC && fp->hasThis() && fp->getThis()->getVMClass()->getCtor() == func && fp->getThis()->getVMClass()->getDtor()) { /* * Looks like an FPushCtor call, but it could still have been called * directly. Check the fpi region to be sure. */ Offset prevPc; auto outer = g_context->getPrevVMState(fp, &prevPc); if (outer) { auto fe = outer->func()->findPrecedingFPI(prevPc); if (fe && isFPushCtor(outer->func()->unit()->getOp(fe->m_fpushOff))) { fp->getThis()->setNoDestruct(); } } } auto const decRefLocals = [&] { /* * 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(), phpException); } catch (...) {} } }; if (LIKELY(!fp->resumed())) { decRefLocals(); if (UNLIKELY(func->isAsyncFunction()) && phpException && !fp->isFCallAwait()) { // If in an eagerly executed async function, wrap the user exception // into a failed StaticWaitHandle and return it to the caller. auto const waitHandle = c_StaticWaitHandle::CreateFailed(phpException); phpException = nullptr; stack.ndiscard(func->numSlotsInFrame()); stack.ret(); assert(stack.topTV() == &fp->m_r); cellCopy(make_tv<KindOfObject>(waitHandle), fp->m_r); } else { // Free ActRec. stack.ndiscard(func->numSlotsInFrame()); stack.discardAR(); } } else if (func->isAsyncFunction()) { auto const waitHandle = frame_afwh(fp); if (phpException) { // Handle exception thrown by async function. decRefLocals(); waitHandle->fail(phpException); phpException = nullptr; } else if (waitHandle->isRunning()) { // Let the C++ exception propagate. If the current frame represents async // function that is running, mark it as abruptly interrupted. Some opcodes // like Await may change state of the async function just before exit hook // decides to throw C++ exception. decRefLocals(); waitHandle->failCpp(); } } else if (func->isAsyncGenerator()) { auto const gen = frame_async_generator(fp); if (phpException) { // Handle exception thrown by async generator. decRefLocals(); auto eagerResult = gen->fail(phpException); phpException = nullptr; if (eagerResult) { stack.pushObjectNoRc(eagerResult); } } else if (gen->isEagerlyExecuted() || gen->getWaitHandle()->isRunning()) { // Fail the async generator and let the C++ exception propagate. decRefLocals(); gen->failCpp(); } } else if (func->isNonAsyncGenerator()) { // Mark the generator as finished. decRefLocals(); frame_generator(fp)->fail(); } else { not_reached(); } /* * At the final ActRec in this nesting level. */ if (UNLIKELY(!prevFp)) { pc = nullptr; fp = nullptr; return phpException; } 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 phpException; }