1
0
mirror of https://github.com/danog/amp.git synced 2024-11-27 04:24:42 +01:00

Optimize coroutine resolution for performance

This commit is contained in:
Daniel Lowrey 2015-05-19 23:07:29 -04:00
parent fc600d46ab
commit c8d6a0b5b3

View File

@ -483,22 +483,12 @@ function coroutine(callable $func, Reactor $reactor = null, callable $promisifie
* error occurs during coroutine resolution the promise fails.
*/
function resolve(\Generator $generator, Reactor $reactor = null, callable $promisifier = null) {
/* In the php7 branch we use an anonymous class with Struct for this.
* Using a stdclass isn't terribly readable and it's prone to error but
* it's the easiest way to minimize the distance between 5.x and 7 code
* and keep maintenance simple.
*/
$cs = new \StdClass;
$cs->reactor = $reactor ?: getReactor();
$cs->promisor = new Deferred;
$cs->generator = $generator;
$cs->promisifier = $promisifier;
/**
* In php7 we're able to use Generator::getReturn() to retrieve this. Because
* this functionality is unavailable in 5.x users must manually use yield "return" => $foo
* so we can store the "return" value ourselves.
*/
$cs->currentPromise = null;
$cs->returnValue = null;
__coroutineAdvance($cs);
@ -508,22 +498,56 @@ function resolve(\Generator $generator, Reactor $reactor = null, callable $promi
function __coroutineAdvance($cs) {
try {
if ($cs->generator->valid()) {
$promise = __coroutinePromisify($cs);
$cs->reactor->immediately(function() use ($cs, $promise) {
$promise->when(function($error, $result) use ($cs) {
__coroutineSend($cs, $error, $result);
});
});
} else {
if (!$cs->generator->valid()) {
$cs->promisor->succeed($cs->returnValue);
return;
}
$yielded = $cs->generator->current();
if (!isset($yielded)) {
// nothing to do ... jump to the end
} elseif (($key = $cs->generator->key()) === "return") {
$cs->returnValue = $yielded;
} elseif ($yielded instanceof Promise) {
$cs->currentPromise = $yielded;
} elseif ($yielded instanceof Streamable) {
$cs->currentPromise = resolve($yielded->buffer(), $cs->reactor);
} elseif ($cs->promisifier) {
$promise = call_user_func($cs->promisifier, $cs->generator, $key, $yielded);
if ($promise instanceof Promise) {
$cs->currentPromise = $promise;
} else {
$cs->promisor->fail(new \DomainException(sprintf(
"Invalid promisifier yield of type %s; Promise|null expected",
is_object($promise) ? get_class($promise) : gettype($promise)
)));
return;
}
} else {
$cs->promisor->fail(new \DomainException(
__generateYieldError($cs->generator, $key, $yielded)
));
return;
}
$cs->reactor->immediately("Amp\__coroutineNextTick", ["callback_data" => $cs]);
} catch (\Exception $uncaught) {
$cs->promisor->fail($uncaught);
}
}
function __coroutineSend($cs, \Exception $error = null, $result = null) {
function __coroutineNextTick($reactor, $watcherId, $cs) {
if ($promise = $cs->currentPromise) {
$cs->currentPromise = null;
$promise->when("Amp\__coroutineSend", $cs);
} else {
__coroutineSend(null, null, $cs);
}
}
function __coroutineSend($error, $result, $cs) {
try {
if ($error) {
$cs->generator->throw($error);
@ -536,43 +560,18 @@ function __coroutineSend($cs, \Exception $error = null, $result = null) {
}
}
function __coroutinePromisify($cs) {
$generator = $cs->generator;
$yielded = $generator->current();
if (!isset($yielded)) {
return new Success;
function __generateYieldError(\Generator $generator, $key, $yielded) {
$reflectionGen = new \ReflectionGenerator($generator);
$executingGen = $reflectionGen->getExecutingGenerator();
if ($isSubgenerator = ($executingGen !== $generator)) {
$reflectionGen = new \ReflectionGenerator($executingGen);
}
$key = $generator->key();
// We fake generator returns in 5.x using the "return" yield key
if ($key === "return") {
$cs->returnValue = $yielded;
return new Success($yielded);
}
if ($yielded instanceof Promise) {
return $yielded;
}
if ($yielded instanceof Streamable) {
return resolve($yielded->buffer(), $cs->reactor);
}
// Allow custom promisifier callables to create Promise from
// the yielded key/value for extension use-cases
if ($cs->promisifier) {
// In php7 we wrap the promisifier in parens to avoid call_user_func()
// but that's not possible in 5.x :(
return call_user_func($cs->promisifier, $key, $yielded);
}
return new Failure(new \DomainException(
sprintf(
"Unexpected Generator yield of type %s at key %s; Promise|null expected",
(is_object($yielded) ? get_class($yielded) : gettype($yielded)),
$key
)
));
return sprintf(
"Unexpected Generator yield (Promise|null expected); %s yielded at key %s on line %s in %s",
(is_object($yielded) ? get_class($yielded) : gettype($yielded)),
$key,
$reflectionGen->getExecutingLine(),
$reflectionGen->getExecutingFile()
);
}