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.
This commit is contained in:
Dylan K. Taylor 2025-08-06 15:48:29 +01:00
parent a83c62a4a2
commit 89d18f929f
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
2 changed files with 10 additions and 8 deletions

View File

@ -68,9 +68,10 @@ class RakLibServer extends Thread{
public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{ public function startAndWait(int $options = NativeThread::INHERIT_NONE) : void{
$this->start($options); $this->start($options);
$this->synchronized(function() : void{ $this->synchronized(function() : void{
while(!$this->ready && $this->getCrashInfo() === null){ while(!$this->ready && !$this->isTerminated()){
$this->wait(); $this->wait();
} }
});
$crashInfo = $this->getCrashInfo(); $crashInfo = $this->getCrashInfo();
if($crashInfo !== null){ if($crashInfo !== null){
if($crashInfo->getType() === SocketException::class){ if($crashInfo->getType() === SocketException::class){
@ -78,7 +79,6 @@ class RakLibServer extends Thread{
} }
throw new ThreadCrashException("RakLib failed to start", $crashInfo); throw new ThreadCrashException("RakLib failed to start", $crashInfo);
} }
});
} }
protected function onRun() : void{ protected function onRun() : void{

View File

@ -100,6 +100,8 @@ trait CommonThreadPartsTrait{
//*before* the shutdown handler is invoked, so we might land here before the crash info has been set. //*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, //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. //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()){ if($this->isTerminated() && !$this->isJoined()){
$this->join(); $this->join();
} }