PocketMine-MP 5.32.2 git-1ebd7d3960d713d56f77f610fe0c15cdee201069
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 //WARNING: Do not call this inside a synchronized block on this thread's context. Because the shutdown handler
104 //runs in a synchronized block, this will result in a deadlock.
105 if($this->isTerminated() && !$this->isJoined()){
106 $this->join();
107 }
108 return $this->crashInfo;
109 }
110
111 public function start(int $options = NativeThread::INHERIT_NONE) : bool{
112 ThreadManager::getInstance()->add($this);
113
114 if($this->getClassLoaders() === null){
115 $this->setClassLoaders();
116 }
117 return parent::start($options);
118 }
119
120 final public function run() : void{
121 error_reporting(-1);
122 $this->registerClassLoaders();
123 //set this after the autoloader is registered
124 ErrorToExceptionHandler::set();
125
126 //this permits adding extra functionality to the exception and shutdown handlers via overriding
127 set_exception_handler($this->onUncaughtException(...));
128 register_shutdown_function($this->onShutdown(...));
129
130 $this->onRun();
131 $this->isKilled = true;
132 }
133
137 public function quit() : void{
138 $this->isKilled = true;
139
140 if(!$this->isJoined()){
141 $this->notify();
142 $this->join();
143 }
144
145 ThreadManager::getInstance()->remove($this);
146 }
147
151 protected function onUncaughtException(\Throwable $e) : void{
152 $this->synchronized(function() use ($e) : void{
153 $this->crashInfo = ThreadCrashInfo::fromThrowable($e, $this->getThreadName());
154 \GlobalLogger::get()->logException($e);
155 });
156 }
157
162 protected function onShutdown() : void{
163 $this->synchronized(function() : void{
164 if($this->isTerminated() && $this->crashInfo === null){
165 $last = error_get_last();
166 if($last !== null && ($last["type"] & CrashDump::FATAL_ERROR_MASK) !== 0){
167 //fatal error
168 $crashInfo = ThreadCrashInfo::fromLastErrorInfo($last, $this->getThreadName());
169 }else{
170 //probably misused exit()
171 $crashInfo = ThreadCrashInfo::fromThrowable(new \RuntimeException("Thread crashed without an error - perhaps exit() was called?"), $this->getThreadName());
172 }
173 $this->crashInfo = $crashInfo;
174
175 $lines = [];
176 //mimic exception printed format
177 $lines[] = "Fatal error: " . $crashInfo->makePrettyMessage();
178 $lines[] = "--- Stack trace ---";
179 foreach($crashInfo->getTrace() as $frame){
180 $lines[] = " " . $frame->getPrintableFrame();
181 }
182 $lines[] = "--- End of fatal error information ---";
183 \GlobalLogger::get()->critical(implode("\n", $lines));
184 }
185 });
186 }
187
191 abstract protected function onRun() : void;
192
193 public function getThreadName() : string{
194 return (new \ReflectionClass($this))->getShortName();
195 }
196}
static fromLastErrorInfo(array $info, string $threadName)