Require pthreads ^5.1

This version of pthreads has a substantially improved API, improved
performance, improved memory usage, and much less magical and broken
behaviour.
This commit is contained in:
Dylan K. Taylor 2023-01-23 20:02:33 +00:00
parent 14b250c63f
commit 222415859a
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
22 changed files with 245 additions and 118 deletions

View File

@ -21,7 +21,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
pm-version-major: "5"
phpstan:
name: PHPStan analysis
@ -42,7 +42,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
pm-version-major: "5"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -81,7 +81,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
pm-version-major: "5"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -122,7 +122,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
pm-version-major: "5"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php
@ -161,7 +161,7 @@ jobs:
with:
php-version: ${{ matrix.php }}
install-path: "./bin"
pm-version-major: "4"
pm-version-major: "5"
- name: Install Composer
run: curl -sS https://getcomposer.org/installer | php

View File

@ -22,7 +22,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": "^4.0",
"ext-pthreads": "^5.1",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",
@ -40,17 +40,17 @@
"pocketmine/bedrock-protocol": "~18.0.0+bedrock-1.19.50",
"pocketmine/binaryutils": "^0.2.1",
"pocketmine/callback-validator": "^1.0.2",
"pocketmine/classloader": "^0.2.0",
"pocketmine/classloader": "dev-pthreads-v5",
"pocketmine/color": "^0.3.0",
"pocketmine/errorhandler": "^0.6.0",
"pocketmine/locale-data": "~2.18.0",
"pocketmine/log": "^0.4.0",
"pocketmine/log-pthreads": "^0.4.0",
"pocketmine/log-pthreads": "dev-pthreads-v5",
"pocketmine/math": "^0.4.0",
"pocketmine/nbt": "^0.3.2",
"pocketmine/raklib": "^0.14.2",
"pocketmine/raklib-ipc": "^0.1.0",
"pocketmine/snooze": "^0.3.0",
"pocketmine/snooze": "dev-pthreads-v5",
"ramsey/uuid": "^4.1",
"symfony/filesystem": "^5.4"
},

91
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "19f6b653d5b36e04f2d799de5972e567",
"content-hash": "5725b919e65c02842812b1fed8a3d77c",
"packages": [
{
"name": "adhocore/json-comment",
@ -469,20 +469,20 @@
},
{
"name": "pocketmine/classloader",
"version": "0.2.0",
"version": "dev-pthreads-v5",
"source": {
"type": "git",
"url": "https://github.com/pmmp/ClassLoader.git",
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78"
"reference": "dc98186e947d8940b8f6f4dbb2837f7c961a4812"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/49ea303993efdfb39cd302e2156d50aa78209e78",
"reference": "49ea303993efdfb39cd302e2156d50aa78209e78",
"url": "https://api.github.com/repos/pmmp/ClassLoader/zipball/dc98186e947d8940b8f6f4dbb2837f7c961a4812",
"reference": "dc98186e947d8940b8f6f4dbb2837f7c961a4812",
"shasum": ""
},
"require": {
"ext-pthreads": "~3.2.0 || ^4.0",
"ext-pthreads": "^5.0",
"ext-reflection": "*",
"php": "^8.0"
},
@ -491,8 +491,8 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4",
"phpstan/phpstan": "1.9.4",
"phpstan/phpstan-strict-rules": "^1.0",
"phpunit/phpunit": "^9.5"
},
"type": "library",
@ -508,9 +508,9 @@
"description": "Ad-hoc autoloading components used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/ClassLoader/issues",
"source": "https://github.com/pmmp/ClassLoader/tree/0.2.0"
"source": "https://github.com/pmmp/ClassLoader/tree/pthreads-v5"
},
"time": "2021-11-01T20:17:27+00:00"
"time": "2023-01-20T18:50:37+00:00"
},
{
"name": "pocketmine/color",
@ -591,16 +591,16 @@
},
{
"name": "pocketmine/locale-data",
"version": "2.18.0",
"version": "2.18.3",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Language.git",
"reference": "0f50afc3d0fec29f769a62e93c71f8a0fb968f76"
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Language/zipball/0f50afc3d0fec29f769a62e93c71f8a0fb968f76",
"reference": "0f50afc3d0fec29f769a62e93c71f8a0fb968f76",
"url": "https://api.github.com/repos/pmmp/Language/zipball/da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
"reference": "da25bfe9ee4822a84feb9b7e620c56ad4000aed0",
"shasum": ""
},
"type": "library",
@ -608,9 +608,9 @@
"description": "Language resources used by PocketMine-MP",
"support": {
"issues": "https://github.com/pmmp/Language/issues",
"source": "https://github.com/pmmp/Language/tree/2.18.0"
"source": "https://github.com/pmmp/Language/tree/2.18.3"
},
"time": "2023-01-14T17:52:46+00:00"
"time": "2023-01-17T21:43:36+00:00"
},
{
"name": "pocketmine/log",
@ -655,21 +655,21 @@
},
{
"name": "pocketmine/log-pthreads",
"version": "0.4.0",
"version": "dev-pthreads-v5",
"source": {
"type": "git",
"url": "https://github.com/pmmp/LogPthreads.git",
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd"
"reference": "83a66c9b8c39531b97a3b08c0ea97db967a3c60a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/61f709e8cf36bcc24e4efe02acded680a1ce23cd",
"reference": "61f709e8cf36bcc24e4efe02acded680a1ce23cd",
"url": "https://api.github.com/repos/pmmp/LogPthreads/zipball/83a66c9b8c39531b97a3b08c0ea97db967a3c60a",
"reference": "83a66c9b8c39531b97a3b08c0ea97db967a3c60a",
"shasum": ""
},
"require": {
"ext-pthreads": "~3.2.0 || ^4.0",
"php": "^7.4 || ^8.0",
"ext-pthreads": "^5.0",
"php": "^8.0",
"pocketmine/log": "^0.4.0"
},
"conflict": {
@ -677,8 +677,8 @@
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.88",
"phpstan/phpstan-strict-rules": "^0.12.4"
"phpstan/phpstan": "1.8.11",
"phpstan/phpstan-strict-rules": "^1.0"
},
"type": "library",
"autoload": {
@ -693,9 +693,9 @@
"description": "Logging components specialized for pthreads used by PocketMine-MP and related projects",
"support": {
"issues": "https://github.com/pmmp/LogPthreads/issues",
"source": "https://github.com/pmmp/LogPthreads/tree/0.4.0"
"source": "https://github.com/pmmp/LogPthreads/tree/pthreads-v5"
},
"time": "2021-11-01T21:42:09+00:00"
"time": "2023-01-20T19:45:45+00:00"
},
{
"name": "pocketmine/math",
@ -866,26 +866,26 @@
},
{
"name": "pocketmine/snooze",
"version": "0.3.1",
"version": "dev-pthreads-v5",
"source": {
"type": "git",
"url": "https://github.com/pmmp/Snooze.git",
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b"
"reference": "8589ddfa1672215dcc78d8edb7acb4cf67d59d5a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
"reference": "0ac8fc2a781c419a1f64ebca4d5835028f59e29b",
"url": "https://api.github.com/repos/pmmp/Snooze/zipball/8589ddfa1672215dcc78d8edb7acb4cf67d59d5a",
"reference": "8589ddfa1672215dcc78d8edb7acb4cf67d59d5a",
"shasum": ""
},
"require": {
"ext-pthreads": "~3.2.0 || ^4.0",
"php-64bit": "^7.3 || ^8.0"
"ext-pthreads": "^5.0",
"php-64bit": "^8.0"
},
"require-dev": {
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "0.12.99",
"phpstan/phpstan-strict-rules": "^0.12.4"
"phpstan/phpstan": "1.9.14",
"phpstan/phpstan-strict-rules": "^1.0"
},
"type": "library",
"autoload": {
@ -900,9 +900,9 @@
"description": "Thread notification management library for code using the pthreads extension",
"support": {
"issues": "https://github.com/pmmp/Snooze/issues",
"source": "https://github.com/pmmp/Snooze/tree/0.3.1"
"source": "https://github.com/pmmp/Snooze/tree/pthreads-v5"
},
"time": "2021-11-01T20:50:08+00:00"
"time": "2023-01-20T18:19:39+00:00"
},
{
"name": "ramsey/collection",
@ -1610,16 +1610,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.2",
"version": "v4.15.3",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"shasum": ""
},
"require": {
@ -1660,9 +1660,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3"
},
"time": "2022-11-12T15:38:23+00:00"
"time": "2023-01-16T22:05:37+00:00"
},
{
"name": "phar-io/manifest",
@ -3374,7 +3374,10 @@
"stability-flags": {
"pocketmine/bedrock-block-upgrade-schema": 20,
"pocketmine/bedrock-data": 20,
"pocketmine/bedrock-item-upgrade-schema": 20
"pocketmine/bedrock-item-upgrade-schema": 20,
"pocketmine/classloader": 20,
"pocketmine/log-pthreads": 20,
"pocketmine/snooze": 20
},
"prefer-stable": false,
"prefer-lowest": false,
@ -3396,7 +3399,7 @@
"ext-openssl": "*",
"ext-pcre": "*",
"ext-phar": "*",
"ext-pthreads": "^4.0",
"ext-pthreads": "^5.1",
"ext-reflection": "*",
"ext-simplexml": "*",
"ext-sockets": "*",

View File

@ -122,8 +122,8 @@ namespace pocketmine {
if(substr_count($pthreads_version, ".") < 2){
$pthreads_version = "0.$pthreads_version";
}
if(version_compare($pthreads_version, "4.0.0") < 0 || version_compare($pthreads_version, "5.0.0") >= 0){
$messages[] = "pthreads ^4.0.0 is required, while you have $pthreads_version.";
if(version_compare($pthreads_version, "5.1.0") < 0 || version_compare($pthreads_version, "6.0.0") >= 0){
$messages[] = "pthreads ^5.0.0 is required, while you have $pthreads_version.";
}
}

View File

@ -46,13 +46,17 @@ if($socket === false){
throw new \RuntimeException("Failed to connect to server process ($errCode): $errMessage");
}
$channel = new \Threaded();
/** @phpstan-var \ThreadedArray<int, string> $channel */
$channel = new \ThreadedArray();
$thread = new class($channel) extends \Thread{
/**
* @phpstan-param \ThreadedArray<int, string> $channel
*/
public function __construct(
private \Threaded $channel,
private \ThreadedArray $channel,
){}
public function run(){
public function run() : void{
require dirname(__DIR__, 2) . '/vendor/autoload.php';
$channel = $this->channel;
@ -75,7 +79,6 @@ while(!feof($socket)){
if(count($channel) === 0){
$channel->wait(1_000_000);
}
/** @var string|null $line */
$line = $channel->shift();
return $line;
});

View File

@ -33,6 +33,7 @@ use pocketmine\network\mcpe\protocol\serializer\PacketSerializerContext;
use pocketmine\network\mcpe\protocol\types\ChunkPosition;
use pocketmine\network\mcpe\serializer\ChunkSerializer;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\world\format\Chunk;
use pocketmine\world\format\io\FastChunkSerializer;
@ -43,14 +44,15 @@ class ChunkRequestTask extends AsyncTask{
protected string $chunk;
protected int $chunkX;
protected int $chunkZ;
protected Compressor $compressor;
/** @phpstan-var NonThreadSafeValue<Compressor> */
protected NonThreadSafeValue $compressor;
private string $tiles;
/**
* @phpstan-param (\Closure() : void)|null $onError
*/
public function __construct(int $chunkX, int $chunkZ, Chunk $chunk, CompressBatchPromise $promise, Compressor $compressor, ?\Closure $onError = null){
$this->compressor = $compressor;
$this->compressor = new NonThreadSafeValue($compressor);
$this->chunk = FastChunkSerializer::serializeTerrain($chunk);
$this->chunkX = $chunkX;
@ -66,7 +68,7 @@ class ChunkRequestTask extends AsyncTask{
$subCount = ChunkSerializer::getSubChunkCount($chunk);
$encoderContext = new PacketSerializerContext(GlobalItemTypeDictionary::getInstance()->getDictionary());
$payload = ChunkSerializer::serializeFullChunk($chunk, RuntimeBlockMapping::getInstance(), $encoderContext, $this->tiles);
$this->setResult($this->compressor->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
$this->setResult($this->compressor->deserialize()->compress(PacketBatch::fromPackets($encoderContext, LevelChunkPacket::create(new ChunkPosition($this->chunkX, $this->chunkZ), $subCount, false, null, $payload))->getBuffer()));
}
public function onError() : void{

View File

@ -30,6 +30,7 @@ use pocketmine\network\mcpe\JwtUtils;
use pocketmine\network\mcpe\protocol\types\login\JwtChainLinkBody;
use pocketmine\network\mcpe\protocol\types\login\JwtHeader;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
use function base64_decode;
use function igbinary_serialize;
use function igbinary_unserialize;
@ -49,8 +50,10 @@ class ProcessLoginTask extends AsyncTask{
* Whether the keychain signatures were validated correctly. This will be set to an error message if any link in the
* keychain is invalid for whatever reason (bad signature, not in nbf-exp window, etc). If this is non-null, the
* keychain might have been tampered with. The player will always be disconnected if this is non-null.
*
* @phpstan-var NonThreadSafeValue<Translatable>|string|null
*/
private Translatable|string|null $error = "Unknown";
private NonThreadSafeValue|string|null $error = "Unknown";
/**
* Whether the player is logged into Xbox Live. This is true if any link in the keychain is signed with the Mojang
* root public key.
@ -77,7 +80,8 @@ class ProcessLoginTask extends AsyncTask{
$this->clientPublicKey = $this->validateChain();
$this->error = null;
}catch(VerifyLoginException $e){
$this->error = $e->getDisconnectMessage();
$disconnectMessage = $e->getDisconnectMessage();
$this->error = $disconnectMessage instanceof Translatable ? new NonThreadSafeValue($disconnectMessage) : $disconnectMessage;
}
}
@ -195,6 +199,6 @@ class ProcessLoginTask extends AsyncTask{
* @phpstan-var \Closure(bool, bool, Translatable|string|null, ?string) : void $callback
*/
$callback = $this->fetchLocal(self::TLS_KEY_ON_COMPLETION);
$callback($this->authenticated, $this->authRequired, $this->error, $this->clientPublicKey);
$callback($this->authenticated, $this->authRequired, $this->error instanceof NonThreadSafeValue ? $this->error->deserialize() : $this->error, $this->clientPublicKey);
}
}

View File

@ -24,21 +24,26 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\compression;
use pocketmine\scheduler\AsyncTask;
use pocketmine\thread\NonThreadSafeValue;
class CompressBatchTask extends AsyncTask{
private const TLS_KEY_PROMISE = "promise";
/** @phpstan-var NonThreadSafeValue<Compressor> */
private NonThreadSafeValue $compressor;
public function __construct(
private string $data,
CompressBatchPromise $promise,
private Compressor $compressor
Compressor $compressor
){
$this->compressor = new NonThreadSafeValue($compressor);
$this->storeLocal(self::TLS_KEY_PROMISE, $promise);
}
public function onRun() : void{
$this->setResult($this->compressor->compress($this->data));
$this->setResult($this->compressor->deserialize()->compress($this->data));
}
public function onCompletion() : void{

View File

@ -26,7 +26,10 @@ namespace pocketmine\network\mcpe\raklib;
use raklib\server\ipc\InterThreadChannelReader;
final class PthreadsChannelReader implements InterThreadChannelReader{
public function __construct(private \Threaded $buffer){}
/**
* @phpstan-param \ThreadedArray<int, string> $buffer
*/
public function __construct(private \ThreadedArray $buffer){}
public function read() : ?string{
return $this->buffer->shift();

View File

@ -26,7 +26,10 @@ namespace pocketmine\network\mcpe\raklib;
use raklib\server\ipc\InterThreadChannelWriter;
final class PthreadsChannelWriter implements InterThreadChannelWriter{
public function __construct(private \Threaded $buffer){}
/**
* @phpstan-param \ThreadedArray<int, string> $buffer
*/
public function __construct(private \ThreadedArray $buffer){}
public function write(string $str) : void{
$this->buffer[] = $str;

View File

@ -85,8 +85,10 @@ class RakLibInterface implements ServerEventListener, AdvancedNetworkInterface{
$this->sleeper = new SleeperNotifier();
$mainToThreadBuffer = new \Threaded();
$threadToMainBuffer = new \Threaded();
/** @phpstan-var \ThreadedArray<int, string> $mainToThreadBuffer */
$mainToThreadBuffer = new \ThreadedArray();
/** @phpstan-var \ThreadedArray<int, string> $threadToMainBuffer */
$threadToMainBuffer = new \ThreadedArray();
$this->rakLib = new RakLibServer(
$this->server->getLogger(),

View File

@ -24,6 +24,7 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe\raklib;
use pocketmine\snooze\SleeperNotifier;
use pocketmine\thread\NonThreadSafeValue;
use pocketmine\thread\Thread;
use raklib\generic\Socket;
use raklib\generic\SocketException;
@ -43,19 +44,27 @@ class RakLibServer extends Thread{
protected bool $cleanShutdown = false;
protected bool $ready = false;
protected string $mainPath;
public ?RakLibThreadCrashInfo $crashInfo = null;
/** @phpstan-var NonThreadSafeValue<RakLibThreadCrashInfo>|null */
public ?NonThreadSafeValue $crashInfo = null;
/** @phpstan-var NonThreadSafeValue<InternetAddress> */
protected NonThreadSafeValue $address;
/**
* @phpstan-param \ThreadedArray<int, string> $mainToThreadBuffer
* @phpstan-param \ThreadedArray<int, string> $threadToMainBuffer
*/
public function __construct(
protected \ThreadedLogger $logger,
protected \Threaded $mainToThreadBuffer,
protected \Threaded $threadToMainBuffer,
protected InternetAddress $address,
protected \ThreadedArray $mainToThreadBuffer,
protected \ThreadedArray $threadToMainBuffer,
InternetAddress $address,
protected int $serverId,
protected int $maxMtuSize,
protected int $protocolVersion,
protected SleeperNotifier $mainThreadNotifier
){
$this->mainPath = \pocketmine\PATH;
$this->address = new NonThreadSafeValue($address);
}
/**
@ -75,12 +84,12 @@ class RakLibServer extends Thread{
}
public function getCrashInfo() : ?RakLibThreadCrashInfo{
return $this->crashInfo;
return $this->crashInfo?->deserialize();
}
private function setCrashInfo(RakLibThreadCrashInfo $info) : void{
$this->synchronized(function(RakLibThreadCrashInfo $info) : void{
$this->crashInfo = $info;
$this->crashInfo = new NonThreadSafeValue($info);
$this->notify();
}, $info);
}
@ -91,7 +100,7 @@ class RakLibServer extends Thread{
while(!$this->ready && $this->crashInfo === null){
$this->wait();
}
$crashInfo = $this->crashInfo;
$crashInfo = $this->crashInfo?->deserialize();
if($crashInfo !== null){
if($crashInfo->getClass() === SocketException::class){
throw new SocketException($crashInfo->getMessage());
@ -110,7 +119,7 @@ class RakLibServer extends Thread{
register_shutdown_function([$this, "shutdownHandler"]);
try{
$socket = new Socket($this->address);
$socket = new Socket($this->address->deserialize());
}catch(SocketException $e){
$this->setCrashInfo(RakLibThreadCrashInfo::fromThrowable($e));
return;

View File

@ -27,8 +27,11 @@ use pocketmine\snooze\SleeperNotifier;
use raklib\server\ipc\InterThreadChannelWriter;
final class SnoozeAwarePthreadsChannelWriter implements InterThreadChannelWriter{
/**
* @phpstan-param \ThreadedArray<int, string> $buffer
*/
public function __construct(
private \Threaded $buffer,
private \ThreadedArray $buffer,
private SleeperNotifier $notifier
){}

View File

@ -158,7 +158,7 @@ class AsyncPool{
throw new \InvalidArgumentException("Cannot submit the same AsyncTask instance more than once");
}
$task->progressUpdates = new \Threaded();
$task->progressUpdates = new \ThreadedArray();
$task->setSubmitted();
$this->getWorker($worker)->stack($task);

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\thread\NonThreadSafeValue;
use function igbinary_serialize;
use function igbinary_unserialize;
use function is_null;
use function is_scalar;
use function is_string;
use function spl_object_id;
/**
@ -51,15 +51,10 @@ use function spl_object_id;
* thread, e.g. during {@link AsyncTask::onCompletion()} or {@link AsyncTask::onProgressUpdate()}. This means that
* whatever you do in onRun() must be able to work without the Server instance.
*
* WARNING: Any non-Threaded objects WILL BE SERIALIZED when assigned to members of AsyncTasks or other Threaded object.
* If later accessed from said Threaded object, you will be operating on a COPY OF THE OBJECT, NOT THE ORIGINAL OBJECT.
* If you want to store non-serializable objects to access when the task completes, store them using
* If you want to store non-thread-safe objects to access when the task completes, store them using
* {@link AsyncTask::storeLocal}.
*
* WARNING: Arrays are converted to Volatile objects when assigned as members of Threaded objects.
* Keep this in mind when using arrays stored as members of your AsyncTask.
*/
abstract class AsyncTask extends \Threaded{
abstract class AsyncTask extends \ThreadedRunnable{
/**
* @var \ArrayObject|mixed[]|null object hash => mixed data
* @phpstan-var \ArrayObject<int, array<string, mixed>>|null
@ -71,10 +66,11 @@ abstract class AsyncTask extends \Threaded{
/** @var AsyncWorker|null $worker */
public $worker = null;
public \Threaded $progressUpdates;
/** @phpstan-var \ThreadedArray<int, string> */
public \ThreadedArray $progressUpdates;
private string|int|bool|null|float $result = null;
private bool $serialized = false;
/** @phpstan-var NonThreadSafeValue<mixed>|string|int|bool|float|null */
private NonThreadSafeValue|string|int|bool|null|float $result = null;
private bool $cancelRun = false;
private bool $submitted = false;
@ -117,15 +113,14 @@ abstract class AsyncTask extends \Threaded{
* @return mixed
*/
public function getResult(){
if($this->serialized){
if(!is_string($this->result)) throw new AssumptionFailedError("Result expected to be a serialized string");
return igbinary_unserialize($this->result);
if($this->result instanceof NonThreadSafeValue){
return $this->result->deserialize();
}
return $this->result;
}
public function setResult(mixed $result) : void{
$this->result = ($this->serialized = !is_scalar($result)) ? igbinary_serialize($result) : $result;
$this->result = is_scalar($result) || is_null($result) ? $result : new NonThreadSafeValue($result);
}
public function cancelRun() : void{
@ -164,15 +159,14 @@ abstract class AsyncTask extends \Threaded{
* @param mixed $progress A value that can be safely serialize()'ed.
*/
public function publishProgress(mixed $progress) : void{
$this->progressUpdates[] = igbinary_serialize($progress);
$this->progressUpdates[] = igbinary_serialize($progress) ?? throw new \InvalidArgumentException("Progress must be serializable");
}
/**
* @internal Only call from AsyncPool.php on the main thread
*/
public function checkProgressUpdates() : void{
while($this->progressUpdates->count() !== 0){
$progress = $this->progressUpdates->shift();
while(($progress = $this->progressUpdates->shift()) !== null){
$this->onProgressUpdate(igbinary_unserialize($progress));
}
}

View File

@ -28,8 +28,11 @@ use pocketmine\Server;
use function error_reporting;
trait CommonThreadPartsTrait{
/** @var \Threaded|\ClassLoader[]|null */
private ?\Threaded $classLoaders = null;
/**
* @var \ThreadedArray|\ClassLoader[]|null
* @phpstan-var \ThreadedArray<int, \ClassLoader>|null
*/
private ?\ThreadedArray $classLoaders = null;
protected ?string $composerAutoloaderPath = null;
protected bool $isKilled = false;
@ -52,7 +55,7 @@ trait CommonThreadPartsTrait{
}
if($this->classLoaders === null){
$this->classLoaders = new \Threaded();
$this->classLoaders = new \ThreadedArray();
}else{
foreach($this->classLoaders as $k => $autoloader){
unset($this->classLoaders[$k]);

View File

@ -0,0 +1,57 @@
<?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 function get_debug_type;
use function igbinary_serialize;
use function igbinary_unserialize;
/**
* This class automatically serializes values which can't be shared between threads.
* This class does NOT enable sharing the variable between threads. Each call to deserialize() will return a new copy
* of the variable.
*
* @phpstan-template TValue
*/
final class NonThreadSafeValue extends \ThreadedBase{
private string $variable;
/**
* @phpstan-param TValue $variable
*/
public function __construct(
mixed $variable
){
$this->variable = igbinary_serialize($variable) ?? throw new \InvalidArgumentException("Cannot serialize variable of type " . get_debug_type($variable));
}
/**
* Returns a deserialized copy of the original variable.
*
* @phpstan-return TValue
*/
public function deserialize() : mixed{
return igbinary_unserialize($this->variable);
}
}

View File

@ -25,7 +25,7 @@ namespace pocketmine\thread;
use function spl_object_id;
class ThreadManager extends \Volatile{
class ThreadManager extends \ThreadedBase{
private static ?self $instance = null;
@ -40,12 +40,19 @@ class ThreadManager extends \Volatile{
return self::$instance;
}
/** @phpstan-var \ThreadedArray<int, Thread|Worker> */
private \ThreadedArray $threads;
private function __construct(){
$this->threads = new \ThreadedArray();
}
public function add(Worker|Thread $thread) : void{
$this[spl_object_id($thread)] = $thread;
$this->threads[spl_object_id($thread)] = $thread;
}
public function remove(Worker|Thread $thread) : void{
unset($this[spl_object_id($thread)]);
unset($this->threads[spl_object_id($thread)]);
}
/**
@ -56,7 +63,7 @@ class ThreadManager extends \Volatile{
/**
* @var Worker|Thread $thread
*/
foreach($this as $key => $thread){
foreach($this->threads as $key => $thread){
$array[$key] = $thread;
}

View File

@ -194,6 +194,9 @@ class MainLogger extends \AttachableThreadedLogger implements \BufferedLogger{
Terminal::writeLine($message);
$this->logWriterThread->write($time->format("Y-m-d") . " " . TextFormat::clean($message) . PHP_EOL);
/**
* @var \ThreadedLoggerAttachment $attachment
*/
foreach($this->attachments as $attachment){
$attachment->log($level, $message);
}

View File

@ -30,14 +30,15 @@ use function is_resource;
use function touch;
final class MainLoggerThread extends \Thread{
private \Threaded $buffer;
/** @phpstan-var \ThreadedArray<int, string> */
private \ThreadedArray $buffer;
private bool $syncFlush = false;
private bool $shutdown = false;
public function __construct(
private string $logFile
){
$this->buffer = new \Threaded();
$this->buffer = new \ThreadedArray();
touch($this->logFile);
}
@ -72,9 +73,7 @@ final class MainLoggerThread extends \Thread{
* @param resource $logResource
*/
private function writeLogStream($logResource) : void{
while($this->buffer->count() > 0){
/** @var string $chunk */
$chunk = $this->buffer->shift();
while(($chunk = $this->buffer->shift()) !== null){
fwrite($logResource, $chunk);
}

View File

@ -46,9 +46,9 @@ parameters:
path: ../../../src/plugin/PluginManager.php
-
message: "#^Offset \\(int\\|string\\) on array\\<pocketmine\\\\plugin\\\\Plugin\\> in isset\\(\\) always exists and is not nullable\\.$#"
message: "#^Parameter \\#1 \\$work of method Worker\\:\\:stack\\(\\) expects Threaded, pocketmine\\\\scheduler\\\\AsyncTask given\\.$#"
count: 1
path: ../../../src/plugin/PluginManager.php
path: ../../../src/scheduler/AsyncPool.php
-
message: "#^Static property pocketmine\\\\scheduler\\\\AsyncTask\\:\\:\\$threadLocalStorage \\(ArrayObject\\<int, array\\<string, mixed\\>\\>\\|null\\) does not accept non\\-empty\\-array\\<int, non\\-empty\\-array\\<string, mixed\\>\\>\\|ArrayObject\\<int, array\\<string, mixed\\>\\>\\.$#"
@ -56,12 +56,12 @@ parameters:
path: ../../../src/scheduler/AsyncTask.php
-
message: "#^Property pocketmine\\\\thread\\\\Thread\\:\\:\\$classLoaders \\(\\(iterable\\<ClassLoader\\>&Threaded\\)\\|null\\) does not accept array\\<int, ClassLoader\\>\\|\\(iterable\\<ClassLoader\\>&Threaded\\)\\.$#"
message: "#^Property pocketmine\\\\thread\\\\Thread\\:\\:\\$classLoaders \\(ThreadedArray\\<int, ClassLoader\\>\\|null\\) does not accept array\\{ClassLoader\\}\\|ThreadedArray\\<int, ClassLoader\\>\\.$#"
count: 1
path: ../../../src/thread/Thread.php
-
message: "#^Property pocketmine\\\\thread\\\\Worker\\:\\:\\$classLoaders \\(\\(iterable\\<ClassLoader\\>&Threaded\\)\\|null\\) does not accept array\\<int, ClassLoader\\>\\|\\(iterable\\<ClassLoader\\>&Threaded\\)\\.$#"
message: "#^Property pocketmine\\\\thread\\\\Worker\\:\\:\\$classLoaders \\(ThreadedArray\\<int, ClassLoader\\>\\|null\\) does not accept array\\{ClassLoader\\}\\|ThreadedArray\\<int, ClassLoader\\>\\.$#"
count: 1
path: ../../../src/thread/Worker.php

View File

@ -1,6 +1,33 @@
<?php
/**
* @implements \Traversable<array-key, mixed>
* @implements \IteratorAggregate<array-key, mixed>
*/
class Threaded implements \Traversable{}
abstract class ThreadedBase implements \IteratorAggregate{
/**
* @template TReturn
* @param \Closure() : TReturn $function
* @param mixed ...$args
* @return TReturn
*/
public function synchronized(\Closure $function, mixed ...$args) : mixed{}
}
/**
* @template TKey of array-key
* @template TValue
* @implements ArrayAccess<TKey, TValue>
*/
final class ThreadedArray extends ThreadedBase implements Countable, ArrayAccess{
/**
* @return TValue|null
*/
public function pop() : mixed{}
/**
* @return TValue|null
*/
public function shift() : mixed{}
}