litespeedtech / litespeedtech/lscache_wp
Object cache Redis _connect() leaks litespeed_exception_handler; later PHP deprecations become fatals on PHP 8.1+
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 257
- Forks
- 123
- PR merge metrics
- No merged PRs in 30d
Description
> **Note:** This is a code-level bug report (with a suggested fix), not a support question — filing here per the readme's "Contribute to the LSCWP GitHub repo" guidance rather than the support forum.
### Summary
`LiteSpeed_Cache_Object`'s Redis `_connect()` registers a global error handler
(`litespeed_exception_handler`, which rethrows PHP errors as `ErrorException`)
around the connection attempt, but only `catch`es `\Exception` / `\ErrorException`
and calls `restore_error_handler()` at the end of the `try`/`catch` — with **no
`finally`**. If anything in the connect block throws a `\Error` (e.g. a
`TypeError`/`ValueError` on PHP 8.x), both `catch` blocks are skipped,
`restore_error_handler()` never runs, and `litespeed_exception_handler` stays
registered **for the rest of the request**.
The leaked handler then converts *every* subsequent PHP notice/deprecation —
from unrelated plugins — into a fatal `Uncaught ErrorException`, white-screening
the whole site. On PHP 8.1+ this is triggered by common third-party
deprecations, e.g. Gravity Forms `dirname(null)` and Wordfence `strrpos(null)`,
**even with `WP_DEBUG` off**.
### Affected code
`src/object-cache.cls.php`, `_connect()` (as of 7.8.1 and current `trunk`):
```php
set_error_handler( 'litespeed_exception_handler' ); // ~line 486
try {
$this->_conn = new \Redis();
// ... connect / auth / select / PING ...
} catch ( \Exception $e ) { // ~line 529
$failed = true;
} catch ( \ErrorException $e ) { // ~line 532 (dead: \ErrorException extends \Exception)
$failed = true;
}
restore_error_handler(); // ~line 536 (skipped if a \Error escapes)
```
Two problems:
1. `restore_error_handler()` is not in a `finally`, so it is skipped on any escaping `\Throwable` that isn't an `\Exception`.
2. `catch ( \ErrorException $e )` after `catch ( \Exception $e )` is unreachable — `\ErrorException` is a subclass of `\Exception`.
### Impact
A single unrelated PHP deprecation anywhere later in the request becomes a hard
fatal. Because the trigger is a third-party deprecation, the failure looks like
it originates in the *other* plugin (Gravity Forms, Wordfence, ACF, ...),
making it very hard to trace back to the object cache.
### Steps to reproduce
1. WordPress on PHP 8.1+ with LiteSpeed Cache object cache (Redis) enabled.
2. Have any active plugin that emits a native `E_DEPRECATED` during load (e.g.
Gravity Forms `GFAddOn->load_text_domain()` → `dirname(null)`).
3. Load any page. Result: `Uncaught ErrorException: dirname(): Passing null ...`
with `#0 [internal function]: litespeed_exception_handler()` at the top of
the stack — thrown from unrelated plugin code.
### Suggested fix
Guarantee the handler is restored, and catch `\Throwable`:
```php
set_error_handler( 'litespeed_exception_handler' );
try {
// ... existing connect logic ...
} catch ( \Throwable $e ) {
$this->debug_oc( 'Redis connect error: ' . $e->getMessage() );
$failed = true;
} finally {
restore_error_handler();
}
```
(Or, minimally, just move `restore_error_handler()` into a `finally` block.)
### Environment
- LiteSpeed Cache: 7.8.1 (also confirmed in current `trunk`)
- PHP: 8.1.32, SAPI FPM/FastCGI
- Redis: phpredis 6.2.0, server up
- WordPress Multisite (subdomain)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/object-cache.cls.php at LiteSpeed_Cache_Object::_connect(), especially the set_error_handler(), catch blocks, and restore_error_handler() call. Reproduce with Redis object caching on PHP 8.1+ and a later deprecation, then verify the handler is restored for every escaping Throwable and the redundant catch is removed without changing normal connection behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php, redis
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100