Lines
92.50%
37 / 40
Methods
87.50%
14 / 16
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| isLive | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 2 | ||
| isSpawning | 0.00% | 0 / 1 | 0.00% | 0 / 1 | 2 | ||
| isStopping | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| getOutboxStatus | 0.00% | 0 / 2 | 0.00% | 0 / 1 | 6 | ||
| beginSpawn | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| markSpawnFailed | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| recordSpawnedHandle | 100.00% | 7 / 7 | 100.00% | 1 / 1 | 2 | ||
| markGone | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| beginStopping | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 1 | ||
| recordScheduledRestart | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 1 | ||
| markQuarantined | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| stop | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 2 | ||
| cancelReadyDeadline | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| moveTo | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 4 | ||
| cancelRestart | 100.00% | 3 / 3 | 100.00% | 1 / 1 | 1 | ||
| 13 | final class WorkerSlotState | |
| 14 | { | |
| 15 | public private(set) WorkerPhase $phase = WorkerPhase::Stopped; | |
| 16 | ||
| 17 | public private(set) ?WorkerHandle $handle = null; | |
| 18 | ||
| 19 | private ?TimerHandle $readyDeadline = null; | |
| 20 | ||
| 21 | private ?TimerHandle $restartTimer = null; | |
| 22 | ||
| 23 | public private(set) ?Instant $restartDueAt = null; | |
| 24 | ||
| 25 | public private(set) int $restarts = 0; | |
| 26 | ||
| 27 | public private(set) int $quarantines = 0; | |
| 28 | ||
| 29 | private OutboxStatus $retiredOutboxes; | |
| 30 | ||
| 31 | public function __construct( | |
| 32 | public readonly WorkerSlot $slot, | |
| 33 | public readonly WorkerPoolListener $listener, | |
| 34 | ) { | |
| 35 | $this->retiredOutboxes = OutboxStatus::zero(); | |
| 36 | } | |
| 37 | ||
| 38 | public function isLive(WorkerHandle $handle): bool | |
| 39 | { | |
| 40 | return $this->phase === WorkerPhase::Live && $this->handle === $handle; | |
| 41 | } | |
| 42 | ||
| 43 | public function isSpawning(): bool | |
| 44 | { | |
| 45 | return $this->phase === WorkerPhase::Spawning; | |
| 46 | } | |
| 47 | ||
| 48 | public function isStopping(): bool | |
| 49 | { | |
| 50 | return $this->phase === WorkerPhase::Stopping; | |
| 51 | } | |
| 52 | ||
| 53 | public function getOutboxStatus(): OutboxStatus | |
| 54 | { | |
| 55 | $total = $this->retiredOutboxes->plus($this->handle?->getOutboxStatus() ?? OutboxStatus::zero()); | |
| 56 | ||
| 57 | return $this->phase === WorkerPhase::Live ? $total : $total->withoutQueued(); | |
| 58 | } | |
| 59 | ||
| 60 | public function beginSpawn(): void | |
| 61 | { | |
| 62 | $this->moveTo(WorkerPhase::Spawning); | |
| 63 | $this->cancelRestart(); | |
| 64 | } | |
| 65 | ||
| 66 | public function markSpawnFailed(): void | |
| 67 | { | |
| 68 | $this->moveTo(WorkerPhase::Stopped, from: WorkerPhase::Spawning); | |
| 69 | } | |
| 70 | ||
| 71 | public function recordSpawnedHandle(WorkerHandle $handle, ?TimerHandle $readyDeadline): ?WorkerHandle | |
| 72 | { | |
| 73 | $this->moveTo(WorkerPhase::Live); | |
| 74 | ||
| 75 | $previous = $this->handle; | |
| 76 | ||
| 77 | if ($previous !== null) { | |
| 78 | $this->retiredOutboxes = $this->retiredOutboxes->plus($previous->getOutboxStatus()->withoutQueued()); | |
| 79 | } | |
| 80 | ||
| 81 | $this->handle = $handle; | |
| 82 | $this->readyDeadline = $readyDeadline; | |
| 83 | ||
| 84 | return $previous; | |
| 85 | } | |
| 86 | ||
| 87 | public function markGone(): void | |
| 88 | { | |
| 89 | $this->moveTo(WorkerPhase::Stopped, from: WorkerPhase::Live); | |
| 90 | $this->cancelReadyDeadline(); | |
| 91 | } | |
| 92 | ||
| 93 | public function beginStopping(): WorkerHandle | |
| 94 | { | |
| 95 | $this->moveTo(WorkerPhase::Stopping); | |
| 96 | $this->cancelReadyDeadline(); | |
| 97 | ++$this->restarts; | |
| 98 | ||
| 99 | return $this->handle ?? throw new LogicException(\sprintf('Worker %d is stopping without a process.', $this->slot->workerId->value)); | |
| 100 | } | |
| 101 | ||
| 102 | public function recordScheduledRestart(TimerHandle $timer, Instant $dueAt): void | |
| 103 | { | |
| 104 | $this->moveTo(WorkerPhase::RestartScheduled); | |
| 105 | $this->restartTimer = $timer; | |
| 106 | $this->restartDueAt = $dueAt; | |
| 107 | ++$this->restarts; | |
| 108 | } | |
| 109 | ||
| 110 | public function markQuarantined(): void | |
| 111 | { | |
| 112 | $this->moveTo(WorkerPhase::Quarantined); | |
| 113 | ++$this->quarantines; | |
| 114 | } | |
| 115 | ||
| 116 | public function stop(): void | |
| 117 | { | |
| 118 | if ($this->phase !== WorkerPhase::Stopped) { | |
| 119 | $this->moveTo(WorkerPhase::Stopped); | |
| 120 | } | |
| 121 | ||
| 122 | $this->cancelRestart(); | |
| 123 | $this->cancelReadyDeadline(); | |
| 124 | } | |
| 125 | ||
| 126 | public function cancelReadyDeadline(): void | |
| 127 | { | |
| 128 | $this->readyDeadline?->cancel(); | |
| 129 | $this->readyDeadline = null; | |
| 130 | } | |
| 131 | ||
| 132 | private function moveTo(WorkerPhase $next, ?WorkerPhase $from = null): void | |
| 133 | { | |
| 134 | if (!$this->phase->canEnter($next) || ($from !== null && $this->phase !== $from)) { | |
| 135 | throw new LogicException(\sprintf('Worker %d cannot move from %s to %s.', $this->slot->workerId->value, $this->phase->value, $next->value)); | |
| 136 | } | |
| 137 | ||
| 138 | $this->phase = $next; | |
| 139 | } | |
| 140 | ||
| 141 | private function cancelRestart(): void | |
| 142 | { | |
| 143 | $this->restartTimer?->cancel(); | |
| 144 | $this->restartTimer = null; | |
| 145 | $this->restartDueAt = null; | |
| 146 | } | |
| 147 | } |