• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orisai / scheduler / 24056471189

06 Apr 2026 11:33PM UTC coverage: 97.997% (-1.6%) from 99.561%
24056471189

push

github

mabar
Maintenance mode

308 of 318 new or added lines in 15 files covered. (96.86%)

33 existing lines in 4 files now uncovered.

2593 of 2646 relevant lines covered (98.0%)

61.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.11
/src/Command/RunCommand.php
1
<?php declare(strict_types = 1);
2

3
namespace Orisai\Scheduler\Command;
4

5
use Generator;
6
use Orisai\Clock\SystemClock;
7
use Orisai\Scheduler\Maintenance\MaintenanceManager;
8
use Orisai\Scheduler\Scheduler;
9
use Orisai\Scheduler\Status\JobResultState;
10
use Orisai\Scheduler\Status\JobSummary;
11
use Orisai\Scheduler\Status\RunSummary;
12
use Psr\Clock\ClockInterface;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use function assert;
17
use function function_exists;
18
use function json_encode;
19
use function pcntl_async_signals;
20
use function pcntl_signal;
21
use function pcntl_signal_get_handler;
22
use const JSON_PRETTY_PRINT;
23
use const JSON_THROW_ON_ERROR;
24
use const SIGINT;
25
use const SIGTERM;
26

27
final class RunCommand extends BaseRunCommand
28
{
29

30
        private const EXIT_MAINTENANCE = 2;
31

32
        private Scheduler $scheduler;
33

34
        private ?MaintenanceManager $maintenanceManager;
35

36
        public function __construct(
37
                Scheduler $scheduler,
38
                ?ClockInterface $clock = null,
39
                ?MaintenanceManager $maintenanceManager = null
40
        )
41
        {
42
                parent::__construct($clock ?? new SystemClock());
80✔
43
                $this->scheduler = $scheduler;
80✔
44
                $this->maintenanceManager = $maintenanceManager;
80✔
45
        }
46

47
        public static function getDefaultName(): string
48
        {
49
                return 'scheduler:run';
80✔
50
        }
51

52
        public static function getDefaultDescription(): string
53
        {
54
                return 'Run scheduler once';
80✔
55
        }
56

57
        protected function configure(): void
58
        {
59
                $this->addOption('json', null, InputOption::VALUE_NONE, 'Output in json format');
80✔
60
        }
61

62
        protected function execute(InputInterface $input, OutputInterface $output): int
63
        {
64
                $previousHandlers = $this->registerSignalHandlers();
80✔
65

66
                try {
67
                        $generator = $this->scheduler->runPromise();
80✔
68

69
                        $success = $input->getOption('json')
80✔
70
                                ? $this->renderJobsAsJson($output, $generator)
24✔
71
                                : $this->renderJobs($output, $generator);
74✔
72

73
                        $runSummary = $generator->getReturn();
80✔
74
                        assert($runSummary instanceof RunSummary);
80✔
75
                        if ($runSummary->isMaintenanceActive()) {
80✔
76
                                return self::EXIT_MAINTENANCE;
32✔
77
                        }
78

79
                        return $success ? self::SUCCESS : self::FAILURE;
48✔
80
                } finally {
81
                        $this->restoreSignalHandlers($previousHandlers);
80✔
82
                }
83
        }
84

85
        /**
86
         * @return array{callable|int, callable|int}|null
87
         */
88
        private function registerSignalHandlers(): ?array
89
        {
90
                if ($this->maintenanceManager === null) {
80✔
91
                        return null;
48✔
92
                }
93

94
                if (!function_exists('pcntl_async_signals')) {
32✔
NEW
95
                        return null;
×
96
                }
97

98
                pcntl_async_signals(true);
32✔
99

100
                $previousTermHandler = pcntl_signal_get_handler(SIGTERM);
32✔
101
                $previousIntHandler = pcntl_signal_get_handler(SIGINT);
32✔
102

103
                $maintenanceManager = $this->maintenanceManager;
32✔
104
                $shutdownRequested = false;
32✔
105
                $handler = static function () use ($maintenanceManager, &$shutdownRequested): void {
32✔
106
                        if ($shutdownRequested) {
8✔
107
                                // Second signal - force exit, tested via real subprocess in SignalHandlingTest
108
                                exit(1); // @codeCoverageIgnore
109
                        }
110

111
                        $shutdownRequested = true;
8✔
112
                        $maintenanceManager->requestShutdown();
8✔
113
                };
32✔
114

115
                pcntl_signal(SIGTERM, $handler);
32✔
116
                pcntl_signal(SIGINT, $handler);
32✔
117

118
                return [$previousTermHandler, $previousIntHandler];
32✔
119
        }
120

121
        /**
122
         * @param array{callable|int, callable|int}|null $previousHandlers
123
         */
124
        private function restoreSignalHandlers(?array $previousHandlers): void
125
        {
126
                if ($previousHandlers === null) {
80✔
127
                        return;
48✔
128
                }
129

130
                pcntl_signal(SIGTERM, $previousHandlers[0]);
32✔
131
                pcntl_signal(SIGINT, $previousHandlers[1]);
32✔
132
        }
133

134
        /**
135
         * @param Generator<int, JobSummary, void, RunSummary> $generator
136
         */
137
        private function renderJobsAsJson(OutputInterface $output, Generator $generator): bool
138
        {
139
                $summaries = [];
24✔
140
                $success = true;
24✔
141
                foreach ($generator as $jobSummary) {
24✔
142
                        if ($success && $jobSummary->getResult()->getState() === JobResultState::fail()) {
24✔
143
                                $success = false;
8✔
144
                        }
145

146
                        $summaries[] = $jobSummary->toArray();
24✔
147
                }
148

149
                $output->writeln(json_encode($summaries, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT));
24✔
150

151
                return $success;
24✔
152
        }
153

154
        /**
155
         * @param Generator<int, JobSummary, void, RunSummary> $generator
156
         */
157
        private function renderJobs(OutputInterface $output, Generator $generator): bool
158
        {
159
                $terminalWidth = $this->getTerminalWidth();
72✔
160

161
                $success = true;
72✔
162
                foreach ($generator as $jobSummary) {
72✔
163
                        if ($success && $jobSummary->getResult()->getState() === JobResultState::fail()) {
56✔
164
                                $success = false;
16✔
165
                        }
166

167
                        $this->renderJob($jobSummary, $terminalWidth, $output);
56✔
168
                }
169

170
                return $success;
72✔
171
        }
172

173
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc