c6475efbec
Currently, we detect dependency loops by waiting until there is nothing else to execute. If the wait handle we are waiting for did not finish, it means it is in a cycle. We find the cycle by simply following the dependency chain. Once the cycle is found, one edge is eliminated and an exception is injected. There are multiple problems with this approach: 1. Unability to exit contet safely We are unable to exit context safely. When a context is exited, all wait handles in that context must be kicked out. But we maintain only references to the SCHEDULED wait handles + BLOCKED wait handles that recursively depend on them. If we do not kick out all unfinished wait handles, we end up in corrupted state. 2. Unability to break edge that caused the cycle Once the cycle is detected, we don't know which edge caused the cycle to be formed. We can only use heuristics to eliminate the edge that likely formed the cycle, we cannot be sure. This may make it very hard to fix the PHP code that caused the cycle. Solution: This diff implements online cycle detection with a naive approach of visiting the dependency chain from child at a time new edge between parent and child is being added. If a parent is visited, a cycle is found. Otherwise we eventually reach non-BLOCKED wait handle as it is guaranteed the rest of the graph is cycle-free.
199 linhas
6.2 KiB
C++
199 linhas
6.2 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
| Copyright (c) 1997-2010 The PHP Group |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <runtime/ext/ext_asio.h>
|
|
#include <runtime/ext/asio/asio_context.h>
|
|
#include <runtime/ext/asio/asio_session.h>
|
|
#include <system/lib/systemlib.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace {
|
|
StaticString s_genArray("<gen-array>");
|
|
|
|
void putException(Object& exception_field, ObjectData* new_exception) {
|
|
assert(new_exception);
|
|
assert(new_exception->o_instanceof("Exception"));
|
|
|
|
if (exception_field.isNull()) {
|
|
exception_field = new_exception;
|
|
}
|
|
}
|
|
|
|
void putInvalidArgumentException(Object& exception_field) {
|
|
STATIC_METHOD_INJECTION_BUILTIN(GenArrayWaitHandle, GenArrayWaitHandle::create);
|
|
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
|
"Expected dependencies to be an array of WaitHandle instances"));
|
|
putException(exception_field, e.get());
|
|
}
|
|
}
|
|
|
|
c_GenArrayWaitHandle::c_GenArrayWaitHandle(const ObjectStaticCallbacks *cb)
|
|
: c_BlockableWaitHandle(cb), m_exception() {
|
|
}
|
|
|
|
c_GenArrayWaitHandle::~c_GenArrayWaitHandle() {
|
|
}
|
|
|
|
void c_GenArrayWaitHandle::t___construct() {
|
|
INSTANCE_METHOD_INJECTION_BUILTIN(GenArrayWaitHandle, GenArrayWaitHandle::__construct);
|
|
Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
|
"Use GenArrayWaitHandle::create() instead of constructor"));
|
|
throw e;
|
|
}
|
|
|
|
Object c_GenArrayWaitHandle::ti_create(const char* cls, CArrRef dependencies) {
|
|
Array deps = dependencies->copy();
|
|
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 (UNLIKELY(current->m_type == KindOfRef)) {
|
|
tvUnbox(current);
|
|
}
|
|
|
|
if (IS_NULL_TYPE(current->m_type)) {
|
|
// {uninit,null} yields null
|
|
tvWriteNull(current);
|
|
continue;
|
|
}
|
|
|
|
c_WaitHandle* child = c_WaitHandle::fromTypedValue(current);
|
|
if (UNLIKELY(!child)) {
|
|
putInvalidArgumentException(exception);
|
|
} else if (child->isSucceeded()) {
|
|
tvSetIgnoreRef(child->getResult(), current);
|
|
} else if (child->isFailed()) {
|
|
putException(exception, child->getException());
|
|
} else {
|
|
c_WaitableWaitHandle* child_wh =
|
|
static_cast<c_WaitableWaitHandle*>(child);
|
|
|
|
c_GenArrayWaitHandle* my_wh = NEWOBJ(c_GenArrayWaitHandle)();
|
|
my_wh->initialize(exception, deps, iter_pos, child_wh);
|
|
return my_wh;
|
|
}
|
|
}
|
|
|
|
if (exception.isNull()) {
|
|
return c_StaticResultWaitHandle::t_create(deps);
|
|
} else {
|
|
return c_StaticExceptionWaitHandle::t_create(exception);
|
|
}
|
|
}
|
|
|
|
void c_GenArrayWaitHandle::initialize(CObjRef exception, CArrRef deps, ssize_t iter_pos, c_WaitableWaitHandle* child) {
|
|
m_exception = exception;
|
|
m_deps = deps;
|
|
m_iterPos = iter_pos;
|
|
try {
|
|
blockOn(child);
|
|
} catch (Object cycle_exception) {
|
|
putException(m_exception, cycle_exception.get());
|
|
m_iterPos = m_deps->iter_advance(m_iterPos);
|
|
onUnblocked();
|
|
}
|
|
}
|
|
|
|
void c_GenArrayWaitHandle::onUnblocked() {
|
|
for (;
|
|
m_iterPos != ArrayData::invalid_index;
|
|
m_iterPos = m_deps->iter_advance(m_iterPos)) {
|
|
|
|
TypedValue* current = m_deps->nvGetValueRef(m_iterPos);
|
|
if (UNLIKELY(current->m_type == KindOfRef)) {
|
|
tvUnbox(current);
|
|
}
|
|
|
|
if (IS_NULL_TYPE(current->m_type)) {
|
|
// {uninit,null} yields null
|
|
tvWriteNull(current);
|
|
continue;
|
|
}
|
|
|
|
c_WaitHandle* child = c_WaitHandle::fromTypedValue(current);
|
|
if (UNLIKELY(!child)) {
|
|
putInvalidArgumentException(m_exception);
|
|
} else if (child->isSucceeded()) {
|
|
tvSetIgnoreRef(child->getResult(), current);
|
|
} else if (child->isFailed()) {
|
|
putException(m_exception, child->getException());
|
|
} else {
|
|
c_WaitableWaitHandle* child_wh =
|
|
static_cast<c_WaitableWaitHandle*>(child);
|
|
|
|
try {
|
|
blockOn(child_wh);
|
|
return;
|
|
} catch (Object cycle_exception) {
|
|
putException(m_exception, cycle_exception.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_exception.isNull()) {
|
|
TypedValue result;
|
|
result.m_type = KindOfArray;
|
|
result.m_data.parr = m_deps.get();
|
|
setResult(&result);
|
|
m_deps = nullptr;
|
|
} else {
|
|
setException(m_exception.get());
|
|
m_exception = nullptr;
|
|
m_deps = nullptr;
|
|
}
|
|
}
|
|
|
|
String c_GenArrayWaitHandle::getName() {
|
|
return s_genArray;
|
|
}
|
|
|
|
c_WaitableWaitHandle* c_GenArrayWaitHandle::getChild() {
|
|
assert(getState() == STATE_BLOCKED);
|
|
return static_cast<c_WaitableWaitHandle*>(
|
|
m_deps->nvGetValueRef(m_iterPos)->m_data.pobj);
|
|
}
|
|
|
|
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);
|
|
|
|
setContextIdx(ctx_idx);
|
|
|
|
// TODO: enterContext() for all remaining dependencies
|
|
c_WaitableWaitHandle* wait_handle = static_cast<c_WaitableWaitHandle*>(
|
|
m_deps->nvGetValueRef(m_iterPos)->m_data.pobj);
|
|
wait_handle->enterContext(ctx_idx);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|