generate_lock_secret derived the secret from hostname plus pid,
roughly 22 bits of guessable entropy and also readable from Redis
under <key>_LOCK. Replace with 16 bytes from php_random_bytes_silent,
hex-encoded.
Defense in depth: an attacker with write access to the Redis
instance can already bypass the lock by DELing the key, so this is
not a primary defense; worth fixing for the case where only the
lock key itself is exposed. The hostname|pid path stays as a fallback
when php_random_bytes_silent fails, so the caller always gets a
non-NULL secret.
Rarely CI hangs forever (or until it hits the maximum workflow execution
time) when Redis doesn't come up properly.
This just adds a configurable timeout so we can rerun the workflow when
it hangs.
Add support for Redis' `DELEX` and Valkey's `DELIFEQ` when deleting the
session lock key. Local testing shows about a 10-15% improvement over
the current `EVAL[SHA]` strategy.
This commit adds a new INI settingg:
```ini
redis.session.lock_release_cmd = delex|delifeq|eval
```
By default we continue to use the `EVAL` logic and if a user specifies
another mechanism but the command doesn't exist, we warn the user and
fall back to EVAL.
This commit also refactors a few functions to avoid UB and simplify key
construction.
The stub declares $seeds as ?array but the C code used format
specifier 'a' (non-nullable) instead of 'a!' in
zend_parse_method_parameters. This caused new RedisCluster(null, null)
to throw TypeError instead of RedisClusterException, contradicting
the declared type signature.
Also treat z_seeds == NULL the same as ZEND_NUM_ARGS() < 2 so that
explicitly passing null falls through to INI-based seed loading,
matching the behaviour when the argument is omitted entirely.
Fixes GH-2810.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of currying around a `php_stream_context` object, just retain
the context array provided by the user itself like we do with other
connection information like host and port. This lets users reconnect in
a loop without leaking memory.
```php
$redis = new \Redis;
while (true) {
// Previously each reconnect call would leak the
// `php_stream_context` structure.
$redis->connect('tls://127.0.0.1', 9999, 1, null, 0, 0, [
'stream' => ['verify_peer' => false, 'verify_peer_name' => false],
]);
$redis->ping();
$redis->close();
}
```
We had a `delExType` enum which was later made redundant by
`redisEqType` as they have the same semantics.
This commit just removes `delExType` and uses the new enum.
In `array_zip_values_and_scores` we were blindly calling `Z_STR_P` on
the `zval` assuming it must be a string.
This isn't the case however if the user did something like this:
```php
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->zAdd('zs', 3.14, ['pi', 'is', 'cool']);
// segfault when we try to get `Z_STR_P` from `['pi', 'is', 'cool']`
$redis->zRange('zs', 0, -1, true);
```
Potential fix for #2791
Technically `INFO` can return false in the event of a protocol error or
similar. If that happens the tests are unlikely to be useful but we
should at least account for it to avoid an immediate fatal error when
calling helpers like `detectKeyDB` and `detectValkey`.
See #2783