Skip to content

Commit e942d6f

Browse files
authored
Merge pull request #11 from vrza/multiple-sockets
Inverted control of configuration updates
2 parents b4a3d39 + 21af8a6 commit e942d6f

13 files changed

Lines changed: 325 additions & 219 deletions

bin/lrpm

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ const EXIT_USAGE = 64;
88
const EXIT_AUTOLOADER_NOT_FOUND = 1;
99
const EXIT_CONFIG_SOURCE_CLASS_NOT_FOUND = 2;
1010

11-
$shortopts = 'h';
11+
$shortopts = 'hi::';
1212
$longopts = [
13-
'help'
13+
'help',
14+
'interval::'
1415
];
1516
$options = getopt($shortopts, $longopts, $rest_index);
1617
$progname = basename($argv[0]);
1718
$helpmsg = "Usage: $progname [options] <configuration-class>
1819
1920
Options:
20-
-h, --help display this help and exit
21+
-h, --help display this help and exit
22+
-i<seconds>, --interval=<seconds> configuration poll interval
2123
";
2224

2325
if (array_key_exists('h', $options) || array_key_exists('help', $options)) {
@@ -40,9 +42,12 @@ if (!class_exists($configSourceClass)) {
4042
exit(EXIT_CONFIG_SOURCE_CLASS_NOT_FOUND);
4143
}
4244

45+
use PHPLRPM\ConfigurationProcess;
4346
use PHPLRPM\ProcessManager;
4447

45-
$processManager = new ProcessManager($configSourceClass);
48+
$interval = intval($options['interval'] ?? $options['i'] ?? ConfigurationProcess::DEFAULT_CONFIG_POLL_INTERVAL);
49+
50+
$processManager = new ProcessManager($configSourceClass, $interval);
4651
$processManager->run();
4752

4853
function probeForAutoloader($programName): void

bin/lrpmctl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,22 @@ if (empty($request)) {
5353

5454
probeForAutoloader($progname);
5555

56-
use TIPC\UnixSocketStreamClient;
56+
use TIPC\FileSystemUtils;
57+
use TIPC\SocketStreamClient;
58+
use TIPC\UnixDomainSocketAddress;
5759
use TextTableFormatter\Table;
5860
use TerminalPalette\AnsiSeq;
5961
use PHPLRPM\IPCUtilities;
62+
use PHPLRPM\MessageService;
6063

61-
$socket = UnixSocketStreamClient::findSocketPath('control', IPCUtilities::getSocketDirs());
64+
$socket = FileSystemUtils::findWritableFilePath(
65+
MessageService::CONTROL_SOCKET_FILE_NAME,
66+
IPCUtilities::getSocketDirs()
67+
);
6268
if (is_null($socket)) {
6369
exitNoConnection();
6470
}
65-
$client = new UnixSocketStreamClient($socket);
71+
$client = new SocketStreamClient(new UnixDomainSocketAddress($socket));
6672
if ($client->connect() === false) {
6773
exitNoConnection();
6874
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"ext-sockets": "*",
3737
"vrza/array-with-secondary-keys": "dev-main",
3838
"vrza/cardinal-collections": "dev-main",
39-
"vrza/php-tipc": "dev-main",
39+
"vrza/php-tipc": "dev-multiple-sockets",
4040
"vrza/terminal-palette": "dev-main",
4141
"vrza/text-table-formatter": "dev-main"
4242
},

src/ConfigurationClient.php

Lines changed: 0 additions & 55 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace PHPLRPM;
4+
5+
use TIPC\MessageHandler;
6+
7+
class ConfigurationMessageHandler implements MessageHandler
8+
{
9+
public const RESP_OK = 'ok';
10+
11+
private $processManager;
12+
13+
public function __construct(ProcessManager $processManager)
14+
{
15+
$this->processManager = $processManager;
16+
}
17+
18+
public function handleMessage(string $msg): string
19+
{
20+
$config = Serialization::deserialize($msg);
21+
$this->processManager->setNewConfig($config);
22+
return self::RESP_OK;
23+
}
24+
25+
}

src/ConfigurationPollException.php

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/ConfigurationProcess.php

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
namespace PHPLRPM;
4+
5+
use Exception;
6+
use RuntimeException;
7+
use TIPC\FileSystemUtils;
8+
use TIPC\SocketStreamClient;
9+
use TIPC\UnixDomainSocketAddress;
10+
11+
class ConfigurationProcess
12+
{
13+
public const DEFAULT_CONFIG_POLL_INTERVAL = 30;
14+
15+
private const CONFIG_POLL_TIME_INIT = 0;
16+
17+
private $configurationSource;
18+
private $config = [];
19+
private $configSocket;
20+
private $configPollIntervalSeconds;
21+
private $timeOfLastConfigPoll = self::CONFIG_POLL_TIME_INIT;
22+
private $client;
23+
24+
public function findConfigSocket()
25+
{
26+
$this->configSocket = FileSystemUtils::findWritableFilePath(
27+
MessageService::CONFIG_SOCKET_FILE_NAME,
28+
IPCUtilities::getSocketDirs()
29+
);
30+
if (is_null($this->configSocket)) {
31+
throw new RuntimeException('Config process could not find supervisor config Unix domain socket');
32+
}
33+
}
34+
35+
public function __construct(string $configurationSourceClass, int $configPollIntervalSeconds)
36+
{
37+
$this->configurationSource = new $configurationSourceClass();
38+
$this->configPollIntervalSeconds = $configPollIntervalSeconds;
39+
}
40+
41+
private function installSignalHandlers(): void
42+
{
43+
fwrite(STDERR, '--> Config process installing signal handlers' . PHP_EOL);
44+
pcntl_signal(SIGHUP, function (int $signo, $_siginfo) {
45+
fwrite(STDERR, "--> Config process caught SIGHUP ($signo), will reload configuration" . PHP_EOL);
46+
$this->timeOfLastConfigPoll = self::CONFIG_POLL_TIME_INIT;
47+
});
48+
}
49+
50+
public function runConfigurationProcessLoop($supervisorPid)
51+
{
52+
$this->installSignalHandlers();
53+
$this->initClient();
54+
fwrite(STDERR, "--> Signaling parent $supervisorPid that we are up and running" . PHP_EOL);
55+
posix_kill($supervisorPid, SIGUSR1);
56+
while (true) {
57+
$ppid = posix_getppid();
58+
if ($ppid != $supervisorPid) {
59+
fwrite(STDERR, '--> Parent PID changed, config process exiting' . PHP_EOL);
60+
$this->shutdown();
61+
}
62+
$haveNewConfig = $this->pollConfigurationSourceForChanges();
63+
if ($haveNewConfig) {
64+
try {
65+
$this->sendConfigToSupervisor();
66+
} catch (ConfigurationSendException $e) {
67+
fwrite(STDERR, '--> Could not send config to supervisor: ' . $e->getMessage() . PHP_EOL);
68+
}
69+
}
70+
if ($this->configPollIntervalSeconds > 0) {
71+
sleep($this->configPollIntervalSeconds);
72+
}
73+
pcntl_signal_dispatch();
74+
}
75+
}
76+
77+
private function shutdown(): void
78+
{
79+
$this->disconnectFromSupervisor();
80+
exit(ExitCodes::EXIT_PPID_CHANGED);
81+
}
82+
83+
private function pollConfigurationSourceForChanges(): bool
84+
{
85+
$haveNewConfig = false;
86+
$now = time();
87+
if ($this->timeOfLastConfigPoll + $this->configPollIntervalSeconds <= $now) {
88+
fwrite(STDERR, '--> Polling configuration source' . PHP_EOL);
89+
$this->timeOfLastConfigPoll = $now;
90+
try {
91+
$newConfig = $this->configurationSource->loadConfiguration();
92+
$haveNewConfig = static::isFresher($this->config, $newConfig);
93+
if ($haveNewConfig) {
94+
$this->config = $newConfig;
95+
}
96+
if (!$haveNewConfig) fwrite(STDERR, '--> No new config found' . PHP_EOL);
97+
} catch (Exception $e) {
98+
fwrite(STDERR, '--> Error loading configuration from source: ' . $e->getMessage() . PHP_EOL);
99+
}
100+
}
101+
return $haveNewConfig;
102+
}
103+
104+
private static function isFresher(array $oldConfig, array $newConfig): bool
105+
{
106+
foreach ($newConfig as $newJobId => $newJobConfig) {
107+
if (array_key_exists($newJobId, $oldConfig)) {
108+
$oldJobConfig = $oldConfig[$newJobId];
109+
if ($newJobConfig['mtime'] > $oldJobConfig['mtime']) {
110+
return true;
111+
}
112+
} else {
113+
return true;
114+
}
115+
}
116+
foreach ($oldConfig as $oldJobId => $oldJobConfig) {
117+
if (!array_key_exists($oldJobId, $newConfig)) {
118+
return true;
119+
}
120+
}
121+
return false;
122+
}
123+
124+
private function initClient(): void
125+
{
126+
$this->findConfigSocket();
127+
$recvBufSize = 4 * 1024;
128+
$this->client = new SocketStreamClient(new UnixDomainSocketAddress($this->configSocket), $recvBufSize);
129+
}
130+
131+
private function disconnectFromSupervisor(): void
132+
{
133+
if (!is_null($this->client) && $this->client->isConnected()) {
134+
$this->client->disconnect();
135+
}
136+
}
137+
138+
private function sendConfigToSupervisor(): void
139+
{
140+
if (!$this->client->isConnected() && $this->client->connect() === false) {
141+
throw new RuntimeException("Could not connect to socket {$this->configSocket}");
142+
}
143+
fwrite(STDERR, '--> Sending new configuration to supervisor' . PHP_EOL);
144+
$msg = Serialization::serialize($this->config);
145+
if ($this->client->sendMessage($msg) === false) {
146+
$this->client->disconnect();
147+
throw new ConfigurationSendException("Could not send config over socket {$this->configSocket}");
148+
}
149+
if (empty($response = $this->client->receiveMessage())) {
150+
$this->client->disconnect();
151+
throw new ConfigurationSendException("Failed to read response from {$this->configSocket}");
152+
}
153+
if ($response !== ConfigurationMessageHandler::RESP_OK) {
154+
throw new ConfigurationSendException("Supervisor at {$this->configSocket} did not acknowledge new config");
155+
}
156+
}
157+
158+
}

src/ConfigurationProcessManager.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private function scheduleRestartWithBackoff()
3737
$this->configProcessRetries = 0;
3838
} else {
3939
$backoff = min(2 ** $this->configProcessRetries, self::CONFIG_PROCESS_MAX_BACKOFF_SECONDS);
40-
fwrite(STDERR, "==> Backing off on config process spawn (retry: " . $this->configProcessRetries . ", seconds: $backoff)" . PHP_EOL);
40+
fwrite(STDERR, "==> Backing off on config process spawn (retry: {$this->configProcessRetries}, seconds: $backoff)" . PHP_EOL);
4141
$this->configProcessRestartAt = $now + $backoff;
4242
$this->configProcessRetries++;
4343
}
@@ -76,4 +76,10 @@ public function stopConfigurationProcess(): void
7676
}
7777
}
7878

79+
public function sendSignalToConfigProcess(int $signal): void
80+
{
81+
fwrite(STDERR, "==> Sending signal $signal to config process with pid {$this->configProcessId}" . PHP_EOL);
82+
posix_kill($this->configProcessId, $signal);
83+
}
84+
7985
}

src/ConfigurationSendException.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace PHPLRPM;
4+
5+
use Exception;
6+
7+
class ConfigurationSendException extends Exception
8+
{
9+
10+
}

0 commit comments

Comments
 (0)