mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-09-06 17:59:48 +00:00
Allow thread errors and their traces to be properly recorded in crashdumps (#5910)
until now, any thread crash would show as a generic crash since we aren't able to get the trace from the crashed thread directly. This uses some dirty tricks to export a partially serialized stack trace to the main thread, where it can be written into a crashdump. This enables us to see proper crash information for async tasks in the crash archive (finally!!!) as well as being able to capture RakLib errors properly.
This commit is contained in:
@ -26,7 +26,10 @@ namespace pocketmine\thread;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use pocketmine\errorhandler\ErrorToExceptionHandler;
|
||||
use pocketmine\Server;
|
||||
use function error_get_last;
|
||||
use function error_reporting;
|
||||
use function register_shutdown_function;
|
||||
use function set_exception_handler;
|
||||
|
||||
trait CommonThreadPartsTrait{
|
||||
/**
|
||||
@ -38,6 +41,8 @@ trait CommonThreadPartsTrait{
|
||||
|
||||
protected bool $isKilled = false;
|
||||
|
||||
private ?ThreadCrashInfo $crashInfo = null;
|
||||
|
||||
/**
|
||||
* @return ThreadSafeClassLoader[]
|
||||
*/
|
||||
@ -88,12 +93,48 @@ trait CommonThreadPartsTrait{
|
||||
}
|
||||
}
|
||||
|
||||
public function getCrashInfo() : ?ThreadCrashInfo{ return $this->crashInfo; }
|
||||
|
||||
final public function run() : void{
|
||||
error_reporting(-1);
|
||||
$this->registerClassLoaders();
|
||||
//set this after the autoloader is registered
|
||||
ErrorToExceptionHandler::set();
|
||||
|
||||
//this permits adding extra functionality to the exception and shutdown handlers via overriding
|
||||
set_exception_handler($this->onUncaughtException(...));
|
||||
register_shutdown_function($this->onShutdown(...));
|
||||
|
||||
$this->onRun();
|
||||
$this->isKilled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by set_exception_handler() when an uncaught exception is thrown.
|
||||
*/
|
||||
protected function onUncaughtException(\Throwable $e) : void{
|
||||
$this->synchronized(function() use ($e) : void{
|
||||
$this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by register_shutdown_function() when the thread shuts down. This may be because of a benign shutdown, or
|
||||
* because of a fatal error. Use isKilled to determine which.
|
||||
*/
|
||||
protected function onShutdown() : void{
|
||||
$this->synchronized(function() : void{
|
||||
if(!$this->isKilled && $this->crashInfo === null){
|
||||
$last = error_get_last();
|
||||
if($last !== null){
|
||||
//fatal error
|
||||
$this->crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
|
||||
}else{
|
||||
//probably misused exit()
|
||||
$this->crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
38
src/thread/ThreadCrashException.php
Normal file
38
src/thread/ThreadCrashException.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\thread;
|
||||
|
||||
final class ThreadCrashException extends ThreadException{
|
||||
|
||||
private ThreadCrashInfo $crashInfo;
|
||||
|
||||
public function __construct(string $message, ThreadCrashInfo $crashInfo){
|
||||
parent::__construct($message);
|
||||
$this->crashInfo = $crashInfo;
|
||||
}
|
||||
|
||||
public function getCrashInfo() : ThreadCrashInfo{
|
||||
return $this->crashInfo;
|
||||
}
|
||||
}
|
89
src/thread/ThreadCrashInfo.php
Normal file
89
src/thread/ThreadCrashInfo.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\thread;
|
||||
|
||||
use pmmp\thread\ThreadSafe;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use pocketmine\errorhandler\ErrorTypeToStringMap;
|
||||
use pocketmine\utils\Filesystem;
|
||||
use pocketmine\utils\Utils;
|
||||
use function get_class;
|
||||
use function sprintf;
|
||||
|
||||
final class ThreadCrashInfo extends ThreadSafe{
|
||||
|
||||
/** @phpstan-var ThreadSafeArray<int, ThreadCrashInfoFrame> */
|
||||
private ThreadSafeArray $trace;
|
||||
|
||||
/**
|
||||
* @param ThreadCrashInfoFrame[] $trace
|
||||
*/
|
||||
public function __construct(
|
||||
private string $type,
|
||||
private string $message,
|
||||
private string $file,
|
||||
private int $line,
|
||||
array $trace,
|
||||
private string $threadName
|
||||
){
|
||||
$this->trace = ThreadSafeArray::fromArray($trace);
|
||||
}
|
||||
|
||||
public static function fromThrowable(\Throwable $e, string $threadName) : self{
|
||||
return new self(get_class($e), $e->getMessage(), $e->getFile(), $e->getLine(), Utils::printableTraceWithMetadata($e->getTrace()), $threadName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param array{type: int, message: string, file: string, line: int} $info
|
||||
*/
|
||||
public static function fromLastErrorInfo(array $info, string $threadName) : self{
|
||||
try{
|
||||
$class = ErrorTypeToStringMap::get($info["type"]);
|
||||
}catch(\InvalidArgumentException){
|
||||
$class = "Unknown error type (" . $info["type"] . ")";
|
||||
}
|
||||
return new self($class, $info["message"], $info["file"], $info["line"], Utils::printableTraceWithMetadata(Utils::currentTrace()), $threadName);
|
||||
}
|
||||
|
||||
public function getType() : string{ return $this->type; }
|
||||
|
||||
public function getMessage() : string{ return $this->message; }
|
||||
|
||||
public function getFile() : string{ return $this->file; }
|
||||
|
||||
public function getLine() : int{ return $this->line; }
|
||||
|
||||
/**
|
||||
* @return ThreadCrashInfoFrame[]
|
||||
*/
|
||||
public function getTrace() : array{
|
||||
return (array) $this->trace;
|
||||
}
|
||||
|
||||
public function getThreadName() : string{ return $this->threadName; }
|
||||
|
||||
public function makePrettyMessage() : string{
|
||||
return sprintf("%s: \"%s\" in \"%s\" on line %d", $this->type ?? "Fatal error", $this->message, Filesystem::cleanPath($this->file), $this->line);
|
||||
}
|
||||
}
|
41
src/thread/ThreadCrashInfoFrame.php
Normal file
41
src/thread/ThreadCrashInfoFrame.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
* ____ _ _ __ __ _ __ __ ____
|
||||
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
|
||||
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
|
||||
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
|
||||
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* @author PocketMine Team
|
||||
* @link http://www.pocketmine.net/
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace pocketmine\thread;
|
||||
|
||||
use pmmp\thread\ThreadSafe;
|
||||
|
||||
final class ThreadCrashInfoFrame extends ThreadSafe{
|
||||
|
||||
public function __construct(
|
||||
private string $printableFrame,
|
||||
private ?string $file,
|
||||
private int $line,
|
||||
){}
|
||||
|
||||
public function getPrintableFrame() : string{ return $this->printableFrame; }
|
||||
|
||||
public function getFile() : ?string{ return $this->file; }
|
||||
|
||||
public function getLine() : int{ return $this->line; }
|
||||
}
|
Reference in New Issue
Block a user