Fix uncaught PDOException in manual lookup when sqlite is unavailable#1969
Open
svpernova09 wants to merge 1 commit into
Open
Fix uncaught PDOException in manual lookup when sqlite is unavailable#1969svpernova09 wants to merge 1 commit into
svpernova09 wants to merge 1 commit into
Conversation
find_manual_page() was written for PDO's pre-8.0 ERRMODE_SILENT default, where a failed prepare()/execute() returns false and the code falls back to find_manual_page_slow(). PHP 8 defaults to ERRMODE_EXCEPTION, so those failures now throw and escaped uncaught -- 19,892 'General error: 8 attempt to write a readonly database' fatals in a 15-day window, almost all reached via the 404 handler (error.php -> find_manual_page). Restore ERRMODE_SILENT on the connection so the existing fallback works, and make the bare-keyword branch fall back to the slow search instead of calling error_noservice() (a hard error page) when prepare() fails. Add integration tests covering both SQL branches with a broken database, a missing database, and the healthy fast path.
c831f03 to
804460a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix uncaught
PDOExceptionin manual lookup when SQLite is unavailableSummary
find_manual_page()(ininclude/manual-lookup.inc) throws an uncaughtPDOExceptionwhenever the manual-lookup SQLite database can't be queried: aread-only/locked file, or a corrupt/truncated one. It was written for PDO's
pre-8.0
ERRMODE_SILENTbehaviour, where a failedprepare()/execute()returns
falseand the code falls back to the filesystem search(
find_manual_page_slow()). PHP 8 changed PDO's default toERRMODE_EXCEPTION, so those failures now throw, and the onlytry/catchwraps the connection, not the queries.
This is the second-largest source of PHP fatals on www.php.net:
19,892 fatal errors in a recent 15-day window, almost all reached through the
404 handler (
error.php→find_manual_page()), which runs a manual lookup forevery not-found URL.
The bug
The SQLite fast-path relies on the old silent-mode contract:
Under PHP 8's default
ERRMODE_EXCEPTION,prepare()/execute()throw insteadof returning
false, so theif (!$stm)guards are dead and the exceptionpropagates uncaught.
A second, related defect sits in the same function: when
prepare()fails in thebare-keyword branch, the code calls
error_noservice()instead of falling back to the slow search likeevery other failure path.
error_noservice()renders a hard "service not working"page and then calls
exit.Evidence from production logs
The fatal (
error.log, client IP redacted):Volume: 19,892 fatals (30 Jun – 14 Jul 2026), every one
General error: 8 attempt to write a read-only database.It hits every query path, both SQL branches, at both
prepare()andexecute():prepare()(dotted-keyword branch)manual-lookup.inc:169execute()(dotted-keyword branch)manual-lookup.inc:173prepare()(bare-keyword branch)manual-lookup.inc:186execute()(bare-keyword branch)manual-lookup.inc:188The 820 hits at line 186 are the
prepare()-in-the-bare-branch case that reacheserror_noservice(): 820 users were shown a hard "service not working" pagefor what should have been a transparent fallback.
How it's reached, almost entirely via the 404 handler running a manual
lookup for missing URLs:
error.php→find_manual_page()manual-lookup.php→find_manual_page()Root cause
PHP 8 changed PDO's default error mode from
ERRMODE_SILENTtoERRMODE_EXCEPTION. The code's "sqlite is a best-effort optimization; fall backto the filesystem search on any problem" design depended on the silent behavior,
so every sqlite failure that used to degrade gracefully now throws an uncaught.
(Why the database is unwritable in the first place is a separate,
infrastructure-side issue; see below.)
The fix
Two changes, both restoring the function's clear original intent (any sqlite
failure → fall back to
find_manual_page_slow()):PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENTon the connection, soprepare()/execute()returnfalseon failure and the existing fallbackguards work again.
error_noservice()call with a fall-back to the slow search, so aprepare()failure in the bare-keyword branch degrades gracefully like theother paths instead of showing a hard error page.
Testing
tests/Unit/ManualLookup/FindManualPageTest.php, integration teststhat drive the real
find_manual_page():branches),
uncaught
PDOExceptionagainst the original code before the fix.clean.