User tests: Successful: Unsuccessful:
Pull Request resolves #48587 , #44322
Add condition to check for NULL locked state
The task runs again
The task is not started, because it is locked
Please select:
Documentation link for guide.joomla.org:
No documentation changes for guide.joomla.org needed
Pull Request link for manual.joomla.org:
No documentation changes for manual.joomla.org needed
| Status | New | ⇒ | Pending |
| Category | ⇒ | Administration |
I have tested this item 🔴 unsuccessfully on 47a4981
I have tested this item 🔴 unsuccessfully on 47a4981
A freshly locked task is no longer started, but a task with an expired lock is now never started again.
Setup: task "Checkin A" (Global Check-in, id 4), due, default Task Timeout (300 s), locked set directly in the database.
locked |
Command | Before | With PR | With suggested fix |
|---|---|---|---|---|
| NULL | scheduler:run --id=4 |
runs | ✅ runs | ✅ runs |
| now | scheduler:run --id=4 |
runs again (the bug) | ✅ not started, exit 3, "failed to run. Is it already running?" | ✅ not started |
| 1 hour ago | scheduler:run --id=4 |
runs | ❌ not started, stays locked | ✅ runs |
| NULL | scheduler:run --all |
runs | ✅ runs | ✅ runs |
| now | scheduler:run --all |
not started ("No tasks due!") | not started | not started |
| 1 hour ago | scheduler:run --all |
runs | ❌ not started, stays locked | ✅ runs |
locked IS NULL ignores the Task Timeout. If the process that runs a task dies (fatal error, max_execution_time, killed), locked is never reset. Before the PR the task was picked up again once the lock was older than the timeout; now getTask() never selects it again until someone unlocks it by hand in the backend. getTask() is also what the Lazy Scheduler and Web Cron use (same code path, not tested through the web).
Task::acquireLock() and hasRunningTasks() already treat a lock older than the timeout as expired, and issue #48587 asks for exactly that condition.
In buildLockQuery() (and the same in getNextTaskId() with $idQuery), instead of locked IS NULL:
$timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
$threshold = Factory::getDate('now', 'UTC')->modify("-$timeout seconds")->toSql();
$lockQuery->extendWhere(
'AND',
[
$db->quoteName('locked') . ' IS NULL',
$db->quoteName('locked') . ' < :lockThreshold',
],
'OR'
)->bind(':lockThreshold', $threshold);Tested: results in the last column of the table above.
Note on #44322: with the timeout condition a task that runs longer than the Task Timeout is still started again after the timeout (existing behaviour); that issue is then only avoided by a higher timeout.
| Labels |
Added:
bug
PR-5.4-dev
|
||
I have tested this item 🔴 unsuccessfully on 2f0e8d1
I have tested this item 🔴 unsuccessfully on 2f0e8d1
I have tested this item 🔴 unsuccessfully on 2f0e8d1
Task "Checkin A" (Global Check-in, id 4), due, default Task Timeout (300 s):
locked |
Command | 47a4981 | 2f0e8d1 |
|---|---|---|---|
| NULL | scheduler:run --id=4 |
runs | ✅ runs |
| now | scheduler:run --id=4 |
not started | ✅ not started, exit 3 |
| 1 hour ago | scheduler:run --id=4 |
❌ not started, stays locked | ✅ runs |
| NULL | scheduler:run --all |
runs | ✅ runs |
| now | scheduler:run --all |
not started | ✅ not started ("No tasks due!") |
| 1 hour ago | scheduler:run --all |
❌ not started, stays locked | ✅ runs |
The finding from the first test (expired locks never released) is resolved.
$now is overwritten, the task is locked but not found againbuildLockQuery() and getNextTaskId() now start with
$now = Factory::getDate('now', 'UTC');That replaces the $now string passed in by getTask() with a new Date object. buildLockQuery() then writes this new time into locked (:now1), while getTask() afterwards calls fetchTask($db, $now) with its original string and looks for locked = :now. If the two differ by a second, no row is found and fetchTask() fails on null.
Reproduced by making the scheduler wait for its table lock (another session holds LOCK TABLES #__scheduler_tasks READ for 3 seconds while scheduler:run --id=4 starts on the unlocked, due task):
| Result | |
|---|---|
| Before (5.4-dev) | task runs, exit 0 |
| With PR | Attempt to assign property "execution_rules" on null (TaskModel.php line 587), exit 1, task not run and still locked |
| With fix below | task runs, exit 0 |
Without the artificial wait this happens whenever the second changes between getTask() taking its timestamp and buildLockQuery() running; the task then stays locked until the Task Timeout has passed.
Fix: keep $now as passed in and derive only the threshold (same in getNextTaskId()):
$timeout = ComponentHelper::getParams('com_scheduler')->get('timeout', 300);
$threshold = Factory::getDate($now, 'UTC')->modify("-$timeout seconds")->toSql();Tested: the wait scenario above and all six rows of the first table pass.
Nit: there are two blank lines in a row after the new extendWhere() block in getNextTaskId().
I have tested this item ✅ successfully on b6a8341
I have tested this item ✅ successfully on b6a8341
I have tested this item 🔴 unsuccessfully on 47a4981
This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/48592.