diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..ec60da7e79 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] + experimental: [false] + include: + - php: '8.1' + experimental: true + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: true + - name: Install PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: json, igbinary, msgpack, :redis + coverage: none + tools: none + - name: Install dependencies + run: | + sudo add-apt-repository ppa:redislabs/redis + sudo add-apt-repository ppa:ondrej/php + sudo apt-get update + sudo apt-get install redis valgrind libzstd-dev liblz4-dev + - name: Build phpredis + run: | + phpize + ./configure --enable-redis-lzf --enable-redis-zstd --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lz4 --with-liblz4 + sudo make install + echo 'extension = redis.so' | sudo tee -a $(php --ini | grep 'Scan for additional .ini files' | awk '{print $7}')/90-redis.ini + - name: Start redis + run: | + redis-cli SHUTDOWN NOSAVE + for PORT in $(seq 6379 6382) $(seq 32767 32769); do + redis-server --port $PORT --daemonize yes --aclfile tests/users.acl + done + redis-server --port 0 --unixsocket /tmp/redis.sock --daemonize yes --aclfile tests/users.acl + - name: Start redis cluster + run: | + mkdir -p tests/nodes + echo -n > tests/nodes/nodemap + for PORT in $(seq 7000 7011); do + redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --aclfile tests/users.acl + echo 127.0.0.1:$PORT >> tests/nodes/nodemap + done + echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7006) --cluster-replicas 1 --user phpredis -a phpredis + - name: Start redis sentinel + run: | + wget raw.githubusercontent.com/redis/redis/6.2/sentinel.conf + for PORT in $(seq 26379 26380); do + cp sentinel.conf $PORT.conf + sed -i '/^sentinel/d' $PORT.conf + redis-server $PORT.conf --port $PORT --daemonize yes --sentinel monitor mymaster 127.0.0.1 6379 1 --sentinel auth-pass mymaster phpredis + done + - name: Run tests + run: | + php tests/TestRedis.php --class Redis --user phpredis --auth phpredis + php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis + php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis + php tests/TestRedis.php --class RedisSentinel --auth phpredis + env: + TEST_PHP_ARGS: -e + - name: Run tests using valgrind + continue-on-error: true + run: | + valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --user phpredis --auth phpredis + valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis + valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis + valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisSentinel --auth phpredis + env: + TEST_PHP_ARGS: -e + USE_ZEND_ALLOC: 0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 124c0727ec..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -language: php -php: - - 7.0 - - 7.1 - - 7.2 - - 7.3 - - 7.4 - - nightly -env: CC=gcc -matrix: - allow_failures: - - php: 7.3 - env: CC=clang - - php: 7.4 - env: CC=clang - - php: nightly - include: - - php: 7.0 - env: CC=clang - - php: 7.1 - env: CC=clang - - php: 7.2 - env: CC=clang - - php: 7.3 - env: CC=clang - - php: 7.4 - env: CC=clang -addons: - apt: - update: true - sources: - - sourceline: ppa:redislabs/redis - packages: - - clang - - libzstd1-dev - - liblz4-dev - - pkg-config - - valgrind - - stunnel - - redis -before_install: - - phpize - - CFGARGS="--enable-redis-lzf --enable-redis-zstd --enable-redis-lz4 --with-liblz4" - - pecl install igbinary && CFGARGS="$CFGARGS --enable-redis-igbinary" || true - - pecl install msgpack && CFGARGS="$CFGARGS --enable-redis-msgpack" || true - - ./configure $CFGARGS -install: make install -before_script: - - mkdir -p tests/nodes/ && echo > tests/nodes/nodemap - - redis-server --port 0 --daemonize yes --aclfile tests/users.acl --unixsocket /tmp/redis.sock - - for PORT in $(seq 6379 6382) $(seq 32767 32769); do redis-server --port $PORT --daemonize yes --aclfile tests/users.acl; done - - for PORT in $(seq 7000 7011); do redis-server --port $PORT --cluster-enabled yes --cluster-config-file $PORT.conf --daemonize yes --aclfile tests/users.acl; echo 127.0.0.1:$PORT >> tests/nodes/nodemap; done - - for PORT in $(seq 26379 26380); do wget download.redis.io/redis-stable/sentinel.conf -O $PORT.conf; echo sentinel auth-pass mymaster phpredis >> $PORT.conf; redis-server $PORT.conf --port $PORT --daemonize yes --sentinel; done - - echo yes | redis-cli --cluster create $(seq -f 127.0.0.1:%g 7000 7011) --cluster-replicas 3 --user phpredis -a phpredis - - echo 'extension = redis.so' >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - openssl req -x509 -newkey rsa:1024 -nodes -keyout stunnel.key -out stunnel.pem -days 1 -subj '/CN=localhost' - - echo -e 'key=stunnel.key\ncert=stunnel.pem\npid=/tmp/stunnel.pid\n[redis]\naccept=6378\nconnect=6379' > stunnel.conf - - stunnel stunnel.conf -script: - - php tests/TestRedis.php --class Redis --user phpredis --auth phpredis - - php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis - - php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis - - php tests/TestRedis.php --class RedisSentinel --auth phpredis - - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class Redis --user phpredis --auth phpredis - - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisArray --user phpredis --auth phpredis - - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisCluster --user phpredis --auth phpredis - - USE_ZEND_ALLOC=0 valgrind --error-exitcode=1 php tests/TestRedis.php --class RedisSentinel --auth phpredis diff --git a/Changelog.md b/Changelog.md index 70ec00ebc3..1b63608cd4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,289 @@ and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +## [5.3.7] - 2021-02-15 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7), [PECL](https://pecl.php.net/package/redis/5.3.7)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +*There were no changes between 5.3.7 and 5.3.7RC2* + +## [5.3.7RC2] - 2021-02-12 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7RC2), [PECL](https://pecl.php.net/package/redis/5.3.7RC2)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +*There were no changes between 5.3.7RC2 and 5.3.7RC1* + +## [5.3.7RC1] - 2021-02-02 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7RC1), [PECL](https://pecl.php.net/package/redis/5.3.7RC1)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +### Fixed + +- Fix RedisArray::[hsz]scan and tests + [08a9d5db](https://github.com/phpredis/phpredis/commit/08a9d5db), + [0264de18](https://github.com/phpredis/phpredis/commit/0264de18), + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)), + ([Michael Grunder](https://github.com/michael-grunder)) +- Fix RedisArray::scan + [8689ab1c](https://github.com/phpredis/phpredis/commit/8689ab1c) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Fix LZF decompression logic + [0719c1ec](https://github.com/phpredis/phpredis/commit/0719c1ec) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.6] - 2021-01-17 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.6), [PECL](https://pecl.php.net/package/redis/5.3.6)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +### Fixed + +- Fix a segfault in RedisArray::del + [d2f2a7d9](https://github.com/phpredis/phpredis/commit/d2f2a7d9) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) + +## [5.3.5] - 2021-12-18 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5), [PECL](https://pecl.php.net/package/redis/5.3.5)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +### Fixed + +- Fixed typo in cluster_scan_resp + [44affad2](https://github.com/phpredis/phpredis/commit/44affad2) + +## [5.3.5RC1] - 2021-11-16 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5RC1), [PECL](https://pecl.php.net/package/redis/5.3.5RC1)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +### Fixed + +- Fixed segfault in redis_setoption_handler + [#2030](https://github.com/phpredis/phpredis/issues/2030) + [692e4e84](https://github.com/phpredis/phpredis/commit/692e4e84) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Fix masters array in the event of a cluster failover + [bce692962](https://github.com/phpredis/phpredis/commit/bce692962) + [#2025](https://github.com/phpredis/phpredis/pull/2025) + ([Bar Shaul](https://github.com/barshaul)) +- Fix 32bit type error + [672dec87f](https://github.com/phpredis/phpredis/commit/672dec87f) + ([#1956](https://github.com/phpredis/phpredis/issues/1956)) + ([Remi Collet](https://github.com/remicollet)) +- Fix radix character in certain locales + [#1893](https://github.com/phpredis/phpredis/issues/1893) + [89a871e24](https://github.com/phpredis/phpredis/commit/89a871e24) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- ZSTD Validation fix + [6a77ef5cd](https://github.com/phpredis/phpredis/commit/6a77ef5cd) + ([Michael Grunder](https://github.com/michael-grunder)) +- Remove superfluous typecast + [b2871471f](https://github.com/phpredis/phpredis/commit/b2871471f) + ([Remi Collet](https://github.com/remicollet)) +- Updated documentation + [f84168657](https://github.com/phpredis/phpredis/commit/f84168657), + [d017788e7](https://github.com/phpredis/phpredis/commit/d017788e7), + [20ac84710](https://github.com/phpredis/phpredis/commit/20ac84710), + [0adf05260](https://github.com/phpredis/phpredis/commit/0adf05260), + [aee29bf73](https://github.com/phpredis/phpredis/commit/aee29bf73), + [09a095e72](https://github.com/phpredis/phpredis/commit/09a095e72), + [12ffbf33a](https://github.com/phpredis/phpredis/commit/12ffbf33a), + [ff331af98](https://github.com/phpredis/phpredis/commit/ff331af98), + [a6bdb8731](https://github.com/phpredis/phpredis/commit/a6bdb8731), + [305c15840](https://github.com/phpredis/phpredis/commit/305c15840), + [1aa10e93a](https://github.com/phpredis/phpredis/commit/1aa10e93a), + [d78b0c79d](https://github.com/phpredis/phpredis/commit/d78b0c79d), + [c6d37c27c](https://github.com/phpredis/phpredis/commit/c6d37c27c), + [a6303f5b9](https://github.com/phpredis/phpredis/commit/a6303f5b9), + [d144bd2c7](https://github.com/phpredis/phpredis/commit/d144bd2c7), + [a6fb815ef](https://github.com/phpredis/phpredis/commit/a6fb815ef), + [9ef862bc6](https://github.com/phpredis/phpredis/commit/9ef862bc6) + ([neodisco](https://github.com/neodisco), [Billy Wilson](https://github.com/wilsonwr), + [Clément Tessier](https://github.com/ctessier), [wangqr](https://github.com/wangqr), + [T. Todua](https://github.com/ttodua), [Naphat Deepar](https://github.com/feverxai), + [dengliming](https://github.com/dengliming), [Poplary](https://github.com/poplary), + [Maxime Cornet](https://github.com/xElysioN), [Michael Grunder](https://github.com/michael-grunder), + [Emanuele Filannino](https://github.com/tatekan), [MiRacLe](https://github.com/MiRacLe-RPZ), + [Michael Grunder](https://github.com/michael-grunder)) +- Travis CI Fixes + [a43f4586e](https://github.com/phpredis/phpredis/commit/a43f4586e), + [4fde8178f](https://github.com/phpredis/phpredis/commit/4fde8178f), + [7bd5415ac](https://github.com/phpredis/phpredis/commit/7bd5415ac), + [fdb8c4bb7](https://github.com/phpredis/phpredis/commit/fdb8c4bb7), + [d4f407470](https://github.com/phpredis/phpredis/commit/d4f407470) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Minor fixes/cleanup + [2e190adc1](https://github.com/phpredis/phpredis/commit/2e190adc1), + [99975b592](https://github.com/phpredis/phpredis/commit/99975b592), + [9d0879fa5](https://github.com/phpredis/phpredis/commit/9d0879fa5), + [22b06457b](https://github.com/phpredis/phpredis/commit/22b06457b), + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Fix RedisArray constructor bug + [85dc883ba](https://github.com/phpredis/phpredis/commit/85dc883ba) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) + +### Changed + +- Moved to GitHub Actions + [4d2afa786](https://github.com/phpredis/phpredis/commit/4d2afa786), + [502d09fd5](https://github.com/phpredis/phpredis/commit/502d09fd5) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Use more appropriate array iteration macro + [6008900c2](https://github.com/phpredis/phpredis/commit/6008900c2) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Clean up session tests + [ab25ae7f3](https://github.com/phpredis/phpredis/commit/ab25ae7f3) + ([Michael Grunder](https://github.com/michael-grunder)) +- RedisArray refactors + [1250f0001](https://github.com/phpredis/phpredis/commit/1250f0001), + [017b2ea7f](https://github.com/phpredis/phpredis/commit/017b2ea7f), + [37ed3f079](https://github.com/phpredis/phpredis/commit/37ed3f079) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Use zend_parse_parameters_none helper + [a26b14dbe](https://github.com/phpredis/phpredis/commit/a26b14dbe) + ([Remi Collet](https://github.com/remicollet)) + +### Added + +- Support for various exponential backoff strategies + [#1986](https://github.com/phpredis/phpredis/commit/#1986), + [#1993](https://github.com/phpredis/phpredis/commit/#1993), + [732eb8dcb](https://github.com/phpredis/phpredis/commit/732eb8dcb) + [05129c3a3](https://github.com/phpredis/phpredis/commit/05129c3a3) + [5bba6a7fc](https://github.com/phpredis/phpredis/commit/5bba6a7fc) + ([Nathaniel Braun](https://github.com/nbraun-amazon)) +- Added experimental support for detecting a dirty connection by + trying to determine if the underlying stream is readable. + [d68579562](https://github.com/phpredis/phpredis/commit/d68579562) + [#2013](https://github.com/phpredis/phpredis/issues/2013) + ([Michael Grunder](https://github.com/michael-grunder)) +- Created distinct compression utility methods (pack/unpack) + [#1939](https://github.com/phpredis/phpredis/issues/1939) + [da2790aec](https://github.com/phpredis/phpredis/commit/da2790aec) + ([Michael Grunder](https://github.com/michael-grunder)) +- SMISMEMBER Command + [#1894](https://github.com/phpredis/phpredis/commit/#1894) + [ae2382472](https://github.com/phpredis/phpredis/commit/ae2382472), + [ed283e1ab](https://github.com/phpredis/phpredis/commit/ed283e1ab), + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) + +## [5.3.4] - 2021-03-24 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.4), [PECL](https://pecl.php.net/package/redis/5.3.4)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) + +### Fixed + +- Fix multi/pipeline segfault on Apple silicon [#1917](https://github.com/phpredis/phpredis/issues/1917) + [e0796d48](https://github.com/phpredis/phpredis/commit/e0796d48af18adac2b93982474e7df8de79ec854) + ([Michael Grunder](https://github.com/michael-grunder)) +- Pass compression flag on HMGET in RedisCluster [#1945](https://github.com/phpredis/phpredis/issues/1945) + [edc724e6](https://github.com/phpredis/phpredis/commit/edc724e6022620414abf4f90256522d03c3160fd) + ([Adam Olley](https://github.com/aolley)) +- Abide by ZSTD error return constants [#1936](https://github.com/phpredis/phpredis/issues/1936) + [8400ed1c](https://github.com/phpredis/phpredis/pull/1937/commits/8400ed1cb23a22f70727cb60e88ca5397ee10d23) + ([Michael Grunder](https://github.com/michael-grunder)) +- Fix timing related CI session tests + [9b986bf8](https://github.com/phpredis/phpredis/commit/9b986bf81859f5a5983cd148cb15ee6ce292d288) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.3] - 2021-02-01 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.3), [PECL](https://pecl.php.net/package/redis/5.3.3)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [BlueHost](https://bluehost.com) +- [Redis Cache Pro for WordPress](https://wprediscache.com) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Oleg Babushkin](https://github.com/olbabushkin) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) + +### Fixed + +- Fixed Windows includes for PHP 8 + [270b4db8](https://www.github.com/phpredis//phpredis/commit/270b4db821fcbe9fb881eef83e046f87587c4110) + ([Jan-E](https://github.com/Jan-E)) +- Fix hash_ops for PHP 8.0.1 + [87297cbb](https://www.github.com/phpredis/phpredis/commit/87297cbb4000c88b07e729b9379de321ead74aa2) + ([defender-11](https://github.com/defender-11)) +- Disable clone for Redis and RedisCluster objects. Presently they segfault. + [cd05a344](https://www.github.com/phpredis/phpredis/commit/87297cbb4000c88b07e729b9379de321ead74aa2) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.2] - 2020-10-22 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.2), [PECL](https://pecl.php.net/package/redis/5.3.2)) + ### Sponsors :sparkling_heart: - [Audiomack](https://audiomack.com) diff --git a/INSTALL.markdown b/INSTALL.markdown index 845c90d755..8c8ed16e6a 100644 --- a/INSTALL.markdown +++ b/INSTALL.markdown @@ -1,9 +1,12 @@ -# Installation from pecl +# Installation from pecl/pickle -To pull latest stable released version, from [pecl](https://pecl.php.net/package/redis): +To pull latest stable released version, from [pecl](https://pecl.php.net/package/redis) / [pickle](https://wiki.php.net/rfc/deprecate-pear-include-composer): ~~~ pecl install redis + +// If using PHP >= 7.3 +pickle install redis ~~~ # Installation from sources @@ -11,6 +14,8 @@ pecl install redis To build this extension for the sources tree: ~~~ +git clone https://github.com/phpredis/phpredis.git +cd phpredis phpize ./configure [--enable-redis-igbinary] [--enable-redis-msgpack] [--enable-redis-lzf [--with-liblzf[=DIR]]] [--enable-redis-zstd] make && make install @@ -84,7 +89,7 @@ See also: [Install Redis & PHP Extension PHPRedis with Macports](http://www.lecl You can install it using MacPorts: - [Get macports-php](https://www.macports.org/) -- `sudo port install php56-redis` (or php53-redis, php54-redis, php55-redis, php70-redis, php71-redis, php72-redis) +- `sudo port install php56-redis` (or php53-redis, php54-redis, php55-redis, php70-redis, php71-redis, php72-redis, php73-redis, php74-redis) # Building on Windows diff --git a/README.markdown b/README.markdown index 79d857387f..6a9e8f524b 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,8 @@ # PhpRedis -[![Build Status](https://travis-ci.org/phpredis/phpredis.svg?branch=develop)](https://travis-ci.org/phpredis/phpredis) +[![Build Status](https://github.com/phpredis/phpredis/actions/workflows/ci.yml/badge.svg)](https://github.com/phpredis/phpredis/actions/workflows/ci.yml) [![Coverity Scan Build Status](https://scan.coverity.com/projects/13205/badge.svg)](https://scan.coverity.com/projects/phpredis-phpredis) +[![PHP version from Travis config](https://img.shields.io/travis/php-v/phpredis/phpredis/develop)](https://img.shields.io/travis/php-v/phpredis/phpredis/develop) The phpredis extension provides an API for communicating with the [Redis](http://redis.io/) key-value store. It is released under the [PHP License, version 3.01](http://www.php.net/license/3_01.txt). This code has been developed and maintained by Owlient from November 2009 to March 2011. @@ -20,7 +21,10 @@ You can also make a one-time contribution with one of the links below. [![Ethereum](https://en.cryptobadges.io/badge/micro/0x43D54E32357B96f68dFF0a6B46976d014Bd603E1)](https://en.cryptobadges.io/donate/0x43D54E32357B96f68dFF0a6B46976d014Bd603E1) ## Sponsors -Audiomack.comBluehost.com +Audiomack.com +Bluehost.com +Object Cache Pro +OpenLMS.net # Table of contents ----- @@ -34,6 +38,7 @@ You can also make a one-time contribution with one of the links below. 1. [Classes and methods](#classes-and-methods) * [Usage](#usage) * [Connection](#connection) + * [Retry and backoff](#retry-and-backoff) * [Server](#server) * [Keys and strings](#keys-and-strings) * [Hashes](#hashes) @@ -77,7 +82,7 @@ session.save_path = "tcp://host1:6379?weight=1, tcp://host2:6379?weight=2&timeou Sessions have a lifetime expressed in seconds and stored in the INI variable "session.gc_maxlifetime". You can change it with [`ini_set()`](http://php.net/ini_set). The session handler requires a version of Redis supporting `EX` and `NX` options of `SET` command (at least 2.6.12). -phpredis can also connect to a unix domain socket: `session.save_path = "unix:///var/run/redis/redis.sock?persistent=1&weight=1&database=0`. +phpredis can also connect to a unix domain socket: `session.save_path = "unix:///var/run/redis/redis.sock?persistent=1&weight=1&database=0"`. ### Session locking @@ -293,7 +298,7 @@ $redis->auth(['phpredis', 'haxx00r']); $redis->auth(['foobared']); /* You can also use an associative array specifying user and pass */ -$redis->auth(['user' => 'phpredis', 'pass' => 'phpredis]); +$redis->auth(['user' => 'phpredis', 'pass' => 'phpredis']); $redis->auth(['pass' => 'phpredis']); ~~~ @@ -356,6 +361,7 @@ $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // Don't ser $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // Use built-in serialize/unserialize $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // Use igBinary serialize/unserialize $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_MSGPACK); // Use msgpack serialize/unserialize +$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_JSON); // Use JSON to serialize/unserialize $redis->setOption(Redis::OPT_PREFIX, 'myAppName:'); // use custom prefix on all keys @@ -388,7 +394,7 @@ Parameter value. ##### *Example* ~~~php // return Redis::SERIALIZER_NONE, Redis::SERIALIZER_PHP, -// Redis::SERIALIZER_IGBINARY, or Redis::SERIALIZER_MSGPACK +// Redis::SERIALIZER_IGBINARY, Redis::SERIALIZER_MSGPACK or Redis::SERIALIZER_JSON $redis->getOption(Redis::OPT_SERIALIZER); ~~~ @@ -427,6 +433,41 @@ _**Description**_: Sends a string to Redis, which replies with the same string *STRING*: the same message. +## Retry and backoff + +1. [Maximum retries](#maximum-retries) +1. [Backoff algorithms](#backoff-algorithms) + +### Maximum retries +You can set and get the maximum retries upon connection issues using the `OPT_MAX_RETRIES` option. Note that this is the number of _retries_, meaning if you set this option to _n_, there will be a maximum _n+1_ attemps overall. Defaults to 10. + +##### *Example* + +~~~php +$redis->setOption(Redis::OPT_MAX_RETRIES, 5); +$redis->getOption(Redis::OPT_MAX_RETRIES); +~~~ + +### Backoff algorithms +You can set the backoff algorithm using the `Redis::OPT_BACKOFF_ALGORITHM` option and choose among the following algorithms described in this blog post by Marc Brooker from AWS: [Exponential Backoff And Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter): + +* Default: `Redis::BACKOFF_ALGORITHM_DEFAULT` +* Decorrelated jitter: `Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER` +* Full jitter: `Redis::BACKOFF_ALGORITHM_FULL_JITTER` +* Equal jitter: `Redis::BACKOFF_ALGORITHM_EQUAL_JITTER` +* Exponential: `Redis::BACKOFF_ALGORITHM_EXPONENTIAL` +* Uniform: `Redis::BACKOFF_ALGORITHM_UNIFORM` +* Constant: `Redis::BACKOFF_ALGORITHM_CONSTANT` + +These algorithms depend on the _base_ and _cap_ parameters, both in milliseconds, which you can set using the `Redis::OPT_BACKOFF_BASE` and `Redis::OPT_BACKOFF_CAP` options, respectively. + +##### *Example* + +~~~php +$redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER); +$redis->setOption(Redis::OPT_BACKOFF_BASE, 500); // base for backoff computation: 500ms +$redis->setOption(Redis::OPT_BACKOFF_CAP, 750); // backoff time capped at 750ms +~~~ ## Server @@ -882,7 +923,7 @@ $redis->exists('key'); /* 1 */ $redis->exists('NonExistingKey'); /* 0 */ $redis->mset(['foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz']); -$redis->exists(['foo', 'bar', 'baz]); /* 3 */ +$redis->exists(['foo', 'bar', 'baz']); /* 3 */ $redis->exists('foo', 'bar', 'baz'); /* 3 */ ~~~ @@ -2254,7 +2295,7 @@ $redis->lLen('key1');/* 2 */ ### sAdd ----- -_**Description**_: Adds a value to the set value stored at key. If this value is already in the set, `FALSE` is returned. +_**Description**_: Adds a value to the set value stored at key. ##### *Parameters* *key* *value* @@ -2615,7 +2656,7 @@ $redis->sAdd('s2', '4'); var_dump($redis->sUnion('s0', 's1', 's2')); /* Pass a single array */ -var_dump($redis->sUnion(['s0', 's1', 's2']); +var_dump($redis->sUnion(['s0', 's1', 's2'])); ~~~ Return value: all elements that are either in s0 or in s1 or in s2. @@ -3794,7 +3835,7 @@ $obj_redis->xRange('mystream', '-', '+', 2); ##### *Prototype* ~~~php -$obj_redis->xRead($arr_streams [, $i_count, $i_block); +$obj_redis->xRead($arr_streams [, $i_count, $i_block]); ~~~ _**Description**_: Read data from one or more streams and only return IDs greater than sent in the command. @@ -4010,7 +4051,7 @@ $redis->rawCommand("set", "foo", "bar"); $redis->rawCommand("get", "foo"); /* Returns: 3 */ -$redis->rawCommand("rpush", "mylist", "one", 2, 3.5)); +$redis->rawCommand("rpush", "mylist", "one", 2, 3.5); /* Returns: ["one", "2", "3.5000000000000000"] */ $redis->rawCommand("lrange", "mylist", 0, -1); diff --git a/backoff.c b/backoff.c new file mode 100644 index 0000000000..d0961fcfaf --- /dev/null +++ b/backoff.c @@ -0,0 +1,90 @@ +#include "common.h" + +#include + +#if PHP_VERSION_ID >= 70100 +#include +#else +static zend_long php_mt_rand_range(zend_long min, zend_long max) { + zend_long number = php_rand(); + RAND_RANGE(number, min, max, PHP_RAND_MAX); + return number; +} +#endif + +#include "backoff.h" + +static zend_ulong random_range(zend_ulong min, zend_ulong max) { + if (max < min) { + return php_mt_rand_range(max, min); + } + + return php_mt_rand_range(min, max); +} + +static zend_ulong redis_default_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong backoff = retry_index ? self->base : random_range(0, self->base); + return MIN(self->cap, backoff); +} + +static zend_ulong redis_constant_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong backoff = self->base; + return MIN(self->cap, backoff); +} + +static zend_ulong redis_uniform_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong backoff = random_range(0, self->base); + return MIN(self->cap, backoff); +} + +static zend_ulong redis_exponential_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong pow = MIN(retry_index, 10); + zend_ulong backoff = self->base * (1 << pow); + return MIN(self->cap, backoff); +} + +static zend_ulong redis_full_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong pow = MIN(retry_index, 10); + zend_ulong backoff = self->base * (1 << pow); + zend_ulong cap = MIN(self->cap, backoff); + return random_range(0, cap); +} + +static zend_ulong redis_equal_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) { + zend_ulong pow = MIN(retry_index, 10); + zend_ulong backoff = self->base * (1 << pow); + zend_ulong temp = MIN(self->cap, backoff); + return temp / 2 + random_range(0, temp) / 2; +} + +static zend_ulong redis_decorrelated_jitter_backoff(struct RedisBackoff *self, unsigned int retry_index) { + self->previous_backoff = random_range(self->base, self->previous_backoff * 3); + return MIN(self->cap, self->previous_backoff); +} + +typedef zend_ulong (*redis_backoff_algorithm)(struct RedisBackoff *self, unsigned int retry_index); + +static redis_backoff_algorithm redis_backoff_algorithms[REDIS_BACKOFF_ALGORITHMS] = { + redis_default_backoff, + redis_decorrelated_jitter_backoff, + redis_full_jitter_backoff, + redis_equal_jitter_backoff, + redis_exponential_backoff, + redis_uniform_backoff, + redis_constant_backoff, +}; + +void redis_initialize_backoff(struct RedisBackoff *self, unsigned long retry_interval) { + self->algorithm = 0; // default backoff + self->base = retry_interval; + self->cap = retry_interval; + self->previous_backoff = 0; +} + +void redis_backoff_reset(struct RedisBackoff *self) { + self->previous_backoff = 0; +} + +zend_ulong redis_backoff_compute(struct RedisBackoff *self, unsigned int retry_index) { + return redis_backoff_algorithms[self->algorithm](self, retry_index); +} diff --git a/backoff.h b/backoff.h new file mode 100644 index 0000000000..bc6828c612 --- /dev/null +++ b/backoff.h @@ -0,0 +1,17 @@ +#ifndef REDIS_BACKOFF_H +#define REDIS_BACKOFF_H + +/* {{{ struct RedisBackoff */ +struct RedisBackoff { + unsigned int algorithm; /* index of algorithm function, returns backoff in microseconds*/ + zend_ulong base; /* base backoff in microseconds */ + zend_ulong cap; /* max backoff in microseconds */ + zend_ulong previous_backoff; /* previous backoff in microseconds */ +}; +/* }}} */ + +void redis_initialize_backoff(struct RedisBackoff *self, unsigned long retry_interval); +void redis_backoff_reset(struct RedisBackoff *self); +zend_ulong redis_backoff_compute(struct RedisBackoff *self, unsigned int retry_index); + +#endif diff --git a/cluster.markdown b/cluster.markdown index cecae1c99e..a52787e050 100644 --- a/cluster.markdown +++ b/cluster.markdown @@ -9,7 +9,7 @@ To maintain consistency with the RedisArray class, one can create and connect to #### Declaring a cluster with an array of seeds ~~~php -// Create a cluster setting two nodes as seeds +// Create a cluster setting three nodes as seeds $obj_cluster = new RedisCluster(NULL, Array('host:7000', 'host:7001', 'host:7003')); // Connect and specify timeout and read_timeout @@ -45,7 +45,7 @@ $obj_cluster = new RedisCluster('mycluster'); On construction, the RedisCluster class will iterate over the provided seed nodes until it can attain a connection to the cluster and run CLUSTER SLOTS to map every node in the cluster locally. Once the keyspace is mapped, RedisCluster will only connect to nodes when it needs to (e.g. you're getting a key that we believe is on that node.) ## Slot caching -Each time the a `RedisCluster` class is constructed from scratch, phpredis needs to execute a `CLUSTER SLOTS` command to map the keyspace. Although this isn't an expensive command, it does require a round trip for each newly created object, which is inefficient. Starting from PhpRedis 5.0.0 these slots can be cached by setting `redis.clusters.cache_slots = 1` in `php.ini`. +Each time the `RedisCluster` class is constructed from scratch, phpredis needs to execute a `CLUSTER SLOTS` command to map the keyspace. Although this isn't an expensive command, it does require a round trip for each newly created object, which is inefficient. Starting from PhpRedis 5.0.0 these slots can be cached by setting `redis.clusters.cache_slots = 1` in `php.ini`. ## Timeouts Because Redis cluster is intended to provide high availability, timeouts do not work in the same way they do in normal socket communication. It's fully possible to have a timeout or even exception on a given socket (say in the case that a master node has failed), and continue to serve the request if and when a slave can be promoted as the new master. @@ -181,9 +181,9 @@ The save path for cluster based session storage takes the form of a PHP GET requ * _timeout (double)_: The amount of time phpredis will wait when connecting or writing to the cluster. * _read_timeout (double)_: The amount of time phpredis will wait for a result from the cluster. * _persistent_: Tells phpredis whether persistent connections should be used. -* _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing). * _failover (string)_: How phpredis should distribute session reads between master and slave nodes. * _none_ : phpredis will only communicate with master nodes * _error_: phpredis will communicate with master nodes unless one fails, in which case an attempt will be made to read session information from a slave. + * _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing). * _auth (string, empty by default)_: The password used to authenticate with the server prior to sending commands. * _stream (array)_: ssl/tls stream context options. diff --git a/cluster_library.c b/cluster_library.c index 5bcd23385f..1d3e0ebf9a 100644 --- a/cluster_library.c +++ b/cluster_library.c @@ -683,7 +683,7 @@ static int cluster_map_slots(redisCluster *c, clusterReply *r) { clusterReply *r2, *r3; unsigned short port; char *host, key[1024]; - + zend_hash_clean(c->nodes); for (i = 0; i < r->elements; i++) { // Inner response r2 = r->element[i]; @@ -833,7 +833,7 @@ PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout, c->err = NULL; /* Set up our waitms based on timeout */ - c->waitms = (long)(1000 * timeout); + c->waitms = (long)(1000 * (timeout + read_timeout)); /* Allocate our seeds hash table */ ALLOC_HASHTABLE(c->seeds); @@ -1396,7 +1396,7 @@ static void cluster_update_slot(redisCluster *c) { /* Do we already have the new slot mapped */ if (c->master[c->redir_slot]) { /* No need to do anything if it's the same node */ - if (!CLUSTER_REDIR_CMP(c)) { + if (!CLUSTER_REDIR_CMP(c, SLOT_SOCK(c,c->redir_slot))) { return; } @@ -1407,6 +1407,22 @@ static void cluster_update_slot(redisCluster *c) { /* Just point to this slot */ c->master[c->redir_slot] = node; } else { + /* If the redirected node is a replica of the previous slot owner, a failover has taken place. + We must then remap the cluster's keyspace in order to update the cluster's topology. */ + redisClusterNode *prev_master = SLOT(c,c->redir_slot); + redisClusterNode *slave; + ZEND_HASH_FOREACH_PTR(prev_master->slaves, slave) { + if (slave == NULL) { + continue; + } + if (!CLUSTER_REDIR_CMP(c, slave->sock)) { + // Detected a failover, the redirected node was a replica + // Remap the cluster's keyspace + cluster_map_keyspace(c); + return; + } + } ZEND_HASH_FOREACH_END(); + /* Create our node */ node = cluster_node_create(c, c->redir_host, c->redir_host_len, c->redir_port, c->redir_slot, 0); @@ -2112,6 +2128,7 @@ PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, if (c->reply_len > 0) { /* Push serialization settings from the cluster into our socket */ c->cmd_sock->serializer = c->flags->serializer; + c->cmd_sock->compression = c->flags->compression; /* Call our specified callback */ if (cb(c->cmd_sock, &z_result, c->reply_len, ctx) == FAILURE) { @@ -2142,8 +2159,8 @@ PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster * } // Read the BULK size - if (cluster_check_response(c, &c->reply_type),0 || - c->reply_type != TYPE_BULK) + if (cluster_check_response(c, &c->reply_type) || + c->reply_type != TYPE_BULK) { return FAILURE; } diff --git a/cluster_library.h b/cluster_library.h index f8f1eec845..232f67d0ca 100644 --- a/cluster_library.h +++ b/cluster_library.h @@ -45,11 +45,11 @@ #define CMD_SOCK(c) (c->cmd_sock) #define CMD_STREAM(c) (c->cmd_sock->stream) -/* Compare redirection slot information with what we have */ -#define CLUSTER_REDIR_CMP(c) \ - (SLOT_SOCK(c,c->redir_slot)->port != c->redir_port || \ - ZSTR_LEN(SLOT_SOCK(c,c->redir_slot)->host) != c->redir_host_len || \ - memcmp(ZSTR_VAL(SLOT_SOCK(c,c->redir_slot)->host),c->redir_host,c->redir_host_len)) +/* Compare redirection slot information with the passed node */ +#define CLUSTER_REDIR_CMP(c, sock) \ + (sock->port != c->redir_port || \ + ZSTR_LEN(sock->host) != c->redir_host_len || \ + memcmp(ZSTR_VAL(sock->host),c->redir_host,c->redir_host_len)) /* Clear out our "last error" */ #define CLUSTER_CLEAR_ERROR(c) do { \ diff --git a/common.h b/common.h index 80b34e6568..83f01dc63d 100644 --- a/common.h +++ b/common.h @@ -12,7 +12,6 @@ #include #include -#define PHPREDIS_ZVAL_IS_STRICT_FALSE(z) (Z_TYPE_P(z) == IS_FALSE) #define PHPREDIS_GET_OBJECT(class_entry, o) (class_entry *)((char *)o - XtOffsetOf(class_entry, std)) #define PHPREDIS_ZVAL_GET_OBJECT(class_entry, z) PHPREDIS_GET_OBJECT(class_entry, Z_OBJ_P(z)) @@ -21,6 +20,8 @@ #define NULL ((void *) 0) #endif +#include "backoff.h" + typedef enum { REDIS_SOCK_STATUS_FAILED = -1, REDIS_SOCK_STATUS_DISCONNECTED, @@ -83,6 +84,10 @@ typedef enum _PUBSUB_TYPE { #define REDIS_OPT_REPLY_LITERAL 8 #define REDIS_OPT_COMPRESSION_LEVEL 9 #define REDIS_OPT_NULL_MBULK_AS_NULL 10 +#define REDIS_OPT_MAX_RETRIES 11 +#define REDIS_OPT_BACKOFF_ALGORITHM 12 +#define REDIS_OPT_BACKOFF_BASE 13 +#define REDIS_OPT_BACKOFF_CAP 14 /* cluster options */ #define REDIS_FAILOVER_NONE 0 @@ -109,6 +114,16 @@ typedef enum { #define REDIS_SCAN_PREFIX 2 #define REDIS_SCAN_NOPREFIX 3 +/* BACKOFF_ALGORITHM options */ +#define REDIS_BACKOFF_ALGORITHMS 7 +#define REDIS_BACKOFF_ALGORITHM_DEFAULT 0 +#define REDIS_BACKOFF_ALGORITHM_DECORRELATED_JITTER 1 +#define REDIS_BACKOFF_ALGORITHM_FULL_JITTER 2 +#define REDIS_BACKOFF_ALGORITHM_EQUAL_JITTER 3 +#define REDIS_BACKOFF_ALGORITHM_EXPONENTIAL 4 +#define REDIS_BACKOFF_ALGORITHM_UNIFORM 5 +#define REDIS_BACKOFF_ALGORITHM_CONSTANT 6 + /* GETBIT/SETBIT offset range limits */ #define BITOP_MIN_OFFSET 0 #define BITOP_MAX_OFFSET 4294967295U @@ -118,6 +133,17 @@ typedef enum { #define MULTI 1 #define PIPELINE 2 +#define PHPREDIS_DEBUG_LOGGING 0 + +#if PHPREDIS_DEBUG_LOGGING == 1 +#define redisDbgFmt(fmt, ...) \ + php_printf("%s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__) +#define redisDbgStr(str) phpredisDebugFmt("%s", str) +#else +#define redisDbgFmt(fmt, ...) ((void)0) +#define redisDbgStr(str) ((void)0) +#endif + #define IS_ATOMIC(redis_sock) (redis_sock->mode == ATOMIC) #define IS_MULTI(redis_sock) (redis_sock->mode & MULTI) #define IS_PIPELINE(redis_sock) (redis_sock->mode & PIPELINE) @@ -140,7 +166,7 @@ typedef enum { #define REDIS_SAVE_CALLBACK(callback, closure_context) do { \ fold_item *fi = malloc(sizeof(fold_item)); \ - fi->fun = (void *)callback; \ + fi->fun = callback; \ fi->ctx = closure_context; \ fi->next = NULL; \ if (redis_sock->current) { \ @@ -195,7 +221,7 @@ typedef enum { REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \ } -/* Process a command but with a specific command building function +/* Process a command but with a specific command building function * and keyword which is passed to us*/ #define REDIS_PROCESS_KW_CMD(kw, cmdfunc, resp_func) \ RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \ @@ -256,52 +282,58 @@ typedef enum { #endif #endif -typedef struct fold_item { - zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...); - void *ctx; - struct fold_item *next; -} fold_item; - /* {{{ struct RedisSock */ typedef struct { - php_stream *stream; - php_stream_context *stream_ctx; - zend_string *host; - int port; - zend_string *user; - zend_string *pass; - double timeout; - double read_timeout; - long retry_interval; - redis_sock_status status; - int persistent; - int watching; - zend_string *persistent_id; - - redis_serializer serializer; - int compression; - int compression_level; - long dbNumber; - - zend_string *prefix; - - short mode; - fold_item *head; - fold_item *current; - - zend_string *pipeline_cmd; - - zend_string *err; - - int scan; - - int readonly; - int reply_literal; - int null_mbulk_as_null; - int tcp_keepalive; + php_stream *stream; + php_stream_context *stream_ctx; + zend_string *host; + int port; + zend_string *user; + zend_string *pass; + double timeout; + double read_timeout; + long retry_interval; + int max_retries; + struct RedisBackoff backoff; + redis_sock_status status; + int persistent; + int watching; + zend_string *persistent_id; + + redis_serializer serializer; + int compression; + int compression_level; + long dbNumber; + + zend_string *prefix; + + short mode; + struct fold_item *head; + struct fold_item *current; + + zend_string *pipeline_cmd; + + zend_string *err; + + int scan; + + int readonly; + int reply_literal; + int null_mbulk_as_null; + int tcp_keepalive; } RedisSock; /* }}} */ +/* Redis response handler function callback prototype */ +typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +typedef int (*FailableResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock*, zval*, void*); + +typedef struct fold_item { + FailableResultCallback fun; + void *ctx; + struct fold_item *next; +} fold_item; + typedef struct { zend_llist list; int nb_active; @@ -739,4 +771,15 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_xdel, 0, 0, 2) ZEND_ARG_ARRAY_INFO(0, arr_ids, 0) ZEND_END_ARG_INFO() +/** + * Argument info for key scanning + */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) + ZEND_ARG_INFO(0, str_key) + ZEND_ARG_INFO(1, i_iterator) + ZEND_ARG_INFO(0, str_pattern) + ZEND_ARG_INFO(0, i_count) +ZEND_END_ARG_INFO() + + #endif diff --git a/config.m4 b/config.m4 index 5ffd49d21c..750e58ac64 100644 --- a/config.m4 +++ b/config.m4 @@ -270,7 +270,7 @@ if test "$PHP_REDIS" != "no"; then ]) PHP_SUBST(REDIS_SHARED_LIBADD) else - AC_MSG_ERROR([only system libz4 is supported]) + AC_MSG_ERROR([only system liblz4 is supported]) fi fi @@ -323,5 +323,5 @@ if test "$PHP_REDIS" != "no"; then fi PHP_SUBST(REDIS_SHARED_LIBADD) - PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c $lzf_sources, $ext_shared) + PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c backoff.c $lzf_sources, $ext_shared) fi diff --git a/config.w32 b/config.w32 index e2b4365752..751bf73dee 100644 --- a/config.w32 +++ b/config.w32 @@ -5,7 +5,7 @@ ARG_ENABLE("redis-session", "whether to enable sessions", "yes"); ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no"); if (PHP_REDIS != "no") { - var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c"; + var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c redis_sentinel.c sentinel_library.c backoff.c"; if (PHP_REDIS_SESSION != "no") { ADD_EXTENSION_DEP("redis", "session"); ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 '); diff --git a/library.c b/library.c index 9008ffe0b7..c1414cb716 100644 --- a/library.c +++ b/library.c @@ -301,7 +301,7 @@ redis_error_throw(RedisSock *redis_sock) PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock, int no_throw) { - int count; + unsigned int retry_index; char *errmsg; if (!redis_sock || !redis_sock->stream || redis_sock->status == REDIS_SOCK_STATUS_FAILED) { @@ -333,18 +333,17 @@ redis_check_eof(RedisSock *redis_sock, int no_throw) errmsg = "Connection lost and socket is in MULTI/watching mode"; } else { errmsg = "Connection lost"; - /* TODO: configurable max retry count */ - for (count = 0; count < 10; ++count) { + redis_backoff_reset(&redis_sock->backoff); + for (retry_index = 0; retry_index < redis_sock->max_retries; ++retry_index) { /* close existing stream before reconnecting */ if (redis_sock->stream) { redis_sock_disconnect(redis_sock, 1); } - // Wait for a while before trying to reconnect - if (redis_sock->retry_interval) { - // Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time - long retry_interval = (count ? redis_sock->retry_interval : (php_rand() % redis_sock->retry_interval)); - usleep(retry_interval); - } + /* Sleep based on our backoff algorithm */ + zend_ulong delay = redis_backoff_compute(&redis_sock->backoff, retry_index); + if (delay != 0) + usleep(delay); + /* reconnect */ if (redis_sock_connect(redis_sock) == 0) { /* check for EOF again. */ @@ -721,7 +720,11 @@ static zend_string *redis_hash_auth(zend_string *user, zend_string *pass) { smart_str_appendl_ex(&salted, REDIS_G(salt), sizeof(REDIS_G(salt)), 0); ctx = emalloc(ops->context_size); +#if PHP_VERSION_ID >= 80100 + ops->hash_init(ctx,NULL); +#else ops->hash_init(ctx); +#endif ops->hash_update(ctx, (const unsigned char *)ZSTR_VAL(salted.s), ZSTR_LEN(salted.s)); digest = emalloc(ops->digest_size); @@ -947,12 +950,15 @@ int redis_cmd_append_sstr_i64(smart_string *str, int64_t append) { int redis_cmd_append_sstr_dbl(smart_string *str, double value) { - char tmp[64]; + char tmp[64], *p; int len; /* Convert to string */ len = snprintf(tmp, sizeof(tmp), "%.17g", value); + /* snprintf depends on locale, replace comma with point */ + if ((p = strchr(tmp, ',')) != NULL) *p = '.'; + // Append the string return redis_cmd_append_sstr(str, tmp, len); } @@ -1006,7 +1012,8 @@ int redis_cmd_append_sstr_arrkey(smart_string *cmd, zend_string *kstr, zend_ulon return redis_cmd_append_sstr(cmd, arg, len); } -PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +PHP_REDIS_API int +redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; @@ -1014,32 +1021,36 @@ PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, Redi if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { if (IS_ATOMIC(redis_sock)) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); } - add_next_index_bool(z_tab, 0); - return; + return FAILURE; } ret = atof(response); efree(response); if (IS_ATOMIC(redis_sock)) { - RETURN_DOUBLE(ret); + RETVAL_DOUBLE(ret); } else { add_next_index_double(z_tab, ret); } + + return SUCCESS; } -PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +PHP_REDIS_API int redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; long l; if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { if (IS_ATOMIC(redis_sock)) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); } - add_next_index_bool(z_tab, 0); - return; + return FAILURE; } if (strncmp(response, "+string", 7) == 0) { @@ -1060,20 +1071,23 @@ PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock * efree(response); if (IS_ATOMIC(redis_sock)) { - RETURN_LONG(l); + RETVAL_LONG(l); } else { add_next_index_long(z_tab, l); } + + return SUCCESS; } -PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +PHP_REDIS_API int redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; zval z_ret; /* Read bulk response */ if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { - RETURN_FALSE; + RETVAL_FALSE; + return FAILURE; } /* Parse it into a zval array */ @@ -1088,6 +1102,8 @@ PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock * } else { add_next_index_zval(z_tab, &z_ret); } + + return SUCCESS; } PHP_REDIS_API void @@ -1144,14 +1160,16 @@ redis_parse_info_response(char *response, zval *z_ret) * Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code * to handle. */ -PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) { +PHP_REDIS_API int +redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *resp; int resp_len; zval z_ret; /* Make sure we can read the bulk response from Redis */ if ((resp = redis_sock_read(redis_sock, &resp_len)) == NULL) { - RETURN_FALSE; + RETVAL_FALSE; + return FAILURE; } /* Parse it out */ @@ -1166,6 +1184,8 @@ PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSo } else { add_next_index_zval(z_tab, &z_ret); } + + return SUCCESS; } PHP_REDIS_API void @@ -1263,7 +1283,7 @@ redis_parse_client_list_response(char *response, zval *z_ret) } } -PHP_REDIS_API void +PHP_REDIS_API int redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback) @@ -1282,36 +1302,38 @@ redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, success_callback(redis_sock); } if (IS_ATOMIC(redis_sock)) { - RETURN_BOOL(ret); + RETVAL_BOOL(ret); } else { add_next_index_bool(z_tab, ret); } + + return ret ? SUCCESS : FAILURE; } -PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, +PHP_REDIS_API int redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, - z_tab, ctx, NULL); + return redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, ctx, NULL); } -PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, - RedisSock *redis_sock, zval * z_tab, - void *ctx) +PHP_REDIS_API int redis_long_response(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock, zval * z_tab, + void *ctx) { char *response; int response_len; - if ((response = redis_sock_read(redis_sock, &response_len)) - == NULL) - { + if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) { if (IS_ATOMIC(redis_sock)) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); } - add_next_index_bool(z_tab, 0); - return; + + return FAILURE; } if(response[0] == ':') { @@ -1336,8 +1358,12 @@ PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, } else { add_next_index_null(z_tab); } + efree(response); + return FAILURE; } + efree(response); + return SUCCESS; } /* Helper method to convert [key, value, key, value] into [key => value, @@ -1918,7 +1944,7 @@ PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, Re z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE); } -PHP_REDIS_API void +PHP_REDIS_API int redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; @@ -1931,13 +1957,15 @@ redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_ta } if (IS_ATOMIC(redis_sock)) { - RETURN_BOOL(ret); + RETVAL_BOOL(ret); } else { add_next_index_bool(z_tab, ret); } + + return ret ? SUCCESS : FAILURE; } -PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { +PHP_REDIS_API int redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char *response; int response_len; @@ -1946,10 +1974,11 @@ PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock == NULL) { if (IS_ATOMIC(redis_sock)) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); } - add_next_index_bool(z_tab, 0); - return; + return FAILURE; } if (IS_ATOMIC(redis_sock)) { if (!redis_unpack(redis_sock, response, response_len, return_value)) { @@ -1963,7 +1992,9 @@ PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock add_next_index_stringl(z_tab, response, response_len); } } + efree(response); + return SUCCESS; } PHP_REDIS_API @@ -1991,7 +2022,7 @@ void redis_single_line_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock } /* like string response, but never unserialized. */ -PHP_REDIS_API void +PHP_REDIS_API int redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { @@ -2003,17 +2034,20 @@ redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, == NULL) { if (IS_ATOMIC(redis_sock)) { - RETURN_FALSE; + RETVAL_FALSE; + } else { + add_next_index_bool(z_tab, 0); } - add_next_index_bool(z_tab, 0); - return; + return FAILURE; } if (IS_ATOMIC(redis_sock)) { RETVAL_STRINGL(response, response_len); } else { add_next_index_stringl(z_tab, response, response_len); } + efree(response); + return SUCCESS; } /* Response for DEBUG object which is a formatted single line reply */ @@ -2092,6 +2126,8 @@ redis_sock_create(char *host, int host_len, int port, redis_sock->host = zend_string_init(host, host_len, 0); redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED; redis_sock->retry_interval = retry_interval * 1000; + redis_sock->max_retries = 10; + redis_initialize_backoff(&redis_sock->backoff, retry_interval); redis_sock->persistent = persistent; if (persistent && persistent_id != NULL) { @@ -2123,6 +2159,82 @@ static int redis_stream_liveness_check(php_stream *stream) { SUCCESS : FAILURE; } +/* Try to get the underlying socket FD for use with poll/select. + * Returns -1 on failure. */ +static php_socket_t redis_stream_fd_for_select(php_stream *stream) { + php_socket_t fd; + int flags; + + flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL; + if (php_stream_cast(stream, flags, (void*)&fd, 1) == FAILURE) + return -1; + + return fd; +} + +static int redis_detect_dirty_config(void) { + int val = INI_INT("redis.pconnect.pool_detect_dirty"); + + if (val >= 0 && val <= 2) + return val; + else if (val > 2) + return 2; + else + return 0; +} + +static int redis_pool_poll_timeout(void) { + int val = INI_INT("redis.pconnect.pool_poll_timeout"); + if (val >= 0) + return val; + + return 0; +} + +#define REDIS_POLL_FD_SET(_pfd, _fd, _events) \ + (_pfd).fd = _fd; (_pfd).events = _events; (_pfd).revents = 0 + +/* Try to determine if the socket is out of sync (has unconsumed replies) */ +static int redis_stream_detect_dirty(php_stream *stream) { + php_socket_t fd; + php_pollfd pfd; + int rv, action; + + /* Short circuit if this is disabled */ + if ((action = redis_detect_dirty_config()) == 0) + return SUCCESS; + + /* Seek past unconsumed bytes if we detect them */ + if (stream->readpos < stream->writepos) { + redisDbgFmt("%s on unconsumed buffer (%ld < %ld)", + action > 1 ? "Aborting" : "Seeking", + (long)stream->readpos, (long)stream->writepos); + + /* Abort if we are configured to immediately fail */ + if (action == 1) + return FAILURE; + + /* Seek to the end of buffered data */ + zend_off_t offset = stream->writepos - stream->readpos; + if (php_stream_seek(stream, offset, SEEK_CUR) == FAILURE) + return FAILURE; + } + + /* Get the underlying FD */ + if ((fd = redis_stream_fd_for_select(stream)) == -1) + return FAILURE; + + /* We want to detect a readable socket (it shouln't be) */ + REDIS_POLL_FD_SET(pfd, fd, PHP_POLLREADABLE); + rv = php_poll2(&pfd, 1, redis_pool_poll_timeout()); + + /* If we detect the socket is readable, it's dirty which is + * a failure. Otherwise as best we can tell it's good. + * TODO: We could attempt to consume up to N bytes */ + redisDbgFmt("Detected %s socket", rv > 0 ? "readable" : "unreadable"); + return rv == 0 ? SUCCESS : FAILURE; +} + static int redis_sock_check_liveness(RedisSock *redis_sock) { @@ -2131,11 +2243,14 @@ redis_sock_check_liveness(RedisSock *redis_sock) smart_string cmd = {0}; size_t len; - /* Short circuit if we detect the stream has gone bad or if the user has - * configured persistent connection "YOLO mode". */ - if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS) { + /* Short circuit if PHP detects the stream isn't live */ + if (redis_stream_liveness_check(redis_sock->stream) != SUCCESS) + goto failure; + + /* Short circuit if we detect the stream is "dirty", can't or are + configured not to try and fix it */ + if (redis_stream_detect_dirty(redis_sock->stream) != SUCCESS) goto failure; - } redis_sock->status = REDIS_SOCK_STATUS_CONNECTED; if (!INI_INT("redis.pconnect.echo_check_liveness")) { @@ -2254,6 +2369,7 @@ PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock) if (redis_sock_check_liveness(redis_sock) == SUCCESS) { return SUCCESS; } + p->nb_active--; } @@ -2713,19 +2829,7 @@ static uint8_t crc8(unsigned char *input, size_t len) { #endif PHP_REDIS_API int -redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) -{ - char *buf; - int valfree; - size_t len; - - valfree = redis_serialize(redis_sock, z, &buf, &len); - if (redis_sock->compression == REDIS_COMPRESSION_NONE) { - *val = buf; - *val_len = len; - return valfree; - } - +redis_compress(RedisSock *redis_sock, char **dst, size_t *dstlen, char *buf, size_t len) { switch (redis_sock->compression) { case REDIS_COMPRESSION_LZF: #ifdef HAVE_REDIS_LZF @@ -2738,9 +2842,8 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) size = len + MIN(UINT_MAX - len, MAX(LZF_MARGIN, len / 25)); data = emalloc(size); if ((res = lzf_compress(buf, len, data, size)) > 0) { - if (valfree) efree(buf); - *val = data; - *val_len = res; + *dst = data; + *dstlen = res; return 1; } efree(data); @@ -2770,10 +2873,8 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) data = emalloc(size); size = ZSTD_compress(data, size, buf, len, level); if (!ZSTD_isError(size)) { - if (valfree) efree(buf); - data = erealloc(data, size); - *val = data; - *val_len = size; + *dst = erealloc(data, size); + *dstlen = size; return 1; } efree(data); @@ -2821,48 +2922,45 @@ redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) break; } - if (valfree) efree(buf); - *val = lz4buf; - *val_len = lz4len + REDIS_LZ4_HDR_SIZE; + *dst = lz4buf; + *dstlen = lz4len + REDIS_LZ4_HDR_SIZE; return 1; } #endif break; } - *val = buf; - *val_len = len; - return valfree; + + *dst = buf; + *dstlen = len; + return 0; } PHP_REDIS_API int -redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) -{ +redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char *src, size_t len) { switch (redis_sock->compression) { case REDIS_COMPRESSION_LZF: #ifdef HAVE_REDIS_LZF { - char *data; - int i; + char *data = NULL; uint32_t res; + int i; - if (val_len == 0) + if (len == 0) break; - /* start from two-times bigger buffer and - * increase it exponentially if needed */ + /* Grow our buffer until we succeed or get a non E2BIG error */ errno = E2BIG; for (i = 2; errno == E2BIG; i *= 2) { - data = emalloc(i * val_len); - if ((res = lzf_decompress(val, val_len, data, i * val_len)) == 0) { - /* errno != E2BIG will brake for loop */ - efree(data); - continue; - } else if (redis_unserialize(redis_sock, data, res, z_ret) == 0) { - ZVAL_STRINGL(z_ret, data, res); + data = erealloc(data, len * i); + if ((res = lzf_decompress(src, len, data, len * i)) > 0) { + *dst = data; + *dstlen = res; + return 1; } - efree(data); - return 1; } + + efree(data); + break; } #endif break; @@ -2870,21 +2968,21 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) #ifdef HAVE_REDIS_ZSTD { char *data; - size_t len; - - len = ZSTD_getFrameContentSize(val, val_len); - if (len >= 0) { - data = emalloc(len); - len = ZSTD_decompress(data, len, val, val_len); - if (ZSTD_isError(len)) { - efree(data); - break; - } else if (redis_unserialize(redis_sock, data, len, z_ret) == 0) { - ZVAL_STRINGL(z_ret, data, len); - } + unsigned long long zlen; + + zlen = ZSTD_getFrameContentSize(src, len); + if (zlen == ZSTD_CONTENTSIZE_ERROR || zlen == ZSTD_CONTENTSIZE_UNKNOWN || zlen > INT_MAX) + break; + + data = emalloc(zlen); + *dstlen = ZSTD_decompress(data, zlen, src, len); + if (ZSTD_isError(*dstlen) || *dstlen != zlen) { efree(data); - return 1; + break; } + + *dst = data; + return 1; } #endif break; @@ -2897,12 +2995,12 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) /* We must have at least enough bytes for our header, and can't have more than * INT_MAX + our header size. */ - if (val_len < REDIS_LZ4_HDR_SIZE || val_len > INT_MAX + REDIS_LZ4_HDR_SIZE) + if (len < REDIS_LZ4_HDR_SIZE || len > INT_MAX + REDIS_LZ4_HDR_SIZE) break; /* Operate on copies in case our CRC fails */ - const char *copy = val; - size_t copylen = val_len; + const char *copy = src; + size_t copylen = len; /* Read in our header bytes */ memcpy(&lz4crc, copy, sizeof(uint8_t)); @@ -2917,23 +3015,59 @@ redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret) /* Finally attempt decompression */ data = emalloc(datalen); if (LZ4_decompress_safe(copy, data, copylen, datalen) > 0) { - if (redis_unserialize(redis_sock, data, datalen, z_ret) == 0) { - ZVAL_STRINGL(z_ret, data, datalen); - } - efree(data); + *dst = data; + *dstlen = datalen; return 1; } + efree(data); } #endif break; } - return redis_unserialize(redis_sock, val, val_len, z_ret); + + *dst = (char*)src; + *dstlen = len; + return 0; +} + +PHP_REDIS_API int +redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) { + size_t tmplen; + int tmpfree; + char *tmp; + + /* First serialize */ + tmpfree = redis_serialize(redis_sock, z, &tmp, &tmplen); + + /* Now attempt compression */ + if (redis_compress(redis_sock, val, val_len, tmp, tmplen)) { + if (tmpfree) efree(tmp); + return 1; + } + + return tmpfree; +} + +PHP_REDIS_API int +redis_unpack(RedisSock *redis_sock, const char *src, int srclen, zval *zdst) { + size_t len; + char *buf; + + /* Uncompress, then unserialize */ + if (redis_uncompress(redis_sock, &buf, &len, src, srclen)) { + if (!redis_unserialize(redis_sock, buf, len, zdst)) { + ZVAL_STRINGL(zdst, buf, len); + } + efree(buf); + return 1; + } + + return redis_unserialize(redis_sock, buf, len, zdst); } PHP_REDIS_API int -redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len - ) +redis_serialize(RedisSock *redis_sock, zval *z, char **val, size_t *val_len) { php_serialize_data_t ht; diff --git a/library.h b/library.h index db47545dc9..6305475d96 100644 --- a/library.h +++ b/library.h @@ -49,20 +49,20 @@ PHP_REDIS_API zend_string *redis_pool_spprintf(RedisSock *redis_sock, char *fmt, PHP_REDIS_API char *redis_sock_read(RedisSock *redis_sock, int *buf_len); PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t* line_len); -PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); +PHP_REDIS_API int redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx); typedef void (*SuccessCallback)(RedisSock *redis_sock); -PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); -PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback); +PHP_REDIS_API int redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_single_line_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API void redis_parse_info_response(char *response, zval *z_ret); PHP_REDIS_API void redis_parse_client_list_response(char *response, zval *z_ret); -PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, int port, double timeout, double read_timeout, int persistent, char *persistent_id, long retry_interval); PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock); PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock); @@ -120,6 +120,11 @@ redis_key_prefix(RedisSock *redis_sock, char **key, size_t *key_len); PHP_REDIS_API int redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret); +PHP_REDIS_API int +redis_compress(RedisSock *redis_sock, char **dst, size_t *dstlen, char *buf, size_t len); +PHP_REDIS_API int +redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char *src, size_t len); + PHP_REDIS_API int redis_pack(RedisSock *redis_sock, zval *z, char **val, size_t *val_len); PHP_REDIS_API int redis_unpack(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret); @@ -148,7 +153,7 @@ PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, long lon PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_read_raw_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); PHP_REDIS_API int redis_read_variant_reply_strings(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); -PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab); +PHP_REDIS_API int redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); /* Helper methods to get configuration values from a HashTable. */ diff --git a/package.xml b/package.xml index 466be5fcba..abd4a305d0 100644 --- a/package.xml +++ b/package.xml @@ -27,10 +27,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> n.favrefelix@gmail.com no - 2020-07-07 + 2022-02-15 - 5.3.1 - 5.3.1 + 5.3.7 + 5.3.7 stable @@ -38,32 +38,35 @@ http://pear.php.net/dtd/package-2.0.xsd"> PHP - phpredis 5.3.1 + --- Sponsors --- - This is a small bugfix release that fixes a couple of issues in 5.3.0. + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec - You should upgrade if you're using persistent_id in session.save_path or - of if you're having trouble building 5.3.0 because the php_hash_bin2hex - symbol is missing. + phpredis 5.3.7 - You can find a detailed list of changes in Changelog.md and package.xml + - There were no changes between 5.3.7 and 5.3.7RC2. - * Sponsors - ~ Audiomack - https://audiomack.com - ~ BlueHost - https://bluehost.com - ~ Redis Cache Pro for WordPress - https://wprediscache.com - ~ Avtandil Kikabidze - https://github.com/akalongman + --- + + phpredis 5.3.7RC2 + + - There were no changes between 5.3.7RC2 and 5.3.7RC1. --- - * Properly clean up on session start failure [066cff6a] (Michael Grunder) - * Treat NULL as a failure for redis_extract_auth_info [49428a2f, 14ac969d] - (Michael Grunder) - * Don't dereference a NULL zend_string or efree one [ff2e160f, 7fed06f2] - (Michael Grunder) - * Fix config.m4 messages and test for and include php_hash.h [83a1b7c5, - 3c56289c, 08f202e7] (Remi Collet) - * Add openSUSE installation instructions [13a168f4] (Pavlo Yatsukhnenko) - * Remove EOL Fedora installation instructions [b4779e6a] (Remi Collet) + + phpredis 5.3.7RC1 + + - Fix RedisArray::[hsz]scan and tests [08a9d5db, 0264de18] (Pavlo Yatsukhnenko, Michael Grunder) + - Fix RedisArray::scan [8689ab1c] (Pavlo Yatsukhnenko) + - Fix LZF decompression logic [0719c1ec] (Michael Grunder) + @@ -74,6 +77,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -125,7 +130,6 @@ http://pear.php.net/dtd/package-2.0.xsd"> 7.0.0 - 7.9.99 1.4.0b1 @@ -139,6 +143,290 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + stablestable + 5.3.75.3.7 + 2022-02-15 + + --- Sponsors --- + + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec + + phpredis 5.3.7 + + - There were no changes between 5.3.7 and 5.3.7RC2. + + --- + + phpredis 5.3.7RC2 + + - There were no changes between 5.3.7RC2 and 5.3.7RC1. + + --- + + phpredis 5.3.7RC1 + + - Fix RedisArray::[hsz]scan and tests [08a9d5db, 0264de18] (Pavlo Yatsukhnenko, Michael Grunder) + - Fix RedisArray::scan [8689ab1c] (Pavlo Yatsukhnenko) + - Fix LZF decompression logic [0719c1ec] (Michael Grunder) + + + + + stablestable + 5.3.65.3.6 + 2022-01-17 + + --- Sponsors --- + + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec + + --- + + phpredis 5.3.6 + + - Fix a segfault in RedisArray::del [d2f2a7d9] (Pavlo Yatsukhnenko) + + + + + stablestable + 5.3.55.3.5 + 2021-12-18 + + phpredis 5.3.5 + + This release adds support for exponential backoff w/jitter, experimental + support for detecting a dirty connection, as well as many other fixes + and improvements. + + You can find a detailed list of changes in Changelog.md and package.xml + or by inspecting the git commit logs. + + --- Sponsors --- + + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec + + --- + + phpredis 5.3.5 + + * Fix typo in cluster_scan_resp [44affad2] (Michael Grunder) + + --- + + phpredis 5.3.5RC1 + + * Fixed segfault in redis_setoption_handler [692e4e84] (Pavlo Yatsukhnenko) + * Fix masters array in the event of a cluster failover [bce692962] (Bar Shaul) + * Fix 32 bit type error [672dec87f] (Remi Collet) + * Fix radix character in certain locales [89a871e24] (Pavlo Yatsukhnenko) + * ZSTD Validation fix [6a77ef5cd] (Michael Grunder) + * Remove superfluous typecast [b2871471f] (Remi Collet) + + * Updated documentation [f84168657, d017788e7, 20ac84710, 0adf05260, + aee29bf73, 09a095e72, 12ffbf33a, ff331af98, a6bdb8731, 305c15840, + 1aa10e93a, d78b0c79d, c6d37c27c, a6303f5b9, d144bd2c7, a6fb815ef, 9ef862bc6] + (neodisco, Clement Tessier, T. Todua, dengliming, Maxime Cornet, + Emanuele Filannino Michael Grunder) + + * Travis CI Fixes + [a43f4586e, 4fde8178f, 7bd5415ac, fdb8c4bb7, d4f407470] + (Pavlo Yatsukhnenko) + + * Minor fixes/cleanup + [2e190adc1, 99975b592, 9d0879fa5, 22b06457b] + (Pavlo Yatsukhnenko) + + * Fix RedisArray constructor bug + [85dc883ba](https://github.com/phpredis/phpredis/commit/85dc883ba) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) + + * Moved to GitHub Actions + [4d2afa786, 502d09fd5] (Pavlo Yatsukhnenko) + + * Use more appropriate array iteration macro + [6008900c2] (Pavlo Yatsukhnenko) + + * Clean up session tests + [ab25ae7f3] (Michael Grunder) + + * RedisArray refactors [1250f0001, 017b2ea7f, 37ed3f079] + (Pavlo Yatsukhnenko) + + * Use zend_parse_parameters_none helper + [a26b14dbe] (Remi Collet) + + * Support for various exponential backoff strategies + [#1986, #1993, 732eb8dcb, 05129c3a3, 5bba6a7fc], + (Nathaniel Braun) + + * Added experimental support for detecting a dirty connection + [d68579562] (Michael Grunder) + + * Created distinct compression utility methods (pack/unpack) + [#1939, da2790aec] (Michael Grunder) + + * SMISMEMBER Command + [#1894, ae2382472, ed283e1ab] (Pavlo Yatsukhnenko) + + + + stablestable + 5.3.45.3.4 + 2021-03-24 + + phpredis 5.3.4 + + This release fixes a multi/pipeline segfault on apple silicon as well as + two small compression related bugs. + + You can find a detailed list of changes in Changelog.md and package.xml + + * Fix multi/pipeline segfault on Apple silicon [e0796d48] (Michael Grunder) + * Pass compression flag on HMGET in RedisCluster [edc724e6] (Adam Olley) + * Abide by ZSTD error return constants [8400ed1c] (Michael Grunder) + * Fix timing related CI session tests [9b986bf8] (Michael Grunder) + + * Sponsors + ~ Audiomack - https://audiomack.com + ~ Open LMS - https://openlms.net + ~ BlueHost - https://bluehost.com + ~ Object Cache Pro for WordPress - https://objectcache.pro + ~ Avtandil Kikabidze - https://github.com/akalongman + ~ Zaher Ghaibeh - https://github.com/zaherg + ~ BatchLabs - https://batch.com + + + + stablestable + 5.3.35.3.3 + 2021-02-01 + + phpredis 5.3.3 + + This release mostly includes just small PHP 8 Windows compatibility fixes + such that pecl.php.net can automatically build Windows DLLs. + + You can find a detailed list of changes in Changelog.md and package.xml + + * Fix PHP8 Windows includes [270b4db8] (Jan-E) + * Fix hash ops for php 8.0.1 [87297cbb] (defender-11) + * Disable cloning Redis and RedisCluster objects [cd05a344] + (Michael Grunder) + + * Sponsors + ~ Audiomack - https://audiomack.com + ~ BlueHost - https://bluehost.com + ~ Redis Cache Pro for WordPress - https://wprediscache.com + ~ Avtandil Kikabidze - https://github.com/akalongman + ~ Zaher Ghaibeh - https://github.com/zaherg + ~ BatchLabs - https://batch.com + + + + + stablestable + 5.3.25.3.2 + 2020-10-22 + + This release containse some bugfixes and small improvements. + You can find a detailed list of changes in Changelog.md and package.xml + + * Sponsors + ~ Audiomack - https://audiomack.com + ~ BlueHost - https://bluehost.com + ~ Redis Cache Pro for WordPress - https://wprediscache.com + ~ Avtandil Kikabidze - https://github.com/akalongman + ~ Oleg Babushkin - https://github.com/olbabushkin + + phpredis 5.3.2 + + * Use "%.17g" sprintf format for doubles as done in Redis server. [32be3006] (Pavlo Yatsukhnenko) + * Allow to pass NULL as RedisCluster stream context options. [72024afe] (Pavlo Yatsukhnenko) + + --- + + phpredis 5.3.2RC2 + + --- + + * Verify SET options are strings before testing them as strings [514bc371] (Michael Grunder) + + --- + + phpredis 5.3.2RC1 + + --- + * Fix cluster segfault when dealing with NULL multi bulk replies in RedisCluster [950e8de8] (Michael Grunder, Alex Offshore) + * Fix xReadGroup() must return message id [500916a4] (Pavlo Yatsukhnenko) + * Fix memory leak in rediscluster session handler [b2cffffc] (Pavlo Yatsukhnenko) + * Fix XInfo() returns false if the stream is empty [5719c9f7, 566fdeeb] (Pavlo Yatsukhnenko, Michael Grunder) + * Relax requirements on set's expire argument [36458071] (Michael Grunder) + * Refactor redis_sock_check_liveness [c5950644] (Pavlo Yatsukhnenko) + * PHP8 compatibility [a7662da7, f4a30cb2, 17848791] (Pavlo Yatsukhnenko, Remi Collet) + * Update documentation [c9ed151d, 398c99d9] (Ali Alwash, Gregoire Pineau) + * Add Redis::OPT_NULL_MULTIBULK_AS_NULL setting to treat NULL multi bulk replies as NULL instead of []. [950e8de8] (Michael Grunder, Alex Offshore) + * Allow to specify stream context for rediscluster session handler [a8daaff8, 4fbe7df7] (Pavlo Yatsukhnenko) + * Add new parameter to RedisCluster to specify stream ssl/tls context. [f771ea16] (Pavlo Yatsukhnenko) + * Add new parameter to RedisSentinel to specify auth information [81c502ae] (Pavlo Yatsukhnenko) + + + + + stablestable + 5.3.15.3.1 + 2020-07-07 + + phpredis 5.3.1 + + This is a small bugfix release that fixes a couple of issues in 5.3.0. + + You should upgrade if you're using persistent_id in session.save_path or + of if you're having trouble building 5.3.0 because the php_hash_bin2hex + symbol is missing. + + You can find a detailed list of changes in Changelog.md and package.xml + + * Sponsors + ~ Audiomack - https://audiomack.com + ~ BlueHost - https://bluehost.com + ~ Redis Cache Pro for WordPress - https://wprediscache.com + ~ Avtandil Kikabidze - https://github.com/akalongman + + --- + * Properly clean up on session start failure [066cff6a] (Michael Grunder) + * Treat NULL as a failure for redis_extract_auth_info [49428a2f, 14ac969d] + (Michael Grunder) + * Don't dereference a NULL zend_string or efree one [ff2e160f, 7fed06f2] + (Michael Grunder) + * Fix config.m4 messages and test for and include php_hash.h [83a1b7c5, + 3c56289c, 08f202e7] (Remi Collet) + * Add openSUSE installation instructions [13a168f4] (Pavlo Yatsukhnenko) + * Remove EOL Fedora installation instructions [b4779e6a] (Remi Collet) + + stablestable 5.3.05.3.0 @@ -223,6 +511,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> (Michael Grunder) + stablestable 5.2.25.2.2 @@ -242,6 +531,7 @@ http://pear.php.net/dtd/package-2.0.xsd"> ~ Till Kruss - https://github.com/tillkruss + stablestable 5.2.15.2.1 diff --git a/php_redis.h b/php_redis.h index 76983fe0df..9b639bcd57 100644 --- a/php_redis.h +++ b/php_redis.h @@ -23,7 +23,7 @@ #define PHP_REDIS_H /* phpredis version */ -#define PHP_REDIS_VERSION "5.3.1" +#define PHP_REDIS_VERSION "5.3.7" PHP_METHOD(Redis, __construct); PHP_METHOD(Redis, __destruct); @@ -94,6 +94,7 @@ PHP_METHOD(Redis, sDiffStore); PHP_METHOD(Redis, sInter); PHP_METHOD(Redis, sInterStore); PHP_METHOD(Redis, sMembers); +PHP_METHOD(Redis, sMisMember); PHP_METHOD(Redis, sMove); PHP_METHOD(Redis, sPop); PHP_METHOD(Redis, sRandMember); @@ -157,9 +158,15 @@ PHP_METHOD(Redis, role); PHP_METHOD(Redis, getLastError); PHP_METHOD(Redis, clearLastError); PHP_METHOD(Redis, _prefix); +PHP_METHOD(Redis, _pack); +PHP_METHOD(Redis, _unpack); + PHP_METHOD(Redis, _serialize); PHP_METHOD(Redis, _unserialize); +PHP_METHOD(Redis, _compress); +PHP_METHOD(Redis, _uncompress); + PHP_METHOD(Redis, mset); PHP_METHOD(Redis, msetnx); PHP_METHOD(Redis, rpoplpush); @@ -273,11 +280,6 @@ PHP_MINIT_FUNCTION(redis); PHP_MSHUTDOWN_FUNCTION(redis); PHP_MINFO_FUNCTION(redis); -/* Redis response handler function callback prototype */ -typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); - -typedef int (*FailableResultCallback)(INTERNAL_FUNCTION_PARAMETERS, RedisSock*, zval*, void*); - PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent); PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock); diff --git a/redis.c b/redis.c index 821e8bcbba..d63beecc14 100644 --- a/redis.c +++ b/redis.c @@ -98,6 +98,8 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("redis.pconnect.pooling_enabled", "1", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.pconnect.connection_limit", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.pconnect.echo_check_liveness", "1", PHP_INI_ALL, NULL) + PHP_INI_ENTRY("redis.pconnect.pool_detect_dirty", "0", PHP_INI_ALL, NULL) + PHP_INI_ENTRY("redis.pconnect.pool_poll_timeout", "0", PHP_INI_ALL, NULL) PHP_INI_ENTRY("redis.pconnect.pool_pattern", "", PHP_INI_ALL, NULL) /* redis session */ @@ -241,22 +243,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO() -/** - * Argument info for key scanning - */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) - ZEND_ARG_INFO(0, str_key) - ZEND_ARG_INFO(1, i_iterator) - ZEND_ARG_INFO(0, str_pattern) - ZEND_ARG_INFO(0, i_count) -ZEND_END_ARG_INFO() - static zend_function_entry redis_functions[] = { PHP_ME(Redis, __construct, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(Redis, __destruct, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(Redis, _prefix, arginfo_key, ZEND_ACC_PUBLIC) PHP_ME(Redis, _serialize, arginfo_value, ZEND_ACC_PUBLIC) PHP_ME(Redis, _unserialize, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(Redis, _pack, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(Redis, _unpack, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(Redis, _compress, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(Redis, _uncompress, arginfo_value, ZEND_ACC_PUBLIC) PHP_ME(Redis, acl, arginfo_acl, ZEND_ACC_PUBLIC) PHP_ME(Redis, append, arginfo_key_value, ZEND_ACC_PUBLIC) PHP_ME(Redis, auth, arginfo_auth, ZEND_ACC_PUBLIC) @@ -385,6 +381,7 @@ static zend_function_entry redis_functions[] = { PHP_ME(Redis, sInter, arginfo_nkeys, ZEND_ACC_PUBLIC) PHP_ME(Redis, sInterStore, arginfo_dst_nkeys, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMembers, arginfo_key, ZEND_ACC_PUBLIC) + PHP_ME(Redis, sMisMember, arginfo_key_members, ZEND_ACC_PUBLIC) PHP_ME(Redis, sMove, arginfo_smove, ZEND_ACC_PUBLIC) PHP_ME(Redis, sPop, arginfo_key, ZEND_ACC_PUBLIC) PHP_ME(Redis, sRandMember, arginfo_srand_member, ZEND_ACC_PUBLIC) @@ -611,6 +608,7 @@ create_redis_object(zend_class_entry *ce) memcpy(&redis_object_handlers, zend_get_std_object_handlers(), sizeof(redis_object_handlers)); redis_object_handlers.offset = XtOffsetOf(redis_object, std); redis_object_handlers.free_obj = free_redis_object; + redis_object_handlers.clone_obj = NULL; redis->std.handlers = &redis_object_handlers; return &redis->std; @@ -753,6 +751,9 @@ static void add_class_constants(zend_class_entry *ce, int is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_PREFIX"), REDIS_SCAN_PREFIX); zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_NOPREFIX"), REDIS_SCAN_NOPREFIX); + zend_declare_class_constant_stringl(ce, "AFTER", 5, "after", 5); + zend_declare_class_constant_stringl(ce, "BEFORE", 6, "before", 6); + /* Cluster option to allow for slave failover */ if (is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SLAVE_FAILOVER"), REDIS_OPT_FAILOVER); @@ -762,8 +763,21 @@ static void add_class_constants(zend_class_entry *ce, int is_cluster) { zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_DISTRIBUTE_SLAVES"), REDIS_FAILOVER_DISTRIBUTE_SLAVES); } - zend_declare_class_constant_stringl(ce, "AFTER", 5, "after", 5); - zend_declare_class_constant_stringl(ce, "BEFORE", 6, "before", 6); + /* retry/backoff options*/ + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_MAX_RETRIES"), REDIS_OPT_MAX_RETRIES); + + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_ALGORITHM"), REDIS_OPT_BACKOFF_ALGORITHM); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_DEFAULT"), REDIS_BACKOFF_ALGORITHM_DEFAULT); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_CONSTANT"), REDIS_BACKOFF_ALGORITHM_CONSTANT); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_UNIFORM"), REDIS_BACKOFF_ALGORITHM_UNIFORM); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_EXPONENTIAL"), REDIS_BACKOFF_ALGORITHM_EXPONENTIAL); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_FULL_JITTER"), REDIS_BACKOFF_ALGORITHM_FULL_JITTER); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_EQUAL_JITTER"), REDIS_BACKOFF_ALGORITHM_EQUAL_JITTER); + zend_declare_class_constant_long(ce, ZEND_STRL("BACKOFF_ALGORITHM_DECORRELATED_JITTER"), REDIS_BACKOFF_ALGORITHM_DECORRELATED_JITTER); + + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_BASE"), REDIS_OPT_BACKOFF_BASE); + + zend_declare_class_constant_long(ce, ZEND_STRL("OPT_BACKOFF_CAP"), REDIS_OPT_BACKOFF_CAP); } static ZEND_RSRC_DTOR_FUNC(redis_connections_pool_dtor) @@ -967,7 +981,7 @@ PHP_MINFO_FUNCTION(redis) Public constructor */ PHP_METHOD(Redis, __construct) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { + if (zend_parse_parameters_none() == FAILURE) { RETURN_FALSE; } } @@ -977,7 +991,7 @@ PHP_METHOD(Redis, __construct) Public Destructor */ PHP_METHOD(Redis,__destruct) { - if(zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { + if (zend_parse_parameters_none() == FAILURE) { RETURN_FALSE; } @@ -1108,10 +1122,10 @@ redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) } /* {{{ proto long Redis::bitop(string op, string key, ...) */ -PHP_METHOD(Redis, bitop) -{ +PHP_METHOD(Redis, bitop) { REDIS_PROCESS_CMD(bitop, redis_long_response); } + /* }}} */ /* {{{ proto long Redis::bitcount(string key, [int start], [int end]) @@ -1247,8 +1261,7 @@ PHP_METHOD(Redis, incrBy){ /* {{{ proto float Redis::incrByFloat(string key, float value) */ PHP_METHOD(Redis, incrByFloat) { - REDIS_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd, - redis_bulk_double_response); + REDIS_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd, redis_bulk_double_response); } /* }}} */ @@ -1344,10 +1357,10 @@ PHP_REDIS_API void redis_set_watch(RedisSock *redis_sock) redis_sock->watching = 1; } -PHP_REDIS_API void redis_watch_response(INTERNAL_FUNCTION_PARAMETERS, +PHP_REDIS_API int redis_watch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + return redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx, redis_set_watch); } @@ -1364,12 +1377,12 @@ PHP_REDIS_API void redis_clear_watch(RedisSock *redis_sock) redis_sock->watching = 0; } -PHP_REDIS_API void redis_unwatch_response(INTERNAL_FUNCTION_PARAMETERS, +PHP_REDIS_API int redis_unwatch_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { - redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, - z_tab, ctx, redis_clear_watch); + return redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + z_tab, ctx, redis_clear_watch); } /* {{{ proto boolean Redis::unwatch() @@ -1684,6 +1697,12 @@ PHP_METHOD(Redis, sMembers) REDIS_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd, redis_sock_read_multibulk_reply); } + +/* {{{ proto array Redis::sMisMember(string key, string member0, ...memberN) */ +PHP_METHOD(Redis, sMisMember) +{ + REDIS_PROCESS_KW_CMD("SMISMEMBER", redis_key_varval_cmd, redis_read_variant_reply); +} /* }}} */ /* {{{ proto array Redis::sInter(string key0, ... string keyN) */ @@ -2055,7 +2074,7 @@ PHP_METHOD(Redis, move) { /* }}} */ static -void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, ResultCallback fun) +void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, FailableResultCallback fun) { RedisSock *redis_sock; smart_string cmd = {0}; @@ -2101,6 +2120,7 @@ void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, ResultCallback fun) if (IS_ATOMIC(redis_sock)) { fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL); } + REDIS_PROCESS_RESPONSE(fun); } @@ -3244,6 +3264,51 @@ PHP_METHOD(Redis, _unserialize) { redis_exception_ce); } +PHP_METHOD(Redis, _compress) { + RedisSock *redis_sock; + + // Grab socket + if ((redis_sock = redis_sock_get_instance(getThis(), 0)) == NULL) { + RETURN_FALSE; + } + + redis_compress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); +} + +PHP_METHOD(Redis, _uncompress) { + RedisSock *redis_sock; + + // Grab socket + if ((redis_sock = redis_sock_get_instance(getThis(), 0)) == NULL) { + RETURN_FALSE; + } + + redis_uncompress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, + redis_exception_ce); +} + +PHP_METHOD(Redis, _pack) { + RedisSock *redis_sock; + + // Grab socket + if ((redis_sock = redis_sock_get_instance(getThis(), 0)) == NULL) { + RETURN_FALSE; + } + + redis_pack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); +} + +PHP_METHOD(Redis, _unpack) { + RedisSock *redis_sock; + + // Grab socket + if ((redis_sock = redis_sock_get_instance(getThis(), 0)) == NULL) { + RETURN_FALSE; + } + + redis_unpack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock); +} + /* {{{ proto Redis::getLastError() */ PHP_METHOD(Redis, getLastError) { zval *object; @@ -3418,8 +3483,9 @@ PHP_METHOD(Redis, getAuth) { RedisSock *redis_sock; zval zret; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) + if (zend_parse_parameters_none() == FAILURE) { RETURN_FALSE; + } redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU); if (redis_sock == NULL) @@ -3476,8 +3542,7 @@ PHP_METHOD(Redis, client) { /* We handle CLIENT LIST with a custom response function */ if(!strncasecmp(opt, "list", 4)) { if (IS_ATOMIC(redis_sock)) { - redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, - NULL); + redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, NULL, NULL); } REDIS_PROCESS_RESPONSE(redis_client_list_reply); } else { diff --git a/redis_array.c b/redis_array.c index 998d6a2324..2cf4d9e2cb 100644 --- a/redis_array.c +++ b/redis_array.c @@ -20,18 +20,16 @@ #endif #include "common.h" -#include "ext/standard/info.h" -#include "php_ini.h" -#include "php_redis.h" -#include - #include "library.h" #include "redis_array.h" #include "redis_array_impl.h" +#include +#include + /* Simple macro to detect failure in a RedisArray call */ #define RA_CALL_FAILED(rv, cmd) ( \ - PHPREDIS_ZVAL_IS_STRICT_FALSE(rv) || \ + (Z_TYPE_P(rv) == IS_FALSE) || \ (Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \ (Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE")) \ ) @@ -99,6 +97,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_flush, 0, 0, 0) ZEND_ARG_INFO(0, async) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 2) + ZEND_ARG_INFO(1, iterator) + ZEND_ARG_INFO(0, node) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, __call, arginfo_call, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, __construct, arginfo_ctor, ZEND_ACC_PUBLIC) @@ -116,6 +121,7 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, flushall, arginfo_flush, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushdb, arginfo_flush, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, getOption, arginfo_getopt, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, hscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, info, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, keys, arginfo_keys, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mget, arginfo_mget, ZEND_ACC_PUBLIC) @@ -123,10 +129,13 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, multi, arginfo_multi, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, ping, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, save, arginfo_void, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, scan, arginfo_scan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, select, arginfo_select, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption,arginfo_setopt, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unlink, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unwatch, arginfo_void, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, delete, del, arginfo_del, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, getMultiple, mget, arginfo_mget, ZEND_ACC_PUBLIC) PHP_FE_END @@ -228,7 +237,7 @@ PHP_METHOD(RedisArray, __construct) RedisArray *ra = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0; HashTable *hPrev = NULL, *hOpts = NULL; - long l_retry_interval = 0; + zend_long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0, read_timeout = 0.0; zend_string *algorithm = NULL, *user = NULL, *pass = NULL; @@ -242,8 +251,14 @@ PHP_METHOD(RedisArray, __construct) * Note: WRONG_PARAM_COUNT seems wrong but this is what we have been doing * for ages so we can't really change it until the next major version. */ - if (Z_TYPE_P(z0) != IS_ARRAY && Z_TYPE_P(z0) != IS_STRING) + if (Z_TYPE_P(z0) != IS_ARRAY && Z_TYPE_P(z0) != IS_STRING) { +#if PHP_VERSION_ID < 80000 WRONG_PARAM_COUNT; +#else + zend_argument_type_error(1, "must be of type string|array, %s given", zend_zval_type_name(z0)); + RETURN_THROWS(); +#endif + } /* If it's a string we want to load the array from ini information */ if (Z_TYPE_P(z0) == IS_STRING) { @@ -338,7 +353,7 @@ ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, /* pass call through */ ZVAL_STRINGL(&z_fun, cmd, cmd_len); /* method name */ - z_callargs = ecalloc(argc, sizeof(zval)); + z_callargs = ecalloc(argc, sizeof(*z_callargs)); /* copy args to array */ i = 0; @@ -475,12 +490,11 @@ PHP_METHOD(RedisArray, _instance) { zval *object; RedisArray *ra; - char *target; - size_t target_len; + zend_string *host; zval *z_redis; - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", - &object, redis_array_ce, &target, &target_len) == FAILURE) { + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OS", + &object, redis_array_ce, &host) == FAILURE) { RETURN_FALSE; } @@ -488,17 +502,15 @@ PHP_METHOD(RedisArray, _instance) RETURN_FALSE; } - z_redis = ra_find_node_by_name(ra, target, target_len); - if(z_redis) { - RETURN_ZVAL(z_redis, 1, 0); - } else { + if ((z_redis = ra_find_node_by_name(ra, host)) == NULL) { RETURN_NULL(); } + RETURN_ZVAL(z_redis, 1, 0); } PHP_METHOD(RedisArray, _function) { - zval *object, *z_fun; + zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", @@ -510,13 +522,12 @@ PHP_METHOD(RedisArray, _function) RETURN_FALSE; } - z_fun = &ra->z_fun; - RETURN_ZVAL(z_fun, 1, 0); + RETURN_ZVAL(&ra->z_fun, 1, 0); } PHP_METHOD(RedisArray, _distributor) { - zval *object, *z_dist; + zval *object; RedisArray *ra; if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O", @@ -528,8 +539,7 @@ PHP_METHOD(RedisArray, _distributor) RETURN_FALSE; } - z_dist = &ra->z_dist; - RETURN_ZVAL(z_dist, 1, 0); + RETURN_ZVAL(&ra->z_dist, 1, 0); } PHP_METHOD(RedisArray, _rehash) @@ -818,12 +828,10 @@ PHP_METHOD(RedisArray, select) /* MGET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mget) { - zval *object, *z_keys, z_argarray, *data, z_ret, *z_cur, z_tmp_array; - int i, j, n; - RedisArray *ra; - int *pos, argc, *argc_each; + zval *object, *z_keys, *data, z_ret, *z_cur, z_tmp_array, z_fun, z_arg, **argv; + int i, j, n, *pos, argc, *argc_each; HashTable *h_keys; - zval **argv; + RedisArray *ra; if ((ra = redis_array_get(getThis())) == NULL) { RETURN_FALSE; @@ -843,11 +851,10 @@ PHP_METHOD(RedisArray, mget) if ((argc = zend_hash_num_elements(h_keys)) == 0) { RETURN_FALSE; } - argv = emalloc(argc * sizeof(zval*)); - pos = emalloc(argc * sizeof(int)); + argv = ecalloc(argc, sizeof(*argv)); + pos = ecalloc(argc, sizeof(*pos)); - argc_each = emalloc(ra->count * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); + argc_each = ecalloc(ra->count, sizeof(*argc_each)); /* associate each key to a redis node */ i = 0; @@ -859,95 +866,87 @@ PHP_METHOD(RedisArray, mget) /* Handle the possibility that we're a reference */ ZVAL_DEREF(data); - /* phpredis proper can only use string or long keys, so restrict to that here */ - if (Z_TYPE_P(data) != IS_STRING && Z_TYPE_P(data) != IS_LONG) { - php_error_docref(NULL, E_ERROR, "MGET: all keys must be strings or longs"); - efree(argv); - efree(pos); - efree(argc_each); - RETURN_FALSE; - } - /* Convert to a string for hash lookup if it isn't one */ if (Z_TYPE_P(data) == IS_STRING) { key_len = Z_STRLEN_P(data); key_lookup = Z_STRVAL_P(data); - } else { + } else if (Z_TYPE_P(data) == IS_LONG) { key_len = snprintf(kbuf, sizeof(kbuf), ZEND_LONG_FMT, Z_LVAL_P(data)); key_lookup = (char*)kbuf; + } else { + /* phpredis proper can only use string or long keys, so restrict to that here */ + php_error_docref(NULL, E_ERROR, "MGET: all keys must be strings or longs"); + RETVAL_FALSE; + goto cleanup; } /* Find our node */ if (ra_find_node(ra, key_lookup, key_len, &pos[i]) == NULL) { - /* TODO: handle */ + RETVAL_FALSE; + goto cleanup; } argc_each[pos[i]]++; /* count number of keys per node */ argv[i++] = data; } ZEND_HASH_FOREACH_END(); + /* prepare call */ array_init(&z_tmp_array); + ZVAL_STRINGL(&z_fun, "MGET", sizeof("MGET") - 1); + /* calls */ for(n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ if(!argc_each[n]) continue; /* copy args for MGET call on node. */ - array_init(&z_argarray); + array_init(&z_arg); for(i = 0; i < argc; ++i) { - if(pos[i] != n) continue; - - zval z_ret; - ZVAL_ZVAL(&z_ret, argv[i], 1, 0); - add_next_index_zval(&z_argarray, &z_ret); + if (pos[i] == n) { + ZVAL_ZVAL(&z_ret, argv[i], 1, 0); + add_next_index_zval(&z_arg, &z_ret); + } } - zval z_fun; - /* prepare call */ - ZVAL_STRINGL(&z_fun, "MGET", 4); /* call MGET on the node */ - call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); - zval_dtor(&z_fun); + call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_arg); /* cleanup args array */ - zval_dtor(&z_argarray); + zval_dtor(&z_arg); /* Error out if we didn't get a proper response */ if (Z_TYPE(z_ret) != IS_ARRAY) { /* cleanup */ zval_dtor(&z_ret); zval_dtor(&z_tmp_array); - efree(argv); - efree(pos); - efree(argc_each); - - /* failure */ - RETURN_FALSE; + RETVAL_FALSE; + goto cleanup; } for(i = 0, j = 0; i < argc; ++i) { if (pos[i] != n || (z_cur = zend_hash_index_find(Z_ARRVAL(z_ret), j++)) == NULL) continue; - zval z_ret; - ZVAL_ZVAL(&z_ret, z_cur, 1, 0); - add_index_zval(&z_tmp_array, i, &z_ret); + ZVAL_ZVAL(&z_arg, z_cur, 1, 0); + add_index_zval(&z_tmp_array, i, &z_arg); } zval_dtor(&z_ret); } + zval_dtor(&z_fun); + array_init(return_value); /* copy temp array in the right order to return_value */ for(i = 0; i < argc; ++i) { if ((z_cur = zend_hash_index_find(Z_ARRVAL(z_tmp_array), i)) == NULL) continue; - zval z_ret; - ZVAL_ZVAL(&z_ret, z_cur, 1, 0); - add_next_index_zval(return_value, &z_ret); + ZVAL_ZVAL(&z_arg, z_cur, 1, 0); + add_next_index_zval(return_value, &z_arg); } /* cleanup */ zval_dtor(&z_tmp_array); +cleanup: efree(argv); efree(pos); efree(argc_each); @@ -957,13 +956,11 @@ PHP_METHOD(RedisArray, mget) /* MSET will distribute the call to several nodes and regroup the values. */ PHP_METHOD(RedisArray, mset) { - zval *object, *z_keys, z_argarray, *data, z_ret, **argv; - int i = 0, n; + zval *object, *z_keys, z_argarray, *data, z_fun, z_ret, **argv; + int i = 0, n, *pos, argc, *argc_each, key_len; RedisArray *ra; - int *pos, argc, *argc_each; HashTable *h_keys; char *key, kbuf[40]; - int key_len; zend_string **keys, *zkey; zend_ulong idx; @@ -985,12 +982,11 @@ PHP_METHOD(RedisArray, mset) if ((argc = zend_hash_num_elements(h_keys)) == 0) { RETURN_FALSE; } - argv = emalloc(argc * sizeof(zval*)); - pos = emalloc(argc * sizeof(int)); - keys = ecalloc(argc, sizeof(zend_string *)); + argv = ecalloc(argc, sizeof(*argv)); + pos = ecalloc(argc, sizeof(*pos)); + keys = ecalloc(argc, sizeof(*keys)); - argc_each = emalloc(ra->count * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); + argc_each = ecalloc(ra->count, sizeof(*argc_each)); /* associate each key to a redis node */ ZEND_HASH_FOREACH_KEY_VAL(h_keys, idx, zkey, data) { @@ -1004,16 +1000,26 @@ PHP_METHOD(RedisArray, mset) } if (ra_find_node(ra, key, (int)key_len, &pos[i]) == NULL) { - // TODO: handle + for (n = 0; n < i; ++n) { + zend_string_release(keys[n]); + } + efree(keys); + efree(argv); + efree(pos); + efree(argc_each); + RETURN_FALSE; } argc_each[pos[i]]++; /* count number of keys per node */ - keys[i] = zend_string_init(key, key_len, 0); + keys[i] = zkey ? zend_string_copy(zkey) : zend_string_init(key, key_len, 0); argv[i] = data; i++; } ZEND_HASH_FOREACH_END(); + /* prepare call */ + ZVAL_STRINGL(&z_fun, "MSET", sizeof("MSET") - 1); + /* calls */ for (n = 0; n < ra->count; ++n) { /* for each node */ /* We don't even need to make a call to this node if no keys go there */ @@ -1026,7 +1032,6 @@ PHP_METHOD(RedisArray, mset) for(i = 0; i < argc; ++i) { if(pos[i] != n) continue; - zval z_ret; if (argv[i] == NULL) { ZVAL_NULL(&z_ret); } else { @@ -1043,26 +1048,19 @@ PHP_METHOD(RedisArray, mset) if(ra->index) { /* add MULTI */ ra_index_multi(&ra->redis[n], MULTI); - } - - zval z_fun; - - /* prepare call */ - ZVAL_STRINGL(&z_fun, "MSET", 4); - - /* call */ - call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); - zval_dtor(&z_fun); - zval_dtor(&z_ret); - - if(ra->index) { + call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); ra_index_keys(&z_argarray, &ra->redis[n]); /* use SADD to add keys to node index */ ra_index_exec(&ra->redis[n], NULL, 0); /* run EXEC */ + } else { + call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); } zval_dtor(&z_argarray); + zval_dtor(&z_ret); } + zval_dtor(&z_fun); + /* Free any keys that we needed to allocate memory for, because they weren't strings */ for(i = 0; i < argc; i++) { zend_string_release(keys[i]); @@ -1078,15 +1076,14 @@ PHP_METHOD(RedisArray, mset) } /* Generic handler for DEL or UNLINK which behave identically to phpredis */ -static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { - zval *object, z_keys, z_fun, *data, z_ret, *z_args; - int i, n; - RedisArray *ra; - int *pos, argc = ZEND_NUM_ARGS(), *argc_each; +static void +ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) +{ + zval *object, z_keys, z_fun, *data, z_ret, *z_args, **argv; + int i, n, *pos, argc = ZEND_NUM_ARGS(), *argc_each, free_zkeys = 0; HashTable *h_keys; - zval **argv; + RedisArray *ra; long total = 0; - int free_zkeys = 0; if ((ra = redis_array_get(getThis())) == NULL) { RETURN_FALSE; @@ -1096,7 +1093,7 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { HANDLE_MULTI_EXEC(ra, kw, kw_len); /* get all args in z_args */ - z_args = emalloc(argc * sizeof(zval)); + z_args = ecalloc(argc, sizeof(*z_args)); if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { efree(z_args); RETURN_FALSE; @@ -1109,10 +1106,7 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { /* copy all elements to z_keys */ array_init(&z_keys); for (i = 0; i < argc; ++i) { - zval *z_arg = &z_args[i]; - zval z_ret; - ZVAL_ZVAL(&z_ret, z_arg, 1, 0); - /* add copy to z_keys */ + ZVAL_ZVAL(&z_ret, &z_args[i], 1, 0); add_next_index_zval(&z_keys, &z_ret); } free_zkeys = 1; @@ -1125,27 +1119,23 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { efree(z_args); RETURN_FALSE; } - argv = emalloc(argc * sizeof(zval*)); - pos = emalloc(argc * sizeof(int)); + argv = ecalloc(argc, sizeof(*argv)); + pos = ecalloc(argc, sizeof(*pos)); - argc_each = emalloc(ra->count * sizeof(int)); - memset(argc_each, 0, ra->count * sizeof(int)); + argc_each = ecalloc(ra->count, sizeof(*argc_each)); /* associate each key to a redis node */ i = 0; ZEND_HASH_FOREACH_VAL(h_keys, data) { if (Z_TYPE_P(data) != IS_STRING) { php_error_docref(NULL, E_ERROR, "DEL: all keys must be string."); - if (free_zkeys) zval_dtor(&z_keys); - efree(z_args); - efree(argv); - efree(pos); - efree(argc_each); - RETURN_FALSE; + RETVAL_FALSE; + goto cleanup; } if (ra_find_node(ra, Z_STRVAL_P(data), Z_STRLEN_P(data), &pos[i]) == NULL) { - // TODO: handle + RETVAL_FALSE; + goto cleanup; } argc_each[pos[i]]++; /* count number of keys per node */ argv[i++] = data; @@ -1165,12 +1155,11 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { /* copy args */ array_init(&z_argarray); for(i = 0; i < argc; ++i) { - if(pos[i] != n) continue; - - zval z_ret; - ZVAL_ZVAL(&z_ret, argv[i], 1, 0); - add_next_index_zval(&z_argarray, &z_ret); - found++; + if (pos[i] == n) { + ZVAL_ZVAL(&z_ret, argv[i], 1, 0); + add_next_index_zval(&z_argarray, &z_ret); + found++; + } } if(!found) { /* don't run empty DEL or UNLINK commands */ @@ -1180,15 +1169,11 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { if(ra->index) { /* add MULTI */ ra_index_multi(&ra->redis[n], MULTI); - } - - /* call */ - call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); - - if(ra->index) { - zval_dtor(&z_ret); + call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); ra_index_del(&z_argarray, &ra->redis[n]); /* use SREM to remove keys from node index */ ra_index_exec(&ra->redis[n], &z_ret, 0); /* run EXEC */ + } else { + call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray); } total += Z_LVAL(z_ret); /* increment total */ @@ -1196,8 +1181,11 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { zval_dtor(&z_ret); } - /* cleanup */ zval_dtor(&z_fun); + + RETVAL_LONG(total); + +cleanup: efree(argv); efree(pos); efree(argc_each); @@ -1205,9 +1193,7 @@ static void ra_generic_del(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len) { if(free_zkeys) { zval_dtor(&z_keys); } - efree(z_args); - RETURN_LONG(total); } /* DEL will distribute the call to several nodes and regroup the values. */ @@ -1220,17 +1206,96 @@ PHP_METHOD(RedisArray, unlink) { ra_generic_del(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNLINK", sizeof("UNLINK") - 1); } +static void +ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, const char *kw, int kw_len) +{ + RedisArray *ra; + zend_string *key, *pattern = NULL; + zval *object, *redis_inst, *z_iter, z_fun, z_args[4]; + zend_long count = 0; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OSz/|S!l", + &object, redis_array_ce, &key, &z_iter, &pattern, &count) == FAILURE) { + RETURN_FALSE; + } + + if ((ra = redis_array_get(object)) == NULL) { + RETURN_FALSE; + } + + if ((redis_inst = ra_find_node(ra, ZSTR_VAL(key), ZSTR_LEN(key), NULL)) == NULL) { + php_error_docref(NULL, E_ERROR, "Could not find any redis servers for this key."); + RETURN_FALSE; + } + + ZVAL_STR(&z_args[0], key); + ZVAL_NEW_REF(&z_args[1], z_iter); + if (pattern) ZVAL_STR(&z_args[2], pattern); + ZVAL_LONG(&z_args[3], count); + + ZVAL_STRINGL(&z_fun, kw, kw_len); + call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, ZEND_NUM_ARGS(), z_args); + zval_dtor(&z_fun); + + ZVAL_ZVAL(z_iter, &z_args[1], 0, 1); +} + +PHP_METHOD(RedisArray, hscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSCAN", sizeof("HSCAN") - 1); +} + +PHP_METHOD(RedisArray, sscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SSCAN", sizeof("SSCAN") - 1); +} + +PHP_METHOD(RedisArray, zscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZSCAN", sizeof("ZSCAN") - 1); +} + +PHP_METHOD(RedisArray, scan) +{ + RedisArray *ra; + zend_string *host, *pattern = NULL; + zval *object, *redis_inst, *z_iter, z_fun, z_args[3]; + zend_long count = 0; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oz/S|S!l", + &object, redis_array_ce, &z_iter, &host, &pattern, &count) == FAILURE) { + RETURN_FALSE; + } + + if ((ra = redis_array_get(object)) == NULL) { + RETURN_FALSE; + } + + if ((redis_inst = ra_find_node_by_name(ra, host)) == NULL) { + RETURN_FALSE; + } + + ZVAL_NEW_REF(&z_args[0], z_iter); + if (pattern) ZVAL_STR(&z_args[1], pattern); + ZVAL_LONG(&z_args[2], count); + + ZVAL_STRING(&z_fun, "SCAN"); + call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, ZEND_NUM_ARGS() - 1, z_args); + zval_dtor(&z_fun); + + ZVAL_ZVAL(z_iter, &z_args[0], 0, 1); +} + PHP_METHOD(RedisArray, multi) { zval *object; RedisArray *ra; zval *z_redis; - char *host; - size_t host_len; + zend_string *host; zend_long multi_value = MULTI; - if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os|l", - &object, redis_array_ce, &host, &host_len, &multi_value) == FAILURE) { + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OS|l", + &object, redis_array_ce, &host, &multi_value) == FAILURE) { RETURN_FALSE; } @@ -1239,8 +1304,7 @@ PHP_METHOD(RedisArray, multi) } /* find node */ - z_redis = ra_find_node_by_name(ra, host, host_len); - if(!z_redis) { + if ((z_redis = ra_find_node_by_name(ra, host)) == NULL) { RETURN_FALSE; } diff --git a/redis_array.h b/redis_array.h index 34460b10a8..7cdc6057c2 100644 --- a/redis_array.h +++ b/redis_array.h @@ -1,42 +1,44 @@ #ifndef REDIS_ARRAY_H #define REDIS_ARRAY_H -#ifdef PHP_WIN32 +#if (defined(_MSC_VER) && _MSC_VER <= 1920) #include "win32/php_stdint.h" #else #include #endif #include "common.h" -PHP_METHOD(RedisArray, __construct); PHP_METHOD(RedisArray, __call); +PHP_METHOD(RedisArray, __construct); +PHP_METHOD(RedisArray, _continuum); +PHP_METHOD(RedisArray, _distributor); +PHP_METHOD(RedisArray, _function); PHP_METHOD(RedisArray, _hosts); -PHP_METHOD(RedisArray, _target); PHP_METHOD(RedisArray, _instance); -PHP_METHOD(RedisArray, _function); -PHP_METHOD(RedisArray, _distributor); PHP_METHOD(RedisArray, _rehash); -PHP_METHOD(RedisArray, _continuum); - -PHP_METHOD(RedisArray, select); -PHP_METHOD(RedisArray, info); -PHP_METHOD(RedisArray, ping); -PHP_METHOD(RedisArray, flushdb); +PHP_METHOD(RedisArray, _target); +PHP_METHOD(RedisArray, bgsave); +PHP_METHOD(RedisArray, del); +PHP_METHOD(RedisArray, discard); +PHP_METHOD(RedisArray, exec); PHP_METHOD(RedisArray, flushall); +PHP_METHOD(RedisArray, flushdb); +PHP_METHOD(RedisArray, getOption); +PHP_METHOD(RedisArray, hscan); +PHP_METHOD(RedisArray, info); +PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, mget); PHP_METHOD(RedisArray, mset); -PHP_METHOD(RedisArray, del); -PHP_METHOD(RedisArray, unlink); -PHP_METHOD(RedisArray, keys); -PHP_METHOD(RedisArray, getOption); -PHP_METHOD(RedisArray, setOption); -PHP_METHOD(RedisArray, save); -PHP_METHOD(RedisArray, bgsave); - PHP_METHOD(RedisArray, multi); -PHP_METHOD(RedisArray, exec); -PHP_METHOD(RedisArray, discard); +PHP_METHOD(RedisArray, ping); +PHP_METHOD(RedisArray, save); +PHP_METHOD(RedisArray, scan); +PHP_METHOD(RedisArray, select); +PHP_METHOD(RedisArray, setOption); +PHP_METHOD(RedisArray, sscan); +PHP_METHOD(RedisArray, unlink); PHP_METHOD(RedisArray, unwatch); +PHP_METHOD(RedisArray, zscan); typedef struct { uint32_t value; diff --git a/redis_array_impl.c b/redis_array_impl.c index 8d8ceceede..8c1bc6eef2 100644 --- a/redis_array_impl.c +++ b/redis_array_impl.c @@ -158,7 +158,7 @@ RedisArray *ra_load_array(const char *name) { zend_string *algorithm = NULL, *user = NULL, *pass = NULL; zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0, consistent = 0; - long l_retry_interval = 0; + zend_long l_retry_interval = 0; zend_bool b_lazy_connect = 0; double d_connect_timeout = 0, read_timeout = 0.0; HashTable *hHosts = NULL, *hPrev = NULL; @@ -494,7 +494,11 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos) void *ctx = emalloc(ops->context_size); unsigned char *digest = emalloc(ops->digest_size); +#if PHP_VERSION_ID >= 80100 + ops->hash_init(ctx,NULL); +#else ops->hash_init(ctx); +#endif ops->hash_update(ctx, (const unsigned char *)ZSTR_VAL(out), ZSTR_LEN(out)); ops->hash_final(digest, ctx); @@ -542,11 +546,11 @@ ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos) } zval * -ra_find_node_by_name(RedisArray *ra, const char *host, int host_len) { +ra_find_node_by_name(RedisArray *ra, zend_string *host) { int i; for(i = 0; i < ra->count; ++i) { - if (ZSTR_LEN(ra->hosts[i]) == host_len && strcmp(ZSTR_VAL(ra->hosts[i]), host) == 0) { + if (zend_string_equals(host, ra->hosts[i])) { return &ra->redis[i]; } } diff --git a/redis_array_impl.h b/redis_array_impl.h index 0ef5a73f10..acf7f0939a 100644 --- a/redis_array_impl.h +++ b/redis_array_impl.h @@ -1,7 +1,7 @@ #ifndef REDIS_ARRAY_IMPL_H #define REDIS_ARRAY_IMPL_H -#ifdef PHP_WIN32 +#if (defined(_MSC_VER) && _MSC_VER <= 1920) #include #else #include @@ -19,7 +19,7 @@ RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, zend_string *algorithm, zend_string *auth, zend_string *pass); -zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len); +zval *ra_find_node_by_name(RedisArray *ra, zend_string *host); zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos); void ra_init_function_table(RedisArray *ra); diff --git a/redis_cluster.c b/redis_cluster.c index ab9d55b7d5..7daf65c18b 100644 --- a/redis_cluster.c +++ b/redis_cluster.c @@ -110,6 +110,10 @@ zend_function_entry redis_cluster_functions[] = { PHP_ME(RedisCluster, _redir, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _serialize, arginfo_value, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, _unserialize, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, _compress, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, _uncompress, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, _pack, arginfo_value, ZEND_ACC_PUBLIC) + PHP_ME(RedisCluster, _unpack, arginfo_value, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, acl, arginfo_acl_cl, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, append, arginfo_key_value, ZEND_ACC_PUBLIC) PHP_ME(RedisCluster, bgrewriteaof, arginfo_key_or_address, ZEND_ACC_PUBLIC) @@ -333,6 +337,7 @@ zend_object * create_cluster_context(zend_class_entry *class_type) { memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers)); RedisCluster_handlers.offset = XtOffsetOf(redisCluster, std); RedisCluster_handlers.free_obj = free_cluster_context; + RedisCluster_handlers.clone_obj = NULL; cluster->std.handlers = &RedisCluster_handlers; @@ -376,7 +381,7 @@ static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double time c->flags->timeout = timeout; c->flags->read_timeout = read_timeout; c->flags->persistent = persistent; - c->waitms = timeout * 1000L; + c->waitms = (long)(1000 * (timeout + read_timeout)); /* Attempt to load slots from cache if caching is enabled */ if (CLUSTER_CACHING_ENABLED()) { @@ -1970,6 +1975,27 @@ PHP_METHOD(RedisCluster, _unserialize) { } /* }}} */ +PHP_METHOD(RedisCluster, _compress) { + redisCluster *c = GET_CONTEXT(); + redis_compress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags); +} + +PHP_METHOD(RedisCluster, _uncompress) { + redisCluster *c = GET_CONTEXT(); + redis_uncompress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, + redis_cluster_exception_ce); +} + +PHP_METHOD(RedisCluster, _pack) { + redisCluster *c = GET_CONTEXT(); + redis_pack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags); +} + +PHP_METHOD(RedisCluster, _unpack) { + redisCluster *c = GET_CONTEXT(); + redis_unpack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags); +} + /* {{{ proto array RedisCluster::_masters() */ PHP_METHOD(RedisCluster, _masters) { redisCluster *c = GET_CONTEXT(); diff --git a/redis_cluster.h b/redis_cluster.h index c6959fde10..41f40c1af7 100644 --- a/redis_cluster.h +++ b/redis_cluster.h @@ -13,7 +13,7 @@ redis_##name##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, \ &cmd_len, &slot) -/* Append information required to handle MULTI commands to the tail of our MULTI +/* Append information required to handle MULTI commands to the tail of our MULTI * linked list. */ #define CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx) \ clusterFoldItem *_item; \ @@ -69,8 +69,8 @@ CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \ RETURN_ZVAL(getThis(), 1, 0); \ } \ - resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); - + resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); + /* More generic processing, where only the keyword differs */ #define CLUSTER_PROCESS_KW_CMD(kw, cmdfunc, resp_func, readcmd) \ redisCluster *c = GET_CONTEXT(); \ @@ -89,7 +89,7 @@ CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \ RETURN_ZVAL(getThis(), 1, 0); \ } \ - resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); + resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx); /* Create cluster context */ zend_object *create_cluster_context(zend_class_entry *class_type); @@ -293,6 +293,10 @@ PHP_METHOD(RedisCluster, setoption); PHP_METHOD(RedisCluster, _prefix); PHP_METHOD(RedisCluster, _serialize); PHP_METHOD(RedisCluster, _unserialize); +PHP_METHOD(RedisCluster, _compress); +PHP_METHOD(RedisCluster, _uncompress); +PHP_METHOD(RedisCluster, _pack); +PHP_METHOD(RedisCluster, _unpack); PHP_METHOD(RedisCluster, _masters); PHP_METHOD(RedisCluster, _redir); diff --git a/redis_commands.c b/redis_commands.c index aa89b7c228..00484309fc 100644 --- a/redis_commands.c +++ b/redis_commands.c @@ -509,12 +509,6 @@ int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len, return cmdstr.len; } -/* ZRANGEBYSCORE/ZREVRANGEBYSCORE */ -#define IS_WITHSCORES_ARG(s, l) \ - (l == sizeof("withscores") - 1 && !strncasecmp(s, "withscores", l)) -#define IS_LIMIT_ARG(s, l) \ - (l == sizeof("limit") - 1 && !strncasecmp(s,"limit", l)) - /* ZRANGE/ZREVRANGE */ int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, int *withscores, @@ -540,7 +534,7 @@ int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, if (Z_TYPE_P(z_ws) == IS_ARRAY) { ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(z_ws), zkey, z_ele) { ZVAL_DEREF(z_ele); - if (IS_WITHSCORES_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey))) { + if (zend_string_equals_literal_ci(zkey, "withscores")) { *withscores = zval_is_true(z_ele); break; } @@ -570,11 +564,8 @@ int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, size_t key_len, start_len, end_len; zval *z_opt=NULL, *z_ele; zend_string *zkey; - zend_ulong idx; HashTable *ht_opt; - PHPREDIS_NOTUSED(idx); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len, &start, &start_len, &end, &end_len, &z_opt) ==FAILURE) @@ -585,14 +576,14 @@ int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, // Check for an options array if (z_opt && Z_TYPE_P(z_opt) == IS_ARRAY) { ht_opt = Z_ARRVAL_P(z_opt); - ZEND_HASH_FOREACH_KEY_VAL(ht_opt, idx, zkey, z_ele) { + ZEND_HASH_FOREACH_STR_KEY_VAL(ht_opt, zkey, z_ele) { /* All options require a string key type */ if (!zkey) continue; ZVAL_DEREF(z_ele); /* Check for withscores and limit */ - if (IS_WITHSCORES_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey))) { + if (zend_string_equals_literal_ci(zkey, "withscores")) { *withscores = zval_is_true(z_ele); - } else if (IS_LIMIT_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey)) && Z_TYPE_P(z_ele) == IS_ARRAY) { + } else if (zend_string_equals_literal_ci(zkey, "limit") && Z_TYPE_P(z_ele) == IS_ARRAY) { HashTable *htlimit = Z_ARRVAL_P(z_ele); zval *zoff, *zcnt; @@ -1349,13 +1340,10 @@ int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, if (z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) { HashTable *kt = Z_ARRVAL_P(z_opts); zend_string *zkey; - zend_ulong idx; zval *v; - PHPREDIS_NOTUSED(idx); - /* Iterate our option array */ - ZEND_HASH_FOREACH_KEY_VAL(kt, idx, zkey, v) { + ZEND_HASH_FOREACH_STR_KEY_VAL(kt, zkey, v) { ZVAL_DEREF(v); /* Detect PX or EX argument and validate timeout */ if (zkey && ZSTR_IS_EX_PX_ARG(zkey)) { @@ -2675,16 +2663,28 @@ int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, // Now the rest of our arguments while (i < num) { // Append score and member - if (Z_TYPE(z_args[i]) == IS_STRING && ( - /* The score values should be the string representation of a double + switch (Z_TYPE(z_args[i])) { + case IS_LONG: + case IS_DOUBLE: + redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i])); + break; + case IS_STRING: + /* The score values must be the string representation of a double * precision floating point number. +inf and -inf values are valid * values as well. */ - strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 || - strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0 - )) { - redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i])); - } else { - redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i])); + if (strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 || + strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0 || + is_numeric_string(Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]), NULL, NULL, 0) != 0 + ) { + redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i])); + break; + } + // fall through + default: + php_error_docref(NULL, E_WARNING, "Scores must be numeric or '-inf','+inf'"); + smart_string_free(&cmdstr); + efree(z_args); + return FAILURE; } // serialize value if requested val_free = redis_pack(redis_sock, &z_args[i+1], &val, &val_len); @@ -2778,15 +2778,12 @@ geoStoreType get_georadius_store_type(zend_string *key) { /* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */ static int get_georadius_opts(HashTable *ht, geoOptions *opts) { - zend_ulong idx; char *optstr; zend_string *zkey; zval *optval; - PHPREDIS_NOTUSED(idx); - /* Iterate over our argument array, collating which ones we have */ - ZEND_HASH_FOREACH_KEY_VAL(ht, idx, zkey, optval) { + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, optval) { ZVAL_DEREF(optval); /* If the key is numeric it's a non value option */ @@ -3674,11 +3671,8 @@ static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) { zend_string *zkey; char *kval; size_t klen; - zend_ulong idx; zval *zv; - PHPREDIS_NOTUSED(idx); - /* Initialize options array to sane defaults */ memset(opt, 0, sizeof(*opt)); opt->retrycount = -1; @@ -3690,7 +3684,7 @@ static void get_xclaim_options(zval *z_arr, xclaimOptions *opt) { /* Iterate over our options array */ ht = Z_ARRVAL_P(z_arr); - ZEND_HASH_FOREACH_KEY_VAL(ht, idx, zkey, zv) { + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, zkey, zv) { if (zkey) { kval = ZSTR_VAL(zkey); klen = ZSTR_LEN(zkey); @@ -3940,7 +3934,8 @@ int redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { + if (zend_parse_parameters_none() == FAILURE) { + return FAILURE; } *cmd_len = REDIS_CMD_SPPRINTF(cmd, "SENTINEL", "s", kw, strlen(kw)); @@ -4002,6 +3997,14 @@ void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, RETURN_LONG(redis_sock->null_mbulk_as_null); case REDIS_OPT_FAILOVER: RETURN_LONG(c->failover); + case REDIS_OPT_MAX_RETRIES: + RETURN_LONG(redis_sock->max_retries); + case REDIS_OPT_BACKOFF_ALGORITHM: + RETURN_LONG(redis_sock->backoff.algorithm); + case REDIS_OPT_BACKOFF_BASE: + RETURN_LONG(redis_sock->backoff.base / 1000); + case REDIS_OPT_BACKOFF_CAP: + RETURN_LONG(redis_sock->backoff.cap / 1000); default: RETURN_FALSE; } @@ -4125,6 +4128,7 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, } RETURN_TRUE; case REDIS_OPT_FAILOVER: + if (c == NULL) RETURN_FALSE; val_long = zval_get_long(val); if (val_long == REDIS_FAILOVER_NONE || val_long == REDIS_FAILOVER_ERROR || @@ -4135,6 +4139,35 @@ void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, RETURN_TRUE; } break; + case REDIS_OPT_MAX_RETRIES: + val_long = zval_get_long(val); + if(val_long >= 0) { + redis_sock->max_retries = val_long; + RETURN_TRUE; + } + break; + case REDIS_OPT_BACKOFF_ALGORITHM: + val_long = zval_get_long(val); + if(val_long >= 0 && + val_long < REDIS_BACKOFF_ALGORITHMS) { + redis_sock->backoff.algorithm = val_long; + RETURN_TRUE; + } + break; + case REDIS_OPT_BACKOFF_BASE: + val_long = zval_get_long(val); + if(val_long >= 0) { + redis_sock->backoff.base = val_long * 1000; + RETURN_TRUE; + } + break; + case REDIS_OPT_BACKOFF_CAP: + val_long = zval_get_long(val); + if(val_long >= 0) { + redis_sock->backoff.cap = val_long * 1000; + RETURN_TRUE; + } + break; EMPTY_SWITCH_DEFAULT_CASE() } RETURN_FALSE; @@ -4204,4 +4237,67 @@ void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS, RETURN_ZVAL(&z_ret, 0, 0); } +void redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { + zend_string *zstr; + size_t len; + char *buf; + int cmp_free; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) { + RETURN_FALSE; + } + + cmp_free = redis_compress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr)); + RETVAL_STRINGL(buf, len); + if (cmp_free) efree(buf); +} + +void redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, + zend_class_entry *ex) +{ + zend_string *zstr; + size_t len; + char *buf; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &zstr) == FAILURE) { + RETURN_FALSE; + } else if (ZSTR_LEN(zstr) == 0 || redis_sock->compression == REDIS_COMPRESSION_NONE) { + RETURN_STR_COPY(zstr); + } + + if (!redis_uncompress(redis_sock, &buf, &len, ZSTR_VAL(zstr), ZSTR_LEN(zstr))) { + zend_throw_exception(ex, "Invalid compressed data or uncompression error", 0); + RETURN_FALSE; + } + + RETVAL_STRINGL(buf, len); + efree(buf); +} + +void redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { + int valfree; + size_t len; + char *val; + zval *zv; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv) == FAILURE) { + RETURN_FALSE; + } + + valfree = redis_pack(redis_sock, zv, &val, &len); + RETVAL_STRINGL(val, len); + if (valfree) efree(val); +} + +void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) { + zend_string *str; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { + RETURN_FALSE; + } + + if (redis_unpack(redis_sock, ZSTR_VAL(str), ZSTR_LEN(str), return_value) == 0) { + RETURN_STR_COPY(str); + } +} /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ diff --git a/redis_commands.h b/redis_commands.h index 54bf7ee8f4..c82a0cfd47 100644 --- a/redis_commands.h +++ b/redis_commands.h @@ -52,7 +52,7 @@ int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); + char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); @@ -96,11 +96,11 @@ typedef int (*zrange_cb)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *,char**,int*,int*,short*,void**); int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, + char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx); int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, - char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, + char *kw, char **cmd, int *cmd_len, int *withscores, short *slot, void **ctx); int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, @@ -134,7 +134,7 @@ int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_s char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); /* Commands which need a unique construction mechanism. This is either because - * they don't share a signature with any other command, or because there is + * they don't share a signature with any other command, or because there is * specific processing we do (e.g. verifying subarguments) that make them * unique */ @@ -165,7 +165,7 @@ int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); -int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, +int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char **cmd, int *cmd_len, short *slot, void **ctx); int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, @@ -298,22 +298,29 @@ int redis_sentinel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, int redis_sentinel_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw, char **cmd, int *cmd_len, short *slot, void **ctx); -/* Commands that don't communicate with Redis at all (such as getOption, +/* Commands that don't communicate with Redis at all (such as getOption, * setOption, _prefix, _serialize, etc). These can be handled in one place - * with the method of grabbing our RedisSock* object in different ways + * with the method of grabbing our RedisSock* object in different ways * depending if this is a Redis object or a RedisCluster object. */ -void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, +void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c); -void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, +void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, redisCluster *c); -void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, +void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock); +void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); -void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS, +void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS, + RedisSock *redis_sock, zend_class_entry *ex); +void redis_compress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); -void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS, +void redis_uncompress_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zend_class_entry *ex); +void redis_pack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); +void redis_unpack_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock); + #endif /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */ diff --git a/redis_sentinel.c b/redis_sentinel.c index cbdf331cf5..a05a6e69c8 100644 --- a/redis_sentinel.c +++ b/redis_sentinel.c @@ -134,7 +134,7 @@ PHP_METHOD(RedisSentinel, masters) PHP_METHOD(RedisSentinel, ping) { - REDIS_PROCESS_KW_CMD("PING", redis_empty_cmd, redis_boolean_response); + REDIS_PROCESS_KW_CMD("ping", redis_empty_cmd, redis_boolean_response); } PHP_METHOD(RedisSentinel, reset) diff --git a/rpm/README.md b/rpm/README.md new file mode 100644 index 0000000000..ac51cbe38e --- /dev/null +++ b/rpm/README.md @@ -0,0 +1,3 @@ +You can find and up to date version of this RPM builder here : + +https://src.fedoraproject.org/rpms/php-pecl-redis5/tree/master diff --git a/rpm/php-redis.spec b/rpm/php-redis.spec deleted file mode 100644 index 5363d1eead..0000000000 --- a/rpm/php-redis.spec +++ /dev/null @@ -1,48 +0,0 @@ -%global php_apiver %((echo 0; php -i 2>/dev/null | sed -n 's/^PHP API => //p') | tail -1) -%global php_extdir %(php-config --extension-dir 2>/dev/null || echo "undefined") -%global php_version %(php-config --version 2>/dev/null || echo 0) - -Name: php-redis -Version: 2.2.5 -Release: 1%{?dist} -Summary: The phpredis extension provides an API for communicating with the Redis key-value store. - -Group: Development/Languages -License: PHP -URL: https://github.com/nicolasff/phpredis -Source0: https://github.com/nicolasff/phpredis/tarball/master -Source1: redis.ini -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -BuildRequires: php-devel -Requires: php(zend-abi) = %{php_zend_api} -Requires: php(api) = %{php_apiver} - -%description -The phpredis extension provides an API for communicating with the Redis key-value store. - -%prep -%setup -q -n nicolasff-phpredis-43bc590 - -%build -%{_bindir}/phpize -%configure -make %{?_smp_mflags} - -%install -rm -rf $RPM_BUILD_ROOT -make install INSTALL_ROOT=$RPM_BUILD_ROOT - -# install configuration -%{__mkdir} -p $RPM_BUILD_ROOT%{_sysconfdir}/php.d -%{__cp} %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/php.d/redis.ini - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root,-) -%doc CREDITS -%config(noreplace) %{_sysconfdir}/php.d/redis.ini -%{php_extdir}/redis.so - diff --git a/rpm/redis.ini b/rpm/redis.ini deleted file mode 100644 index 6aecae4895..0000000000 --- a/rpm/redis.ini +++ /dev/null @@ -1 +0,0 @@ -extension=redis.so diff --git a/sentinel.markdown b/sentinel.markdown index 2bd3655313..ec0adec52b 100644 --- a/sentinel.markdown +++ b/sentinel.markdown @@ -16,6 +16,7 @@ Redis Sentinel also provides other collateral tasks such as monitoring, notifica *persistent*: String, persistent connection id (optional, default is NULL meaning not persistent) *retry_interval*: Int, value in milliseconds (optional, default is 0) *read_timeout*: Float, value in seconds (optional, default is 0 meaning unlimited) +*auth*:String, or an Array with one or two elements, used to authenticate with the redis-sentinel. (optional, default is NULL meaning NOAUTH) ##### *Example* @@ -25,6 +26,7 @@ $sentinel = new RedisSentinel('127.0.0.1', 26379, 2.5); // 2.5 sec timeout. $sentinel = new RedisSentinel('127.0.0.1', 26379, 0, 'sentinel'); // persistent connection with id 'sentinel' $sentinel = new RedisSentinel('127.0.0.1', 26379, 0, ''); // also persistent connection with id '' $sentinel = new RedisSentinel('127.0.0.1', 26379, 1, null, 100); // 1 sec timeout, 100ms delay between reconnection attempts. +$sentinel = new RedisSentinel('127.0.0.1', 26379, 0, NULL, 0, 0, "secret"); // connect sentinel with password authentication ~~~ ### Usage diff --git a/sentinel_library.c b/sentinel_library.c index 481985467f..0fe64cc145 100644 --- a/sentinel_library.c +++ b/sentinel_library.c @@ -30,7 +30,7 @@ create_sentinel_object(zend_class_entry *ce) return &obj->std; } -PHP_REDIS_API void +PHP_REDIS_API int sentinel_mbulk_reply_zipped_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) { char inbuf[4096]; @@ -40,14 +40,17 @@ sentinel_mbulk_reply_zipped_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis /* Throws exception on failure */ if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len) < 0) { - RETURN_FALSE; + RETVAL_FALSE; + return FAILURE; } if (*inbuf != TYPE_MULTIBULK) { if (*inbuf == TYPE_ERR) { redis_sock_set_err(redis_sock, inbuf + 1, len - 1); } - RETURN_FALSE; + + RETVAL_FALSE; + return FAILURE; } array_init(&z_ret); nelem = atoi(inbuf + 1); @@ -57,5 +60,7 @@ sentinel_mbulk_reply_zipped_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab, ctx); add_next_index_zval(&z_ret, return_value); } - RETURN_ZVAL(&z_ret, 0, 1); + + RETVAL_ZVAL(&z_ret, 0, 1); + return SUCCESS; } diff --git a/sentinel_library.h b/sentinel_library.h index 460ccfad1d..88d9a564e7 100644 --- a/sentinel_library.h +++ b/sentinel_library.h @@ -8,6 +8,6 @@ typedef redis_object redis_sentinel_object; zend_object *create_sentinel_object(zend_class_entry *ce); -PHP_REDIS_API void sentinel_mbulk_reply_zipped_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); +PHP_REDIS_API int sentinel_mbulk_reply_zipped_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx); #endif /* REDIS_SENTINEL_LIBRARY_H */ diff --git a/tests/RedisArrayTest.php b/tests/RedisArrayTest.php index a852688625..5a20d271c7 100644 --- a/tests/RedisArrayTest.php +++ b/tests/RedisArrayTest.php @@ -166,6 +166,43 @@ public function testKeyDistributor() } } + /* Scan a whole key and return the overall result */ + protected function execKeyScan($cmd, $key) { + $res = []; + + $it = NULL; + do { + $chunk = $this->ra->$cmd($key, $it); + foreach ($chunk as $field => $value) { + $res[$field] = $value; + } + } while ($it !== 0); + + return $res; + } + + public function testKeyScanning() { + $h_vals = ['foo' => 'bar', 'baz' => 'bop']; + $z_vals = ['one' => 1, 'two' => 2, 'three' => 3]; + $s_vals = ['mem1', 'mem2', 'mem3']; + + $this->ra->del(['scan-hash', 'scan-set', 'scan-zset']); + + $this->ra->hMSet('scan-hash', $h_vals); + foreach ($z_vals as $k => $v) + $this->ra->zAdd('scan-zset', $v, $k); + $this->ra->sAdd('scan-set', ...$s_vals); + + $s_scan = $this->execKeyScan('sScan', 'scan-set'); + $this->assertTrue(count(array_diff_key(array_flip($s_vals), array_flip($s_scan))) == 0); + + $this->assertEquals($h_vals, $this->execKeyScan('hScan', 'scan-hash')); + + $z_scan = $this->execKeyScan('zScan', 'scan-zset'); + $this->assertTrue(count($z_scan) == count($z_vals) && + count(array_diff_key($z_vals, $z_scan)) == 0 && + array_sum($z_scan) == array_sum($z_vals)); + } } class Redis_Rehashing_Test extends TestSuite diff --git a/tests/RedisClusterTest.php b/tests/RedisClusterTest.php index a1baf90dd2..b7746c5ec5 100644 --- a/tests/RedisClusterTest.php +++ b/tests/RedisClusterTest.php @@ -49,6 +49,13 @@ public function testConnectException() { return $this->markTestSkipped(); } public function testTlsConnect() { return $this->markTestSkipped(); } public function testInvalidAuthArgs() { return $this->markTestSkipped(); } + public function testlMove() { return $this->markTestSkipped(); } + public function testsMisMember() { return $this->markTestSkipped(); } + public function testzDiff() { return $this->markTestSkipped(); } + public function testzDiffStore() { return $this->markTestSkipped(); } + public function testzMscore() { return $this->marktestSkipped(); } + public function testCopy() { return $this->marktestSkipped(); } + /* Session locking feature is currently not supported in in context of Redis Cluster. The biggest issue for this is the distribution nature of Redis cluster */ public function testSession_lockKeyCorrect() { return $this->markTestSkipped(); } diff --git a/tests/RedisSentinelTest.php b/tests/RedisSentinelTest.php index b88e006477..70ea8543a0 100644 --- a/tests/RedisSentinelTest.php +++ b/tests/RedisSentinelTest.php @@ -40,7 +40,7 @@ public function setUp() public function testCkquorum() { - $this->assertTrue($this->sentinel->ckquorum(self::NAME)); + $this->assertTrue(is_bool($this->sentinel->ckquorum(self::NAME))); } public function testFailover() diff --git a/tests/RedisTest.php b/tests/RedisTest.php index c9b147b87b..7484849d38 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -1455,6 +1455,27 @@ public function testsmembers() $this->assertEquals($array, $sMembers); // test alias } + public function testsMisMember() + { + // Only available since 6.2.0 + if (version_compare($this->version, '6.2.0') < 0) { + $this->markTestSkipped(); + return; + } + + $this->redis->del('set'); + + $this->redis->sAdd('set', 'val'); + $this->redis->sAdd('set', 'val2'); + $this->redis->sAdd('set', 'val3'); + + $misMembers = $this->redis->sMisMember('set', 'val', 'notamember', 'val3'); + $this->assertEquals([1, 0, 1], $misMembers); + + $misMembers = $this->redis->sMisMember('wrongkey', 'val', 'val2', 'val3'); + $this->assertEquals([0, 0, 0], $misMembers); + } + public function testlSet() { $this->redis->del('list'); @@ -1873,10 +1894,8 @@ public function testdbSize() { public function testttl() { $this->redis->set('x', 'y'); $this->redis->expire('x', 5); - for($i = 5; $i > 0; $i--) { - $this->assertEquals($i, $this->redis->ttl('x')); - sleep(1); - } + $ttl = $this->redis->ttl('x'); + $this->assertTrue($ttl > 0 && $ttl <= 5); // A key with no TTL $this->redis->del('x'); $this->redis->set('x', 'bar'); @@ -4565,6 +4584,14 @@ public function testCompressionLZF() if (!defined('Redis::COMPRESSION_LZF')) { $this->markTestSkipped(); } + + /* Don't crash on improperly compressed LZF data */ + $payload = 'not-actually-lzf-compressed'; + $this->redis->set('badlzf', $payload); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_LZF); + $this->assertEquals($payload, $this->redis->get('badlzf')); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); + $this->checkCompression(Redis::COMPRESSION_LZF, 0); } @@ -4573,6 +4600,14 @@ public function testCompressionZSTD() if (!defined('Redis::COMPRESSION_ZSTD')) { $this->markTestSkipped(); } + + /* Issue 1936 regression. Make sure we don't overflow on bad data */ + $this->redis->del('badzstd'); + $this->redis->set('badzstd', '123'); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_ZSTD); + $this->assertEquals('123', $this->redis->get('badzstd')); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); + $this->checkCompression(Redis::COMPRESSION_ZSTD, 0); $this->checkCompression(Redis::COMPRESSION_ZSTD, 9); } @@ -4610,6 +4645,10 @@ private function checkCompression($mode, $level) $this->assertEquals($val, $this->redis->get('key')); } } + + // Issue 1945. Ensure we decompress data with hmget. + $this->redis->hset('hkey', 'data', 'abc'); + $this->assertEquals('abc', current($this->redis->hmget('hkey', ['data']))); } public function testDumpRestore() { @@ -4930,6 +4969,71 @@ public function testUnserialize() { } } + public function testCompressHelpers() { + $compressors = self::getAvailableCompression(); + + $vals = ['foo', 12345, random_bytes(128), '']; + + $oldcmp = $this->redis->getOption(Redis::OPT_COMPRESSION); + + foreach ($compressors as $cmp) { + foreach ($vals as $val) { + $this->redis->setOption(Redis::OPT_COMPRESSION, $cmp); + $this->redis->set('cmpkey', $val); + + /* Get the value raw */ + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); + $raw = $this->redis->get('cmpkey'); + $this->redis->setOption(Redis::OPT_COMPRESSION, $cmp); + + $this->assertEquals($raw, $this->redis->_compress($val)); + + $uncompressed = $this->redis->get('cmpkey'); + $this->assertEquals($uncompressed, $this->redis->_uncompress($raw)); + } + } + + $this->redis->setOption(Redis::OPT_COMPRESSION, $oldcmp); + } + + public function testPackHelpers() { + list ($oldser, $oldcmp) = [ + $this->redis->getOption(Redis::OPT_SERIALIZER), + $this->redis->getOption(Redis::OPT_COMPRESSION) + ]; + + foreach ($this->serializers as $ser) { + $compressors = self::getAvailableCompression(); + foreach ($compressors as $cmp) { + $this->redis->setOption(Redis::OPT_SERIALIZER, $ser); + $this->redis->setOption(Redis::OPT_COMPRESSION, $cmp); + + foreach (['foo', 12345, random_bytes(128), '', ['an', 'array']] as $v) { + /* Can only attempt the array if we're serializing */ + if (is_array($v) && $ser == Redis::SERIALIZER_NONE) + continue; + + $this->redis->set('packkey', $v); + + /* Get the value raw */ + $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); + $raw = $this->redis->get('packkey'); + $this->redis->setOption(Redis::OPT_SERIALIZER, $ser); + $this->redis->setOption(Redis::OPT_COMPRESSION, $cmp); + + $this->assertEquals($raw, $this->redis->_pack($v)); + + $unpacked = $this->redis->get('packkey'); + $this->assertEquals($unpacked, $this->redis->_unpack($raw)); + } + } + } + + $this->redis->setOption(Redis::OPT_SERIALIZER, $oldser); + $this->redis->setOption(Redis::OPT_COMPRESSION, $oldcmp); + } + public function testPrefix() { // no prefix $this->redis->setOption(Redis::OPT_PREFIX, ''); @@ -5174,6 +5278,50 @@ public function testScanPrefix() { $this->assertTrue(count($arr_all_keys) == 0); } + public function testMaxRetriesOption() { + $maxRetriesExpected = 5; + $this->redis->setOption(Redis::OPT_MAX_RETRIES, $maxRetriesExpected); + $maxRetriesActual=$this->redis->getOption(Redis::OPT_MAX_RETRIES); + $this->assertEquals($maxRetriesActual, $maxRetriesExpected); + } + + public function testBackoffOptions() { + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_DEFAULT); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_DEFAULT); + + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_CONSTANT); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_CONSTANT); + + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_UNIFORM); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_UNIFORM); + + $this->redis -> setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_EXPONENTIAL); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_EXPONENTIAL); + + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_EQUAL_JITTER); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_EQUAL_JITTER); + + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_FULL_JITTER); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_FULL_JITTER); + + $this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_ALGORITHM), Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER); + + $this->assertFalse($this->redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, 55555)); + + $this->redis->setOption(Redis::OPT_BACKOFF_BASE, 500); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_BASE), 500); + + $this->redis->setOption(Redis::OPT_BACKOFF_BASE, 750); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_BASE), 750); + + $this->redis->setOption(Redis::OPT_BACKOFF_CAP, 500); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_CAP), 500); + + $this->redis->setOption(Redis::OPT_BACKOFF_CAP, 750); + $this->assertEquals($this->redis->getOption(Redis::OPT_BACKOFF_CAP), 750); + } + public function testHScan() { if (version_compare($this->version, "2.8.0") < 0) { $this->markTestSkipped(); @@ -6135,7 +6283,6 @@ public function testInvalidAuthArgs() { $obj_new = $this->newInstance(); $arr_args = [ - NULL, [], [NULL, NULL], ['foo', 'bar', 'baz'], @@ -6151,8 +6298,6 @@ public function testInvalidAuthArgs() { try { if (is_array($arr_arg)) { @call_user_func_array([$obj_new, 'auth'], $arr_arg); - } else { - call_user_func([$obj_new, 'auth']); } } catch (Exception $ex) { unset($ex); /* Suppress intellisense warning */ @@ -6308,10 +6453,15 @@ public function testSession_lockKeyCorrect() { $this->setSessionHandler(); $sessionId = $this->generateSessionId(); + $this->startSessionProcess($sessionId, 5, true); - usleep(100000); - $this->assertTrue($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); + $maxwait = (ini_get('redis.session.lock_wait_time') * + ini_get('redis.session.lock_retries') / + 1000000.00); + + $exist = $this->waitForSessionLockKey($sessionId, $maxwait + 1); + $this->assertTrue($exist); } public function testSession_lockingDisabledByDefault() @@ -6336,8 +6486,17 @@ public function testSession_lockReleasedOnClose() $this->setSessionHandler(); $sessionId = $this->generateSessionId(); $this->startSessionProcess($sessionId, 1, true); + + /* Wait for a key to actually exist */ + if ( ! $this->waitForSessionLockKey($sessionId, 1)) { + $this->assertFalse(true); + return; + } + + /* Wait long enough for our background process to exit */ usleep(1100000); + /* Key should have been deleted */ $this->assertFalse($this->redis->exists($this->sessionPrefix . $sessionId . '_LOCK')); } @@ -6396,20 +6555,23 @@ public function testSession_lockHoldCheckBeforeWrite_nobodyHasLock() $this->assertTrue('firstProcess' !== $this->getSessionData($sessionId)); } - public function testSession_correctLockRetryCount() - { + public function testSession_correctLockRetryCount() { $this->setSessionHandler(); $sessionId = $this->generateSessionId(); + + /* Start another process and wait until it has the lock */ $this->startSessionProcess($sessionId, 10, true); - usleep(100000); + if ( ! $this->waitForSessionLockKey($sessionId, 2)) { + $this->assertTrue(false); + return; + } - $start = microtime(true); - $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 10, true, 1000000, 3); - $end = microtime(true); - $elapsedTime = $end - $start; + $t1 = microtime(true); + $ok = $this->startSessionProcess($sessionId, 0, false, 10, true, 100000, 10); + if ( ! $this->assertFalse($ok)) return; + $t2 = microtime(true); - $this->assertTrue($elapsedTime > 3 && $elapsedTime < 4); - $this->assertFalse($sessionSuccessful); + $this->assertTrue($t2 - $t1 >= 1 && $t2 - $t1 <= 3); } public function testSession_defaultLockRetryCount() @@ -6417,7 +6579,14 @@ public function testSession_defaultLockRetryCount() $this->setSessionHandler(); $sessionId = $this->generateSessionId(); $this->startSessionProcess($sessionId, 10, true); - usleep(100000); + + $keyname = $this->sessionPrefix . $sessionId . '_LOCK'; + $begin = microtime(true); + + if ( ! $this->waitForSessionLockKey($sessionId, 3)) { + $this->assertTrue(false); + return; + } $start = microtime(true); $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false, 10, true, 200000, 0); @@ -6432,20 +6601,27 @@ public function testSession_noUnlockOfOtherProcess() { $this->setSessionHandler(); $sessionId = $this->generateSessionId(); - $this->startSessionProcess($sessionId, 3, true, 1); // Process 1 - usleep(100000); - $this->startSessionProcess($sessionId, 5, true); // Process 2 - $start = microtime(true); - // Waiting until TTL of process 1 ended and process 2 locked the session, - // because is not guaranteed which waiting process gets the next lock - sleep(1); - $sessionSuccessful = $this->startSessionProcess($sessionId, 0, false); - $end = microtime(true); - $elapsedTime = $end - $start; + $t1 = microtime(true); - $this->assertTrue($elapsedTime > 5); - $this->assertTrue($sessionSuccessful); + /* 1. Start a background process, and wait until we are certain + * the lock was attained. */ + $nsec = 3; + $this->startSessionProcess($sessionId, $nsec, true, $nsec); + if ( ! $this->waitForSessionLockKey($sessionId, 1)) { + $this->assertFalse(true); + return; + } + + /* 2. Attempt to lock the same session. This should force us to + * wait until the first lock is released. */ + $t2 = microtime(true); + $ok = $this->startSessionProcess($sessionId, 0, false); + $t3 = microtime(true); + + /* 3. Verify that we did in fact have to wait for this lock */ + $this->assertTrue($ok); + $this->assertTrue($t3 - $t2 >= $nsec - ($t2 - $t1)); } public function testSession_lockWaitTime() @@ -6681,13 +6857,20 @@ private function generateSessionId() * @return bool * @throws Exception */ - private function startSessionProcess($sessionId, $sleepTime, $background, $maxExecutionTime = 300, $locking_enabled = true, $lock_wait_time = null, $lock_retries = -1, $lock_expires = 0, $sessionData = '', $sessionLifetime = 1440) + private function startSessionProcess($sessionId, $sleepTime, $background, $maxExecutionTime = 300, + $locking_enabled = true, $lock_wait_time = null, $lock_retries = -1, + $lock_expires = 0, $sessionData = '', $sessionLifetime = 1440) { if (substr(php_uname(), 0, 7) == "Windows"){ $this->markTestSkipped(); return true; } else { - $commandParameters = [$this->getFullHostPath(), $this->sessionSaveHandler, $sessionId, $sleepTime, $maxExecutionTime, $lock_retries, $lock_expires, $sessionData, $sessionLifetime]; + $commandParameters = [ + $this->getFullHostPath(), $this->sessionSaveHandler, $sessionId, + $sleepTime, $maxExecutionTime, $lock_retries, $lock_expires, + $sessionData, $sessionLifetime + ]; + if ($locking_enabled) { $commandParameters[] = '1'; @@ -6704,6 +6887,53 @@ private function startSessionProcess($sessionId, $sleepTime, $background, $maxEx } } + /** + * @param string $session_id + * @param string $max_wait_sec + * + * Sometimes we want to block until a session lock has been detected + * This is better and faster than arbitrarily sleeping. If we don't + * detect the session key within the specified maximum number of + * seconds, the function returns failure. + * + * @return bool + */ + private function waitForSessionLockKey($session_id, $max_wait_sec) { + $now = microtime(true); + $key = $this->sessionPrefix . $session_id . '_LOCK'; + + do { + usleep(10000); + $exists = $this->redis->exists($key); + } while (!$exists && microtime(true) <= $now + $max_wait_sec); + + return $exists || $this->redis->exists($key); + } + + /** + * @param string $str_search pattern to look for in ps + * @param int $timeout Maximum amount of time to wait + * + * Small helper function to wait until we no longer detect a running process. + * This is an attempt to fix timing related false failures on session tests + * when running in CI. + */ + function waitForProcess($str_search, $timeout = 0.0) { + $st = microtime(true); + + do { + $str_procs = shell_exec("ps aux|grep $str_search|grep -v grep"); + $arr_procs = array_filter(explode("\n", $str_procs)); + if (count($arr_procs) == 0) + return true; + + usleep(10000); + $elapsed = microtime(true) - $st; + } while ($timeout < 0 || $elapsed < $timeout); + + return false; + } + /** * @param string $sessionId * @param int $sessionLifetime @@ -6745,7 +6975,7 @@ private function regenerateSessionId($sessionId, $locking = false, $destroyPrevi * * @return string */ - private function getPhpCommand($script) + private static function getPhpCommand($script) { static $cmd = NULL; diff --git a/tests/TestSuite.php b/tests/TestSuite.php index c879b33093..d4f737f994 100644 --- a/tests/TestSuite.php +++ b/tests/TestSuite.php @@ -39,6 +39,18 @@ public function getHost() { return $this->str_host; } public function getPort() { return $this->i_port; } public function getAuth() { return $this->auth; } + public static function getAvailableCompression() { + $result[] = Redis::COMPRESSION_NONE; + if (defined('Redis::COMPRESSION_LZF')) + $result[] = Redis::COMPRESSION_LZF; + if (defined('Redis::COMPRESSION_LZ4')) + $result[] = Redis::COMPRESSION_LZ4; + if (defined('Redis::COMPRESSION_ZSTD')) + $result[] = Redis::COMPRESSION_ZSTD; + + return $result; + } + /** * Returns the fully qualified host path, * which may be used directly for php.ini parameters like session.save_path diff --git a/tests/check-quorum.sh b/tests/check-quorum.sh new file mode 100755 index 0000000000..3980b1f8b5 --- /dev/null +++ b/tests/check-quorum.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +for try in $(seq 0 3); do + RES=$(redis-cli -p 26379 sentinel ckquorum mymaster 2>/dev/null); + if [[ "$RES" == OK* ]]; then + echo "Quorum detected, exiting"; + break; + else + echo "No Quorum - $RES"; + fi; + sleep 1; +done