PocketMine-MP 5.28.3 git-d5a1007c80fcee27feb2251cf5dcf1ad5a59a85c
Loading...
Searching...
No Matches
CommonThreadPartsTrait.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\thread;
25
26use pmmp\thread\Thread as NativeThread;
27use pmmp\thread\ThreadSafeArray;
31use function error_get_last;
32use function error_reporting;
33use function implode;
34use function register_shutdown_function;
35use function set_exception_handler;
36
37trait CommonThreadPartsTrait{
42 private ?ThreadSafeArray $classLoaders = null;
43 protected ?string $composerAutoloaderPath = null;
44
45 protected bool $isKilled = false;
46
47 private ?ThreadCrashInfo $crashInfo = null;
48
52 public function getClassLoaders() : ?array{
53 return $this->classLoaders !== null ? (array) $this->classLoaders : null;
54 }
55
59 public function setClassLoaders(?array $autoloaders = null) : void{
60 $this->composerAutoloaderPath = \pocketmine\COMPOSER_AUTOLOADER_PATH;
61
62 if($autoloaders === null){
63 $autoloaders = [Server::getInstance()->getLoader()];
64 }
65
66 if($this->classLoaders === null){
67 $loaders = $this->classLoaders = new ThreadSafeArray();
68 }else{
69 $loaders = $this->classLoaders;
70 foreach($this->classLoaders as $k => $autoloader){
71 unset($this->classLoaders[$k]);
72 }
73 }
74 foreach($autoloaders as $autoloader){
75 $loaders[] = $autoloader;
76 }
77 }
78
84 public function registerClassLoaders() : void{
85 if($this->composerAutoloaderPath !== null){
86 require $this->composerAutoloaderPath;
87 }
88 $autoloaders = $this->classLoaders;
89 if($autoloaders !== null){
90 foreach($autoloaders as $autoloader){
92 $autoloader->register(false);
93 }
94 }
95 }
96
97 public function getCrashInfo() : ?ThreadCrashInfo{
98 //TODO: Joining a crashed worker might be a bit sus, but we need to make sure the thread's shutdown
99 //handler has run before we try to collect the crash info. As of 6.1.1, pmmpthread sets isTerminated=true
100 //*before* the shutdown handler is invoked, so we might land here before the crash info has been set.
101 //In the future this should probably be fixed by running the shutdown handlers before setting isTerminated,
102 //but this workaround should be good enough for now.
103 if($this->isTerminated() && !$this->isJoined()){
104 $this->join();
105 }
106 return $this->crashInfo;
107 }
108
109 public function start(int $options = NativeThread::INHERIT_NONE) : bool{
110 ThreadManager::getInstance()->add($this);
111
112 if($this->getClassLoaders() === null){
113 $this->setClassLoaders();
114 }
115 return parent::start($options);
116 }
117
118 final public function run() : void{
119 error_reporting(-1);
120 $this->registerClassLoaders();
121 //set this after the autoloader is registered
122 ErrorToExceptionHandler::set();
123
124 //this permits adding extra functionality to the exception and shutdown handlers via overriding
125 set_exception_handler($this->onUncaughtException(...));
126 register_shutdown_function($this->onShutdown(...));
127
128 $this->onRun();
129 $this->isKilled = true;
130 }
131
135 public function quit() : void{
136 $this->isKilled = true;
137
138 if(!$this->isJoined()){
139 $this->notify();
140 $this->join();
141 }
142
143 ThreadManager::getInstance()->remove($this);
144 }
145
149 protected function onUncaughtException(\Throwable $e) : void{
150 $this->synchronized(function() use ($e) : void{
151 $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName());
152 \GlobalLogger::get()->logException($e);
153 });
154 }
155
160 protected function onShutdown() : void{
161 $this->synchronized(function() : void{
162 if($this->isTerminated() && $this->crashInfo === null){
163 $last = error_get_last();
164 if($last !== null && ($last["type"] & CrashDump::FATAL_ERROR_MASK) !== 0){
165 //fatal error
166 $crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
167 }else{
168 //probably misused exit()
169 $crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName());
170 }
171 $this->crashInfo = $crashInfo;
172
173 $lines = [];
174 //mimic exception printed format
175 $lines[] = "Fatal error: " . $crashInfo->makePrettyMessage();
176 $lines[] = "--- Stack trace ---";
177 foreach($crashInfo->getTrace() as $frame){
178 $lines[] = " " . $frame->getPrintableFrame();
179 }
180 $lines[] = "--- End of fatal error information ---";
181 \GlobalLogger::get()->critical(implode("\n", $lines));
182 }
183 });
184 }
185
189 abstract protected function onRun() : void;
190
191 public function getThreadName() : string{
192 return (new \ReflectionClass($this))->getShortName();
193 }
194}
static fromLastErrorInfo(array $info, string $threadName)