If an internal HPHP exception is thrown in a continuation executed by
ext_asio, m_current pointer was not reset and resources were not cleaned
up. This doesn't matter that much in prod, but when used in debug mode,
an assertion was hit.
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.
Currently, wait handles store pointer to the context they are in. This
pointer is not protected with reference counting, as it is expected that
whenever a context is exited, references to it are cleaned thru
exitContext() mechanism.
If a bug is present that violates this assumption, it is impossible to
guard against invalid pointer access and a hard to debug memory
corruption occurs.
Since the structure of contexts is a simple stack, let's reference them
by index instead of by pointer.
As a bonus, one pointer worth of memory is saved for every non-trivial wait handle.
The actual bugs will be fixed by the next 2 diffs that do:
1. implement online cycle detection
2. do enterContext() atomically and properly handle failure
And use them in AsioContext, which was doing a lot of memory
allocation via malloc/free, was itself allocated by malloc, and
needed to be sweepable to deal with the fact that it contained
standard containers.
This change is mostly for FB internal organizational reasons.
Building is not effected beyond the fact that the target now
lands in hphp/hhvm/hhvm rather than src/hhvm/hhvm.