From 10c7c59e0595d80253d707ebf482b783fd4b3a36 Mon Sep 17 00:00:00 2001 From: Bachir Soussi Chiadmi Date: Tue, 23 Jan 2018 21:13:49 +0100 Subject: [PATCH] added redis module --- .../all/modules/contrib/dev/redis/.gitignore | 1 + .../dev/redis/.travis-before-script.sh | 21 + .../all/modules/contrib/dev/redis/.travis.yml | 119 ++++++ .../all/modules/contrib/dev/redis/LICENSE.txt | 339 ++++++++++++++++ .../contrib/dev/redis/README.PhpRedis.txt | 52 +++ .../contrib/dev/redis/README.Predis.txt | 37 ++ sites/all/modules/contrib/dev/redis/README.md | 338 ++++++++++++++++ .../modules/contrib/dev/redis/composer.json | 13 + .../contrib/dev/redis/example.services.yml | 33 ++ .../modules/contrib/dev/redis/redis.info.yml | 12 + .../modules/contrib/dev/redis/redis.install | 40 ++ .../modules/contrib/dev/redis/redis.module | 28 ++ .../contrib/dev/redis/redis.services.yml | 18 + .../redis/src/Cache/CacheBackendFactory.php | 70 ++++ .../contrib/dev/redis/src/Cache/CacheBase.php | 378 ++++++++++++++++++ .../contrib/dev/redis/src/Cache/PhpRedis.php | 111 +++++ .../contrib/dev/redis/src/Cache/Predis.php | 113 ++++++ .../src/Cache/RedisCacheTagsChecksum.php | 150 +++++++ .../contrib/dev/redis/src/Client/PhpRedis.php | 97 +++++ .../contrib/dev/redis/src/Client/Predis.php | 61 +++ .../contrib/dev/redis/src/ClientFactory.php | 215 ++++++++++ .../contrib/dev/redis/src/ClientInterface.php | 26 ++ .../dev/redis/src/Flood/FloodFactory.php | 49 +++ .../contrib/dev/redis/src/Flood/PhpRedis.php | 95 +++++ .../contrib/dev/redis/src/Flood/Predis.php | 95 +++++ .../dev/redis/src/Lock/LockFactory.php | 37 ++ .../contrib/dev/redis/src/Lock/PhpRedis.php | 147 +++++++ .../contrib/dev/redis/src/Lock/Predis.php | 136 +++++++ .../dev/redis/src/PersistentLock/PhpRedis.php | 26 ++ .../contrib/dev/redis/src/Queue/PhpRedis.php | 148 +++++++ .../contrib/dev/redis/src/Queue/Predis.php | 152 +++++++ .../contrib/dev/redis/src/Queue/QueueBase.php | 96 +++++ .../dev/redis/src/Queue/QueueRedisFactory.php | 56 +++ .../dev/redis/src/Queue/ReliablePhpRedis.php | 153 +++++++ .../dev/redis/src/Queue/ReliablePredis.php | 156 ++++++++ .../dev/redis/src/Queue/ReliableQueueBase.php | 14 + .../src/Queue/ReliableQueueRedisFactory.php | 17 + .../dev/redis/src/RedisPrefixTrait.php | 89 +++++ .../Lock/RedisLockFunctionalTest.php | 56 +++ .../redis/tests/src/Functional/WebTest.php | 193 +++++++++ .../redis/tests/src/Kernel/RedisCacheTest.php | 49 +++ .../redis/tests/src/Kernel/RedisFloodTest.php | 58 +++ .../redis/tests/src/Kernel/RedisQueueTest.php | 95 +++++ .../src/Traits/RedisTestInterfaceTrait.php | 36 ++ 44 files changed, 4225 insertions(+) create mode 100644 sites/all/modules/contrib/dev/redis/.gitignore create mode 100644 sites/all/modules/contrib/dev/redis/.travis-before-script.sh create mode 100644 sites/all/modules/contrib/dev/redis/.travis.yml create mode 100644 sites/all/modules/contrib/dev/redis/LICENSE.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.PhpRedis.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.Predis.txt create mode 100644 sites/all/modules/contrib/dev/redis/README.md create mode 100644 sites/all/modules/contrib/dev/redis/composer.json create mode 100644 sites/all/modules/contrib/dev/redis/example.services.yml create mode 100644 sites/all/modules/contrib/dev/redis/redis.info.yml create mode 100644 sites/all/modules/contrib/dev/redis/redis.install create mode 100644 sites/all/modules/contrib/dev/redis/redis.module create mode 100644 sites/all/modules/contrib/dev/redis/redis.services.yml create mode 100644 sites/all/modules/contrib/dev/redis/src/Cache/CacheBackendFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Cache/CacheBase.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Cache/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Cache/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Cache/RedisCacheTagsChecksum.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Client/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Client/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/ClientFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/ClientInterface.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Flood/FloodFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Flood/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Flood/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Lock/LockFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Lock/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/PersistentLock/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/PhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/Predis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/QueueBase.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/QueueRedisFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/ReliablePhpRedis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/ReliablePredis.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/ReliableQueueBase.php create mode 100644 sites/all/modules/contrib/dev/redis/src/Queue/ReliableQueueRedisFactory.php create mode 100644 sites/all/modules/contrib/dev/redis/src/RedisPrefixTrait.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Functional/WebTest.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisCacheTest.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisFloodTest.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisQueueTest.php create mode 100644 sites/all/modules/contrib/dev/redis/tests/src/Traits/RedisTestInterfaceTrait.php diff --git a/sites/all/modules/contrib/dev/redis/.gitignore b/sites/all/modules/contrib/dev/redis/.gitignore new file mode 100644 index 000000000..a48738a76 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/.gitignore @@ -0,0 +1 @@ +predis diff --git a/sites/all/modules/contrib/dev/redis/.travis-before-script.sh b/sites/all/modules/contrib/dev/redis/.travis-before-script.sh new file mode 100644 index 000000000..94f0b72c8 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/.travis-before-script.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e $DRUPAL_TI_DEBUG + +# Ensure the right Drupal version is installed. +# Note: This function is re-entrant. +drupal_ti_ensure_drupal + +# Add needed dependencies. +cd "$DRUPAL_TI_DRUPAL_DIR" + +# Download predis +composer require predis/predis + + +# These variables come from environments/drupal-*.sh +mkdir -p "$DRUPAL_TI_MODULES_PATH" +cd "$DRUPAL_TI_MODULES_PATH" + +#Enable php-redis +echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/.travis.yml b/sites/all/modules/contrib/dev/redis/.travis.yml new file mode 100644 index 000000000..30bba24e7 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/.travis.yml @@ -0,0 +1,119 @@ +# @file +# .travis.yml - Drupal for Travis CI Integration +# +# Template provided by https://github.com/LionsAd/drupal_ti. +# +# Based for simpletest upon: +# https://github.com/sonnym/travis-ci-drupal-module-example + +language: php + +php: + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + fast_finish: true + allow_failures: + - php: hhvm + +env: + global: + # add composer's global bin directory to the path + # see: https://github.com/drush-ops/drush#install---composer + - PATH="$PATH:$HOME/.composer/vendor/bin" + + # Configuration variables. + - DRUPAL_TI_MODULE_NAME="redis" + - DRUPAL_TI_SIMPLETEST_GROUP="redis" + + # Define runners and environment vars to include before and after the + # main runners / environment vars. + #- DRUPAL_TI_SCRIPT_DIR_BEFORE="./.drupal_ti/before" + #- DRUPAL_TI_SCRIPT_DIR_AFTER="./drupal_ti/after" + + # The environment to use, supported are: drupal-7, drupal-8 + - DRUPAL_TI_ENVIRONMENT="drupal-8" + + # Drupal specific variables. + - DRUPAL_TI_DB="drupal_travis_db" + - DRUPAL_TI_DB_URL="mysql://root:@127.0.0.1/drupal_travis_db" + # Note: Do not add a trailing slash here. + - DRUPAL_TI_WEBSERVER_URL="http://127.0.0.1" + - DRUPAL_TI_WEBSERVER_PORT="8080" + + # Simpletest specific commandline arguments, the DRUPAL_TI_SIMPLETEST_GROUP is appended at the end. + - DRUPAL_TI_SIMPLETEST_ARGS="--verbose --color --concurrency 4 --url $DRUPAL_TI_WEBSERVER_URL:$DRUPAL_TI_WEBSERVER_PORT" + + # === Behat specific variables. + # This is relative to $TRAVIS_BUILD_DIR + - DRUPAL_TI_BEHAT_DIR="./tests/behat" + # These arguments are passed to the bin/behat command. + - DRUPAL_TI_BEHAT_ARGS="" + # Specify the filename of the behat.yml with the $DRUPAL_TI_DRUPAL_DIR variables. + - DRUPAL_TI_BEHAT_YML="behat.yml.dist" + # This is used to setup Xvfb. + - DRUPAL_TI_BEHAT_SCREENSIZE_COLOR="1280x1024x16" + # The version of seleniumthat should be used. + - DRUPAL_TI_BEHAT_SELENIUM_VERSION="2.44" + # Set DRUPAL_TI_BEHAT_DRIVER to "selenium" to use "firefox" or "chrome" here. + - DRUPAL_TI_BEHAT_DRIVER="phantomjs" + - DRUPAL_TI_BEHAT_BROWSER="firefox" + + # Use Drupal 8.3.x to run tests. + - DRUPAL_TI_CORE_BRANCH="8.3.x" + + # PHPUnit specific commandline arguments. + - DRUPAL_TI_PHPUNIT_ARGS="--verbose --debug" + # Specifying the phpunit-core src/ directory is useful when e.g. a vendor/ + # directory is present in the module directory, which phpunit would then + # try to find tests in. This option is relative to $TRAVIS_BUILD_DIR. + #- DRUPAL_TI_PHPUNIT_CORE_SRC_DIRECTORY="./tests/src" + + # Code coverage via coveralls.io + - DRUPAL_TI_COVERAGE="satooshi/php-coveralls:0.6.*" + # This needs to match your .coveralls.yml file. + - DRUPAL_TI_COVERAGE_FILE="build/logs/clover.xml" + + # Debug options + #- DRUPAL_TI_DEBUG="-x -v" + # Set to "all" to output all files, set to e.g. "xvfb selenium" or "selenium", + # etc. to only output those channels. + #- DRUPAL_TI_DEBUG_FILE_OUTPUT="selenium xvfb webserver" + + # [[[ SELECT ANY OR MORE OPTIONS ]]] + #- DRUPAL_TI_RUNNERS="phpunit" + #- DRUPAL_TI_RUNNERS="simpletest" + #- DRUPAL_TI_RUNNERS="behat" + - DRUPAL_TI_RUNNERS="phpunit-core" + matrix: + - REDIS_INTERFACE=PhpRedis + - REDIS_INTERFACE=Predis + +# This will create the database +mysql: + database: drupal_travis_db + username: root + encoding: utf8 + +services: + - redis-server + +before_install: + - composer global require "lionsad/drupal_ti:dev-master#396d11d200005eb68491d24170da0a98ae7f51b3" + - drupal-ti before_install + +install: + - drupal-ti install + +before_script: + - drupal-ti --include .travis-before-script.sh + - drupal-ti before_script + +script: + - drupal-ti script + +after_script: + - drupal-ti after_script diff --git a/sites/all/modules/contrib/dev/redis/LICENSE.txt b/sites/all/modules/contrib/dev/redis/LICENSE.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/LICENSE.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt b/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt new file mode 100644 index 000000000..178458c4b --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.PhpRedis.txt @@ -0,0 +1,52 @@ +PhpRedis cache backend +====================== + +This client, for now, is only able to use the PhpRedis extension. + +Get PhpRedis +------------ + +You can download this library at: + + https://github.com/nicolasff/phpredis + +This is PHP extension, too recent for being packaged in most distribution, you +will probably need to compile it yourself. + +Default behavior is to connect via tcp://localhost:6379 but you might want to +connect differently. + +Use the Sentinel high availability mode +--------------------------------------- + +Redis can provide a master/slave mode with sentinels server monitoring them. +More information about setting it : https://redis.io/topics/sentinel. + +This mode needs the following settings: + +Modify the host as follow: + // Sentinels instances list with hostname:port format. + $settings['redis.connection']['host'] = ['1.2.3.4:5000','1.2.3.5:5000','1.2.3.6:5000']; + +Add the new instance setting: + + // Redis instance name. + $settings['redis.connection']['instance'] = 'instance_name'; + +Connect via UNIX socket +----------------------- + +Just add this line to your settings.php file: + + $conf['redis_cache_socket'] = '/tmp/redis.sock'; + +Don't forget to change the path depending on your operating system and Redis +server configuration. + +Connect to a remote host and database +------------------------------------- + +See README.md file. + +For this particular implementation, host settings are overridden by the +UNIX socket parameter. diff --git a/sites/all/modules/contrib/dev/redis/README.Predis.txt b/sites/all/modules/contrib/dev/redis/README.Predis.txt new file mode 100644 index 000000000..09bbbcefb --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.Predis.txt @@ -0,0 +1,37 @@ +Predis cache backend +==================== + +Using Predis for the Drupal 8 version of this module is still experimental. + +Get Predis +---------- + +Predis can be installed to the vendor directory using composer like so: + +composer require nrk/predis + + +Configuration of module for use with Predis +---------------------------- + +There is not much different to configure about Predis. +Adding this to settings.php should suffice for basic usage: + +$settings['redis.connection']['interface'] = 'Predis'; +$settings['redis.connection']['host'] = '1.2.3.4'; // Your Redis instance hostname. +$settings['cache']['default'] = 'cache.backend.redis'; + +To add more magic with a primary/replica setup you can use a config like this: + +$settings['redis.connection']['interface'] = 'Predis'; // Use predis library. +$settings['redis.connection']['replication'] = TRUE; // Turns on replication. +$settings['redis.connection']['replication.host'][1]['host'] = '1.2.3.4'; // Your Redis instance hostname. +$settings['redis.connection']['replication.host'][1]['port'] = '6379'; // Only required if using non-standard port. +$settings['redis.connection']['replication.host'][1]['role'] = 'primary'; // The redis instance role. +$settings['redis.connection']['replication.host'][2]['host'] = '1.2.3.5'; +$settings['redis.connection']['replication.host'][2]['port'] = '6379'; +$settings['redis.connection']['replication.host'][2]['role'] = 'replica'; +$settings['redis.connection']['replication.host'][3]['host'] = '1.2.3.6'; +$settings['redis.connection']['replication.host'][3]['port'] = '6379'; +$settings['redis.connection']['replication.host'][3]['role'] = 'replica'; +$settings['cache']['default'] = 'cache.backend.redis'; \ No newline at end of file diff --git a/sites/all/modules/contrib/dev/redis/README.md b/sites/all/modules/contrib/dev/redis/README.md new file mode 100644 index 000000000..1648bb0db --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/README.md @@ -0,0 +1,338 @@ +Redis backends +==================== + +This package provides two different Redis backends. If you want to use +Redis as cache backend, you have to choose one of the two, but you cannot use +both at the same time. + +PhpRedis +-------- + +This implementation uses the PhpRedis PHP extension. In order to use it, you +will need to compile the extension yourself. + +Predis +------ + +Support for the Predis PHP library is experimental, but feel free to try it out. +You can install the required library using composer. Check out the README.Predis.txt file +for more information. + +Important notice +---------------- + +This module requires at least Redis 2.4, additionally, the lock backend +requires Redis 2.6 to support millisecond timeouts and atomic lock operations. + +Getting started +=============== + +Quick setup +----------- + +Here is a simple yet working easy way to setup the module. + +This method will allow Drupal to use Redis for all caches. + + $settings['redis.connection']['interface'] = 'PhpRedis'; // Can be "Predis". + $settings['redis.connection']['host'] = '1.2.3.4'; // Your Redis instance hostname. + $settings['cache']['default'] = 'cache.backend.redis'; + +To use some Predis goodness, including a redis primary/replica setup you can use a config like this. + + $settings['redis.connection']['interface'] = 'Predis'; // Use predis library. + $settings['redis.connection']['replication'] = TRUE; // Turns on replication. + $settings['redis.connection']['replication.host'][1]['host'] = '1.2.3.4'; // Your Redis instance hostname. + $settings['redis.connection']['replication.host'][1]['port'] = '6379'; // Only required if using non-standard port. + $settings['redis.connection']['replication.host'][1]['role'] = 'primary'; // The redis instance role. + $settings['redis.connection']['replication.host'][2]['host'] = '1.2.3.5'; + $settings['redis.connection']['replication.host'][2]['port'] = '6379'; + $settings['redis.connection']['replication.host'][2]['role'] = 'replica'; + $settings['redis.connection']['replication.host'][3]['host'] = '1.2.3.6'; + $settings['redis.connection']['replication.host'][3]['port'] = '6379'; + $settings['redis.connection']['replication.host'][3]['role'] = 'replica'; + $settings['cache']['default'] = 'cache.backend.redis'; + +Either include the default example.services.yml from the module, which will +replace all supported backend services (that currently includes the cache tags +checksum service and the lock backends, check the file for the current list) +or copy the service definitions into a site specific services.yml. + + $settings['container_yamls'][] = 'modules/redis/example.services.yml'; + +Note that for any of this, the redis module must be enabled. See next chapters +for more information. + +Is there any cache bins that should *never* go into Redis? +---------------------------------------------------------- + +TL;DR: No. + +Redis has been maturing a lot over time, and will apply different sensible +settings for different bins; It's today very stable. + +Advanced configuration +====================== + +Choose the Redis client library to use +-------------------------------------- + +Note: This is not yet supported, only the PhpRedis interface is available. + +Add into your settings.php file: + + $settings['redis.connection']['interface'] = 'PhpRedis'; + +You can replace 'PhpRedis' with 'Predis', depending on the library you chose. + + +Tell Drupal to use the cache backend +------------------------------------ + +Usual cache backend configuration, as follows, to add into your settings.php +file like any other backend: + + # Use for all bins otherwise specified. + $settings['cache']['default'] = 'cache.backend.redis'; + + # Use this to only use it for specific cache bins. + $settings['cache']['bins']['render'] = 'cache.backend.redis'; + +Tell Drupal to use the lock backend +----------------------------------- + +See the provided example.services.yml file on how to override the lock services. + +Tell Drupal to use the queue backend +------------------------------------ + +This module provides reliable and non-reliable queue implementations. Depending +on which is to be use you need to choose "queue.redis" or "queue.redis_reliable" +as a service name. + +When you have configured basic information (host, library, ... - see Quick setup) +add this to your settings.php file: + + # Use for all queues unless otherwise specified for a specific queue. + $settings['queue_default'] = 'queue.redis'; + + # Or if you want to use reliable queue implementation. + $settings['queue_default'] = 'queue.redis_reliable'; + + + # Use this to only use Redis for a specific queue (aggregator_feeds in this case). + $settings['queue_service_aggregator_feeds'] = 'queue.redis'; + + # Or if you want to use reliable queue implementation. + $settings['queue_service_aggregator_feeds'] = 'queue.redis_reliable'; + + +Common settings +=============== + +Connect to a remote host +------------------------ + +If your Redis instance is remote, you can use this syntax: + + $settings['redis.connection']['interface'] = 'PhpRedis'; // Can be "Predis". + $settings['redis.connection']['host'] = '1.2.3.4'; // Your Redis instance hostname. + $settings['redis.connection']['port'] = '6379'; // Redis port + +Port is optional, default is 6379 (default Redis port). + +Using a specific database +------------------------- + +Per default, Redis ships the database "0". All default connections will be use +this one if nothing is specified. + +Depending on you OS or OS distribution, you might have numerous database. To +use one in particular, just add to your settings.php file: + + $settings['redis.connection']['base'] = 12; + +Connection to a password protected instance +------------------------------------------- + +If you are using a password protected instance, specify the password this way: + + $settings['redis.connection']['password'] = "mypassword"; + +Depending on the backend, using a wrong auth will behave differently: + + - Predis will throw an exception and make Drupal fail during early boostrap. + + - PhpRedis will make Redis calls silent and creates some PHP warnings, thus + Drupal will behave as if it was running with a null cache backend (no cache + at all). + +Prefixing site cache entries (avoiding sites name collision) +------------------------------------------------------------ + +If you need to differentiate multiple sites using the same Redis instance and +database, you will need to specify a prefix for your site cache entries. + +Cache prefix configuration attempts to use a unified variable across contrib +backends that support this feature. This variable name is 'cache_prefix'. + +This variable is polymorphic, the simplest version is to provide a raw string +that will be the default prefix for all cache bins: + + $settings['cache_prefix'] = 'mysite_'; + +Alternatively, to provide the same functionality, you can provide the variable +as an array: + + $settings['cache_prefix']['default'] = 'mysite_'; + +This allows you to provide different prefix depending on the bin name. Common +usage is that each key inside the 'cache_prefix' array is a bin name, the value +the associated prefix. If the value is FALSE, then no prefix is +used for this bin. + +The 'default' meta bin name is provided to define the default prefix for non +specified bins. It behaves like the other names, which means that an explicit +FALSE will order the backend not to provide any prefix for any non specified +bin. + +Here is a complex sample: + + // Default behavior for all bins, prefix is 'mysite_'. + $settings['cache_prefix']['default'] = 'mysite_'; + + // Set no prefix explicitely for 'cache' and 'cache_bootstrap' bins. + $settings['cache_prefix']['cache'] = FALSE; + $settings['cache_prefix']['cache_bootstrap'] = FALSE; + + // Set another prefix for 'cache_menu' bin. + $settings['cache_prefix']['cache_menu'] = 'menumysite_'; + +Note that if you don't specify the default behavior, the Redis module will +attempt to use the HTTP_HOST variable in order to provide a multisite safe +default behavior. Notice that this is not failsafe, in such environment you +are strongly advised to set at least an explicit default prefix. + +Note that this last notice is Redis only specific, because per default Redis +server will not namespace data, thus sharing an instance for multiple sites +will create conflicts. This is not true for every contributed backends. + +Flush mode +---------- + +@todo: Update for Drupal 8 + +Redis allows to set a time-to-live at the key level, which frees us from +handling the garbage collection at clear() calls; Unfortunately Drupal never +explicitely clears single cached pages or blocks. If you didn't configure the +"cache_lifetime" core variable, its value is "0" which means that temporary +items never expire: in this specific case, we need to adopt a different +behavior than leting Redis handling the TTL by itself; This is why we have +three different implementations of the flush algorithm you can use: + + * 0: Never flush temporary: leave Redis handling the TTL; This mode is + not compatible for the "page" and "block" bins but is the default for + all others. + + * 1: Keep a copy of temporary items identifiers in a SET and flush them + accordingly to spec (DatabaseCache default backend mimic behavior): + this is the default for "page" and "block" bin if you don't change the + configuration. + + * 2: Flush everything including permanent or valid items on clear() calls: + this behavior mimics the pre-1.0 releases of this module. Use it only + if you experience backward compatibility problems on a production + environement - at the cost of potential performance issues; All other + users should ignore this parameter. + +You can configure a default flush mode which will override the sensible +provided defaults by setting the 'redis_flush_mode' variable. + + // For example this is the safer mode. + $conf['redis_flush_mode'] = 1; + +But you may also want to change the behavior for only a few bins. + + // This will put mode 0 on "bootstrap" bin. + $conf['redis_flush_mode_cache_bootstrap'] = 0; + + // And mode 2 to "page" bin. + $conf['redis_flush_mode_cache_page'] = 2; + +Note that you must prefix your bins with "cache" as the Drupal 7 bin naming +convention requires it. + +Keep in mind that defaults will provide the best balance between performance +and safety for most sites; Non advanced users should ever change them. + +Default lifetime for permanent items +------------------------------------ + +@todo: Update for Drupal 8 + +Redis when reaching its maximum memory limit will stop writing data in its +storage engine: this is a feature that avoid the Redis server crashing when +there is no memory left on the machine. + +As a workaround, Redis can be configured as a LRU cache for both volatile or +permanent items, which means it can behave like Memcache; Problem is that if +you use Redis as a permanent storage for other business matters than this +module you cannot possibly configure it to drop permanent items or you'll +loose data. + +This workaround allows you to explicity set a very long or configured default +lifetime for CACHE_PERMANENT items (that would normally be permanent) which +will mark them as being volatile in Redis storage engine: this then allows you +to configure a LRU behavior for volatile keys without engaging the permenent +business stuff in a dangerous LRU mechanism; Cache items even if permament will +be dropped when unused using this. + +Per default the TTL for permanent items will set to safe-enough value which is +one year; No matter how Redis will be configured default configuration or lazy +admin will inherit from a safe module behavior with zero-conf. + +For advanturous people, you can manage the TTL on a per bin basis and change +the default one: + + // Make CACHE_PERMANENT items being permanent once again + // 0 is a special value usable for all bins to explicitely tell the + // cache items will not be volatile in Redis. + $conf['redis_perm_ttl'] = 0; + + // Make them being volatile with a default lifetime of 1 year. + $conf['redis_perm_ttl'] = "1 year"; + + // You can override on a per-bin basis; + // For example make cached field values live only 3 monthes: + $conf['redis_perm_ttl_cache_field'] = "3 months"; + + // But you can also put a timestamp in there; In this case the + // value must be a STRICTLY TYPED integer: + $conf['redis_perm_ttl_cache_field'] = 2592000; // 30 days. + +Time interval string will be parsed using DateInterval::createFromDateString +please refer to its documentation: + + http://www.php.net/manual/en/dateinterval.createfromdatestring.php + +Last but not least please be aware that this setting affects the +CACHE_PERMANENT ONLY; All other use cases (CACHE_TEMPORARY or user set TTL +on single cache entries) will continue to behave as documented in Drupal core +cache backend documentation. + +Lock backends +------------- + +@todo: Update for Drupal 8 + +Both implementations provides a Redis lock backend. Redis lock backend proved to +be faster than the default SQL based one when using both servers on the same box. + +Both backends, thanks to the Redis WATCH, MULTI and EXEC commands provides a +real race condition free mutexes if you use Redis >= 2.1.0. + +Testing +======= + +I did not find any hint about making tests being configurable, so per default +the tested Redis server must always be on localhost with default configuration. diff --git a/sites/all/modules/contrib/dev/redis/composer.json b/sites/all/modules/contrib/dev/redis/composer.json new file mode 100644 index 000000000..ff18a35a0 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/composer.json @@ -0,0 +1,13 @@ +{ + "name": "drupal/redis", + "type": "drupal-module", + "suggest": { + "predis/predis": "^1.1.1" + }, + "license": "GPL-2.0", + "autoload": { + "psr-4": { + "Drupal\\redis\\": "src" + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/example.services.yml b/sites/all/modules/contrib/dev/redis/example.services.yml new file mode 100644 index 000000000..74a05f66e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/example.services.yml @@ -0,0 +1,33 @@ +# This file contains example services overrides. +# +# Enable with this line in settings.php +# $settings['container_yamls'][] = 'modules/redis/example.services.yml'; +# +# Or copy & paste the desired services into sites/default/services.yml. +# +# Note that the redis module must be enabled for this to work. + +services: + # Cache tag checksum backend. Used by redis and most other cache backend + # to deal with cache tag invalidations. + cache_tags.invalidator.checksum: + class: Drupal\redis\Cache\RedisCacheTagsChecksum + arguments: ['@redis.factory'] + tags: + - { name: cache_tags_invalidator } + + # Replaces the default lock backend with a redis implementation. + lock: + class: Drupal\Core\Lock\LockBackendInterface + factory: ['@redis.lock.factory', get] + + # Replaces the default persistent lock backend with a redis implementation. + lock.persistent: + class: Drupal\Core\Lock\LockBackendInterface + factory: ['@redis.lock.factory', get] + arguments: [true] + + # Replaces the default flood backend with a redis implementation. + flood: + class: Drupal\Core\Flood\FloodInterface + factory: ['@redis.flood.factory', get] diff --git a/sites/all/modules/contrib/dev/redis/redis.info.yml b/sites/all/modules/contrib/dev/redis/redis.info.yml new file mode 100644 index 000000000..cc9f23548 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.info.yml @@ -0,0 +1,12 @@ +name: Redis +description: Provide a module placeholder, for using as dependency for module that needs Redis. +package: Performance +type: module +# core: 8.x +configure: redis.admin_display + +# Information added by Drupal.org packaging script on 2017-09-14 +version: '8.x-1.0-rc2' +core: '8.x' +project: 'redis' +datestamp: 1505390051 diff --git a/sites/all/modules/contrib/dev/redis/redis.install b/sites/all/modules/contrib/dev/redis/redis.install new file mode 100644 index 000000000..3730606bf --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.install @@ -0,0 +1,40 @@ + "Redis", + 'value' => t("Connected, using the @name client.", ['@name' => ClientFactory::getClientName()]), + 'severity' => REQUIREMENT_OK, + ]; + } + else { + $requirements['redis'] = [ + 'title' => "Redis", + 'value' => t("Not connected."), + 'severity' => REQUIREMENT_WARNING, + 'description' => t("No Redis client connected, this module is useless thereof. Ensure that you enabled module using it or disable it."), + ]; + } + + return $requirements; +} diff --git a/sites/all/modules/contrib/dev/redis/redis.module b/sites/all/modules/contrib/dev/redis/redis.module new file mode 100644 index 000000000..a4d7db8e5 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.module @@ -0,0 +1,28 @@ +' . t("Current connected client uses the @name library.", ['@name' => ClientFactory::getClientName()]) . '

'; + } + else { + $messages = '

' . t('No redis connection configured.') . '

'; + } + return $messages; + } +} diff --git a/sites/all/modules/contrib/dev/redis/redis.services.yml b/sites/all/modules/contrib/dev/redis/redis.services.yml new file mode 100644 index 000000000..c7308ab72 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/redis.services.yml @@ -0,0 +1,18 @@ +services: + cache.backend.redis: + class: Drupal\redis\Cache\CacheBackendFactory + arguments: ['@redis.factory', '@cache_tags.invalidator.checksum', '@serialization.phpserialize'] + redis.factory: + class: Drupal\redis\ClientFactory + redis.lock.factory: + class: Drupal\redis\Lock\LockFactory + arguments: ['@redis.factory'] + redis.flood.factory: + class: Drupal\redis\Flood\FloodFactory + arguments: ['@redis.factory', '@request_stack'] + queue.redis_reliable: + class: Drupal\redis\Queue\ReliableQueueRedisFactory + arguments: ['@redis.factory', '@settings'] + queue.redis: + class: Drupal\redis\Queue\QueueRedisFactory + arguments: ['@redis.factory', '@settings'] diff --git a/sites/all/modules/contrib/dev/redis/src/Cache/CacheBackendFactory.php b/sites/all/modules/contrib/dev/redis/src/Cache/CacheBackendFactory.php new file mode 100644 index 000000000..4efa1c650 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Cache/CacheBackendFactory.php @@ -0,0 +1,70 @@ +clientFactory = $client_factory; + $this->checksumProvider = $checksum_provider; + $this->serializer = $serializer; + } + + /** + * {@inheritdoc} + */ + public function get($bin) { + if (!isset($this->bins[$bin])) { + $class_name = $this->clientFactory->getClass(ClientFactory::REDIS_IMPL_CACHE); + $this->bins[$bin] = new $class_name($bin, $this->clientFactory->getClient(), $this->checksumProvider, $this->serializer); + } + return $this->bins[$bin]; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Cache/CacheBase.php b/sites/all/modules/contrib/dev/redis/src/Cache/CacheBase.php new file mode 100644 index 000000000..4fe8e5dce --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Cache/CacheBase.php @@ -0,0 +1,378 @@ +permTtl; + } + + /** + * CacheBase constructor. + * @param $bin + * The cache bin for which the object is created. + * @param \Drupal\Component\Serialization\SerializationInterface $serializer + * The serialization class to use. + */ + public function __construct($bin, SerializationInterface $serializer) { + $this->bin = $bin; + $this->serializer = $serializer; + $this->setPermTtl(); + } + + /** + * {@inheritdoc} + */ + public function get($cid, $allow_invalid = FALSE) { + $cids = [$cid]; + $cache = $this->getMultiple($cids, $allow_invalid); + return reset($cache); + } + + /** + * {@inheritdoc} + */ + public function setMultiple(array $items) { + foreach ($items as $cid => $item) { + $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : []); + } + } + + /** + * {@inheritdoc} + */ + public function delete($cid) { + $this->deleteMultiple([$cid]); + } + + /** + * {@inheritdoc} + */ + public function removeBin() { + $this->deleteAll(); + } + + /** + * {@inheritdoc} + */ + public function invalidate($cid) { + $this->invalidateMultiple([$cid]); + } + + /** + * Return the key for the given cache key. + */ + public function getKey($cid = NULL) { + if (NULL === $cid) { + return $this->getPrefix() . ':' . $this->bin; + } + else { + return $this->getPrefix() . ':' . $this->bin . ':' . $cid; + } + } + + /** + * Calculate the correct expiration time. + * + * @param int $expire + * The expiration time provided for the cache set. + * + * @return int + * The default expiration if expire is PERMANENT or higher than the default. + * May return negative values if the item is already expired. + */ + protected function getExpiration($expire) { + if ($expire == Cache::PERMANENT || $expire > $this->permTtl) { + return $this->permTtl; + } + return $expire - REQUEST_TIME; + } + + /** + * Return the key for the tag used to specify the bin of cache-entries. + */ + protected function getTagForBin() { + return 'x-redis-bin:' . $this->bin; + } + + /** + * Set the minimum TTL (unit testing only). + */ + public function setMinTtl($ttl) { + $this->minTtl = $ttl; + } + + /** + * Set the permanent TTL. + */ + public function setPermTtl($ttl = NULL) { + if (isset($ttl)) { + $this->permTtl = $ttl; + } + else { + // Attempt to set from settings. + if (($settings = Settings::get('redis.settings', [])) && isset($settings['perm_ttl_' . $this->bin])) { + $ttl = $settings['perm_ttl_' . $this->bin]; + if ($ttl === (int) $ttl) { + $this->permTtl = $ttl; + } + else { + if ($iv = DateInterval::createFromDateString($ttl)) { + // http://stackoverflow.com/questions/14277611/convert-dateinterval-object-to-seconds-in-php + $this->permTtl = ($iv->y * 31536000 + $iv->m * 2592000 + $iv->days * 86400 + $iv->h * 3600 + $iv->i * 60 + $iv->s); + } + else { + // Log error about invalid ttl. + trigger_error(sprintf("Parsed TTL '%s' has an invalid value: switching to default", $ttl)); + $this->permTtl = self::LIFETIME_PERM_DEFAULT; + } + + } + } + } + } + + /** + * Prepares a cached item. + * + * Checks that items are either permanent or did not expire, and unserializes + * data as appropriate. + * + * @param array $values + * The hash returned from redis or false. + * @param bool $allow_invalid + * If FALSE, the method returns FALSE if the cache item is not valid. + * + * @return mixed|false + * The item with data unserialized as appropriate and a property indicating + * whether the item is valid, or FALSE if there is no valid item to load. + */ + protected function expandEntry(array $values, $allow_invalid) { + // Check for entry being valid. + if (empty($values['cid'])) { + return FALSE; + } + + $cache = (object) $values; + + $cache->tags = explode(' ', $cache->tags); + + // Check expire time, allow to have a cache invalidated explicitly, don't + // check if already invalid. + if ($cache->valid) { + $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; + + // Check if invalidateTags() has been called with any of the items's tags. + if ($cache->valid && !$this->checksumProvider->isValid($cache->checksum, $cache->tags)) { + $cache->valid = FALSE; + } + } + + // Ensure the entry does not predate the last delete all time. + $last_delete_timestamp = $this->getLastDeleteAll(); + if ($last_delete_timestamp && ((float)$values['created']) < $last_delete_timestamp) { + return FALSE; + } + + if (!$allow_invalid && !$cache->valid) { + return FALSE; + } + + if ($cache->serialized) { + $cache->data = $this->serializer->decode($cache->data); + } + + return $cache; + } + + /** + * Create cache entry. + * + * @param string $cid + * @param mixed $data + * @param int $expire + * @param string[] $tags + * + * @return array + */ + protected function createEntryHash($cid, $data, $expire = Cache::PERMANENT, array $tags) { + // Always add a cache tag for the current bin, so that we can use that for + // invalidateAll(). + $tags[] = $this->getTagForBin(); + assert('\Drupal\Component\Assertion\Inspector::assertAllStrings($tags)', 'Cache Tags must be strings.'); + $hash = [ + 'cid' => $cid, + 'created' => round(microtime(TRUE), 3), + 'expire' => $expire, + 'tags' => implode(' ', $tags), + 'valid' => 1, + 'checksum' => $this->checksumProvider->getCurrentChecksum($tags), + ]; + + // Let Redis handle the data types itself. + if (!is_string($data)) { + $hash['data'] = $this->serializer->encode($data); + $hash['serialized'] = 1; + } + else { + $hash['data'] = $data; + $hash['serialized'] = 0; + } + + return $hash; + } + /** + * {@inheritdoc} + */ + public function invalidateMultiple(array $cids) { + // Loop over all cache items, they are stored as a hash, so we can access + // the valid flag directly, only write if it exists and is not 0. + foreach ($cids as $cid) { + $key = $this->getKey($cid); + if ($this->client->hGet($key, 'valid')) { + $this->client->hSet($key, 'valid', 0); + } + } + } + + /** + * {@inheritdoc} + */ + public function invalidateAll() { + // To invalidate the whole bin, we invalidate a special tag for this bin. + $this->checksumProvider->invalidateTags([$this->getTagForBin()]); + } + + /** + * {@inheritdoc} + */ + public function garbageCollection() { + // @todo Do we need to do anything here? + } + + /** + * Returns the last delete all timestamp. + * + * @return float + * The last delete timestamp as a timestamp with a millisecond precision. + */ + protected function getLastDeleteAll() { + // Cache the last delete all timestamp. + if ($this->lastDeleteAll === NULL) { + $this->lastDeleteAll = (float) $this->client->get($this->getKey(static::LAST_DELETE_ALL_KEY)); + } + return $this->lastDeleteAll; + } + + /** + * {@inheritdoc} + */ + public function deleteAll() { + // The last delete timestamp is in milliseconds, ensure that no cache + // was written in the same millisecond. + // @todo This is needed to make the tests pass, is this safe enough for real + // usage? + usleep(1000); + $this->lastDeleteAll = round(microtime(TRUE), 3); + $this->client->set($this->getKey(static::LAST_DELETE_ALL_KEY), $this->lastDeleteAll); + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Cache/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Cache/PhpRedis.php new file mode 100644 index 000000000..cb30d6055 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Cache/PhpRedis.php @@ -0,0 +1,111 @@ +client = $client; + $this->checksumProvider = $checksum_provider; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(&$cids, $allow_invalid = FALSE) { + // Avoid an error when there are no cache ids. + if (empty($cids)) { + return []; + } + + $return = []; + + // Build the list of keys to fetch. + $keys = array_map([$this, 'getKey'], $cids); + + // Optimize for the common case when only a single cache entry needs to + // be fetched, no pipeline is needed then. + if (count($keys) > 1) { + $pipe = $this->client->multi(\Redis::PIPELINE); + foreach ($keys as $key) { + $pipe->hgetall($key); + } + $result = $pipe->exec(); + } + else { + $result = [$this->client->hGetAll(reset($keys))]; + } + + // Loop over the cid values to ensure numeric indexes. + foreach (array_values($cids) as $index => $key) { + // Check if a valid result was returned from Redis. + if (isset($result[$index]) && is_array($result[$index])) { + // Check expiration and invalidation and convert into an object. + $item = $this->expandEntry($result[$index], $allow_invalid); + if ($item) { + $return[$item->cid] = $item; + } + } + } + + // Remove fetched cids from the list. + $cids = array_diff($cids, array_keys($return)); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { + + $ttl = $this->getExpiration($expire); + + $key = $this->getKey($cid); + + // If the item is already expired, delete it. + if ($ttl <= 0) { + $this->delete($key); + } + + // Build the cache item and save it as a hash array. + $entry = $this->createEntryHash($cid, $data, $expire, $tags); + $pipe = $this->client->multi(\REdis::PIPELINE); + $pipe->hMset($key, $entry); + $pipe->expire($key, $ttl); + $pipe->exec(); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $cids) { + $keys = array_map([$this, 'getKey'], $cids); + $this->client->del($keys); + } + + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Cache/Predis.php b/sites/all/modules/contrib/dev/redis/src/Cache/Predis.php new file mode 100644 index 000000000..6ccd7817a --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Cache/Predis.php @@ -0,0 +1,113 @@ +client = $client; + $this->checksumProvider = $checksum_provider; + } + + /** + * {@inheritdoc} + */ + public function getMultiple(&$cids, $allow_invalid = FALSE) { + // Avoid an error when there are no cache ids. + if (empty($cids)) { + return []; + } + + $return = []; + + // Build the list of keys to fetch. + $keys = array_map([$this, 'getKey'], $cids); + + // Optimize for the common case when only a single cache entry needs to + // be fetched, no pipeline is needed then. + if (count($keys) > 1) { + $pipe = $this->client->pipeline(); + foreach ($keys as $key) { + $pipe->hgetall($key); + } + $result = $pipe->execute(); + } + else { + $result = [$this->client->hGetAll(reset($keys))]; + } + + // Loop over the cid values to ensure numeric indexes. + foreach (array_values($cids) as $index => $key) { + // Check if a valid result was returned from Redis. + if (isset($result[$index]) && is_array($result[$index])) { + // Check expiration and invalidation and convert into an object. + $item = $this->expandEntry($result[$index], $allow_invalid); + if ($item) { + $return[$item->cid] = $item; + } + } + } + + // Remove fetched cids from the list. + $cids = array_diff($cids, array_keys($return)); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) { + + $ttl = $this->getExpiration($expire); + + $key = $this->getKey($cid); + + // If the item is already expired, delete it. + if ($ttl <= 0) { + $this->delete($key); + } + + // Build the cache item and save it as a hash array. + $entry = $this->createEntryHash($cid, $data, $expire, $tags); + $pipe = $this->client->pipeline(); + $pipe->hmset($key, $entry); + $pipe->expire($key, $ttl); + $pipe->execute(); + } + + /** + * {@inheritdoc} + */ + public function deleteMultiple(array $cids) { + if (!empty($cids)) { + $keys = array_map([$this, 'getKey'], $cids); + $this->client->del($keys); + } + } + + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Cache/RedisCacheTagsChecksum.php b/sites/all/modules/contrib/dev/redis/src/Cache/RedisCacheTagsChecksum.php new file mode 100644 index 000000000..9992a2f50 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Cache/RedisCacheTagsChecksum.php @@ -0,0 +1,150 @@ +client = $factory->getClient(); + $this->clientType = $factory->getClientName(); + } + + /** + * {@inheritdoc} + */ + public function invalidateTags(array $tags) { + $keys_to_increment = []; + foreach ($tags as $tag) { + // Only invalidate tags once per request unless they are written again. + if (isset($this->invalidatedTags[$tag])) { + continue; + } + $this->invalidatedTags[$tag] = TRUE; + unset($this->tagCache[$tag]); + $keys_to_increment[] = $this->getTagKey($tag); + } + if ($keys_to_increment) { + + // We want to differentiate between PhpRedis and Redis clients. + if ($this->clientType === 'PhpRedis') { + $multi = $this->client->multi(\Redis::PIPELINE); + foreach ($keys_to_increment as $key) { + $multi->incr($key); + } + $multi->exec(); + } + elseif ($this->clientType === 'Predis') { + + $pipe = $this->client->pipeline(); + foreach ($keys_to_increment as $key) { + $pipe->incr($key); + } + $pipe->execute(); + } + } + + } + + /** + * {@inheritdoc} + */ + public function getCurrentChecksum(array $tags) { + // Remove tags that were already invalidated during this request from the + // static caches so that another invalidation can occur later in the same + // request. Without that, written cache items would not be invalidated + // correctly. + foreach ($tags as $tag) { + unset($this->invalidatedTags[$tag]); + } + return $this->calculateChecksum($tags); + } + + /** + * {@inheritdoc} + */ + public function isValid($checksum, array $tags) { + return $checksum == $this->calculateChecksum($tags); + } + + /** + * {@inheritdoc} + */ + public function calculateChecksum(array $tags) { + $checksum = 0; + + $fetch = array_values(array_diff($tags, array_keys($this->tagCache))); + if ($fetch) { + $keys = array_map([$this, 'getTagKey'], $fetch); + foreach ($this->client->mget($keys) as $index => $invalidations) { + $this->tagCache[$fetch[$index]] = $invalidations ?: 0; + } + } + + foreach ($tags as $tag) { + $checksum += $this->tagCache[$tag]; + } + + return $checksum; + } + + /** + * {@inheritdoc} + */ + public function reset() { + $this->tagCache = []; + $this->invalidatedTags = []; + } + + /** + * Return the key for the given cache tag. + * + * @param string $tag + * The cache tag. + * + * @return string + * The prefixed cache tag. + */ + protected function getTagKey($tag) { + return $this->getPrefix() . ':cachetags:' . $tag; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Client/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Client/PhpRedis.php new file mode 100644 index 000000000..147d2c78d --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Client/PhpRedis.php @@ -0,0 +1,97 @@ +askForMaster($client, $host, $password); + if (is_array($ip_host)) { + list($host, $port) = $ip_host; + } + } + + $client->connect($host, $port); + + if (isset($password)) { + $client->auth($password); + } + + if (isset($base)) { + $client->select($base); + } + + // Do not allow PhpRedis serialize itself data, we are going to do it + // ourself. This will ensure less memory footprint on Redis size when + // we will attempt to store small values. + $client->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_NONE); + + return $client; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'PhpRedis'; + } + + /** + * Connect to sentinels to get Redis master instance. + * + * Just asking one sentinels after another until given the master location. + * More info about this mode at https://redis.io/topics/sentinel. + * + * @param \Redis $client + * The PhpRedis client. + * @param array $sentinels + * An array of the sentinels' ip:port. + * @param string $password + * An optional Sentinels' password. + * + * @return mixed + * An array with ip & port of the Master instance or NULL. + */ + protected function askForMaster(\Redis $client, array $sentinels = [], $password = NULL) { + + $ip_port = NULL; + $settings = Settings::get('redis.connection', []); + $settings += ['instance' => NULL]; + + if ($settings['instance']) { + foreach ($sentinels as $sentinel) { + list($host, $port) = explode(':', $sentinel); + // 0.5s timeout. + $client->connect($host, $port, 0.5); + + if (isset($password)) { + $client->auth($password); + } + + if ($client->isConnected()) { + $ip_port = $client->rawcommand('SENTINEL', 'get-master-addr-by-name', $settings['instance']); + if ($ip_port) { + break; + } + } + $client->close(); + } + } + return $ip_port; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Client/Predis.php b/sites/all/modules/contrib/dev/redis/src/Client/Predis.php new file mode 100644 index 000000000..747d73a46 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Client/Predis.php @@ -0,0 +1,61 @@ + $password, + 'host' => $host, + 'port' => $port, + 'database' => $base + ]; + + foreach ($connectionInfo as $key => $value) { + if (!isset($value)) { + unset($connectionInfo[$key]); + } + } + + // I'm not sure why but the error handler is driven crazy if timezone + // is not set at this point. + // Hopefully Drupal will restore the right one this once the current + // account has logged in. + date_default_timezone_set(@date_default_timezone_get()); + + // If we are passed in an array of $replicationHosts, we should attempt a clustered client connection. + if ($replicationHosts !== NULL) { + $parameters = []; + + foreach ($replicationHosts as $replicationHost) { + // Configure master. + if ($replicationHost['role'] === 'primary') { + $parameters[] = 'tcp://' . $replicationHost['host'] . ':' . $replicationHost['port'] . '?alias=master'; + } + else { + $parameters[] = 'tcp://' . $replicationHost['host'] . ':' . $replicationHost['port']; + } + } + + $options = ['replication' => true]; + $client = new Client($parameters, $options); + } + else { + $client = new Client($connectionInfo); + } + return $client; + + } + + public function getName() { + return 'Predis'; + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/ClientFactory.php b/sites/all/modules/contrib/dev/redis/src/ClientFactory.php new file mode 100644 index 000000000..cddabd96e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/ClientFactory.php @@ -0,0 +1,215 @@ +getName(); + } + + /** + * Get client singleton. + */ + public static function getClient() { + if (!isset(self::$_client)) { + $settings = Settings::get('redis.connection', []); + $settings += [ + 'host' => self::REDIS_DEFAULT_HOST, + 'port' => self::REDIS_DEFAULT_PORT, + 'base' => self::REDIS_DEFAULT_BASE, + 'password' => self::REDIS_DEFAULT_PASSWORD, + ]; + + // If using replication, lets create the client appropriately. + if (isset($settings['replication']) && $settings['replication'] === TRUE) { + foreach ($settings['replication.host'] as $key => $replicationHost) { + if (!isset($replicationHost['port'])) { + $settings['replication.host'][$key]['port'] = self::REDIS_DEFAULT_PORT; + } + } + + self::$_client = self::getClientInterface()->getClient( + $settings['host'], + $settings['port'], + $settings['base'], + $settings['password'], + $settings['replication.host']); + } + else { + self::$_client = self::getClientInterface()->getClient( + $settings['host'], + $settings['port'], + $settings['base'], + $settings['password']); + } + } + + return self::$_client; + } + + /** + * Get specific class implementing the current client usage for the specific + * asked core subsystem. + * + * @param string $system + * One of the ClientFactory::IMPL_* constant. + * @param string $clientName + * Client name, if fixed. + * + * @return string + * Class name, if found. + * + * @throws \Exception + * If not found. + */ + public static function getClass($system, $clientName = NULL) { + $className = $system . ($clientName ?: self::getClientName()); + + if (!class_exists($className)) { + throw new \Exception($className . " does not exists"); + } + + return $className; + } + + /** + * For unit testing only reset internals. + */ + static public function reset() { + self::$_clientInterface = null; + self::$_client = null; + } +} + diff --git a/sites/all/modules/contrib/dev/redis/src/ClientInterface.php b/sites/all/modules/contrib/dev/redis/src/ClientInterface.php new file mode 100644 index 000000000..982dd75cf --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/ClientInterface.php @@ -0,0 +1,26 @@ +clientFactory = $client_factory; + $this->requestStack = $request_stack; + } + + /** + * Get actual flood backend. + * + * @return \Drupal\Core\Flood\FloodInterface + * Return flood instance. + */ + public function get() { + $class_name = $this->clientFactory->getClass(ClientFactory::REDIS_IMPL_FLOOD); + return new $class_name($this->clientFactory, $this->requestStack); + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Flood/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Flood/PhpRedis.php new file mode 100644 index 000000000..06a36047c --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Flood/PhpRedis.php @@ -0,0 +1,95 @@ +client = $client_factory->getClient(); + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public function register($name, $window = 3600, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + + // Add a key for the event to the sorted set, the score is timestamp, so we + // can count them easily. + $this->client->zAdd($key, $_SERVER['REQUEST_TIME'] + $window, microtime(TRUE)); + // Set or update the expiration for the sorted set, it will be removed if + // the newest entry expired. + $this->client->expire($key, $_SERVER['REQUEST_TIME'] + $window); + } + + /** + * {@inheritdoc} + */ + public function clear($name, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + $this->client->del($key); + } + + /** + * {@inheritdoc} + */ + public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + + // Count the in the last $window seconds. + $number = $this->client->zCount($key, $_SERVER['REQUEST_TIME'], 'inf'); + return ($number < $threshold); + } + + /** + * {@inheritdoc} + */ + public function garbageCollection() { + // No garbage collection necessary. + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Flood/Predis.php b/sites/all/modules/contrib/dev/redis/src/Flood/Predis.php new file mode 100644 index 000000000..de28b0496 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Flood/Predis.php @@ -0,0 +1,95 @@ +client = $client_factory->getClient(); + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + */ + public function register($name, $window = 3600, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + + // Add a key for the event to the sorted set, the score is timestamp, so we + // can count them easily. + $this->client->zAdd($key, $_SERVER['REQUEST_TIME'] + $window, microtime(TRUE)); + // Set or update the expiration for the sorted set, it will be removed if + // the newest entry expired. + $this->client->expire($key, $_SERVER['REQUEST_TIME'] + $window); + } + + /** + * {@inheritdoc} + */ + public function clear($name, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + $this->client->del($key); + } + + /** + * {@inheritdoc} + */ + public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) { + if (!isset($identifier)) { + $identifier = $this->requestStack->getCurrentRequest()->getClientIp(); + } + + $key = $this->getPrefix() . ':flood:' . $name . ':' . $identifier; + + // Count the in the last $window seconds. + $number = $this->client->zCount($key, $_SERVER['REQUEST_TIME'], 'inf'); + return ($number < $threshold); + } + + /** + * {@inheritdoc} + */ + public function garbageCollection() { + // No garbage collection necessary. + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Lock/LockFactory.php b/sites/all/modules/contrib/dev/redis/src/Lock/LockFactory.php new file mode 100644 index 000000000..6e8c8b184 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Lock/LockFactory.php @@ -0,0 +1,37 @@ +clientFactory = $client_factory; + } + + /** + * Get actual lock backend. + * + * @param bool $persistent + * (optional) Whether to return a persistent lock implementation or not. + * + * @return \Drupal\Core\Lock\LockBackendInterface + * Return lock backend instance. + */ + public function get($persistent = FALSE) { + $class_name = $this->clientFactory->getClass($persistent ? ClientFactory::REDIS_IMPL_PERSISTENT_LOCK : ClientFactory::REDIS_IMPL_LOCK); + return new $class_name($this->clientFactory); + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php new file mode 100644 index 000000000..2f53e62ab --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Lock/PhpRedis.php @@ -0,0 +1,147 @@ +client = $factory->getClient(); + // __destruct() is causing problems with garbage collections, register a + // shutdown function instead. + drupal_register_shutdown_function([$this, 'releaseAll']); + } + + /** + * Generate a redis key name for the current lock name. + * + * @param string $name + * Lock name. + * + * @return string + * The redis key for the given lock. + */ + protected function getKey($name) { + return $this->getPrefix() . ':lock:' . $name; + } + + /** + * {@inheritdoc} + */ + public function acquire($name, $timeout = 30.0) { + $key = $this->getKey($name); + $id = $this->getLockId(); + + // Insure that the timeout is at least 1 ms. + $timeout = max($timeout, 0.001); + + // If we already have the lock, check for his owner and attempt a new EXPIRE + // command on it. + if (isset($this->locks[$name])) { + + // Create a new transaction, for atomicity. + $this->client->watch($key); + + // Global tells us we are the owner, but in real life it could have expired + // and another process could have taken it, check that. + if ($this->client->get($key) != $id) { + // Explicit UNWATCH we are not going to run the MULTI/EXEC block. + $this->client->unwatch(); + unset($this->locks[$name]); + return FALSE; + } + + $result = $this->client->multi() + ->psetex($key, (int) ($timeout * 1000), $id) + ->exec(); + + // If the set failed, someone else wrote the key, we failed to acquire + // the lock. + if (FALSE === $result) { + unset($this->locks[$name]); + // Explicit transaction release which also frees the WATCH'ed key. + $this->client->discard(); + return FALSE; + } + + return ($this->locks[$name] = TRUE); + } + else { + // Use a SET with microsecond expiration and the NX flag, which will only + // succeed if the key does not exist yet. + $result = $this->client->set($key, $id, ['nx', 'px' => (int) ($timeout * 1000)]); + + // If the result is FALSE, we failed to acquire the lock. + if (FALSE === $result) { + return FALSE; + } + + // Register the lock. + return ($this->locks[$name] = TRUE); + } + } + + /** + * {@inheritdoc} + */ + public function lockMayBeAvailable($name) { + $key = $this->getKey($name); + $value = $this->client->get($key); + + // In Drupal 7, this method treated the lock as available if the ID did + // match. The database backend and test expects it to return FALSE in that + // case, updated accordingly. + return FALSE === $value; + } + + /** + * {@inheritdoc} + */ + public function release($name) { + $key = $this->getKey($name); + $id = $this->getLockId(); + + unset($this->locks[$name]); + + // Ensure the lock deletion is an atomic transaction. If another thread + // manages to removes all lock, we can not alter it anymore else we will + // release the lock for the other thread and cause race conditions. + $this->client->watch($key); + + if ($this->client->get($key) == $id) { + $this->client->multi(); + $this->client->delete($key); + $this->client->exec(); + } + else { + $this->client->unwatch(); + } + } + + /** + * {@inheritdoc} + */ + public function releaseAll($lock_id = NULL) { + // We can afford to deal with a slow algorithm here, this should not happen + // on normal run because we should have removed manually all our locks. + foreach ($this->locks as $name => $foo) { + $this->release($name); + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php b/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php new file mode 100644 index 000000000..bede5d97b --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Lock/Predis.php @@ -0,0 +1,136 @@ +client = $factory->getClient(); + // __destruct() is causing problems with garbage collections, register a + // shutdown function instead. + drupal_register_shutdown_function([$this, 'releaseAll']); + } + + /** + * Generate a redis key name for the current lock name. + * + * @param string $name + * Lock name. + * + * @return string + * The redis key for the given lock. + */ + protected function getKey($name) { + return $this->getPrefix() . ':lock:' . $name; + } + + public function acquire($name, $timeout = 30.0) { + $key = $this->getKey($name); + $id = $this->getLockId(); + + // Insure that the timeout is at least 1 ms. + $timeout = max($timeout, 0.001); + + // If we already have the lock, check for his owner and attempt a new EXPIRE + // command on it. + if (isset($this->locks[$name])) { + + // Create a new transaction, for atomicity. + $this->client->watch($key); + + // Global tells us we are the owner, but in real life it could have expired + // and another process could have taken it, check that. + if ($this->client->get($key) != $id) { + // Explicit UNWATCH we are not going to run the MULTI/EXEC block. + $this->client->unwatch(); + unset($this->locks[$name]); + return FALSE; + } + + $result = $this->client->pipeline() + ->psetex($key, (int) ($timeout * 1000), $id) + ->exec(); + + // If the set failed, someone else wrote the key, we failed to acquire + // the lock. + if (FALSE === $result) { + unset($this->locks[$name]); + // Explicit transaction release which also frees the WATCH'ed key. + $this->client->discard(); + return FALSE; + } + + return ($this->locks[$name] = TRUE); + } + else { + // Use a SET with microsecond expiration and the NX flag, which will only + // succeed if the key does not exist yet. + $result = $this->client->set($key, $id, 'nx', 'px', (int) ($timeout * 1000)); + + // If the result is FALSE, we failed to acquire the lock. + if (FALSE === $result) { + return FALSE; + } + + // Register the lock. + return ($this->locks[$name] = TRUE); + } + } + + public function lockMayBeAvailable($name) { + $key = $this->getKey($name); + $value = $this->client->get($key); + + // In Drupal 7, this method treated the lock as available if the ID did + // match. The database backend and test expects it to return FALSE in that + // case, updated accordingly. + return FALSE === $value; + } + + public function release($name) { + $key = $this->getKey($name); + $id = $this->getLockId(); + + unset($this->locks[$name]); + + // Ensure the lock deletion is an atomic transaction. If another thread + // manages to removes all lock, we can not alter it anymore else we will + // release the lock for the other thread and cause race conditions. + $this->client->watch($key); + + if ($this->client->get($key) == $id) { + $pipe = $this->client->pipeline(); + $pipe->del([$key]); + $pipe->execute(); + } + else { + $this->client->unwatch(); + } + } + + public function releaseAll($lock_id = NULL) { + // We can afford to deal with a slow algorithm here, this should not happen + // on normal run because we should have removed manually all our locks. + foreach ($this->locks as $name => $foo) { + $this->release($name); + } + } +} + diff --git a/sites/all/modules/contrib/dev/redis/src/PersistentLock/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/PersistentLock/PhpRedis.php new file mode 100644 index 000000000..9805f7236 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/PersistentLock/PhpRedis.php @@ -0,0 +1,26 @@ +client = $factory->getClient(); + // Set the lockId to a fixed string to make the lock ID the same across + // multiple requests. The lock ID is used as a page token to relate all the + // locks set during a request to each other. + // @see \Drupal\Core\Lock\LockBackendInterface::getLockId() + $this->lockId = 'persistent'; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/PhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Queue/PhpRedis.php new file mode 100644 index 000000000..530be0dfb --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/PhpRedis.php @@ -0,0 +1,148 @@ +client = $client; + } + + /** + * {@inheritdoc} + */ + public function createItem($data) { + $record = new \stdClass(); + $record->data = $data; + $record->qid = $this->incrementId(); + // We cannot rely on REQUEST_TIME because many items might be created + // by a single request which takes longer than 1 second. + $record->timestamp = time(); + + if (!$this->client->hsetnx($this->availableItems, $record->qid, serialize($record))) { + return FALSE; + } + + $start_len = $this->client->lLen($this->availableListKey); + if ($start_len < $this->client->lpush($this->availableListKey, $record->qid)) { + return $record->qid; + } + + return FALSE; + } + + /** + * Gets next serial ID for Redis queue items. + * + * @return int + * Next serial ID for Redis queue item. + */ + protected function incrementId() { + return $this->client->incr($this->incrementCounterKey); + } + + /** + * {@inheritdoc} + */ + public function numberOfItems() { + return $this->client->lLen($this->availableListKey); + } + + /** + * {@inheritdoc} + */ + public function claimItem($lease_time = 30) { + // Is it OK to do garbage collection here (we need to loop list of claimed + // items)? + $this->garbageCollection(); + $item = FALSE; + + if ($this->reserveTimeout !== NULL) { + // A blocking version of claimItem to be used with long-running queue workers. + $qid = $this->client->brpoplpush($this->availableListKey, $this->claimedListKey, $this->reserveTimeout); + } + else { + $qid = $this->client->rpoplpush($this->availableListKey, $this->claimedListKey); + } + + if ($qid) { + $job = $this->client->hget($this->availableItems, $qid); + if ($job) { + $item = unserialize($job); + $this->client->setex($this->leasedKeyPrefix . $item->qid, $lease_time, '1'); + } + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function releaseItem($item) { + $this->client->lrem($this->claimedListKey, $item->qid, -1); + $this->client->lpush($this->availableListKey, $item->qid); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($item) { + $this->client->lrem($this->claimedListKey, $item->qid, -1); + $this->client->hdel($this->availableItems, $item->qid); + } + + /** + * {@inheritdoc} + */ + public function deleteQueue() { + $keys_to_remove = [ + $this->claimedListKey, + $this->availableListKey, + $this->availableItems, + $this->incrementCounterKey + ]; + + foreach ($this->client->keys($this->leasedKeyPrefix . '*') as $key) { + $keys_to_remove[] = $key; + } + + $this->client->del($keys_to_remove); + } + + /** + * Automatically release items, that have been claimed and exceeded lease time. + */ + protected function garbageCollection() { + foreach ($this->client->lrange($this->claimedListKey, 0, -1) as $qid) { + if (!$this->client->exists($this->leasedKeyPrefix . $qid)) { + // The lease expired for this ID. + $this->client->lrem($this->claimedListKey, $qid, -1); + $this->client->lpush($this->availableListKey, $qid); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/Predis.php b/sites/all/modules/contrib/dev/redis/src/Queue/Predis.php new file mode 100644 index 000000000..4eec38e8c --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/Predis.php @@ -0,0 +1,152 @@ +client = $client; + } + + /** + * {@inheritdoc} + */ + public function createItem($data) { + // TODO: Fixme + $record = new \stdClass(); + $record->data = $data; + $record->qid = $this->incrementId(); + // We cannot rely on REQUEST_TIME because many items might be created + // by a single request which takes longer than 1 second. + $record->timestamp = time(); + + if (!$this->client->hsetnx($this->availableItems, $record->qid, serialize($record))) { + return FALSE; + } + + $start_len = $this->client->lLen($this->availableListKey); + if ($start_len < $this->client->lpush($this->availableListKey, $record->qid)) { + return $record->qid; + } + } + + /** + * Gets next serial ID for Redis queue items. + * + * @return int + * Next serial ID for Redis queue item. + */ + protected function incrementId() { + // TODO: Fixme + return $this->client->incr($this->incrementCounterKey); + } + + /** + * {@inheritdoc} + */ + public function numberOfItems() { + // TODO: Fixme + return $this->client->lLen($this->availableListKey); + } + + /** + * {@inheritdoc} + */ + public function claimItem($lease_time = 30) { + // Is it OK to do garbage collection here (we need to loop list of claimed + // items)? + $this->garbageCollection(); + $item = FALSE; + + if ($this->reserveTimeout !== NULL) { + // A blocking version of claimItem to be used with long-running queue workers. + $qid = $this->client->brpoplpush($this->availableListKey, $this->claimedListKey, $this->reserveTimeout); + } + else { + $qid = $this->client->rpoplpush($this->availableListKey, $this->claimedListKey); + } + + if ($qid) { + $job = $this->client->hget($this->availableItems, $qid); + if ($job) { + $item = unserialize($job); + $this->client->setex($this->leasedKeyPrefix . $item->qid, $lease_time, '1'); + } + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function releaseItem($item) { + // TODO: Fixme + $this->client->lrem($this->claimedListKey, $item->qid, -1); + $this->client->lpush($this->availableListKey, $item->qid); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($item) { + // TODO: Fixme + $this->client->lrem($this->claimedListKey, $item->qid, -1); + $this->client->hdel($this->availableItems, $item->qid); + } + + /** + * {@inheritdoc} + */ + public function deleteQueue() { + // TODO: Fixme + $keys_to_remove = [ + $this->claimedListKey, + $this->availableListKey, + $this->availableItems, + $this->incrementCounterKey + ]; + + foreach ($this->client->keys($this->leasedKeyPrefix . '*') as $key) { + $keys_to_remove[] = $key; + } + + $this->client->del($keys_to_remove); + } + + /** + * Automatically release items, that have been claimed and exceeded lease time. + */ + protected function garbageCollection() { + foreach ($this->client->lrange($this->claimedListKey, 0, -1) as $qid) { + if (!$this->client->exists($this->leasedKeyPrefix . $qid)) { + // The lease expired for this ID. + $this->client->lrem($this->claimedListKey, $qid, -1); + $this->client->lpush($this->availableListKey, $qid); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/QueueBase.php b/sites/all/modules/contrib/dev/redis/src/Queue/QueueBase.php new file mode 100644 index 000000000..a09148e0c --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/QueueBase.php @@ -0,0 +1,96 @@ +name = $name; + $this->reserveTimeout = $settings['reserve_timeout']; + $this->availableListKey = static::KEY_PREFIX . $name . ':avail'; + $this->availableItems = static::KEY_PREFIX . $name . ':items'; + $this->claimedListKey = static::KEY_PREFIX . $name . ':claimed'; + $this->leasedKeyPrefix = static::KEY_PREFIX . $name . ':lease:'; + $this->incrementCounterKey = static::KEY_PREFIX . $name . ':counter'; + } + + /** + * {@inheritdoc} + */ + public function createQueue() { + // Nothing to do here. + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/QueueRedisFactory.php b/sites/all/modules/contrib/dev/redis/src/Queue/QueueRedisFactory.php new file mode 100644 index 000000000..aabc1b79f --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/QueueRedisFactory.php @@ -0,0 +1,56 @@ +clientFactory = $client_factory; + $this->settings = $settings; + } + + /** + * Constructs a new queue object for a given name. + * + * @param string $name + * The name of the collection holding key and value pairs. + * + * @return \Drupal\Core\Queue\DatabaseQueue + * A key/value store implementation for the given $collection. + */ + public function get($name) { + $settings = $this->settings->get('redis_queue_' . $name, ['reserve_timeout' => NULL]); + $class_name = $this->clientFactory->getClass(static::CLASS_NAMESPACE); + return new $class_name($name, $settings, $this->clientFactory->getClient()); + } + +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePhpRedis.php b/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePhpRedis.php new file mode 100644 index 000000000..76eb17810 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePhpRedis.php @@ -0,0 +1,153 @@ +client = $client; + } + + /** + * {@inheritdoc} + */ + public function createItem($data) { + $record = new \stdClass(); + $record->data = $data; + $record->qid = $this->incrementId(); + // We cannot rely on REQUEST_TIME because many items might be created + // by a single request which takes longer than 1 second. + $record->timestamp = time(); + + $result = $this->client->multi() + ->hsetnx($this->availableItems, $record->qid, serialize($record)) + ->lLen($this->availableListKey) + ->lpush($this->availableListKey, $record->qid) + ->exec(); + + $success = $result[0] && $result[2] > $result[1]; + + return $success ? $record->qid : FALSE; + } + + /** + * Gets next serial ID for Redis queue items. + * + * @return int + * Next serial ID for Redis queue item. + */ + protected function incrementId() { + return $this->client->incr($this->incrementCounterKey); + } + + /** + * {@inheritdoc} + */ + public function numberOfItems() { + return $this->client->lLen($this->availableListKey); + } + + /** + * {@inheritdoc} + */ + public function claimItem($lease_time = 30) { + // Is it OK to do garbage collection here (we need to loop list of claimed + // items)? + $this->garbageCollection(); + $item = FALSE; + + if ($this->reserveTimeout !== NULL) { + // A blocking version of claimItem to be used with long-running queue workers. + $qid = $this->client->brpoplpush($this->availableListKey, $this->claimedListKey, $this->reserveTimeout); + } + else { + $qid = $this->client->rpoplpush($this->availableListKey, $this->claimedListKey); + } + + if ($qid) { + $job = $this->client->hget($this->availableItems, $qid); + if ($job) { + $item = unserialize($job); + $this->client->setex($this->leasedKeyPrefix . $item->qid, $lease_time, '1'); + } + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function releaseItem($item) { + $this->client->multi() + ->lrem($this->claimedListKey, $item->qid, -1) + ->lpush($this->availableListKey, $item->qid) + ->exec(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($item) { + $this->client->multi() + ->lrem($this->claimedListKey, $item->qid, -1) + ->hdel($this->availableItems, $item->qid) + ->exec(); + } + + /** + * {@inheritdoc} + */ + public function deleteQueue() { + $keys_to_remove = [ + $this->claimedListKey, + $this->availableListKey, + $this->availableItems, + $this->incrementCounterKey + ]; + + foreach ($this->client->keys($this->leasedKeyPrefix . '*') as $key) { + $keys_to_remove[] = $key; + } + + $this->client->del($keys_to_remove); + } + + /** + * Automatically release items, that have been claimed and exceeded lease time. + */ + protected function garbageCollection() { + foreach ($this->client->lrange($this->claimedListKey, 0, -1) as $qid) { + if (!$this->client->exists($this->leasedKeyPrefix . $qid)) { + // The lease expired for this ID. + $this->client->multi() + ->lrem($this->claimedListKey, $qid, -1) + ->lpush($this->availableListKey, $qid) + ->exec(); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePredis.php b/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePredis.php new file mode 100644 index 000000000..7458b745b --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/ReliablePredis.php @@ -0,0 +1,156 @@ +client = $client; + } + + /** + * {@inheritdoc} + */ + public function createItem($data) { + $record = new \stdClass(); + $record->data = $data; + $record->qid = $this->incrementId(); + // We cannot rely on REQUEST_TIME because many items might be created + // by a single request which takes longer than 1 second. + $record->timestamp = time(); + + $pipe = $this->client->pipeline(); + $pipe->hsetnx($this->availableItems, $record->qid, serialize($record)); + $pipe->lLen($this->availableListKey); + $pipe->lpush($this->availableListKey, $record->qid); + $result = $pipe->execute(); + + $success = $result[0] && $result[2] > $result[1]; + + return $success ? $record->qid : FALSE; + } + + /** + * Gets next serial ID for Redis queue items. + * + * @return int + * Next serial ID for Redis queue item. + */ + protected function incrementId() { + // TODO: Fixme + return $this->client->incr($this->incrementCounterKey); + } + + /** + * {@inheritdoc} + */ + public function numberOfItems() { + // TODO: Fixme + return $this->client->lLen($this->availableListKey); + } + + /** + * {@inheritdoc} + */ + public function claimItem($lease_time = 30) { + // Is it OK to do garbage collection here (we need to loop list of claimed + // items)? + $this->garbageCollection(); + $item = FALSE; + + if ($this->reserveTimeout !== NULL) { + // A blocking version of claimItem to be used with long-running queue workers. + $qid = $this->client->brpoplpush($this->availableListKey, $this->claimedListKey, $this->reserveTimeout); + } + else { + $qid = $this->client->rpoplpush($this->availableListKey, $this->claimedListKey); + } + + if ($qid) { + $job = $this->client->hget($this->availableItems, $qid); + if ($job) { + $item = unserialize($job); + $this->client->setex($this->leasedKeyPrefix . $item->qid, $lease_time, '1'); + } + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function releaseItem($item) { + // TODO: Fixme + $this->client->pipeline() + ->lrem($this->claimedListKey, $item->qid, -1) + ->lpush($this->availableListKey, $item->qid) + ->exec(); + } + + /** + * {@inheritdoc} + */ + public function deleteItem($item) { + // TODO: Fixme + $this->client->pipeline() + ->lrem($this->claimedListKey, $item->qid, -1) + ->hdel($this->availableItems, $item->qid) + ->exec(); + } + + /** + * {@inheritdoc} + */ + public function deleteQueue() { + // TODO: Fixme + $keys_to_remove = [ + $this->claimedListKey, + $this->availableListKey, + $this->availableItems, + $this->incrementCounterKey + ]; + + foreach ($this->client->keys($this->leasedKeyPrefix . '*') as $key) { + $keys_to_remove[] = $key; + } + + $this->client->del($keys_to_remove); + } + + /** + * Automatically release items, that have been claimed and exceeded lease time. + */ + protected function garbageCollection() { + foreach ($this->client->lrange($this->claimedListKey, 0, -1) as $qid) { + if (!$this->client->exists($this->leasedKeyPrefix . $qid)) { + // The lease expired for this ID. + $this->client->lrem($this->claimedListKey, $qid, -1); + $this->client->lpush($this->availableListKey, $qid); + } + } + } +} diff --git a/sites/all/modules/contrib/dev/redis/src/Queue/ReliableQueueBase.php b/sites/all/modules/contrib/dev/redis/src/Queue/ReliableQueueBase.php new file mode 100644 index 000000000..f8caba2a0 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/src/Queue/ReliableQueueBase.php @@ -0,0 +1,14 @@ +prefix = $prefix; + } + + /** + * Get prefix + * + * @return string + */ + protected function getPrefix() { + if (!isset($this->prefix)) { + $this->prefix = $this->getDefaultPrefix(); + } + return $this->prefix; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php new file mode 100644 index 000000000..fbf4b0461 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Functional/Lock/RedisLockFunctionalTest.php @@ -0,0 +1,56 @@ +siteDirectory . '/settings.php'; + chmod($filename, 0666); + $contents = file_get_contents($filename); + $redis_interface = self::getRedisInterfaceEnv(); + $contents .= "\n\n" . '$settings[\'container_yamls\'][] = \'modules/redis/example.services.yml\';'; + $contents .= "\n\n" . '$settings["redis.connection"]["interface"] = \'' . $redis_interface . '\';'; + file_put_contents($filename, $contents); + $settings = Settings::getAll(); + $settings['container_yamls'][] = 'modules/redis/example.services.yml'; + $settings['redis.connection']['interface'] = '\'' . $redis_interface . '\''; + new Settings($settings); + OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $filename); + + $this->rebuildContainer(); + + // Get database schema. + $db_schema = Database::getConnection()->schema(); + // Make sure that the semaphore table isn't used. + $db_schema->dropTable('semaphore'); + } + +} diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Functional/WebTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Functional/WebTest.php new file mode 100644 index 000000000..d5cfcb22d --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Functional/WebTest.php @@ -0,0 +1,193 @@ +drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('local_tasks_block'); + + // Set in-memory settings. + $settings = Settings::getAll(); + + // Get REDIS_INTERFACE env variable. + $redis_interface = self::getRedisInterfaceEnv(); + $settings['redis.connection']['interface'] = $redis_interface; + + $settings['cache'] = [ + 'default' => 'cache.backend.redis', + ]; + + $settings['container_yamls'][] = drupal_get_path('module', 'redis') . '/example.services.yml'; + + $settings['bootstrap_container_definition'] = [ + 'parameters' => [], + 'services' => [ + 'redis.factory' => [ + 'class' => 'Drupal\redis\ClientFactory', + ], + 'cache.backend.redis' => [ + 'class' => 'Drupal\redis\Cache\CacheBackendFactory', + 'arguments' => ['@redis.factory', '@cache_tags_provider.container', '@serialization.phpserialize'], + ], + 'cache.container' => [ + 'class' => '\Drupal\redis\Cache\PhpRedis', + 'factory' => ['@cache.backend.redis', 'get'], + 'arguments' => ['container'], + ], + 'cache_tags_provider.container' => [ + 'class' => 'Drupal\redis\Cache\RedisCacheTagsChecksum', + 'arguments' => ['@redis.factory'], + ], + 'serialization.phpserialize' => [ + 'class' => 'Drupal\Component\Serialization\PhpSerialize', + ], + ], + ]; + new Settings($settings); + + // Write the containers_yaml update by hand, since writeSettings() doesn't + // support some of the definitions. + $filename = $this->siteDirectory . '/settings.php'; + chmod($filename, 0666); + $contents = file_get_contents($filename); + + // Add the container_yaml and cache definition. + $contents .= "\n\n" . '$settings["container_yamls"][] = "' . drupal_get_path('module', 'redis') . '/example.services.yml";'; + $contents .= "\n\n" . '$settings["cache"] = ' . var_export($settings['cache'], TRUE) . ';'; + + // Add the classloader. + $contents .= "\n\n" . '$class_loader->addPsr4(\'Drupal\\\\redis\\\\\', \'' . drupal_get_path('module', 'redis') . '/src\');'; + + // Add the bootstrap container definition. + $contents .= "\n\n" . '$settings["bootstrap_container_definition"] = ' . var_export($settings['bootstrap_container_definition'], TRUE) . ';'; + + file_put_contents($filename, $contents); + OpCodeCache::invalidate(DRUPAL_ROOT . '/' . $filename); + + // Reset the cache factory. + $this->container->set('cache.factory', NULL); + $this->rebuildContainer(); + + // Get database schema. + $db_schema = Database::getConnection()->schema(); + + // Make sure that the cache and lock tables aren't used. + $db_schema->dropTable('cache_default'); + $db_schema->dropTable('cache_render'); + $db_schema->dropTable('cache_config'); + $db_schema->dropTable('cache_container'); + $db_schema->dropTable('cachetags'); + $db_schema->dropTable('semaphore'); + $db_schema->dropTable('flood'); + } + + /** + * Tests enabling modules and creating configuration. + */ + public function testModuleInstallation() { + $admin_user = $this->createUser([], NULL, TRUE); + $this->drupalLogin($admin_user); + + // Enable a few modules. + $edit["modules[node][enable]"] = TRUE; + $edit["modules[views][enable]"] = TRUE; + $edit["modules[field_ui][enable]"] = TRUE; + $edit["modules[text][enable]"] = TRUE; + $this->drupalPostForm('admin/modules', $edit, t('Install')); + $this->drupalPostForm(NULL, [], t('Continue')); + + $assert = $this->assertSession(); + + // The order of the modules is not guaranteed, so just assert that they are + // all listed. + $assert->elementTextContains('css', '.messages--status', '6 modules have been enabled'); + $assert->elementTextContains('css', '.messages--status', 'Field UI'); + $assert->elementTextContains('css', '.messages--status', 'Node'); + $assert->elementTextContains('css', '.messages--status', 'Text'); + $assert->elementTextContains('css', '.messages--status', 'Views'); + $assert->elementTextContains('css', '.messages--status', 'Field'); + $assert->elementTextContains('css', '.messages--status', 'Filter'); + $assert->checkboxChecked('edit-modules-field-ui-enable'); + + // Create a node type with a field. + $edit = [ + 'name' => $this->randomString(), + 'type' => $node_type = Unicode::strtolower($this->randomMachineName()), + ]; + $this->drupalPostForm('admin/structure/types/add', $edit, t('Save and manage fields')); + $field_name = Unicode::strtolower($this->randomMachineName()); + $this->fieldUIAddNewField('admin/structure/types/manage/' . $node_type, $field_name, NULL, 'text'); + + // Create a node, check display, edit, verify that it has been updated. + $edit = [ + 'title[0][value]' => $this->randomMachineName(), + 'body[0][value]' => $this->randomMachineName(), + 'field_' . $field_name . '[0][value]' => $this->randomMachineName(), + ]; + $this->drupalPostForm('node/add/' . $node_type, $edit, t('Save and publish')); + + // Test the output as anonymous user. + $this->drupalLogout(); + $this->drupalGet('node'); + $this->assertSession()->responseContains($edit['title[0][value]']); + + $this->drupalLogin($admin_user); + $this->drupalGet('node'); + $this->clickLink($edit['title[0][value]']); + $this->assertSession()->responseContains($edit['body[0][value]']); + $this->clickLink(t('Edit')); + $update = [ + 'title[0][value]' => $this->randomMachineName(), + ]; + $this->drupalPostForm(NULL, $update, t('Save and keep published')); + $this->assertSession()->responseContains($update['title[0][value]']); + $this->drupalGet('node'); + $this->assertSession()->responseContains($update['title[0][value]']); + + $this->drupalLogout(); + $this->drupalGet('node'); + $this->clickLink($update['title[0][value]']); + $this->assertSession()->responseContains($edit['body[0][value]']); + + // Get database schema. + $db_schema = Database::getConnection()->schema(); + $this->assertFalse($db_schema->tableExists('cache_default')); + $this->assertFalse($db_schema->tableExists('cache_render')); + $this->assertFalse($db_schema->tableExists('cache_config')); + $this->assertFalse($db_schema->tableExists('cache_container')); + $this->assertFalse($db_schema->tableExists('cachetags')); + $this->assertFalse($db_schema->tableExists('semaphore')); + $this->assertFalse($db_schema->tableExists('flood')); + } + +} diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisCacheTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisCacheTest.php new file mode 100644 index 000000000..8668be316 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisCacheTest.php @@ -0,0 +1,49 @@ +has('redis.factory')) { + $container->register('cache_tags.invalidator.checksum', 'Drupal\redis\Cache\RedisCacheTagsChecksum') + ->addArgument(new Reference('redis.factory')) + ->addTag('cache_tags_invalidator'); + } + } + + /** + * Creates a new instance of PhpRedis cache backend. + * + * @return \Drupal\redis\Cache\PhpRedis + * A new PhpRedis cache backend. + */ + protected function createCacheBackend($bin) { + $cache = \Drupal::service('cache.backend.redis')->get($bin); + $cache->setMinTtl(10); + return $cache; + } + +} diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisFloodTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisFloodTest.php new file mode 100644 index 000000000..62dd41603 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisFloodTest.php @@ -0,0 +1,58 @@ +assertTrue($flood->isAllowed($name, $threshold)); + + // Register event. + $flood->register($name, $window); + + // The event is still allowed. + $this->assertTrue($flood->isAllowed($name, $threshold)); + + $flood->register($name, $window); + + // Verify event is not allowed. + $this->assertFalse($flood->isAllowed($name, $threshold)); + + // "Sleep" two seconds, then the event is allowed again. + $_SERVER['REQUEST_TIME'] += 2; + $this->assertTrue($flood->isAllowed($name, $threshold)); + + } + +} diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisQueueTest.php b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisQueueTest.php new file mode 100644 index 000000000..137cb9821 --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Kernel/RedisQueueTest.php @@ -0,0 +1,95 @@ + NULL]; + $class_name = $client_factory->getClass(ClientFactory::REDIS_IMPL_QUEUE); + + /** @var \Drupal\Core\Queue\QueueInterface $queue1 */ + $queue1 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue1->createQueue(); + + /** @var \Drupal\Core\Queue\QueueInterface $queue2 */ + $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue2->createQueue(); + + $this->queueTest($queue1, $queue2); + $queue1->deleteQueue(); + $queue2->deleteQueue(); + + $class_name = $client_factory->getClass(ClientFactory::REDIS_IMPL_RELIABLE_QUEUE); + + /** @var \Drupal\Core\Queue\QueueInterface $queue1 */ + $queue1 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue1->createQueue(); + + /** @var \Drupal\Core\Queue\QueueInterface $queue2 */ + $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue2->createQueue(); + + $this->queueTest($queue1, $queue2); + } + + /** + * Tests Redis blocking queue. + */ + public function testRedisBlockingQueue() { + self::setUpSettings(); + // Create two queues. + $client_factory = \Drupal::service('redis.factory'); + $settings = ['reserve_timeout' => 30]; + $class_name = $client_factory->getClass(ClientFactory::REDIS_IMPL_QUEUE); + + /** @var \Drupal\Core\Queue\QueueInterface $queue1 */ + $queue1 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue1->createQueue(); + + /** @var \Drupal\Core\Queue\QueueInterface $queue2 */ + $queue2 = new $class_name($this->randomMachineName(), $settings, $client_factory->getClient()); + $queue2->createQueue(); + + $this->queueTest($queue1, $queue2); + } + + /** + * Overrides \Drupal\system\Tests\Queue\QueueTestQueueTest::testSystemQueue(). + * + * We override tests from core class we extend to prevent them from running. + */ + public function testSystemQueue() {} + + /** + * Overrides \Drupal\system\Tests\Queue\QueueTestQueueTest::testMemoryQueue(). + * + * We override tests from core class we extend to prevent them from running. + */ + public function testMemoryQueue() {} + +} + diff --git a/sites/all/modules/contrib/dev/redis/tests/src/Traits/RedisTestInterfaceTrait.php b/sites/all/modules/contrib/dev/redis/tests/src/Traits/RedisTestInterfaceTrait.php new file mode 100644 index 000000000..092b8c73e --- /dev/null +++ b/sites/all/modules/contrib/dev/redis/tests/src/Traits/RedisTestInterfaceTrait.php @@ -0,0 +1,36 @@ +