Merge pull request #283 from copenhagentruckwash/fix/pwa-selfserve-stop-latency

[codex] Reduce self-serve latency and add PHP-FPM workers
This commit is contained in:
Jeppe B
2026-06-12 13:15:28 +02:00
committed by GitHub
11 changed files with 104 additions and 6 deletions
+1
View File
@@ -40,6 +40,7 @@ COPY . /var/www/html
# Copy Nginx configuration file
COPY nginx.conf /etc/nginx/nginx.conf
COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf
# Install Composer
COPY --from=composer:2.6 /usr/bin/composer /usr/bin/composer
+1
View File
@@ -47,6 +47,7 @@ RUN set -eux; \
COPY services/nginx/app/ /var/www/html/
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf
COPY services/coolify/api/nginx.conf /etc/nginx/nginx.conf
COPY services/coolify/api/start.sh /usr/local/bin/coolify-api-start
+10 -1
View File
@@ -128,7 +128,16 @@ tar \
-C services/nginx/app -cf - . \
| docker compose $compose_files exec -T php1 tar -C /var/www/html -xf -
docker compose $compose_files exec -T php1 sh -lc 'rm -rf /var/www/repo-root && mkdir -p /var/www/repo-root'
tar \
-cf - \
Dockerfile \
Dockerfile.coolify-api \
services/php/Dockerfile \
services/php/php-fpm-pool.conf \
| docker compose $compose_files exec -T php1 tar -C /var/www/repo-root -xf -
composer_install
docker compose $compose_files exec -T php1 sh -lc \
"cd /var/www/html && composer test:ci:$suite"
"cd /var/www/html && PLENO_REPO_ROOT_FOR_TESTS=/var/www/repo-root composer test:ci:$suite"
@@ -425,7 +425,7 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
return $this->getSessionSummary((int)$session->id);
}
public function completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null): ?array
public function completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null, bool $disableRelays = true): ?array
{
$session = $reg !== null
? $this->findLatestOpenSession($laneId, selfserve::standardize_registration($reg), $customerNumber)
@@ -438,7 +438,9 @@ class selfserve_wash_flow implements selfserve_wash_flow_i
if (!$session->markCompletedIfOpen($orderId)) {
return $this->getSessionSummary((int)$session->id);
}
$this->disableMachineRelayForCompletedWash($laneId);
if ($disableRelays) {
$this->disableMachineRelayForCompletedWash($laneId);
}
$this->logSessionEvent((int)$session->id, selfserve_wash_event_type::SESSION_COMPLETED, [
'lane_id' => $laneId,
'reg' => $reg === null ? (string)$session->reg->value() : selfserve::standardize_registration($reg),
@@ -14,7 +14,7 @@ interface selfserve_wash_flow_i
public function getLatestSessionSummary(int $laneId, string $reg): array;
public function completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null): ?array;
public function completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null, bool $disableRelays = true): ?array;
public function forceStopLane(int $laneId, ?int $sessionId = null, bool $bill = false, ?string $reason = null, ?int $userId = null): array;
}
@@ -439,7 +439,7 @@ Public methods:
| `recordMachineStartWebhook(int $laneId, ?string $reg = null, array $payload = [])` | The machine button or hardware event fired. | Full session summary after the machine-start event. |
| `getSessionSummary(int $sessionId)` | You have a session id already. | Full session summary. |
| `getLatestSessionSummary(int $laneId, string $reg)` | You want the latest session for a lane and vehicle. | Full session summary. |
| `completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null)` | STOP has finished and you want to close the latest open session. | Full summary, or `null` if no open session exists. |
| `completeLatestSessionForLane(int $laneId, ?string $reg = null, ?int $customerNumber = null, ?int $orderId = null, bool $disableRelays = true)` | STOP has finished and you want to close the latest open session. Normal STOP passes `false` because it already disabled relays before opening the exit port. | Full summary, or `null` if no open session exists. |
Key implementation details:
@@ -538,7 +538,8 @@ trait selfserve_lane_command_t
$this->id,
$this->getLicensePlate() ?: null,
$this->getCustomerNumber() ?: null,
method_exists($this, 'getLastInvoiceOrderId') ? $this->getLastInvoiceOrderId() : null
method_exists($this, 'getLastInvoiceOrderId') ? $this->getLastInvoiceOrderId() : null,
false
);
} catch (\Throwable) {
// Session completion must not block STOP flow.
@@ -0,0 +1,55 @@
<?php
function phpFpmWorkerConfigRepoRoot(): string
{
$configuredRoot = getenv('PLENO_REPO_ROOT_FOR_TESTS');
if (is_string($configuredRoot) && $configuredRoot !== '') {
return rtrim($configuredRoot, DIRECTORY_SEPARATOR);
}
$appRoot = defined('WD') ? WD : dirname(__DIR__, 3);
return dirname($appRoot, 3);
}
function phpFpmWorkerConfigRepoPath(string $relative): string
{
return phpFpmWorkerConfigRepoRoot() . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relative);
}
function phpFpmWorkerConfigValue(string $poolConfig, string $key): ?int
{
if (!preg_match('/^' . preg_quote($key, '/') . '\s*=\s*(\d+)\s*$/m', $poolConfig, $matches)) {
return null;
}
return (int)$matches[1];
}
it('configures PHP-FPM with multiple warm request workers', function (): void {
$poolPath = phpFpmWorkerConfigRepoPath('services/php/php-fpm-pool.conf');
expect(is_file($poolPath))->toBeTrue();
$poolConfig = (string)file_get_contents($poolPath);
expect($poolConfig)->toContain('[www]')
->and($poolConfig)->toContain('pm = dynamic')
->and(phpFpmWorkerConfigValue($poolConfig, 'pm.max_children'))->toBeGreaterThanOrEqual(8)
->and(phpFpmWorkerConfigValue($poolConfig, 'pm.start_servers'))->toBeGreaterThanOrEqual(4)
->and(phpFpmWorkerConfigValue($poolConfig, 'pm.min_spare_servers'))->toBeGreaterThanOrEqual(4)
->and(phpFpmWorkerConfigValue($poolConfig, 'pm.max_spare_servers'))->toBeGreaterThanOrEqual(8);
});
it('copies the worker pool config into every API PHP image', function (): void {
$copyInstruction = 'COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf';
$dockerfiles = [
phpFpmWorkerConfigRepoPath('Dockerfile'),
phpFpmWorkerConfigRepoPath('Dockerfile.coolify-api'),
phpFpmWorkerConfigRepoPath('services/php/Dockerfile'),
];
foreach ($dockerfiles as $dockerfile) {
expect(is_file($dockerfile))->toBeTrue();
expect((string)file_get_contents($dockerfile))->toContain($copyInstruction);
}
});
@@ -79,6 +79,8 @@ it('forces machine and cleaner relays off when a self-serve wash session is comp
expect($washFlow)->not->toBeFalse();
expect($washFlow)->toContain('$this->disableMachineRelayForCompletedWash($laneId);');
expect($washFlow)->toContain('bool $disableRelays = true');
expect($washFlow)->toContain('if ($disableRelays) {');
$methodOffset = strpos($washFlow, 'protected function disableMachineRelayForCompletedWash');
expect($methodOffset)->not->toBeFalse();
@@ -108,3 +110,22 @@ it('always dispatches completion relay off for configured machine relays without
[selfserve_lane_relay::MACHINE_CLEANER, false],
]);
});
it('normal STOP completion skips duplicate completion relay cleanup after STOP already disabled relays', function (): void {
$commandTrait = file_get_contents(app_path('modules/selfserve/traits/selfserve_lane_command_t.php'));
expect($commandTrait)->not->toBeFalse();
$methodOffset = strpos($commandTrait, 'protected function completeLatestSessionForStop(): void');
expect($methodOffset)->not->toBeFalse();
$methodBody = substr($commandTrait, (int)$methodOffset, 1500);
expect($methodBody)->toContain(<<<'PHP'
(new \modules\selfserve\classes\selfserve_wash_flow())->completeLatestSessionForLane(
$this->id,
$this->getLicensePlate() ?: null,
$this->getCustomerNumber() ?: null,
method_exists($this, 'getLastInvoiceOrderId') ? $this->getLastInvoiceOrderId() : null,
false
);
PHP);
});
+1
View File
@@ -73,6 +73,7 @@ WORKDIR /var/www/html
# Copy and enable entrypoint that installs Composer deps on first run
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY services/php/php-fpm-pool.conf /usr/local/etc/php-fpm.d/zz-pleno-workers.conf
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
&& chmod +x /usr/local/bin/docker-entrypoint.sh
+7
View File
@@ -0,0 +1,7 @@
[www]
pm = dynamic
pm.max_children = 8
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500