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

LibreSign / libresign / 24160618980

08 Apr 2026 09:52PM UTC coverage: 56.325%. First build
24160618980

Pull #7483

github

web-flow
Merge 37610cb4b into 2c799643d
Pull Request #7483: refactor: replace ps-based process handling with process manager

172 of 309 new or added lines in 7 files covered. (55.66%)

10473 of 18594 relevant lines covered (56.32%)

6.61 hits per line

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

87.88
/lib/Service/Process/ProcessManager.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Service\Process;
10

11
use OCA\Libresign\AppInfo\Application;
12
use OCP\IAppConfig;
13
use Psr\Log\LoggerInterface;
14

15
class ProcessManager {
16
        private const APP_CONFIG_KEY = 'process_registry';
17
        private const APP_CONFIG_HINTS_KEY = 'process_registry_hints';
18

19
        private ProcessSignaler $signaler;
20
        private ListeningPidResolver $pidResolver;
21

22
        public function __construct(
23
                private IAppConfig $appConfig,
24
                private LoggerInterface $logger,
25
                ?ProcessSignaler $signaler = null,
26
                ?ListeningPidResolver $pidResolver = null,
27
        ) {
28
                $this->signaler = $signaler ?? new ProcessSignaler($logger);
83✔
29
                $this->pidResolver = $pidResolver ?? new ListeningPidResolver();
83✔
30
        }
31

32
        /**
33
         * @param array<string, scalar> $context
34
         */
35
        public function register(string $source, int $pid, array $context = []): void {
36
                if ($pid <= 0) {
9✔
37
                        return;
2✔
38
                }
39

40
                $registry = $this->getRegistry();
7✔
41
                $registry[$source][(string)$pid] = [
7✔
42
                        'pid' => $pid,
7✔
43
                        'context' => $context,
7✔
44
                        'createdAt' => time(),
7✔
45
                ];
7✔
46
                $this->saveRegistry($registry);
7✔
47
                $this->setSourceHint($source, $context);
7✔
48
        }
49

50
        public function unregister(string $source, int $pid): void {
51
                $registry = $this->getRegistry();
2✔
52
                unset($registry[$source][(string)$pid]);
2✔
53
                $this->saveRegistry($registry);
2✔
54
        }
55

56
        /**
57
         * @return array<int, array{pid: int, context: array<string, scalar>, createdAt: int}>
58
         */
59
        public function listRunning(string $source): array {
60
                $registry = $this->getRegistry();
10✔
61
                $entries = $registry[$source] ?? [];
10✔
62
                $running = [];
10✔
63
                $changed = false;
10✔
64

65
                foreach ($entries as $pidKey => $entry) {
10✔
66
                        $pid = (int)($entry['pid'] ?? 0);
7✔
67
                        if ($pid <= 0 || !$this->isRunning($pid)) {
7✔
68
                                unset($registry[$source][$pidKey]);
1✔
69
                                $changed = true;
1✔
70
                                continue;
1✔
71
                        }
72

73
                        $running[] = [
7✔
74
                                'pid' => $pid,
7✔
75
                                'context' => is_array($entry['context'] ?? null) ? $entry['context'] : [],
7✔
76
                                'createdAt' => (int)($entry['createdAt'] ?? 0),
7✔
77
                        ];
7✔
78
                }
79

80
                if ($changed) {
10✔
81
                        $this->saveRegistry($registry);
1✔
82
                }
83

84
                return $running;
10✔
85
        }
86

87
        public function countRunning(string $source): int {
88
                return count($this->listRunning($source));
3✔
89
        }
90

91
        /**
92
         * @param null|callable(array{pid: int, context: array<string, scalar>, createdAt: int}): bool $filter
93
         */
94
        public function findRunningPid(string $source, ?callable $filter = null): int {
95
                $entries = $this->listRunning($source);
5✔
96
                foreach ($entries as $entry) {
5✔
97
                        if ($filter !== null && !$filter($entry)) {
3✔
98
                                continue;
2✔
99
                        }
100
                        return $entry['pid'];
2✔
101
                }
102

103
                $this->hydrateFromFallback($source);
3✔
104

105
                foreach ($this->listRunning($source) as $entry) {
3✔
106
                        if ($filter !== null && !$filter($entry)) {
2✔
107
                                continue;
1✔
108
                        }
109
                        return $entry['pid'];
1✔
110
                }
111

112
                return 0;
2✔
113
        }
114

115
        public function stopAll(string $source, int $signal = SIGTERM): int {
116
                $stopped = 0;
2✔
117
                foreach ($this->listRunning($source) as $entry) {
2✔
118
                        if ($this->stopPid($entry['pid'], $signal)) {
2✔
119
                                $stopped++;
2✔
120
                        }
121
                        $this->unregister($source, $entry['pid']);
2✔
122
                }
123

124
                return $stopped;
2✔
125
        }
126

127
        public function stopPid(int $pid, int $signal = SIGTERM): bool {
NEW
128
                return $this->signaler->stopPid($pid, $signal);
×
129
        }
130

131
        /**
132
         * @param array<string, scalar> $context
133
         */
134
        public function setSourceHint(string $source, array $context): void {
135
                if ($source === '' || $context === []) {
7✔
136
                        return;
2✔
137
                }
138

139
                $hints = $this->getHints();
5✔
140
                $hints[$source] = $context;
5✔
141
                $this->saveHints($hints);
5✔
142
        }
143

144
        /**
145
         * @return int[]
146
         */
147
        public function findListeningPids(int $port): array {
148
                return $this->pidResolver->findListeningPids($port);
2✔
149
        }
150

151
        public function isRunning(int $pid): bool {
152
                return $this->signaler->isRunning($pid);
4✔
153
        }
154

155
        private function hydrateFromFallback(string $source): void {
156
                $hint = $this->getSourceHint($source);
3✔
157
                if (!is_array($hint) || $hint === []) {
3✔
158
                        return;
1✔
159
                }
160

161
                $port = $this->getPortFromHint($hint);
2✔
162
                if ($port <= 0) {
2✔
163
                        return;
1✔
164
                }
165

166
                try {
167
                        $pids = $this->findListeningPids($port);
1✔
NEW
168
                } catch (\RuntimeException $e) {
×
NEW
169
                        $this->logger->debug('Unable to hydrate process registry from fallback', [
×
NEW
170
                                'source' => $source,
×
NEW
171
                                'port' => $port,
×
NEW
172
                                'error' => $e->getMessage(),
×
NEW
173
                        ]);
×
NEW
174
                        return;
×
175
                }
176

177
                foreach ($pids as $pid) {
1✔
178
                        if ($pid <= 0) {
1✔
NEW
179
                                continue;
×
180
                        }
181
                        $this->register($source, $pid, $hint);
1✔
182
                }
183
        }
184

185
        /**
186
         * @param array<string, scalar> $hint
187
         */
188
        private function getPortFromHint(array $hint): int {
189
                if (isset($hint['port']) && is_numeric((string)$hint['port'])) {
2✔
190
                        $port = (int)$hint['port'];
1✔
191
                        if ($port > 0) {
1✔
192
                                return $port;
1✔
193
                        }
194
                }
195

196
                if (isset($hint['uri']) && is_string($hint['uri'])) {
1✔
NEW
197
                        return (int)(parse_url($hint['uri'], PHP_URL_PORT) ?? 0);
×
198
                }
199

200
                return 0;
1✔
201
        }
202

203
        /**
204
         * @return array<string, scalar>
205
         */
206
        private function getSourceHint(string $source): array {
207
                $hints = $this->getHints();
3✔
208
                $hint = $hints[$source] ?? [];
3✔
209
                return is_array($hint) ? $hint : [];
3✔
210
        }
211

212
        /**
213
         * @return array<string, array<string, array{pid: int, context: array<string, scalar>, createdAt: int}>>
214
         */
215
        private function getRegistry(): array {
216
                $raw = $this->appConfig->getValueString(Application::APP_ID, self::APP_CONFIG_KEY, '{}');
10✔
217
                $decoded = json_decode($raw, true);
10✔
218
                if (!is_array($decoded)) {
10✔
NEW
219
                        return [];
×
220
                }
221

222
                return $decoded;
10✔
223
        }
224

225
        /**
226
         * @param array<string, array<string, array{pid: int, context: array<string, scalar>, createdAt: int}>> $registry
227
         */
228
        private function saveRegistry(array $registry): void {
229
                $this->appConfig->setValueString(Application::APP_ID, self::APP_CONFIG_KEY, json_encode($registry));
7✔
230
        }
231

232
        /**
233
         * @return array<string, array<string, scalar>>
234
         */
235
        private function getHints(): array {
236
                $raw = $this->appConfig->getValueString(Application::APP_ID, self::APP_CONFIG_HINTS_KEY, '{}');
6✔
237
                $decoded = json_decode($raw, true);
6✔
238
                if (!is_array($decoded)) {
6✔
NEW
239
                        return [];
×
240
                }
241

242
                return $decoded;
6✔
243
        }
244

245
        /**
246
         * @param array<string, array<string, scalar>> $hints
247
         */
248
        private function saveHints(array $hints): void {
249
                $this->appConfig->setValueString(Application::APP_ID, self::APP_CONFIG_HINTS_KEY, json_encode($hints));
5✔
250
        }
251

252
}
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