I’m working on a CodeIgniter 4 project (CI: 4.4.3 production mode | PHP: 8.1.32), and I’m running into a strange issue where my finally
block never executes if a database error happens during execution — even though I’m catching \Throwable
(and doesn’t remove the lock file).
But when I uncomment register_shutdown_function()
, it works and cleans up the lock file.
Here’s a minimal reproduction:
run()
method in CronRunner
namespace App\Models;
class CronRunner extends CI_Model {
public function run($data) {
$result = "OK";
$executedAt = date('Y-m-d H:i:s'); // for logs
$executedAtUnix = time(); // for duration calc
$lockFile = WRITEPATH . 'cache/cronRunner.lock';
if (file_exists($lockFile))
return;
file_put_contents($lockFile, ENVIRONMENT);
// This works — when it's enabled, the file gets removed
// register_shutdown_function(function () use ($lockFile) {
// if (file_exists($lockFile)) {
// unlink($lockFile);
// }
// });
try {
$this->notifyLongRunningTask();
} catch (\Throwable $e2) {
$result = $e2->getMessage();
log_message('error', (string) $result);
} finally {
log_message('error', 'FINALLY BLOCK REACHED');
if (file_exists($lockFile)) {
unlink($lockFile);
}
}
return ['result' => $result];
}
}
Here’s notifyLongRunningTask()
function notifyLongRunningTask() {
$PushSubscriptions = new PushSubscriptions();
$PushSentLog = new PushSentLog();
$db = \Config\Database::connect();
$sql = "
SELECT hs.id as hourly_id, date_started, time_started, hs.organization_id, hs.user_created, c.name Client, p.client_id
FROM HourlySheet hs
JOIN ToDo t ON hs.todo_id = t.id
JOIN Project p ON t.project_id = p.id
JOIN Client c ON p.client_id = c.id
WHERE time_finished IS NULL
";
$query = $db->query($sql);
foreach ($query->getResult() as $row) {
$startedAt = strtotime($row->date_started . ' ' . $row->time_started);
$now = time();
// This line causes the issue
$PushSentLog_res = $PushSentLog->select(" AND user_created = {$row->user_created} AND name = 'notifyLongRunningTask' ORDER BY id DESC LIMIT 1 ");
// ...
}
}
The actual query error
mysqli_sql_exception: Column 'name' in WHERE is ambiguous
And the query is triggered from within the select()
method of our CI_Model-based class. That method includes this logic:
if (!$query) {
$error = $db->error()["message"];
$baseClass = get_parent_class($this);
$this->log_it($error, $baseClass . " " . __FUNCTION__ . ' DB Error');
return ['result' => (string) $error];
}
So the SQL error is:
- Caught
- Logged
- Returned as part of
['result' => '...']
But still — somehow — this causes PHP/CI4 to terminate execution before finally
runs, and the lock file is not removed.
What works
If I uncomment the register_shutdown_function()
, then the lock file is removed as expected.
This tells me the script is being shut down unexpectedly, despite \Throwable
being caught, and no exit()
or fatal visible.
Why is this happening?
- We’re on PHP 8.1.32 and CI4 4.4.3
DBDebug
is set tofalse
(production mode)- The query fails with a typical ambiguous column error
- It is handled inside
select()
(custom function) - But the
finally
block from therun()
method is never reached - The shutdown function does get called
Question
Why is the finally
block not executed, even though the exception is handled and no uncaught fatal is shown?
Is this a PHP 8.1 + CI4 side effect?
Or are certain internal CI4 behaviors forcing a shutdown?
Any insight from CI4 internals or PHP changes in 8.1+ would be much appreciated.