Detects thread safety violations in AsyncTask implementations and global state usage
The Thread Safety analyzer identifies code patterns that can cause crashes, data corruption, or undefined behavior when using PocketMine-MP’s async task system.
Static properties may reference server objects that don’t exist in worker threads.
class PlayerDataTask extends AsyncTask { public function onRun(): void { // ERROR: May access server objects $plugin = MyPlugin::$instance; $config = Settings::$config; }}
The global keyword creates hidden dependencies and makes code harder to test.
$databaseConnection = null;class MyPlugin extends PluginBase { public function onEnable(): void { global $databaseConnection; $databaseConnection = new Database(); }}class UserManager { public function getUser(string $name): ?User { global $databaseConnection; return $databaseConnection->fetchUser($name); }}
Pass simple, serializable data through the constructor:
class WebRequestTask extends AsyncTask { public function __construct( private string $url, private array $headers, private int $timeout ) {} public function onRun(): void { $ch = curl_init($this->url); curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); $result = curl_exec($ch); $this->setResult($result); } public function onCompletion(): void { $response = $this->getResult(); // Process response in main thread }}
Here’s the complete pattern for safe async task usage:
class SafeAsyncTask extends AsyncTask { // Pass only serializable data public function __construct( private string $input, private array $options ) {} // Runs in worker thread - isolated from server public function onRun(): void { // Only use data passed in constructor $result = $this->processData($this->input, $this->options); // Store result for main thread $this->setResult($result); } // Runs in main thread - can access server objects public function onCompletion(): void { $result = $this->getResult(); // Now you can safely access plugin, server, worlds, etc. $plugin = MyPlugin::getInstance(); $plugin->handleResult($result); $player = $plugin->getServer()->getPlayerByPrefix($this->options['player']); if ($player !== null) { $player->sendMessage("Task completed!"); } }}
Access to superglobal $_SERVER in async context is thread-unsafe File: src/tasks/DatabaseTask.php:23 Category: THREAD_SAFETY Severity: ERROR Code: superglobal_in_async Fix: Avoid sharing mutable state between threads. Use storeLocal()/fetchLocal() for data passing.Use of global keyword detected. This can cause issues with thread safety and testability. File: src/Utils.php:15 Category: THREAD_SAFETY Severity: WARNING Code: global_keyword