1
0
mirror of https://github.com/danog/amp.git synced 2025-01-22 05:11:42 +01:00

Avoid loosing exception thrown from generator after invalid yield

This commit is contained in:
Aaron Piotrowski 2016-06-16 22:44:32 -05:00
parent f553e4f08b
commit 92767755ee
2 changed files with 52 additions and 34 deletions

View File

@ -74,18 +74,14 @@ final class Coroutine implements Awaitable {
}
if ($this->generator->valid()) {
throw new InvalidYieldException(
$this->generator,
$yielded,
\sprintf("Unexpected yield (%s or %s::result() expected)", Awaitable::class, self::class)
);
throw new InvalidYieldException($this->generator);
}
$this->resolve(PHP_MAJOR_VERSION >= 7 ? $this->generator->getReturn() : null);
} catch (\Throwable $exception) {
$this->fail($exception);
$this->dispose($exception);
} catch (\Exception $exception) {
$this->fail($exception);
$this->dispose($exception);
}
};
@ -106,50 +102,69 @@ final class Coroutine implements Awaitable {
}
if ($this->generator->valid()) {
throw new InvalidYieldException(
$this->generator,
$yielded,
\sprintf("Unexpected yield (%s or %s::result() expected)", Awaitable::class, self::class)
);
throw new InvalidYieldException($this->generator);
}
$this->resolve(PHP_MAJOR_VERSION >= 7 ? $this->generator->getReturn() : null);
} catch (\Throwable $exception) {
$this->fail($exception);
$this->dispose($exception);
} catch (\Exception $exception) {
$this->fail($exception);
$this->dispose($exception);
}
}
/**
* Runs the generator to completion then fails the coroutine with the given exception.
*
* @param \Throwable|\Exception $exception
*
* @throws \Throwable|\Exception
*/
private function dispose($exception) {
if ($this->generator->valid()) {
try {
try {
// Ensure generator has run to completion to avoid throws from finally blocks on destruction.
do {
$this->generator->throw($exception);
} while ($this->generator->valid());
} finally {
// Throw from finally to attach any exception thrown from generator as previous exception.
throw $exception;
}
} catch (\Throwable $exception) {
// $exception will be used to fail the coroutine.
} catch (\Exception $exception) {
// $exception will be used to fail the coroutine.
}
}
$this->fail($exception);
}
/**
* Attempts to resolves the coroutine with the given value, failing if the generator is still valid after sending
* the null back to the generator.
* Required only for PHP 5.x. Remove once PHP 7 is required.
* @todo Remove once PHP 7 is required.
*
* @param mixed $result Coroutine return value.
*/
private function settle($result) {
$value = $this->generator->send(null);
$this->generator->send(null);
if ($this->generator->valid()) {
$exception = new InvalidYieldException(
throw new InvalidYieldException(
$this->generator,
$value,
\sprintf("Unexpected yield after %s::result()", self::class)
\sprintf("Unexpected yield after %s::result() (use 'return;' to halt generator)", self::class)
);
do {
$this->generator->throw($exception);
} while ($this->generator->valid());
throw $exception;
}
$this->resolve($result);
}
/**
* Return a value from a coroutine. Required for PHP 5.x only. Use the return keyword in PHP 7.
* @todo Remove once PHP 7 is required.
*
* @param mixed $value
*

View File

@ -2,18 +2,21 @@
namespace Amp;
use Interop\Async\Awaitable;
class InvalidYieldException extends \DomainException {
/**
* InvalidYieldException constructor.
*
* @param \Generator $generator
* @param mixed $yielded
* @param string $prefix
* @param string|null $prefix
*/
public function __construct(\Generator $generator, $yielded, $prefix) {
$prefix = \sprintf(
"%s; %s yielded at key %s",
$prefix,
public function __construct(\Generator $generator, $prefix = null) {
if ($prefix === null) {
$prefix = \sprintf("Unexpected yield (%s or %s::result() expected)", Awaitable::class, Coroutine::class);
}
$yielded = $generator->current();
$prefix .= \sprintf(
"; %s yielded at key %s",
\is_object($yielded) ? \get_class($yielded) : \gettype($yielded),
$generator->key()
);