Files
opensourcepos/app/Controllers/Jobs.php
T
objecttothis 14b06eea30 feat(jobs): add task time warning threshold setting
Introduce a soft, log-only per-task time budget (jobs_task_max_seconds)
separate from the existing web request budget (jobs_web_max_seconds),
since PHP cannot preempt a running task mid-execution.

- Config: add taskMaxSeconds default (30s) to app/Config/Jobs.php
- Controller: validate and persist task_max_seconds in
  Jobs::postSaveSettings
- BoundedTaskRunner: accept taskMaxSeconds, measure task runtime via
  microtime, log a warning when a task exceeds the threshold
- JobRunner: read jobs_task_max_seconds from config and pass it to
  BoundedTaskRunner
- Migration: add AddJobsTaskMaxSecondsConfigKey to seed default
  app_config value
- Language: add en strings for label, validation message, and
  enabled/disabled tooltips
- View: add task_max_seconds form field to settings_config.php,
  wired to same web-mode enable/disable toggle as web_max_seconds
- Tests: update JobsControllerTest to cover new field in save/
  validation scenarios

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
2026-09-23 13:22:16 +04:00

166 lines
5.5 KiB
PHP

<?php
namespace App\Controllers;
use App\Models\Appconfig;
use App\Models\JobThrottle;
use CodeIgniter\Database\BaseConnection;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Database;
use ReflectionException;
class Jobs extends Secure_Controller
{
private BaseConnection $db;
private Appconfig $appconfig;
private JobThrottle $jobThrottle;
private array $config;
public function __construct()
{
parent::__construct('jobs');
$this->db = Database::connect();
$this->appconfig = model(Appconfig::class);
$this->jobThrottle = model(JobThrottle::class);
$this->config = $this->global_view_data['config'];
}
/**
* @return string
* @noinspection PhpUnused
*/
public function getIndex(): string
{
$data['config'] = $this->config;
$data['throttles'] = $this->jobThrottle->getAll()->getResultArray();
return view('jobs/manage', $data);
}
/**
* Saves settings configuration. Used in app/Views/jobs/settings_config.php
*
* @throws ReflectionException
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function postSaveSettings(): ResponseInterface
{
$rules = [
'mode' => 'required|in_list[auto,web,manual]',
'web_max_seconds' => 'required|is_natural',
'task_max_seconds' => 'required|is_natural'
];
$messages = [
'mode' => ['in_list' => lang('Jobs.mode_invalid')],
'web_max_seconds' => ['is_natural' => lang('Jobs.web_max_seconds_invalid')],
'task_max_seconds' => ['is_natural' => lang('Jobs.task_max_seconds_invalid')]
];
if ($response = $this->validateFields($rules, $messages)) {
return $response;
}
$batchSaveData = [
'jobs_mode' => $this->request->getPost('mode'),
'jobs_web_max_seconds' => $this->request->getPost('web_max_seconds', FILTER_SANITIZE_NUMBER_INT),
'jobs_task_max_seconds' => $this->request->getPost('task_max_seconds', FILTER_SANITIZE_NUMBER_INT)
];
$success = $this->appconfig->batch_save($batchSaveData);
return $this->response->setJSON(['success' => $success, 'message' => lang('Jobs.saved_' . ($success ? '' : 'un') . 'successfully')]);
}
/**
* Saves throttle configuration. Used in app/Views/jobs/settings_config.php
*
* @throws ReflectionException
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function postSaveThrottles(): ResponseInterface
{
$allowedPeriods = ['minute', 'hour', 'day', 'month'];
$this->db->transStart();
$notToDelete = [];
$arraySave = [];
foreach ($this->request->getPost() as $key => $value) {
if (str_starts_with($key, 'throttle_count_') && preg_match('/^throttle_count_(\d+)$/', $key, $matches)) {
$throttleId = $matches[1];
$notToDelete[] = $throttleId;
$arraySave[$throttleId]['max_count'] = $value;
} elseif (str_starts_with($key, 'throttle_period_') && preg_match('/^throttle_period_(\d+)$/', $key, $matches)) {
$throttleId = $matches[1];
$arraySave[$throttleId]['period'] = $value;
}
}
foreach ($arraySave as $throttleData) {
if (!ctype_digit((string)$throttleData['max_count']) || !in_array($throttleData['period'], $allowedPeriods, true)) {
$this->db->transRollback();
return $this->response->setJSON(['success' => false, 'message' => lang('Jobs.saved_unsuccessfully')]);
}
}
foreach ($arraySave as $throttleId => $throttleData) {
$savedThrottleId = $this->jobThrottle->saveValue($throttleData, $throttleId);
$notToDelete[] = (string)$savedThrottleId;
}
// All throttles not available in post will be deleted now
$deletedThrottles = $this->jobThrottle->getAll()->getResultArray();
foreach ($deletedThrottles as $throttle) {
if (!in_array($throttle['throttle_id'], $notToDelete)) {
$this->jobThrottle->delete($throttle['throttle_id']);
}
}
$this->db->transComplete();
$success = $this->db->transStatus();
return $this->response->setJSON(['success' => $success, 'message' => lang('Jobs.saved_' . ($success ? '' : 'un') . 'successfully')]);
}
/**
* @return string
* @noinspection PhpUnused
*/
public function getThrottles(): string
{
$throttles = $this->jobThrottle->getAll()->getResultArray();
return view('partial/job_throttles', ['throttles' => $throttles]);
}
/**
* Stub for Phase 1 scaffolding. Real processing is wired up in a later phase.
*
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function postProcessAllJobs(): ResponseInterface
{
return $this->response->setJSON(['success' => false, 'stub' => true, 'message' => lang('Jobs.not_yet_implemented')]);
}
/**
* Stub for Phase 1 scaffolding. Real processing is wired up in a later phase.
*
* @return ResponseInterface
* @noinspection PhpUnused
*/
public function postProcessSelectedJobs(): ResponseInterface
{
return $this->response->setJSON(['success' => false, 'stub' => true, 'message' => lang('Jobs.not_yet_implemented')]);
}
}