1
0
mirror of https://github.com/danog/amp.git synced 2024-11-30 04:29:08 +01:00

Remove promise error handler

Exceptions thrown from when callbacks are now forwarded directly to the loop error handler.
This commit is contained in:
Aaron Piotrowski 2017-03-10 15:32:58 -06:00
parent be34c49a2d
commit 754a29e86c
7 changed files with 24 additions and 126 deletions

View File

@ -2,8 +2,6 @@
namespace Amp;
use Amp\Promise\ErrorHandler;
/**
* Creates a failed stream (which is also a promise) using the given exception.
*/
@ -25,7 +23,9 @@ final class Failure implements Stream {
try {
$onResolved($this->exception, null);
} catch (\Throwable $exception) {
ErrorHandler::notify($exception);
Loop::defer(function () use ($exception) {
throw $exception;
});
}
}

View File

@ -3,8 +3,8 @@
namespace Amp\Internal;
use Amp\Failure;
use Amp\Loop;
use Amp\Promise;
use Amp\Promise\ErrorHandler;
/**
* Trait used by Promise implementations. Do not use this trait in your code, instead compose your class from one of
@ -35,7 +35,9 @@ trait Placeholder {
try {
$onResolved(null, $this->result);
} catch (\Throwable $exception) {
ErrorHandler::notify($exception);
Loop::defer(function () use ($exception) {
throw $exception;
});
}
return;
}
@ -80,7 +82,9 @@ trait Placeholder {
try {
$onResolved(null, $this->result);
} catch (\Throwable $exception) {
ErrorHandler::notify($exception);
Loop::defer(function () use ($exception) {
throw $exception;
});
}
}

View File

@ -3,8 +3,8 @@
namespace Amp\Internal;
use Amp\Deferred;
use Amp\Loop;
use Amp\Promise;
use Amp\Promise\ErrorHandler;
use Amp\Success;
/**
@ -80,7 +80,9 @@ trait Producer {
$promises[] = $result;
}
} catch (\Throwable $e) {
ErrorHandler::notify($e);
Loop::defer(function () use ($e) {
throw $e;
});
}
}
@ -92,7 +94,9 @@ trait Producer {
$count = \count($promises);
$f = static function ($e) use ($deferred, $value, &$count) {
if ($e) {
ErrorHandler::notify($e);
Loop::defer(function () use ($e) {
throw $e;
});
}
if (!--$count) {
$deferred->resolve($value);

View File

@ -2,7 +2,7 @@
namespace Amp\Internal;
use Amp\Promise\ErrorHandler;
use Amp\Loop;
/**
* Stores a set of functions to be invoked when a promise is resolved.
@ -47,7 +47,9 @@ class WhenQueue {
try {
$callback($exception, $value);
} catch (\Throwable $exception) {
ErrorHandler::notify($exception);
Loop::defer(function () use ($exception) {
throw $exception;
});
}
}
}

View File

@ -6,7 +6,6 @@ use Amp\Loop\Driver;
use Amp\Loop\Factory;
use Amp\Loop\InvalidWatcherException;
use Amp\Loop\UnsupportedFeatureException;
use Amp\Promise\ErrorHandler;
/**
* Accessor to allow global access to the event loop.
@ -51,23 +50,9 @@ final class Loop {
self::$driver->defer(wrap($callback));
}
self::setupErrorHandlerIfNoneExists();
self::$driver->run();
}
private static function setupErrorHandlerIfNoneExists() {
$errorHandler = ErrorHandler::set(function ($error) {
Loop::defer(function () use ($error) {
throw $error;
});
});
if ($errorHandler !== null) {
// Restore if another handler has already been set
ErrorHandler::set($errorHandler);
}
}
/**
* Stop the event loop.
*

View File

@ -1,97 +0,0 @@
<?php
namespace Amp\Promise;
/**
* Global error handler for promises.
*
* Callbacks passed to `Promise::when()` should never throw, but they might. Such errors have to be passed to this
* global error handler to make them easily loggable. These can't be handled gracefully in any way, so we just enable
* logging with this handler and ignore them otherwise.
*
* If no handler is set or that handler rethrows, it will fail hard by triggering an E_USER_ERROR leading to script
* abortion.
*/
final class ErrorHandler {
/** @var callable|null */
private static $callback = null;
private function __construct() {
// disable construction, only static helper
}
/**
* Set a new handler that will be notified on uncaught errors during promise resolution callback invocations.
*
* This callback can attempt to log the error or exit the execution of the script if it sees need. It receives the
* exception as first and only parameter.
*
* As it's already a last chance handler, the script will be aborted using E_USER_ERROR if the handler throws. Thus
* it's suggested to always wrap the body of your callback in a generic `try` / `catch` block, if you want to avoid
* that.
*
* @param callable|null $onError Callback to invoke on errors or `null` to reset.
*
* @return callable|null Previous callback.
*/
public static function set(callable $onError = null) {
$previous = self::$callback;
self::$callback = $onError;
return $previous;
}
/**
* Notifies the registered handler, that an exception occurred.
*
* This method MUST be called by every promise implementation if a callback passed to `Promise::when()` throws upon
* invocation. It MUST NOT be called otherwise.
*
* @param \Throwable $error Exception that occurred.
*/
public static function notify(\Throwable $error) {
if (self::$callback === null) {
self::triggerErrorHandler(
"An exception has been thrown from an Amp\\Promise::when() handler, but no handler has been registered"
. " via Amp\\Promise\\ErrorHandler::set(). A handler has to be registered to prevent exceptions from"
. " going unnoticed. Do NOT install an empty handler that just does nothing. If the handler is called,"
. " there is ALWAYS something wrong.",
$error
);
return;
}
try {
\call_user_func(self::$callback, $error);
} catch (\Exception $e) {
self::triggerErrorHandler(
"An exception has been thrown from the promise error handler registered to"
. " Amp\\Promise\\ErrorHandler::set().",
$e
);
} catch (\Throwable $e) {
self::triggerErrorHandler(
"An exception has been thrown from the promise error handler registered to"
. " Amp\\Promise\\ErrorHandler::set().",
$e
);
}
}
private static function triggerErrorHandler($message, $error) {
// We're already a last chance handler, throwing doesn't make sense, so use E_USER_ERROR.
// E_USER_ERROR is recoverable by a handler set via set_error_handler, which might throw, too.
$message .= "\n\n" . (string) $error;
try {
\trigger_error($message, E_USER_ERROR);
} catch (\Exception $e) {
\set_error_handler(null);
\trigger_error($message, E_USER_ERROR);
} catch (\Throwable $e) {
\set_error_handler(null);
\trigger_error($message, E_USER_ERROR);
}
}
}

View File

@ -2,8 +2,6 @@
namespace Amp;
use Amp\Promise\ErrorHandler;
/**
* Creates a successful stream (which is also a promise) using the given value (which can be any value except another
* object implementing \Amp\Promise).
@ -32,7 +30,9 @@ final class Success implements Stream {
try {
$onResolved(null, $this->value);
} catch (\Throwable $exception) {
ErrorHandler::notify($exception);
Loop::defer(function () use ($exception) {
throw $exception;
});
}
}