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

LibreSign / libresign / 24157141556

08 Apr 2026 08:30PM UTC coverage: 56.373%. First build
24157141556

Pull #7483

github

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

92 of 145 new or added lines in 5 files covered. (63.45%)

10393 of 18436 relevant lines covered (56.37%)

6.65 hits per line

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

64.1
/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

18
        public function __construct(
19
                private IAppConfig $appConfig,
20
                private LoggerInterface $logger,
21
        ) {
22
        }
77✔
23

24
        /**
25
         * @param array<string, scalar> $context
26
         */
27
        public function register(string $source, int $pid, array $context = []): void {
28
                if ($pid <= 0) {
7✔
29
                        return;
1✔
30
                }
31

32
                $registry = $this->getRegistry();
6✔
33
                $registry[$source][(string)$pid] = [
6✔
34
                        'pid' => $pid,
6✔
35
                        'context' => $context,
6✔
36
                        'createdAt' => time(),
6✔
37
                ];
6✔
38
                $this->saveRegistry($registry);
6✔
39
        }
40

41
        public function unregister(string $source, int $pid): void {
42
                $registry = $this->getRegistry();
2✔
43
                unset($registry[$source][(string)$pid]);
2✔
44
                $this->saveRegistry($registry);
2✔
45
        }
46

47
        /**
48
         * @return array<int, array{pid: int, context: array<string, scalar>, createdAt: int}>
49
         */
50
        public function listRunning(string $source): array {
51
                $registry = $this->getRegistry();
7✔
52
                $entries = $registry[$source] ?? [];
7✔
53
                $running = [];
7✔
54
                $changed = false;
7✔
55

56
                foreach ($entries as $pidKey => $entry) {
7✔
57
                        $pid = (int)($entry['pid'] ?? 0);
6✔
58
                        if ($pid <= 0 || !$this->isRunning($pid)) {
6✔
59
                                unset($registry[$source][$pidKey]);
1✔
60
                                $changed = true;
1✔
61
                                continue;
1✔
62
                        }
63

64
                        $running[] = [
6✔
65
                                'pid' => $pid,
6✔
66
                                'context' => is_array($entry['context'] ?? null) ? $entry['context'] : [],
6✔
67
                                'createdAt' => (int)($entry['createdAt'] ?? 0),
6✔
68
                        ];
6✔
69
                }
70

71
                if ($changed) {
7✔
72
                        $this->saveRegistry($registry);
1✔
73
                }
74

75
                return $running;
7✔
76
        }
77

78
        public function countRunning(string $source): int {
79
                return count($this->listRunning($source));
2✔
80
        }
81

82
        /**
83
         * @param null|callable(array{pid: int, context: array<string, scalar>, createdAt: int}): bool $filter
84
         */
85
        public function findRunningPid(string $source, ?callable $filter = null): int {
86
                foreach ($this->listRunning($source) as $entry) {
3✔
87
                        if ($filter !== null && !$filter($entry)) {
3✔
88
                                continue;
2✔
89
                        }
90
                        return $entry['pid'];
2✔
91
                }
92

93
                return 0;
1✔
94
        }
95

96
        public function stopAll(string $source, int $signal = SIGTERM): int {
97
                $stopped = 0;
2✔
98
                foreach ($this->listRunning($source) as $entry) {
2✔
99
                        if ($this->terminate($entry['pid'], $signal)) {
2✔
100
                                $stopped++;
2✔
101
                        }
102
                        $this->unregister($source, $entry['pid']);
2✔
103
                }
104

105
                return $stopped;
2✔
106
        }
107

108
        public function isRunning(int $pid): bool {
109
                if ($pid <= 0) {
1✔
110
                        return false;
1✔
111
                }
112

NEW
113
                if (function_exists('posix_kill')) {
×
NEW
114
                        return @posix_kill($pid, 0);
×
115
                }
116

NEW
117
                if (!function_exists('exec')) {
×
NEW
118
                        return false;
×
119
                }
120

121
                try {
NEW
122
                        exec(sprintf('kill -0 %d 2>/dev/null', $pid), $output, $exitCode);
×
NEW
123
                        return $exitCode === 0;
×
NEW
124
                } catch (\Throwable $e) {
×
NEW
125
                        $this->logger->debug('Failed to probe process status', [
×
NEW
126
                                'pid' => $pid,
×
NEW
127
                                'error' => $e->getMessage(),
×
NEW
128
                        ]);
×
NEW
129
                        return false;
×
130
                }
131
        }
132

133
        protected function terminate(int $pid, int $signal): bool {
NEW
134
                if ($pid <= 0) {
×
NEW
135
                        return false;
×
136
                }
137

NEW
138
                if (function_exists('posix_kill')) {
×
NEW
139
                        return @posix_kill($pid, $signal);
×
140
                }
141

NEW
142
                if (!function_exists('exec')) {
×
NEW
143
                        return false;
×
144
                }
145

146
                try {
NEW
147
                        exec(sprintf('kill -%d %d 2>/dev/null', $signal, $pid), $output, $exitCode);
×
NEW
148
                        return $exitCode === 0;
×
NEW
149
                } catch (\Throwable $e) {
×
NEW
150
                        $this->logger->debug('Failed to terminate process', [
×
NEW
151
                                'pid' => $pid,
×
NEW
152
                                'signal' => $signal,
×
NEW
153
                                'error' => $e->getMessage(),
×
NEW
154
                        ]);
×
NEW
155
                        return false;
×
156
                }
157
        }
158

159
        /**
160
         * @return array<string, array<string, array{pid: int, context: array<string, scalar>, createdAt: int}>>
161
         */
162
        private function getRegistry(): array {
163
                $raw = $this->appConfig->getValueString(Application::APP_ID, self::APP_CONFIG_KEY, '{}');
7✔
164
                $decoded = json_decode($raw, true);
7✔
165
                if (!is_array($decoded)) {
7✔
NEW
166
                        return [];
×
167
                }
168

169
                return $decoded;
7✔
170
        }
171

172
        /**
173
         * @param array<string, array<string, array{pid: int, context: array<string, scalar>, createdAt: int}>> $registry
174
         */
175
        private function saveRegistry(array $registry): void {
176
                $this->appConfig->setValueString(Application::APP_ID, self::APP_CONFIG_KEY, json_encode($registry));
6✔
177
        }
178
}
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