> */ private array $resolvers = []; private bool $activeExclusiveHandler = false; private bool $activeConcurrentHandler = false; private int $done = 0; public function getName() : string{ return "Async Event Concurrency Lock"; } public function getDescription() : string{ return "Test that exclusive lock on async event handlers works correctly"; } public function run() : void{ HandlerListManager::global()->unregisterAll(); $main = $this->getPlugin(); $pluginManager = $main->getServer()->getPluginManager(); $pluginManager->registerAsyncEvent( GrandchildAsyncEvent::class, function(GrandchildAsyncEvent $event) use ($main) : ?Promise{ if($this->activeExclusiveHandler){ $main->getLogger()->error("Concurrent handler can't run while exclusive handlers are waiting to complete"); $this->setResult(Test::RESULT_FAILED); return null; } $this->activeConcurrentHandler = true; $resolver = new PromiseResolver(); $this->resolvers[] = $resolver; $resolver->getPromise()->onCompletion( fn() => $this->complete($this->activeConcurrentHandler, "concurrent"), fn() => $main->getLogger()->error("Not expecting this to be rejected") ); return $resolver->getPromise(); }, EventPriority::NORMAL, $main, //non-exclusive - this must be completed before any exclusive handlers are run (or run after them) ); $pluginManager->registerAsyncEvent( GrandchildAsyncEvent::class, function(GrandchildAsyncEvent $event) use ($main) : ?Promise{ $main->getLogger()->info("Entering exclusive handler 1"); if($this->activeExclusiveHandler || $this->activeConcurrentHandler){ $main->getLogger()->error("Can't run multiple exclusive handlers at once"); $this->setResult(Test::RESULT_FAILED); return null; } $this->activeExclusiveHandler = true; $resolver = new PromiseResolver(); $this->resolvers[] = $resolver; $resolver->getPromise()->onCompletion( fn() => $this->complete($this->activeExclusiveHandler, "exclusive 1"), fn() => $main->getLogger()->error("Not expecting this to be rejected") ); return $resolver->getPromise(); }, EventPriority::NORMAL, $main, exclusiveCall: true ); $pluginManager->registerAsyncEvent( GrandchildAsyncEvent::class, function(GrandchildAsyncEvent $event) use ($main) : ?Promise{ $this->getPlugin()->getLogger()->info("Entering exclusive handler 2"); if($this->activeExclusiveHandler || $this->activeConcurrentHandler){ $main->getLogger()->error("Exclusive lock handlers must not run at the same time as any other handlers"); $this->setResult(Test::RESULT_FAILED); return null; } $this->activeExclusiveHandler = true; /** @phpstan-var PromiseResolver $resolver */ $resolver = new PromiseResolver(); $this->resolvers[] = $resolver; $resolver->getPromise()->onCompletion( function() use ($main) : void{ $main->getLogger()->info("Exiting exclusive handler asynchronously"); $this->complete($this->activeExclusiveHandler, "exclusive 2"); }, function() use ($main) : void{ $main->getLogger()->error("Not expecting this promise to be rejected"); $this->setResult(Test::RESULT_ERROR); } ); return $resolver->getPromise(); }, EventPriority::NORMAL, $main, exclusiveCall: true ); (new GrandchildAsyncEvent())->call(); } private function complete(bool &$flag, string $what) : void{ $this->getPlugin()->getLogger()->info("Completing $what"); $flag = false; if(++$this->done === 3){ $this->setResult(Test::RESULT_OK); } } public function tick() : void{ foreach($this->resolvers as $k => $resolver){ $resolver->resolve(null); //don't clear the array here - resolving this will trigger adding the next resolver unset($this->resolvers[$k]); } } }