Skip to content

Commit d20ae50

Browse files
committed
feat(process): add process manager service
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 863959d commit d20ae50

1 file changed

Lines changed: 178 additions & 0 deletions

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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+
}
23+
24+
/**
25+
* @param array<string, scalar> $context
26+
*/
27+
public function register(string $source, int $pid, array $context = []): void {
28+
if ($pid <= 0) {
29+
return;
30+
}
31+
32+
$registry = $this->getRegistry();
33+
$registry[$source][(string)$pid] = [
34+
'pid' => $pid,
35+
'context' => $context,
36+
'createdAt' => time(),
37+
];
38+
$this->saveRegistry($registry);
39+
}
40+
41+
public function unregister(string $source, int $pid): void {
42+
$registry = $this->getRegistry();
43+
unset($registry[$source][(string)$pid]);
44+
$this->saveRegistry($registry);
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();
52+
$entries = $registry[$source] ?? [];
53+
$running = [];
54+
$changed = false;
55+
56+
foreach ($entries as $pidKey => $entry) {
57+
$pid = (int)($entry['pid'] ?? 0);
58+
if ($pid <= 0 || !$this->isRunning($pid)) {
59+
unset($registry[$source][$pidKey]);
60+
$changed = true;
61+
continue;
62+
}
63+
64+
$running[] = [
65+
'pid' => $pid,
66+
'context' => is_array($entry['context'] ?? null) ? $entry['context'] : [],
67+
'createdAt' => (int)($entry['createdAt'] ?? 0),
68+
];
69+
}
70+
71+
if ($changed) {
72+
$this->saveRegistry($registry);
73+
}
74+
75+
return $running;
76+
}
77+
78+
public function countRunning(string $source): int {
79+
return count($this->listRunning($source));
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) {
87+
if ($filter !== null && !$filter($entry)) {
88+
continue;
89+
}
90+
return $entry['pid'];
91+
}
92+
93+
return 0;
94+
}
95+
96+
public function stopAll(string $source, int $signal = SIGTERM): int {
97+
$stopped = 0;
98+
foreach ($this->listRunning($source) as $entry) {
99+
if ($this->terminate($entry['pid'], $signal)) {
100+
$stopped++;
101+
}
102+
$this->unregister($source, $entry['pid']);
103+
}
104+
105+
return $stopped;
106+
}
107+
108+
public function isRunning(int $pid): bool {
109+
if ($pid <= 0) {
110+
return false;
111+
}
112+
113+
if (function_exists('posix_kill')) {
114+
return @posix_kill($pid, 0);
115+
}
116+
117+
if (!function_exists('exec')) {
118+
return false;
119+
}
120+
121+
try {
122+
exec(sprintf('kill -0 %d 2>/dev/null', $pid), $output, $exitCode);
123+
return $exitCode === 0;
124+
} catch (\Throwable $e) {
125+
$this->logger->debug('Failed to probe process status', [
126+
'pid' => $pid,
127+
'error' => $e->getMessage(),
128+
]);
129+
return false;
130+
}
131+
}
132+
133+
protected function terminate(int $pid, int $signal): bool {
134+
if ($pid <= 0) {
135+
return false;
136+
}
137+
138+
if (function_exists('posix_kill')) {
139+
return @posix_kill($pid, $signal);
140+
}
141+
142+
if (!function_exists('exec')) {
143+
return false;
144+
}
145+
146+
try {
147+
exec(sprintf('kill -%d %d 2>/dev/null', $signal, $pid), $output, $exitCode);
148+
return $exitCode === 0;
149+
} catch (\Throwable $e) {
150+
$this->logger->debug('Failed to terminate process', [
151+
'pid' => $pid,
152+
'signal' => $signal,
153+
'error' => $e->getMessage(),
154+
]);
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, '{}');
164+
$decoded = json_decode($raw, true);
165+
if (!is_array($decoded)) {
166+
return [];
167+
}
168+
169+
return $decoded;
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));
177+
}
178+
}

0 commit comments

Comments
 (0)