void c_ContinuationWaitHandle::Create(c_Continuation* continuation) {
  assert(continuation);
  assert(continuation->m_waitHandle.isNull());

  AsioSession* session = AsioSession::Get();
  uint16_t depth = session->getCurrentWaitHandleDepth();
  if (UNLIKELY(depth >= MAX_DEPTH)) {
    Object e(SystemLib::AllocInvalidOperationExceptionObject(
        "Asio stack overflow"));
    throw e;
  }

  if (UNLIKELY(continuation->started())) {
    Object e(SystemLib::AllocInvalidOperationExceptionObject(
      continuation->running()
      ? "Encountered an attempt to start currently running continuation"
      : "Encountered an attempt to start tainted continuation"));
    throw e;
  }

  continuation->m_waitHandle = NEWOBJ(c_ContinuationWaitHandle)();
  continuation->m_waitHandle->initialize(continuation, depth + 1);

  // needs to be called after continuation->m_waitHandle is set
  if (UNLIKELY(session->hasOnContinuationCreateCallback())) {
    session->onContinuationCreate(continuation->m_waitHandle.get());
  }
}
Beispiel #2
0
Object c_GenMapWaitHandle::ti_create(const Variant& dependencies) {
  if (UNLIKELY(!dependencies.isObject() ||
      dependencies.getObjectData()->getCollectionType() !=
        Collection::MapType)) {
    Object e(SystemLib::AllocInvalidArgumentExceptionObject(
      "Expected dependencies to be an instance of Map"));
    throw e;
  }
  assert(dependencies.getObjectData()->instanceof(c_Map::classof()));
  auto deps = p_Map::attach(c_Map::Clone(dependencies.getObjectData()));
  for (ssize_t iter_pos = deps->iter_begin();
       deps->iter_valid(iter_pos);
       iter_pos = deps->iter_next(iter_pos)) {

    auto* current = tvAssertCell(deps->iter_value(iter_pos));
    if (UNLIKELY(!c_WaitHandle::fromCell(current))) {
      Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected dependencies to be a map of WaitHandle instances"));
      throw e;
    }
  }

  Object exception;
  for (ssize_t iter_pos = deps->iter_begin();
       deps->iter_valid(iter_pos);
       iter_pos = deps->iter_next(iter_pos)) {

    auto* current = tvAssertCell(deps->iter_value(iter_pos));
    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 = deps->iter_key(iter_pos);
      deps->set(k.asCell(), &child->getResult());
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(child->instanceof(c_WaitableWaitHandle::classof()));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      p_GenMapWaitHandle my_wh = NEWOBJ(c_GenMapWaitHandle)();
      my_wh->initialize(exception, deps.get(), iter_pos, child_wh);

      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenMapCreateCallback())) {
        session->onGenMapCreate(my_wh.get(), dependencies);
      }

      return my_wh;
    }
  }

  if (exception.isNull()) {
    return Object::attach(c_StaticWaitHandle::CreateSucceeded(
      make_tv<KindOfObject>(deps.detach())));
  } else {
    return Object::attach(c_StaticWaitHandle::CreateFailed(exception.detach()));
  }
}
void c_AsyncFunctionWaitHandle::markAsSucceeded() {
  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnAsyncFunctionSuccessCallback())) {
    session->onAsyncFunctionSuccess(this, cellAsCVarRef(m_resultOrException));
  }

  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();
}
Object c_GenVectorWaitHandle::ti_create(const Variant& dependencies) {
  if (UNLIKELY(!dependencies.isObject() ||
      dependencies.getObjectData()->getCollectionType() !=
        Collection::VectorType)) {
    Object e(SystemLib::AllocInvalidArgumentExceptionObject(
      "Expected dependencies to be an instance of Vector"));
    throw e;
  }
  assert(dependencies.getObjectData()->instanceof(c_Vector::classof()));
  auto deps = SmartObject<c_Vector>::attach(
    c_Vector::Clone(dependencies.getObjectData()));
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {
    Cell* current = deps->at(iter_pos);

    if (UNLIKELY(!c_WaitHandle::fromCell(current))) {
      Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected dependencies to be a vector of WaitHandle instances"));
      throw e;
    }
  }

  Object exception;
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {

    auto current = tvAssertCell(deps->at(iter_pos));
    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();
      deps->set(iter_pos, &result);
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(child->instanceof(c_WaitableWaitHandle::classof()));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      SmartObject<c_GenVectorWaitHandle> my_wh(newobj<c_GenVectorWaitHandle>());
      my_wh->initialize(exception, deps.get(), iter_pos, child_wh);
      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenVectorCreateCallback())) {
        session->onGenVectorCreate(my_wh.get(), dependencies);
      }
      return my_wh;
    }
  }

  if (exception.isNull()) {
    return Object::attach(c_StaticWaitHandle::CreateSucceeded(
      make_tv<KindOfObject>(deps.detach())));
  } else {
    return Object::attach(c_StaticWaitHandle::CreateFailed(exception.detach()));
  }
}
void c_ContinuationWaitHandle::markAsFailed(CObjRef exception) {
  AsioSession* session = AsioSession::Get();
  session->onFailed(exception);
  if (UNLIKELY(session->hasOnContinuationFailCallback())) {
    session->onContinuationFail(this, exception);
  }
  setException(exception.get());

  m_continuation = nullptr;
  m_child = nullptr;
}
void c_ContinuationWaitHandle::markAsSucceeded(const TypedValue* result) {
  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnContinuationSuccessCallback())) {
    session->onContinuationSuccess(this, tvAsCVarRef(result));
  }

  setResult(result);

  // free m_continuation / m_child later, result may be stored there
  m_continuation = nullptr;
  m_child = nullptr;
}
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_AsyncGeneratorWaitHandle::fail(ObjectData* exception) {
  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnResumableFailCallback())) {
    session->onResumableFail(this, exception);
  }

  auto parentChain = getParentChain();
  setState(STATE_FAILED);
  cellCopy(make_tv<KindOfObject>(exception), m_resultOrException);
  parentChain.unblock();
  decRefObj(m_generator);
  decRefObj(this);
}
Object c_GenVectorWaitHandle::ti_create(CVarRef dependencies) {
  if (UNLIKELY(!dependencies.instanceof(c_Vector::s_cls))) {
    Object e(SystemLib::AllocInvalidArgumentExceptionObject(
      "Expected dependencies to be an instance of Vector"));
    throw e;
  }
  assert(dynamic_cast<c_Vector*>(dependencies.getObjectData()));
  p_Vector deps = static_cast<c_Vector*>(dependencies.getObjectData())->clone();
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {
    Cell* current = deps->at(iter_pos);

    if (UNLIKELY(!c_WaitHandle::fromCell(current))) {
      Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected dependencies to be a vector of WaitHandle instances"));
      throw e;
    }
  }

  Object exception;
  for (int64_t iter_pos = 0; iter_pos < deps->size(); ++iter_pos) {

    Cell* current = tvAssertCell(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->isSucceeded()) {
      cellSet(child->getResult(), *current);
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(dynamic_cast<c_WaitableWaitHandle*>(child));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      p_GenVectorWaitHandle my_wh = NEWOBJ(c_GenVectorWaitHandle)();
      my_wh->initialize(exception, deps.get(), iter_pos, child_wh);
      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenVectorCreateCallback())) {
        session->onGenVectorCreate(my_wh.get(), dependencies);
      }
      return my_wh;
    }
  }

  if (exception.isNull()) {
    return c_StaticResultWaitHandle::Create(make_tv<KindOfObject>(deps.get()));
  } else {
    return c_StaticExceptionWaitHandle::Create(exception.get());
  }
}
/**
 * Mark the wait handle as failed due to PHP exception.
 *
 * - consumes reference of the given Exception object
 */
void c_AsyncFunctionWaitHandle::fail(ObjectData* exception) {
  assert(isRunning());
  assert(exception);
  assert(exception->instanceof(SystemLib::s_ExceptionClass));

  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnResumableFailCallback())) {
    session->onResumableFail(this, exception);
  }

  auto const parentChain = getFirstParent();
  setState(STATE_FAILED);
  cellCopy(make_tv<KindOfObject>(exception), m_resultOrException);
  UnblockChain(parentChain);
  decRefObj(this);
}
Object c_SetResultToRefWaitHandle::ti_create(CObjRef wait_handle, VRefParam ref) {
  TypedValue* var_or_cell = ref->asTypedValue();
  if (wait_handle.isNull()) {
    tvSet(make_tv<KindOfNull>(), *var_or_cell);
    return wait_handle;
  }

  if (!wait_handle.get()->instanceof(c_WaitHandle::classof())) {
    Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected wait_handle to be an instance of WaitHandle or null"));
    throw e;
  }

  c_WaitHandle* wh = static_cast<c_WaitHandle*>(wait_handle.get());

  // succeeded? set result to ref and give back succeeded wait handle
  if (wh->isSucceeded()) {
    tvSet(wh->getResult(), *var_or_cell);
    return wh;
  }

  // failed? reset ref and give back failed wait handle
  if (wh->isFailed()) {
    tvSet(make_tv<KindOfNull>(), *var_or_cell);
    return wh;
  }

  // it's still running so it must be WaitableWaitHandle
  c_WaitableWaitHandle* child_wh = static_cast<c_WaitableWaitHandle*>(wh);

  // make sure the reference is properly boxed so that we can store cell pointer
  if (UNLIKELY(var_or_cell->m_type != KindOfRef)) {
    tvBox(var_or_cell);
  }

  p_SetResultToRefWaitHandle my_wh = NEWOBJ(c_SetResultToRefWaitHandle)();
  my_wh->initialize(child_wh, var_or_cell->m_data.pref);

  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnSetResultToRefCreateCallback())) {
    session->onSetResultToRefCreate(my_wh.get(), child_wh);
  }

  return my_wh;
}
/**
 * Mark the wait handle as failed due to PHP exception.
 *
 * - consumes reference of the given Exception object
 */
void c_AsyncFunctionWaitHandle::fail(ObjectData* exception) {
  assert(isRunning());
  assert(exception);
  assert(exception->instanceof(SystemLib::s_ThrowableClass));

  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnResumableFail())) {
    try {
      session->onResumableFail(this, Object{exception});
    } catch (...) {
      // TODO(#4557954) Make unwinder able to deal with new exceptions better.
      handle_destructor_exception("AsyncFunctionWaitHandle fail callback");
    }
  }

  auto parentChain = getParentChain();
  setState(STATE_FAILED);
  cellCopy(make_tv<KindOfObject>(exception), m_resultOrException);
  parentChain.unblock();
}
void c_AsyncFunctionWaitHandle::Create(c_Continuation* continuation) {
  assert(continuation);
  assert(continuation->m_label > 0);
  assert(continuation->m_waitHandle.isNull());

  AsioSession* session = AsioSession::Get();
  uint16_t depth = session->getCurrentWaitHandleDepth();
  if (UNLIKELY(depth >= MAX_DEPTH)) {
    Object e(SystemLib::AllocInvalidOperationExceptionObject(
        "Asio stack overflow"));
    throw e;
  }

  Cell* value = tvAssertCell(continuation->m_value.asTypedValue());
  assert(dynamic_cast<c_WaitableWaitHandle*>(c_WaitHandle::fromCell(value)));
  auto child = static_cast<c_WaitableWaitHandle*>(value->m_data.pobj);
  assert(!child->isFinished());

  // import child into the current context, detect cross-context cycles
  if (session->isInContext()) {
    child->enterContext(session->getCurrentContextIdx());
  }

  continuation->m_waitHandle = NEWOBJ(c_AsyncFunctionWaitHandle)();
  continuation->m_waitHandle->initialize(continuation, child, depth + 1);

  // needs to be called after continuation->m_waitHandle is set
  if (UNLIKELY(session->hasOnAsyncFunctionCreateCallback())) {
    session->onAsyncFunctionCreate(continuation->m_waitHandle.get());
  }
}
// throws on context depth level overflows and cross-context cycles
void c_WaitableWaitHandle::join() {
  EagerVMRegAnchor _;
  auto const savedFP = vmfp();

  assert(!isFinished());

  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(session->hasOnJoinCallback())) {
    session->onJoin(this);
  }

  // enter new asio context and set up guard that will exit once we are done
  session->enterContext(savedFP);
  auto exit_guard = folly::makeGuard([&] { session->exitContext(); });

  // import this wait handle to the newly created context
  // throws if cross-context cycle found
  enterContext(session->getCurrentContextIdx());

  // run queues until we are finished
  session->getCurrentContext()->runUntil(this);
  assert(isFinished());
}
Object c_ContinuationWaitHandle::ti_start(const char* cls, CObjRef continuation) {
  AsioSession* session = AsioSession::Get();
  if (UNLIKELY(!continuation.instanceof(SystemLib::s_ContinuationClass))) {
    Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected continuation to be an instance of Continuation"));
    throw e;
  }

  uint16_t depth = session->getCurrentWaitHandleDepth();
  if (UNLIKELY(depth >= MAX_DEPTH)) {
    Object e(SystemLib::AllocInvalidOperationExceptionObject(
        "Asio stack overflow"));
    throw e;
  }

  c_Continuation* cont = static_cast<c_Continuation*>(continuation.get());
  if (!cont->m_waitHandle.isNull()) {
    if (session->isInContext()) {
      // throws if cross-context cycle found
      cont->m_waitHandle->enterContext(session->getCurrentContextIdx());
    }
    return cont->m_waitHandle;
  }

  if (UNLIKELY(cont->m_index != -1)) {
    assert(cont->m_running);
    Object e(SystemLib::AllocInvalidOperationExceptionObject(
        "Encountered an attempt to start currently running continuation"));
    throw e;
  }

  p_ContinuationWaitHandle wh = NEWOBJ(c_ContinuationWaitHandle)();
  wh->start(cont, depth + 1);
  if (UNLIKELY(session->hasOnStartedCallback())) {
    session->onStarted(wh);
  }
  return wh;
}
void c_AsyncFunctionWaitHandle::run() {
  // may happen if scheduled in multiple contexts
  if (getState() != STATE_SCHEDULED) {
    return;
  }

  try {
    setState(STATE_RUNNING);

    // iterate continuation
    if (LIKELY(m_child->isSucceeded())) {
      // child succeeded, pass the result to the continuation
      m_continuation->call_send(m_child->getResult());
    } else if (m_child->isFailed()) {
      // child failed, raise the exception inside continuation
      m_continuation->call_raise(m_child->getException());
    } else {
      throw FatalErrorException(
          "Invariant violation: child neither succeeded nor failed");
    }

    // continuation finished, retrieve result from its m_value
    if (m_continuation->done()) {
      markAsSucceeded(*m_continuation->m_value.asCell());
      return;
    }

  retry:
    // save child
    Cell* value = tvAssertCell(m_continuation->m_value.asTypedValue());
    assert(dynamic_cast<c_WaitableWaitHandle*>(c_WaitHandle::fromCell(value)));

    m_child = static_cast<c_WaitableWaitHandle*>(value->m_data.pobj);
    assert(!m_child->isFinished());

    // import child into the current context, detect cross-context cycles
    try {
      m_child->enterContext(getContextIdx());
    } catch (Object& e) {
      m_continuation->call_raise(e.get());
      goto retry;
    }

    // detect cycles
    if (UNLIKELY(isDescendantOf(m_child.get()))) {
      Object e(createCycleException(m_child.get()));
      m_continuation->call_raise(e.get());
      goto retry;
    }

    // on await callback
    AsioSession* session = AsioSession::Get();
    if (UNLIKELY(session->hasOnAsyncFunctionAwaitCallback())) {
      session->onAsyncFunctionAwait(this, m_child.get());
    }

    // set up dependency
    blockOn(m_child.get());
  } catch (const Object& exception) {
    // process exception thrown by the async function
    markAsFailed(exception);
  } catch (...) {
    // process C++ exception
    markAsFailed(AsioSession::Get()->getAbruptInterruptException());
    throw;
  }
}
Beispiel #18
0
Object c_GenMapWaitHandle::ti_create(const Variant& dependencies) {
  ObjectData* obj;
  if (UNLIKELY(!dependencies.isObject() ||
      !(obj = dependencies.getObjectData())->isCollection() ||
      obj->collectionType() != CollectionType::Map)) {
    SystemLib::throwInvalidArgumentExceptionObject(
      "Expected dependencies to be an instance of Map");
  }
  assertx(obj->collectionType() == CollectionType::Map);
  auto deps = req::ptr<c_Map>::attach(c_Map::Clone(obj));
  auto ctx_idx = std::numeric_limits<context_idx_t>::max();
  for (ssize_t iter_pos = deps->iter_begin();
       deps->iter_valid(iter_pos);
       iter_pos = deps->iter_next(iter_pos)) {

    auto* current = tvAssertCell(deps->iter_value(iter_pos));
    auto const child = c_WaitHandle::fromCell(current);
    if (UNLIKELY(!child)) {
      SystemLib::throwInvalidArgumentExceptionObject(
        "Expected dependencies to be a map of WaitHandle instances");
    }

    if (!child->isFinished()) {
      ctx_idx = std::min(
        ctx_idx,
        static_cast<c_WaitableWaitHandle*>(child)->getContextIdx()
      );
    }
  }

  Object exception;
  for (ssize_t iter_pos = deps->iter_begin();
       deps->iter_valid(iter_pos);
       iter_pos = deps->iter_next(iter_pos)) {

    auto* current = tvAssertCell(deps->iter_value(iter_pos));
    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 = deps->iter_key(iter_pos);
      auto result = child->getResult();
      deps->set(k.asCell(), &result);
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(child->instanceof(c_WaitableWaitHandle::classof()));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      auto my_wh = req::make<c_GenMapWaitHandle>();
      my_wh->initialize(exception, deps.get(), iter_pos, ctx_idx, child_wh);

      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenMapCreate())) {
        session->onGenMapCreate(my_wh.get(), dependencies);
      }

      return Object(std::move(my_wh));
    }
  }

  if (exception.isNull()) {
    return Object::attach(c_StaticWaitHandle::CreateSucceeded(
      make_tv<KindOfObject>(deps.detach())));
  } else {
    return Object::attach(c_StaticWaitHandle::CreateFailed(exception.detach()));
  }
}
// throws on context depth level overflows and cross-context cycles
void c_WaitableWaitHandle::join() {
    AsioSession* session = AsioSession::Get();

    assert(!isFinished());
    assert(!session->isInContext() || session->getCurrentContext()->isRunning());

    if (UNLIKELY(session->hasOnJoinCallback())) {
        session->onJoin(this);
    }

    // enter new asio context and set up guard that will exit once we are done
    session->enterContext();

    assert(session->isInContext());
    assert(!session->getCurrentContext()->isRunning());

    try {
        // import this wait handle to the newly created context
        // throws if cross-context cycle found
        enterContext(session->getCurrentContextIdx());

        // run queues until we are finished
        session->getCurrentContext()->runUntil(this);
    } catch (const Object& exception) {
        // recover from PHP exceptions; HPHP internal exceptions are deliberately
        // ignored as there is no easy way to recover from them
        session->exitContext();
        throw;
    }
    session->exitContext();

    assert(isFinished());
}
Object c_GenArrayWaitHandle::ti_create(CArrRef dependencies) {
  Array deps = dependencies->copy();
  for (ssize_t iter_pos = deps->iter_begin();
       iter_pos != ArrayData::invalid_index;
       iter_pos = deps->iter_advance(iter_pos)) {

    TypedValue* current = deps->nvGetValueRef(iter_pos);
    if (UNLIKELY(current->m_type == KindOfRef)) {
      tvUnbox(current);
    }

    if (!c_WaitHandle::fromTypedValue(current) &&
        !IS_NULL_TYPE(current->m_type)) {
      Object e(SystemLib::AllocInvalidArgumentExceptionObject(
        "Expected dependencies to be an array of WaitHandle instances"));
      throw e;
    }
  }

  Object exception;
  for (ssize_t iter_pos = deps->iter_begin();
       iter_pos != ArrayData::invalid_index;
       iter_pos = deps->iter_advance(iter_pos)) {

    TypedValue* current = deps->nvGetValueRef(iter_pos);
    if (IS_NULL_TYPE(current->m_type)) {
      // {uninit,null} yields null
      tvWriteNull(current);
      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->isSucceeded()) {
      tvSetIgnoreRef(child->getResult(), current);
    } else if (child->isFailed()) {
      putException(exception, child->getException());
    } else {
      assert(dynamic_cast<c_WaitableWaitHandle*>(child));
      auto child_wh = static_cast<c_WaitableWaitHandle*>(child);

      p_GenArrayWaitHandle my_wh = NEWOBJ(c_GenArrayWaitHandle)();
      my_wh->initialize(exception, deps, iter_pos, child_wh);

      AsioSession* session = AsioSession::Get();
      if (UNLIKELY(session->hasOnGenArrayCreateCallback())) {
        session->onGenArrayCreate(my_wh.get(), dependencies);
      }

      return my_wh;
    }
  }

  if (exception.isNull()) {
    TypedValue tv;
    tv.m_type = KindOfArray;
    tv.m_data.parr = deps.get();
    return c_StaticResultWaitHandle::Create(&tv);
  } else {
    return c_StaticExceptionWaitHandle::Create(exception.get());
  }
}
void c_ContinuationWaitHandle::run() {
  // may happen if scheduled in multiple contexts
  if (getState() != STATE_SCHEDULED) {
    return;
  }

  try {
    setState(STATE_RUNNING);

    do {
      // iterate continuation
      if (m_child.isNull()) {
        // first iteration or null dependency
        m_continuation->call_next();
      } else if (m_child->isSucceeded()) {
        // child succeeded, pass the result to the continuation
        m_continuation->call_send(m_child->getResult());
      } else if (m_child->isFailed()) {
        // child failed, raise the exception inside continuation
        m_continuation->call_raise(m_child->getException());
      } else {
        throw FatalErrorException(
            "Invariant violation: child neither succeeded nor failed");
      }

      // continuation finished, retrieve result from its m_value
      if (m_continuation->done()) {
        markAsSucceeded(m_continuation->m_value.asTypedValue());
        return;
      }

      // set up dependency
      TypedValue* value = m_continuation->m_value.asTypedValue();
      if (IS_NULL_TYPE(value->m_type)) {
        // null dependency
        m_child = nullptr;
      } else {
        c_WaitHandle* child = c_WaitHandle::fromTypedValue(value);
        if (UNLIKELY(!child)) {
          Object e(SystemLib::AllocInvalidArgumentExceptionObject(
              "Expected yield argument to be an instance of WaitHandle"));
          throw e;
        }

        AsioSession* session = AsioSession::Get();
        if (UNLIKELY(session->hasOnContinuationYieldCallback())) {
          session->onContinuationYield(this, child);
        }

        m_child = child;
      }
    } while (m_child.isNull() || m_child->isFinished());

    // we are blocked on m_child so it must be WaitableWaitHandle
    assert(dynamic_cast<c_WaitableWaitHandle*>(m_child.get()));
    blockOn(static_cast<c_WaitableWaitHandle*>(m_child.get()));
  } catch (const Object& exception) {
    // process exception thrown by generator or blockOn cycle detection
    markAsFailed(exception);
  } catch (...) {
    // process C++ exception
    markAsFailed(AsioSession::Get()->getAbruptInterruptException());
    throw;
  }
}
void c_AsyncFunctionWaitHandle::run() {
  // may happen if scheduled in multiple contexts
  if (getState() != STATE_SCHEDULED) {
    return;
  }

  try {
    setState(STATE_RUNNING);

    // resume async function
    if (LIKELY(m_child->isSucceeded())) {
      // child succeeded, pass the result to the async function
      g_context->resumeAsyncFunc(resumable(), m_child, m_child->getResult());
    } else if (m_child->isFailed()) {
      // child failed, raise the exception inside the async function
      g_context->resumeAsyncFuncThrow(resumable(), m_child,
                                      m_child->getException());
    } else {
      throw FatalErrorException(
          "Invariant violation: child neither succeeded nor failed");
    }

  retry:
    // async function reached RetC, which already set m_resultOrException
    if (isSucceeded()) {
      m_child = nullptr;
      markAsSucceeded();
      return;
    }

    // async function reached AsyncSuspend, which already set m_child
    assert(!m_child->isFinished());
    assert(m_child->instanceof(c_WaitableWaitHandle::classof()));

    // import child into the current context, detect cross-context cycles
    try {
      child()->enterContext(getContextIdx());
    } catch (Object& e) {
      g_context->resumeAsyncFuncThrow(resumable(), m_child, e.get());
      goto retry;
    }

    // detect cycles
    if (UNLIKELY(isDescendantOf(child()))) {
      Object e(createCycleException(child()));
      g_context->resumeAsyncFuncThrow(resumable(), m_child, e.get());
      goto retry;
    }

    // on await callback
    AsioSession* session = AsioSession::Get();
    if (UNLIKELY(session->hasOnAsyncFunctionAwaitCallback())) {
      session->onAsyncFunctionAwait(this, m_child);
    }

    // set up dependency
    setState(STATE_BLOCKED);
    blockOn(child());
  } catch (const Object& exception) {
    // process exception thrown by the async function
    m_child = nullptr;
    markAsFailed(exception);
  } catch (...) {
    // process C++ exception
    m_child = nullptr;
    markAsFailed(AsioSession::Get()->getAbruptInterruptException());
    throw;
  }
}