Lines
93.33%
28 / 30
Methods
88.88%
8 / 9
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| trackStartup | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| startupCancellation | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| isStopping | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| awaitStopped | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| stopWithConfiguredGrace | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| stopAfterBrokerLoss | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| stop | 100.00% | 14 / 14 | 100.00% | 1 / 1 | 3 | ||
| stopAppsBefore | 71.42% | 5 / 7 | 0.00% | 0 / 1 | 3.21 | ||
| 20 | final class WorkerShutdown | |
| 21 | { | |
| 22 | public private(set) ?string $reason = null; | |
| 23 | ||
| 24 | /** @var DeferredFuture<null> */ | |
| 25 | private readonly DeferredFuture $stopped; | |
| 26 | ||
| 27 | private readonly DeferredCancellation $halt; | |
| 28 | ||
| 29 | /** @var Future<mixed>|null */ | |
| 30 | private ?Future $startup = null; | |
| 31 | ||
| 32 | public function __construct( | |
| 33 | private readonly AppLifecycle $apps, | |
| 34 | private readonly AppResources $resources, | |
| 35 | private readonly PendingRequests $pending, | |
| 36 | private readonly Deadlines $deadlines, | |
| 37 | private readonly LoggerInterface $logger, | |
| 38 | private readonly WorkerSettings $workerSettings, | |
| 39 | ) { | |
| 40 | $this->stopped = new DeferredFuture(); | |
| 41 | $this->halt = new DeferredCancellation(); | |
| 42 | } | |
| 43 | ||
| 44 | /** @param Future<mixed> $startup */ | |
| 45 | public function trackStartup(Future $startup): void | |
| 46 | { | |
| 47 | $this->startup = $startup; | |
| 48 | } | |
| 49 | ||
| 50 | public function startupCancellation(): Cancellation | |
| 51 | { | |
| 52 | return $this->halt->getCancellation(); | |
| 53 | } | |
| 54 | ||
| 55 | public function isStopping(): bool | |
| 56 | { | |
| 57 | return $this->reason !== null; | |
| 58 | } | |
| 59 | ||
| 60 | public function awaitStopped(): void | |
| 61 | { | |
| 62 | $this->stopped->getFuture()->await(); | |
| 63 | } | |
| 64 | ||
| 65 | public function stopWithConfiguredGrace(string $reason): void | |
| 66 | { | |
| 67 | $this->stop($reason, $this->workerSettings->shutdownGrace); | |
| 68 | } | |
| 69 | ||
| 70 | public function stopAfterBrokerLoss(string $reason): void | |
| 71 | { | |
| 72 | $this->pending->failAll('the broker is gone'); | |
| 73 | $this->stopWithConfiguredGrace($reason); | |
| 74 | } | |
| 75 | ||
| 76 | public function stop(string $reason, Duration $grace): void | |
| 77 | { | |
| 78 | if ($this->reason !== null) { | |
| 79 | return; | |
| 80 | } | |
| 81 | ||
| 82 | $this->reason = $reason; | |
| 83 | $this->apps->markStopping(); | |
| 84 | $this->halt->cancel(); | |
| 85 | $deadline = $this->deadlines->timeout($grace); | |
| 86 | ||
| 87 | async(function () use ($deadline, $grace): void { | |
| 88 | $inTime = $this->stopAppsBefore($deadline); | |
| 89 | $abandoned = $this->apps->abandonUnfinishedApps(); | |
| 90 | $this->resources->releaseAll(); | |
| 91 | ||
| 92 | if (!$inTime) { | |
| 93 | $this->logger->warning('Apps did not stop within the grace period', ['grace' => (string) $grace, 'apps' => $abandoned->toStrings()]); | |
| 94 | } | |
| 95 | ||
| 96 | $this->stopped->complete(); | |
| 97 | })->ignore(); | |
| 98 | } | |
| 99 | ||
| 100 | private function stopAppsBefore(Cancellation $deadline): bool | |
| 101 | { | |
| 102 | try { | |
| 103 | $this->apps->disposeAppsBefore($deadline); | |
| 104 | // Apps still in initialize() dispose themselves when it returns. | |
| 105 | $this->startup?->await($deadline); | |
| 106 | } catch (CancelledException) { | |
| 107 | return false; | |
| 108 | } catch (Throwable $e) { | |
| 109 | $this->logger->error('Worker failed while stopping its apps', ['exception' => $e]); | |
| 110 | } | |
| 111 | ||
| 112 | return true; | |
| 113 | } | |
| 114 | } |