From 89d18f929ffdd31d7da0cedb24e120bd33170355 Mon Sep 17 00:00:00 2001 From: "Dylan K. Taylor" Date: Wed, 6 Aug 2025 15:48:29 +0100 Subject: [PATCH] RakLibServer: fixed deadlock on thread crash synchronized block -> getCrashInfo -> join -> synchronized on the same context on the child thread -> deadlock instead we check for isTerminated and then get the crash info outside of the synchronized block. --- src/network/mcpe/raklib/RakLibServer.php | 16 ++++++++-------- src/thread/CommonThreadPartsTrait.php | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/network/mcpe/raklib/RakLibServer.php b/src/network/mcpe/raklib/RakLibServer.php index d5e825bee..f66734ee5 100644 --- a/src/network/mcpe/raklib/RakLibServer.php +++ b/src/network/mcpe/raklib/RakLibServer.php @@ -68,17 +68,17 @@ class RakLibServer extends Thread{ public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{ $this->start($options); $this->synchronized(function() : void{ - while(!$this->ready && $this->getCrashInfo() === null){ + while(!$this->ready && !$this->isTerminated()){ $this->wait(); } - $crashInfo = $this->getCrashInfo(); - if($crashInfo !== null){ - if($crashInfo->getType() === SocketException::class){ - throw new SocketException($crashInfo->getMessage()); - } - throw new ThreadCrashException("RakLib failed to start", $crashInfo); - } }); + $crashInfo = $this->getCrashInfo(); + if($crashInfo !== null){ + if($crashInfo->getType() === SocketException::class){ + throw new SocketException($crashInfo->getMessage()); + } + throw new ThreadCrashException("RakLib failed to start", $crashInfo); + } } protected function onRun() : void{ diff --git a/src/thread/CommonThreadPartsTrait.php b/src/thread/CommonThreadPartsTrait.php index de606a7b2..d7d51d40f 100644 --- a/src/thread/CommonThreadPartsTrait.php +++ b/src/thread/CommonThreadPartsTrait.php @@ -100,6 +100,8 @@ trait CommonThreadPartsTrait{ //*before* the shutdown handler is invoked, so we might land here before the crash info has been set. //In the future this should probably be fixed by running the shutdown handlers before setting isTerminated, //but this workaround should be good enough for now. + //WARNING: Do not call this inside a synchronized block on this thread's context. Because the shutdown handler + //runs in a synchronized block, this will result in a deadlock. if($this->isTerminated() && !$this->isJoined()){ $this->join(); }