From f1eecff25d070904a06b4794b254251101bf292b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 18 Sep 2019 16:01:27 +0200 Subject: [PATCH] Handle some pts errors --- src/danog/MadelineProto/Coroutine.php | 72 +++++++++--------- .../MTProtoSession/CallHandler.php | 9 ++- .../MTProtoSession/ResponseHandler.php | 18 ++++- .../MadelineProto/TL/PrettyException.php | 68 ++++++++++++++++- src/danog/MadelineProto/Tools.php | 42 ----------- src/danog/MadelineProto/Trace.php | 75 +++++++++++++++++++ 6 files changed, 198 insertions(+), 86 deletions(-) create mode 100644 src/danog/MadelineProto/Trace.php diff --git a/src/danog/MadelineProto/Coroutine.php b/src/danog/MadelineProto/Coroutine.php index ad084c35..4e3d3eb5 100644 --- a/src/danog/MadelineProto/Coroutine.php +++ b/src/danog/MadelineProto/Coroutine.php @@ -44,7 +44,9 @@ use ReflectionGenerator; */ final class Coroutine implements Promise, \ArrayAccess { - use Internal\Placeholder; + use Internal\Placeholder { + fail as internalFail; + } /** @var \Generator */ private $generator; /** @var callable(\Throwable|null $exception, mixed $value): void */ @@ -56,41 +58,20 @@ final class Coroutine implements Promise, \ArrayAccess /** @var mixed Promise success value when executing next coroutine step, null at all other times. */ private $value; - private $frames = []; + /** + * Generator trace. + * + * @var Trace + */ + private $trace; /** * @param \Generator $generator */ - public function __construct(\Generator $generator) + public function __construct(\Generator $generator, Trace $trace = null) { - /* - $this->generator = new class($generator) { - private $s = ''; - private $g; - private $trace; - public function __construct($g) { - $this->g = $g; - $this->s .= spl_object_hash($this).', '; - } - public function __call($a, $args) { - $this->s .= "$a, "; - try { - $res = $this->g->{$a}(...$args); - if (is_array($res) && isset($res['my_trace'])) { - $this->trace = $res; - $res = $this->g->{$a}(...$args); - } - return $res; - } catch (\Throwable $e) { - $this->s .= $e->getMessage(); - $this->s .= ', '; - var_dump($this->s, $this->trace); - throw $e; - } - } - //public function __destruct() { var_dump($this->s); } - };*/ $this->generator = $generator; + //$this->trace = $trace ?? new Trace(\debug_backtrace()); try { $yielded = $this->generator->current(); @@ -111,6 +92,20 @@ final class Coroutine implements Promise, \ArrayAccess return; } if ($yielded instanceof \Generator) { + /*if ($this->generator->valid()) { + $reflection = new ReflectionGenerator($this->generator); + $trace = new Trace( + [[ + 'file' => $reflection->getExecutingFile(), + 'line' => $reflection->getExecutingLine(), + 'function' => $reflection->getFunction()->getName(), + ]], + $this->trace + ); + } else { + $trace = $this->trace; + } + $yielded = new self($yielded, $trace);*/ $yielded = new self($yielded); } else { $yielded = $this->generator->send($yielded); @@ -183,6 +178,17 @@ final class Coroutine implements Promise, \ArrayAccess $yielded->onResolve($this->onResolve); } + /** + * @param \Throwable $reason Failure reason. + */ + public function fail(\Throwable $reason) + { + //if (isset(\class_uses($reason)[TL\PrettyException::class])) { + //$reason->updateTLTrace($this->getTrace()); + //} + $this->resolve(new Failure($reason)); + } + public function offsetExists($offset): bool { throw new Exception('Not supported!'); @@ -214,12 +220,10 @@ final class Coroutine implements Promise, \ArrayAccess /** * Get stacktrace from when the generator was started. * - * @param integer $options Backtrace options - * * @return array */ - public function getTrace(int $options = \DEBUG_BACKTRACE_PROVIDE_OBJECT): array + public function getTrace(): array { - return (new ReflectionGenerator($this->generator))->getTrace($options); + return $this->trace->getTrace(); } } diff --git a/src/danog/MadelineProto/MTProtoSession/CallHandler.php b/src/danog/MadelineProto/MTProtoSession/CallHandler.php index f766c36b..91f00a5e 100644 --- a/src/danog/MadelineProto/MTProtoSession/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/CallHandler.php @@ -93,9 +93,12 @@ trait CallHandler $deferred->fail($e); } else { if (\is_array($read_deferred)) { - $read_deferred = \array_map(function ($value) { - return $value->promise(); - }, $read_deferred); + $read_deferred = \array_map( + function ($value) { + return $value->promise(); + }, + $read_deferred + ); $deferred->resolve(all($read_deferred)); } else { $deferred->resolve($read_deferred->promise()); diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 7618a3ed..33e224c0 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -21,6 +21,7 @@ namespace danog\MadelineProto\MTProtoSession; use Amp\Loop; use danog\MadelineProto\MTProto; +use danog\MadelineProto\TL\PrettyException; /** * Manages responses. @@ -307,7 +308,15 @@ trait ResponseHandler return $only_updates; } - public function handle_reject(&$request, $data) + /** + * Reject request with exception + * + * @param array $request Request + * @param \Throwable $data Exception + * + * @return void + */ + public function handle_reject(array &$request, \Throwable $data) { if (isset($request['promise']) && \is_object($request['promise'])) { Loop::defer(function () use (&$request, $data) { @@ -350,12 +359,15 @@ trait ResponseHandler $this->shared->getTempAuthKey()->init(true); } - if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_OUTDATED', 'PERSISTENT_TIMESTAMP_INVALID'])) { + if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) { $this->got_response_for_outgoing_message_id($request_id); $this->handle_reject($request, new \danog\MadelineProto\PTSException($response['error_message'])); return; } + if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') { + $response['error_code'] = 500; + } if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) { $this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call..."); @@ -377,7 +389,7 @@ trait ResponseHandler return; } - if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'RPC_CALL_FAIL', 'RPC_MCGET_FAIL', 'no workers running'])) { + if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running'])) { Loop::delay(1 * 1000, [$this, 'method_recall'], ['message_id' => $request_id, ]); return; } diff --git a/src/danog/MadelineProto/TL/PrettyException.php b/src/danog/MadelineProto/TL/PrettyException.php index bbaf3f31..d8a8a356 100644 --- a/src/danog/MadelineProto/TL/PrettyException.php +++ b/src/danog/MadelineProto/TL/PrettyException.php @@ -19,23 +19,77 @@ namespace danog\MadelineProto\TL; +use danog\MadelineProto\Tools; + +/** + * Handle async stack traces. + */ trait PrettyException { - public $tl_trace; + /** + * TL trace. + * + * @var string + */ + public $tl_trace = ''; - public function getTLTrace() + /** + * Method name. + * + * @var string + */ + private $method = ''; + + /** + * Whether the TL trace was updated. + * + * @var boolean + */ + private $updated = false; + /** + * Update TL trace. + * + * @param array $trace + * + * @return void + */ + public function updateTLTrace(array $trace) + { + if (!$this->updated) { + $this->updated = true; + $this->prettify_tl($this->method, $trace); + } + } + /** + * Get TL trace. + * + * @return string + */ + public function getTLTrace(): string { return $this->tl_trace; } - public function prettify_tl($init = '') + /** + * Generate async trace. + * + * @param string $init Method name + * @param array $trace Async trace + * + * @return void + */ + public function prettify_tl(string $init = '', array $trace = null) { + $this->method = $init; + $previous_trace = $this->tl_trace; + $this->tl_trace = ''; + $eol = PHP_EOL; if (PHP_SAPI !== 'cli') { $eol = '
'.PHP_EOL; } $tl = false; - foreach (\array_reverse($this->getTrace()) as $k => $frame) { + foreach (\array_reverse($trace ?? $this->getTrace()) as $k => $frame) { if (isset($frame['function']) && \in_array($frame['function'], ['serialize_params', 'serialize_object'])) { if ($frame['args'][2] !== '') { $this->tl_trace .= $tl ? "['".$frame['args'][2]."']" : "While serializing: \t".$frame['args'][2]; @@ -58,5 +112,11 @@ trait PrettyException } $this->tl_trace .= $init !== '' ? "['".$init."']" : ''; $this->tl_trace = \implode($eol, \array_reverse(\explode($eol, $this->tl_trace))); + + if ($previous_trace) { + $this->tl_trace .= $eol.$eol; + $this->tl_trace .= "Previous TL trace$eol"; + $this->tl_trace .= $previous_trace; + } } } diff --git a/src/danog/MadelineProto/Tools.php b/src/danog/MadelineProto/Tools.php index df35f6e5..7624598c 100644 --- a/src/danog/MadelineProto/Tools.php +++ b/src/danog/MadelineProto/Tools.php @@ -448,46 +448,4 @@ trait Tools $var instanceof Traversable && $var instanceof Countable); } - - /** - * Custom backtrace for working with generators. - * - * @param boolean $ignoreArgs Whether to ignore method arguments - * @param array $trace Trace to work with - * - * @return array - */ - public static function backtrace(bool $ignoreArgs = false, array $trace = []): array - { - return \iterator_to_array(self::backtraceGenerator($ignoreArgs, $trace)); - } - /** - * Custom backtrace for working with generators. - * - * @param boolean $ignoreArgs Whether to ignore method arguments - * @param array $trace Trace to work with - * - * @return \Generator - */ - public static function backtraceGenerator(bool $ignoreArgs = false, array $trace = []): \Generator - { - $flags = DEBUG_BACKTRACE_PROVIDE_OBJECT; - if ($ignoreArgs) { - $flags |= DEBUG_BACKTRACE_IGNORE_ARGS; - } - if (!$trace) { - $trace = \debug_backtrace($flags); - \array_shift($trace); - } - - foreach ($trace as $frame) { - if (isset($frame['object']) && $frame['object'] instanceof Coroutine) { - yield from self::backtrace($ignoreArgs, $frame['object']->getTrace($flags)); - return; - } - //var_dump(get_class($frame['object'] ?? new class {})); - unset($frame['object']); - yield $frame; - } - } } diff --git a/src/danog/MadelineProto/Trace.php b/src/danog/MadelineProto/Trace.php new file mode 100644 index 00000000..1e7c1e82 --- /dev/null +++ b/src/danog/MadelineProto/Trace.php @@ -0,0 +1,75 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto; + +/** + * Represents a piece of a coroutine stack trace + */ +class Trace +{ + /** + * Next piece of the stack trace + * + * @var Trace + */ + private $next; + /** + * Current stack trace frames + * + * @var array + */ + private $frames = []; + /** + * Create trace + * + * @param array $frames Current frames + * @param self $next Next trace + */ + public function __construct(array $frames, self $next = null) + { + $this->frames = $frames; + $this->next = $next; + } + + /** + * Get stack trace + * + * @return array + */ + public function getTrace(): array + { + return iterator_to_array($this->getTraceGenerator()); + } + + /** + * Get stack trace + * + * @return \Generator + */ + private function getTraceGenerator(): \Generator + { + foreach ($this->frames as $frame) { + yield $frame; + } + if ($this->next) { + yield from $this->next->getTraceGenerator(); + } + } +} \ No newline at end of file