diff --git a/sites/all/modules/memcache/.gitignore b/sites/all/modules/memcache/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1377554ebea6f98a2c748183bc5a96852af12ac2 --- /dev/null +++ b/sites/all/modules/memcache/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/sites/all/modules/memcache/INSTALLATION.txt b/sites/all/modules/memcache/INSTALLATION.txt new file mode 100644 index 0000000000000000000000000000000000000000..6ba171c47c8e32f7002d71640f0eeda7906a3c3e --- /dev/null +++ b/sites/all/modules/memcache/INSTALLATION.txt @@ -0,0 +1,99 @@ + +For instructions on how to compile memcached on Debian Etch, see here: + +http://www.lullabot.com/articles/how_install_memcache_debian_etch + +Fedora memcache + drupal walkthrough + +1) Have a look at the background reading + + A - http://www.danga.com/memcached/ (cache daemon) + + B - http://pecl.php.net/package/memcache (PHP integration for memcache) + + C - http://drupal.org/project/memcache (Drupal Module to use memcache) + +2) Fedora RPMs are available for A and B above + + http://dag.wieers.com/rpm/packages/memcached/ + http://dag.wieers.com/rpm/packages/php-pecl-memcache/ + + I downloaded the SRPMs and rebuilt them. This may require installing some + extra RPMs into your system - rpm will tell you. + + 1003 wget http://dag.wieers.com/rpm/packages/memcached/memcached-1.2.1-4.rf.src.rpm + 1004 rpmbuild --rebuild memcached-1.2.1-4.rf.src.rpm + 1005 yum install libevent-devel + 1006 rpmbuild --rebuild memcached-1.2.1-4.rf.src.rpm + + 1008 wget http://dag.wieers.com/rpm/packages/php-pecl-memcache/php-pecl-memcache-2.0.4-1.rf.src.rpm + 1009 rpmbuild --rebuild php-pecl-memcache-2.0.4-1.rf.src.rpm + 1010 yum install php-devel + 1011 rpmbuild --rebuild php-pecl-memcache-2.0.4-1.rf.src.rpm + +3) Installed RPMS + +[root@yoursite ~]# rpm -Uvh /usr/src/redhat/RPMS/x86_64/memcached-1.2.1-4.rf.x86_64.rpm /usr/src/redhat/RPMS/x86_64/php-pecl-memcache-2.0.4-1.rf.x86_64.rpm +Preparing... ########################################### [100%] + 1:php-pecl-memcache ########################################### [ 50%] + 2:memcached ########################################### [100%] +[root@yoursite ~]# + +4) Verify configuration. Change if required. + +[root@yoursite ~]# cat /etc/sysconfig/memcached +PORT="11211" +USER="nobody" +MAXCONN="1024" +CACHESIZE="64" +OPTIONS="" +[root@yoursite ~]# + +5) Activate memcache + +[root@yoursite ~]# service memcached start +Starting Distributed memory caching (memcached): [ OK ] +[root@yoursite ~]# service httpd restart +Stopping httpd: [ OK ] +Starting httpd: [ OK ] +[root@yoursite ~]# + + Check phpinfo() for memcached status. Mine said + + memcache + memcache support enabled + Active persistent connections 0 + Revision $Revision$ + +6) Optional. Install and enable 'devel' module. Configure it to show SQL +queries being executed. Log in, and check out / and /admin - see how many +SQL queries are run. In my case / took 156 queries, and /admin took 91. +Once you've patched Drupal as described below, even enabling and disabling +the Memcache module won't disable the use of memcache. + +7) Download, install, follow steps in README.txt (move include file, +configure settings.php) + + [simon@yoursite ~]$ cd /home/WHOEVER/www/SOMEWEBSITE.com/html/modules/ + [simon@yoursite modules]$ wget -q http://ftp.osuosl.org/pub/drupal/files/projects/memcache-5.x-1.x-dev.tar.gz + [simon@yoursite modules]$ tar xfz memcache-5.x-1.x-dev.tar.gz + [simon@yoursite modules]$ cd memcache + [simon@yoursite memcache]$ mv memcache.inc ../../includes/ + [simon@yoursite modules]$ + +Apply the default configuration to the bottom of your +DRUPAL/sites/default/settings.php file. If you have already configured +$cfg, you will need to add the element to the array instead of appending the +whole $cfg. Don't forget to also include 'cache.inc' and 'memcache.inc'. The +example below uses the default localhost:11211 server. + + $ tail -3 ../../sites/default/settings.php + include_once('./includes/cache.inc'); + include_once('./sites/default/modules/memcache/memcache.inc'); + $conf['cache_default_class'] = 'MemCacheDrupal'; + $ + +8) Optionally enable the memcache admin drupal module from /admin/build/modules +(it's in the "Other" section). + +See README.txt for more information about configuring Drupal with memcache. diff --git a/sites/all/modules/memcache/LICENSE.txt b/sites/all/modules/memcache/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/memcache/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/memcache/README.txt b/sites/all/modules/memcache/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..4efd86d3880e82e852d7062b867c6ce6a1efa4f7 --- /dev/null +++ b/sites/all/modules/memcache/README.txt @@ -0,0 +1,220 @@ + +## REQUIREMENTS ## + +- PHP 5.1 or greater +- Availability of a memcached daemon: http://memcached.org/ +- One of the two PECL memcache packages: + - http://pecl.php.net/package/memcache + - http://pecl.php.net/package/memcached (recommended + +## INSTALLATION ## + +These are the broad steps you need to take in order to use this software. Order +is important. + +1. Install the memcached binaries on your server. See + +http://www.lullabot.com/articles/how_install_memcache_debian_etch + +2. Install the PECL memcache extension for PHP. This must be version 2.2.1 or + higher or you will experience errors. +3. Put your site into offline mode. +4. Download and install the memcache module. +5. If you have previously been running the memcache module, run update.php. +6. Start at least one instance of memcached on your server. +7. Edit settings.php to configure the servers, clusters and bins that memcache + is supposed to use. +8. Edit settings.php to include cache.inc, memcache.inc. For example: + include_once('./includes/cache.inc'); + include_once('./sites/all/modules/memcache/memcache.inc'); +9. Edit settings.php to make memcache the default caching class: + $conf['cache_default_class'] = 'MemCacheDrupal'; +9. Bring your site back online. + +For instructions on 1 and 2 above, please see the INSTALLATION.txt file that +comes with the memcache module download. + +http://www.lullabot.com/files/memcache-inc.png + +In Drupal 7+, you no longer should set cache_inc in settings.php. Instead, you +will have to manually include 'cache.inc' and 'memcache.inc', then update $conf +to tell Drupal to default to memcache for caching: + + // the path to the core cache file + include_once('./includes/cache.inc'); + // the path to the memcache cache file + include_once('./sites/all/modules/memcache/memcache.inc'); + // make MemCacheDrupal the default cache class + $conf['cache_default_class'] = 'MemCacheDrupal'; + +## SERVERS ## + +If you want the simple version, you can start one default memcache instance on +your web server like this: memcached -m 24 -p 11211 -d +If that is enough to meet your needs, there is no more configuration needed. If +you want to utilize this module's sophisticated clustering feature and spread +your cache over several machines, or if your cache is found on a machine other +than your web server, read on. + +The available memcached servers are specified in $conf in settings.php. If +you do not specify any servers, memcache.inc assumes that you have a +memcached instance running on localhost:11211. If this is true, and it is +the only memcached instance you wish to use, no further configuration is +required. + +If you have more than one memcached instance running, you need to add two +arrays to $conf; memcache_servers and memcache_bins. The arrays follow this +pattern: + +'memcache_servers' => array( + host1:port => cluster, + host2:port => cluster, + hostN:port => cluster +) + +'memcache_bins' => array(bin1 => cluster, bin2 => cluster, binN => cluster) + +The bin/cluster/server model can be described as follows: + +- Servers are memcached instances identified by host:port. + +- Bins are groups of data that get cached together and map 1:1 to the $table + param in cache_set(). Examples from Drupal core are cache_filter, + cache_menu. The default is 'cache'. + +- Clusters are groups of servers that act as a memory pool. + +- many bins can be assigned to a cluster. + +- The default cluster is 'default'. + +Here is a simple setup that has two memcached instances, both running on +localhost. The 11212 instance belongs to the 'pages' cluster and the table +cache_page is mapped to the 'pages' cluster. Thus everything that gets cached, +with the exception of the page cache (cache_page), will be put into 'default', +or the 11211 instance. The page cache will be in 11212. + +$conf = array( + ... + // Important to define a default cluster in both the servers + // and in the bins. This links them together. + 'memcache_servers' => array('localhost:11211' => 'default', + 'localhost:11212' => 'pages'), + 'memcache_bins' => array('cache' => 'default', + 'cache_page' => 'pages'), +); + +Here is an example configuration that has two clusters, 'default' and +'cluster2'. Five memcached instances are divided up between the two +clusters. 'cache_filter' and 'cache_menu' bins goe to 'cluster2'. All other +bins go to 'default'. + +include_once('./includes/cache.inc'); +include_once('./sites/all/modules/memcache/memcache.inc'); +$conf = array( + 'cache_default_class' = 'MemCacheDrupal', + 'memcache_servers' => array('localhost:11211' => 'default', + 'localhost:11212' => 'default', + '123.45.67.890:11211' => 'default', + '123.45.67.891:11211' => 'cluster2', + '123.45.67.892:11211' => 'cluster2'), + + 'memcache_bins' => array('cache' => 'default', + 'cache_filter' => 'cluster2', + 'cache_menu' => 'cluster2'), +); +## PREFIXING ## + +If you want to have multiple Drupal installations share memcached instances, +you need to include a unique prefix for each Drupal installation in the $conf +array of settings.php: + +$conf = array( + ... + 'memcache_key_prefix' => 'something_unique', +); + +## SESSIONS ## + +Here is a sample config that uses memcache for sessions. Note you MUST have +a session and a users server set up for memcached sessions to work. + +NOTE: Session.inc is not yet ported to Drupal 7. + +include_once('./includes/cache.inc'); +include_once('./sites/all/modules/memcache/memcache.inc'); +$conf = array( + 'cache_default_class' = 'MemCacheDrupal', + 'session_inc' => './sites/all/modules/memcache/memcache-session.inc', + 'memcache_servers' => array( + 'localhost:11211' => 'default', + 'localhost:11212' => 'filter', + 'localhost:11213' => 'menu', + 'localhost:11214' => 'page', + 'localhost:11215' => 'session', + 'localhost:11216' => 'users', + ), + 'memcache_bins' => array( + 'cache' => 'default', + 'cache_filter' => 'filter', + 'cache_menu' => 'menu', + 'cache_page' => 'page', + 'session' => 'session', + 'users' => 'users', + ), +); + + +## TROUBLESHOOTING ## + +PROBLEM: +Error: +Failed to set key: Failed to set key: cache_page-...... + +SOLUTION: +Upgrade your PECL library to PECL package (2.2.1) (or higher). + +WARNING: +Zlib compression at the php.ini level and Memcache conflict. +See http://drupal.org/node/273824 + +## MEMCACHE ADMIN ## + +A module offering a UI for memcache is included. It provides stats, a +way to clear the cache, and an interface to organize servers/bins/clusters. + + +## Memcached PECL Extension Support + +We also now support the Memcached PECL extension. If you install this extension, +it will be used by default. This new extension backends to libmemcached and +allows you to use some of the newer advanced features in memcached 1.4. + +NOTE: It is important to realize that the memcache php.ini options do not impact +the memcached extension, this new extension doesn't read in options that way. +Instead, it takes options directly from Drupal. Because of this, you must +configure memcached in settings.php. Please look here for possible options: + +http://us2.php.net/manual/en/memcached.constants.php + +An example configuration block is below, this block also illustrates our +default options. These will be set unless overridden in settings.php. + +$conf['memcache_options'] = array( + Memcached::OPT_COMPRESSION => FALSE, + Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT, +); + +These are as follows: + + * Turn off compression, as this takes more CPU cycles than its worth for most + users + * Turn on consistent distribution, which allows you to add/remove servers + easily + +If you are using memcached 1.4 or above, you should enable the binary protocol, +which is more advanced and faster, by adding the following to settings.php: + +$conf['memcache_options'] = array( + Memcached::OPT_BINARY_PROTOCOL => TRUE, +); diff --git a/sites/all/modules/memcache/dmemcache.inc b/sites/all/modules/memcache/dmemcache.inc new file mode 100644 index 0000000000000000000000000000000000000000..df3c85dc48890962876ff2d03c26889ac1a0a549 --- /dev/null +++ b/sites/all/modules/memcache/dmemcache.inc @@ -0,0 +1,347 @@ +<?php + +/* + * Core dmemcache functions required by: + * memcache.inc + * memcache.db.inc + * session-memcache.inc + * session-memcache.db.inc + */ + +global $_memcache_statistics; +$_memcache_statistics = array(); + +/* + * A memcache API for Drupal. + */ + +/** + * Place an item into memcache + * + * @param $key The string with which you will retrieve this item later. + * @param $value The item to be stored. + * @param $exp Parameter expire is expiration time in seconds. If it's 0, the + * item never expires (but memcached server doesn't guarantee this item to be + * stored all the time, it could be deleted from the cache to make place for + * other items). + * @param $bin The name of the Drupal subsystem that is making this call. + * Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible to + * map different $bin values to different memcache servers. + * @param $mc Optionally pass in the memcache object. Normally this value is + * determined automatically based on the bin the object is being stored to. + * + * @return bool + */ +function dmemcache_set($key, $value, $exp = 0, $bin = 'cache', $mc = NULL) { + global $_memcache_statistics; + $full_key = dmemcache_key($key, $bin); + $_memcache_statistics[] = array('set', $bin, $full_key, ''); + if ($mc || ($mc = dmemcache_object($bin))) { + if (class_exists('Memcached')) { + return $mc->set($full_key, $value, $exp); + } + else { + return $mc->set($full_key, $value, MEMCACHE_COMPRESSED, $exp); + } + } + return FALSE; +} + +/** + * Add an item into memcache + * + * @param $key The string with which you will retrieve this item later. + * @param $value The item to be stored. + * @param $exp Parameter expire is expiration time in seconds. If it's 0, the + * item never expires (but memcached server doesn't guarantee this item to be + * stored all the time, it could be deleted from the cache to make place for + * other items). + * @param $bin The name of the Drupal subsystem that is making this call. + * Examples could be 'cache', 'alias', 'taxonomy term' etc. It is possible + * to map different $bin values to different memcache servers. + * @param $mc Optionally pass in the memcache object. Normally this value is + * determined automatically based on the bin the object is being stored to. + * @param $flag If using the older memcache PECL extension as opposed to the + * newer memcached PECL extension, the MEMCACHE_COMPRESSED flag can be set + * to use zlib to store a compressed copy of the item. This flag option is + * completely ignored when using the newer memcached PECL extension. + * + * @return bool + */ +function dmemcache_add($key, $value, $exp = 0, $bin = 'cache', $mc = NULL, $flag = FALSE) { + global $_memcache_statistics; + $full_key = dmemcache_key($key, $bin); + $_memcache_statistics[] = array('add', $bin, $full_key, ''); + if ($mc || ($mc = dmemcache_object($bin))) { + if (class_exists('Memcached')) { + return $mc->add($full_key, $value, $exp); + } + else { + return $mc->add($full_key, $value, $flag, $exp); + } + } + return FALSE; +} + +/** + * Retrieve a value from the cache. + * + * @param $key The key with which the item was stored. + * @param $bin The bin in which the item was stored. + * + * @return The item which was originally saved or FALSE + */ +function dmemcache_get($key, $bin = 'cache', $mc = NULL) { + global $_memcache_statistics; + $full_key = dmemcache_key($key, $bin); + $statistics = array('get', $bin, $full_key); + $success = '0'; + if ($mc || ($mc = dmemcache_object($bin))) { + $result = $mc->get($full_key); + if ($result) { + $success = '1'; + } + } + $statistics[] = $success; + $_memcache_statistics[] = $statistics; + return $result; +} + +/** + * Retrieve multiple values from the cache. + * + * @param $keys The keys with which the items were stored. + * @param $bin The bin in which the item was stored. + * + * @return The item which was originally saved or FALSE + */ +function dmemcache_get_multi($keys, $bin = 'cache', $mc = NULL) { + global $_memcache_statistics; + $full_keys = array(); + $statistics = array(); + foreach ($keys as $key => $cid) { + $full_key = dmemcache_key($cid, $bin); + $statistics[$full_key] = array('getMulti', $bin, $full_key); + $full_keys[] = $full_key; + } + $results = array(); + if ($mc || ($mc = dmemcache_object($bin))) { + if (class_exists('Memcached')) { + $results = $mc->getMulti($full_keys); + } + else { + $results = $mc->get($full_keys); + } + } + foreach ($statistics as $key => $values) { + $values[] = isset($results[$key]) ? '1': '0'; + $_memcache_statistics[] = $values; + } + return $results; +} + +/** + * Deletes an item from the cache. + * + * @param $key The key with which the item was stored. + * @param $bin The bin in which the item was stored. + * + * @return Returns TRUE on success or FALSE on failure. + */ +function dmemcache_delete($key, $bin = 'cache', $mc = NULL) { + global $_memcache_statistics; + $full_key = dmemcache_key($key, $bin); + $_memcache_statistics[] = array('delete', $bin, $full_key, ''); + if ($mc || ($mc = dmemcache_object($bin))) { + return $mc->delete($full_key); + } + return FALSE; +} + +/** + * Immediately invalidates all existing items. dmemcache_flush doesn't actually free any + * resources, it only marks all the items as expired, so occupied memory will be overwritten by + * new items. + * + * @param $bin The bin to flush. Note that this will flush all bins mapped to the same server + * as $bin. There is no way at this time to empty just one bin. + * + * @return Returns TRUE on success or FALSE on failure. + */ +function dmemcache_flush($bin = 'cache', $mc = NULL) { + global $_memcache_statistics; + $_memcache_statistics[] = array('flush', $bin, '', ''); + if ($mc || ($mc = dmemcache_object($bin))) { + return memcache_flush($mc); + } +} + +function dmemcache_stats($bin = 'cache', $type = '') { + // resolve requests for 'default' type to '' + if ($type == 'default') { + $type = ''; + } + // resolve requests for 'default' bin to 'cache'. + if ($bin == 'default') { + $bin = 'cache'; + } + if ($mc = dmemcache_object($bin)) { + if (class_exists('Memcached')) { + return $mc->getStats(); + } + // The PHP Memcache extension 3.x version throws an error if the stats + // type is NULL or not in {reset, malloc, slabs, cachedump, items, sizes}. + // If $type is 'default', then no parameter should be passed to the + // Memcache memcache_get_extended_stats() function. + if ($type == 'default' || $type == '') { + if (class_exists('Memcached')) { + return $mc->getStats(); + } + else if (class_exists('Memcache')) { + return $mc->getExtendedStats(); + } + } + else { + if (class_exists('Memcached')) { + return $mc->getStats(); + } + else if (class_exists('Memcache')) { + return $mc->getExtendedStats($type); + } + } + } +} + +/** + * Returns an Memcache object based on the bin requested. Note that there is + * nothing preventing developers from calling this function directly to get the + * Memcache object. Do this if you need functionality not provided by this API + * or if you need to use legacy code. Otherwise, use the dmemcache (get, set, + * delete, flush) API functions provided here. + * + * @param $bin The bin which is to be used. + * + * @param $flush Rebuild the bin/server/cache mapping. + * + * @return an Memcache object or FALSE. + */ +function dmemcache_object($bin = NULL, $flush = FALSE) { + static $memcacheCache = array(), $memcache_servers, $memcache_bins; + + if ($flush) { + foreach ($memcacheCache as $cluster) { + memcache_close($cluster); + } + $memcacheCache = array(); + } + + if (empty($memcacheCache) || empty($memcacheCache[$bin])) { + // $memcache_servers and $memcache_bins originate from settings.php. + // $memcache_servers_custom and $memcache_bins_custom get set by + // memcache.module. They are then merged into $memcache_servers and + // $memcache_bins, which are statically cached for performance. + if (empty($memcache_servers)) { + // Values from settings.php + $memcache_servers = variable_get('memcache_servers', array('127.0.0.1:11211' => 'default')); + $memcache_bins = variable_get('memcache_bins', array('cache' => 'default')); + } + + // If there is no cluster for this bin in $memcache_bins, cluster is 'default'. + $cluster = empty($memcache_bins[$bin]) ? 'default' : $memcache_bins[$bin]; + + // If this bin isn't in our $memcache_bins configuration array, and the + // 'default' cluster is already initialized, map the bin to 'cache' because + // we always map the 'cache' bin to the 'default' cluster. + if (empty($memcache_bins[$bin]) && !empty($memcacheCache['cache'])) { + $memcacheCache[$bin] = &$memcacheCache['cache']; + } + else { + // Create a new Memcache object. Each cluster gets its own Memcache object. + if (class_exists('Memcached')) { + $memcache = new Memcached; + $default_opts = array( + Memcached::OPT_COMPRESSION => FALSE, + Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT, + ); + foreach ($default_opts as $key => $value) { + $memcache->setOption($key, $value); + } + $memconf = variable_get('memcache_options', array()); + foreach ($memconf as $key => $value) { + $memcache->setOption($key, $value); + } + } + else if (class_exists('Memcache')) { + $memcache = new Memcache; + } + else { + drupal_set_message(t('You must enable the PECL memcached or memcache extension to use memcache.inc.'), 'error'); + return; + } + // A variable to track whether we've connected to the first server. + $init = FALSE; + + // Link all the servers to this cluster. + foreach ($memcache_servers as $s => $c) { + if ($c == $cluster) { + list($host, $port) = explode(':', $s); + + // This is a server that belongs to this cluster. + if (!class_exists('Memcached') && !$init) { + // If using PECL memcache extension, use ->connect for first server + if ($memcache->connect($host, $port)) { + $init = TRUE; + } + } + else { + if ($memcache->addServer($host, $port) && !$init) { + $init = TRUE; + } + } + } + } + + if ($init) { + // Map the current bin with the new Memcache object. + $memcacheCache[$bin] = $memcache; + + // Now that all the servers have been mapped to this cluster, look for + // other bins that belong to the cluster and map them too. + foreach ($memcache_bins as $b => $c) { + if ($c == $cluster && $b != $bin) { + // Map this bin and cluster by reference. + $memcacheCache[$b] = &$memcacheCache[$bin]; + } + } + } + } + } + + return empty($memcacheCache[$bin]) ? FALSE : $memcacheCache[$bin]; +} + +function dmemcache_key($key, $bin = 'cache') { + static $prefix; + // memcache_key_prefix can be set in settings.php to support site namespaces + // in a multisite environment. + if (empty($prefix)) { + if ($prefix = variable_get('memcache_key_prefix', '')) { + $prefix .= '-'; + } + // When simpletest is running, emulate the simpletest database prefix here + // to avoid the child site setting cache entries in the parent site. + if (isset($GLOBALS['drupal_test_info']['test_run_id'])) { + $prefix .= $GLOBALS['drupal_test_info']['test_run_id']; + } + } + $full_key = urlencode($prefix . $bin . '-' . $key); + + // Memcache only supports key lengths up to 250 bytes. If we have generated + // a longer key, hash it with sha1 which will shrink the key down to 40 bytes + // while still keeping it unique. + if (strlen($full_key) > 250) { + $full_key = $prefix . $bin . '-' . sha1($key); + } + + return $full_key; +} diff --git a/sites/all/modules/memcache/memcache-lock.inc b/sites/all/modules/memcache/memcache-lock.inc new file mode 100644 index 0000000000000000000000000000000000000000..5d252840668f0240c92e82d558d39af0257475a5 --- /dev/null +++ b/sites/all/modules/memcache/memcache-lock.inc @@ -0,0 +1,122 @@ +<?php + +/** + * @file + * A memcache based implementation of a locking mechanism. + * See includes/lock.inc for documenation + */ + +// Set up a define to make the code more readable, so we know we're setting a +// value for our lock and not simply passing dmemcache a flag. +define('LOCK_VALUE', TRUE); + + +/** + * Initialize the locking system. + */ +function lock_initialize() { + global $locks; + + $locks = array(); +} + +/** + * Acquire (or renew) a lock, but do not block if it fails. + * + * @param $name + * The name of the lock. + * @param $timeout + * A number of seconds (int) before the lock expires (minimum of 1). + * @return + * TRUE if the lock was acquired, FALSE if it failed. + */ +function lock_acquire($name, $timeout = 30) { + global $locks; + + // Ensure that the timeout is at least 1 sec. This is a limitation + // imposed by memcached. + $timeout = max($timeout, 1); + $now = microtime(TRUE); + $expire = $now + $timeout; + + $result = dmemcache_get($name, 'semaphore'); + if ($result && isset($locks[$name]) && $locks[$name] > $now) { + // Only renew the lock if we already set it and it has not expired. + if (dmemcache_set($name, LOCK_VALUE, $timeout, 'semaphore')) { + $locks[$name] = $expire; + } + } + else if (dmemcache_add($name, LOCK_VALUE, $timeout, 'semaphore')) { + $locks[$name] = $expire; + } + else { + // Failed to acquire the lock. Unset the key from the $locks array even if + // not set, PHP 5+ allows this without error or warning. + unset($locks[$name]); + } + + return isset($locks[$name]); +} + +/** + * Check if lock acquired by a different process may be available. + * + * If an existing lock has expired, it is removed. + * + * @param $name + * The name of the lock. + * @return + * TRUE if there is no lock or it was removed, FALSE otherwise. + */ +function lock_may_be_available($name) { + return !dmemcache_get($name, 'semaphore'); +} + +/** + * Wait for a lock to be available. + * + * This function may be called in a request that fails to acquire a desired + * lock. This will block further execution until the lock is available or the + * specified delay in seconds is reached. This should not be used with locks + * that are acquired very frequently, since the lock is likely to be acquired + * again by a different request during the sleep(). + * + * @param $name + * The name of the lock. + * @param $delay + * The maximum number of seconds to wait, as an integer. + * @return + * TRUE if the lock holds, FALSE if it is available. + */ +function lock_wait($name, $delay = 30) { + $delay = (int) $delay; + while ($delay--) { + // This function should only be called by a request that failed to get a + // lock, so we sleep first to give the parallel request a chance to finish + // and release the lock. + sleep(1); + if (!dmemcache_get($name, 'semaphore')) { + // No longer need to wait. + return FALSE; + } + } + // The caller must still wait longer to get the lock. + return TRUE; +} + +/** + * Release a lock previously acquired by lock_acquire(). + * + * This will release the named lock if it is still held by the current request. + * + * @param $name + * The name of the lock. + */ +function lock_release($name) { + global $locks; + + dmemcache_delete($name, 'semaphore'); + // We unset unconditionally since caller assumes lock is released anyway. + unset($locks[$name]); +} + diff --git a/sites/all/modules/memcache/memcache-session.inc b/sites/all/modules/memcache/memcache-session.inc new file mode 100644 index 0000000000000000000000000000000000000000..a0137aeec9a40805b4154bfc80e7379cebaf4d6e --- /dev/null +++ b/sites/all/modules/memcache/memcache-session.inc @@ -0,0 +1,404 @@ +<?php + +require_once dirname(__FILE__) . '/dmemcache.inc'; + +/** + * @file + * User session handling functions. + * + * An alternative to includes/session.inc. + */ + +/** + * Implement hook_user() using a required module's namespace since memcache is + * not a module and thus can't implement hooks directly. + */ +function filter_user($op, &$edit, &$account, $category = NULL) { + if ($op == 'update') { + // Invalidate cached user object. + cache_clear_all($account->uid, 'users'); + } +} + +function _drupal_session_open() { + return TRUE; +} + +function _drupal_session_close() { + return TRUE; +} + +function _drupal_session_read($key) { + global $user; + + // Write and Close handlers are called after destructing objects since PHP 5.0.5 + // Thus destructors can use sessions but session handler can't use objects. + // So we are moving session closure before destructing objects. + register_shutdown_function('session_write_close'); + + // Handle the case of first time visitors and clients that don't store + // cookies (eg. web crawlers). + if (!isset($_COOKIE[session_name()])) { + $user = drupal_anonymous_user(); + return ''; + } + + // Otherwise, if the session is still active, we have a record of the + // client's session in memcache. + $session = dmemcache_get($key, 'session'); + + $user = _memcache_session_user_load($session); + + // Record whether this session contains data so that in sess_write() it can + // be determined whether to skip a write. + $user->session_data_present_at_load = !empty($session->session); + + return $user->session; +} + +/** + * Write a session to session storage. + * + * We have the following cases to handle. + * 1. Anonymous user + * 1a. Without session data. + * 1b. With session data. + * 1c. Session saving has been turned off programatically + * (see drupal_save_session()). + * 1d. Without session data but had session data at the beginning of the request + * (thus a write must be made to clear stored session data). + * 2. Authenticated user. + * 2a. Without session data. + * 2b. With session data. + * 2c. Session saving has been turned off programatically + * (see drupal_save_session()). + * + * @param $key + * The session ID. + * @param $value + * Any data to store in the session. + * @return + * TRUE. + */ +function _drupal_session_write($key, $value) { + global $user; + + if (!drupal_save_session()) { + return TRUE; + } + + // Prepare the information to be saved. + $session = new stdClass; + $session->sid = $key; + $session->uid = $user->uid; + $session->cache = isset($user->cache) ? $user->cache : ''; + $session->hostname = ip_address(); + $session->session = $value; + $session->timestamp = REQUEST_TIME; + + // Be sure that we have the latest user object. If user_save() has been + // called, we need to refresh the object from the database. + $user = _memcache_session_user_load($session); + + // If this is an authenticated user, or there is something to save in the + // session, or this is an anonymous user who currently has nothing in the + // session but did have something in session storage, write it to memcache. + // If $user->session_data_present_at_load is not set, the current user + // was created during this request and it's safest to do a write. + // Cases 1b, 1d, 2a, and 2b are covered here. + if ($user->uid || !empty($value) || empty($value) && (!isset($user->session_data_present_at_load) || $user->session_data_present_at_load)) { + dmemcache_set($key, $session, ini_get('session.gc_maxlifetime'), 'session'); + if ($user->uid && $session->timestamp - $user->access > variable_get('session_write_interval', 360)) { + db_update('users') + ->fields(array('access' => $user->uid)) + ->condition(array('uid' => $user->uid)); + // Update the user access time so that the dmemcache_set() call + // caches the updated time. + $user->access = $session->timestamp; + } + // Data stored in session is stored in session memcache; no need + // to duplicate it in users memcache. + unset($user->session); + unset($user->session_data_present_at_load); + // Store the session id so we can locate the session with the user id. + $user->sid = $key; + + dmemcache_set($user->uid, $user, ini_get('session.gc_maxlifetime'), 'users'); + } + + return TRUE; +} + + +/** + * Called by PHP session handling with the PHP session ID to end a user's session. + * + * @param string $sid + * the session id + */ +function _drupal_session_destroy($sid) { + dmemcache_delete($sid, 'session'); + // Reset $_SESSION and $user to prevent a new session from being started + // in drupal_session_commit(). + $_SESSION = array(); + $user = drupal_anonymous_user(); + + // Unset the session cookies. + _drupal_session_delete_cookie(session_name()); +} + +function _drupal_session_garbage_collection($lifetime) { + // Automatic with memcached. + // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough + // value. For example, if you want user sessions to stay in your database + // for three weeks before deleting them, you need to set gc_maxlifetime + // to '1814400'. At that value, only after a user doesn't log in after + // three weeks (1814400 seconds) will his/her session be removed. + return TRUE; +} + +/** + * Initialize the session handler, starting a session if needed. + */ +function drupal_session_initialize() { + global $user; + + session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection'); + + if (isset($_COOKIE[session_name()])) { + // If a session cookie exists, initialize the session. Otherwise the + // session is only started on demand in drupal_session_commit(), making + // anonymous users not use a session cookie unless something is stored in + // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. + drupal_session_start(); + if (!empty($user->uid) || !empty($_SESSION)) { + drupal_page_is_cacheable(FALSE); + } + } + else { + // Set a session identifier for this request. This is necessary because + // we lazyly start sessions at the end of this request, and some + // processes (like drupal_get_token()) needs to know the future + // session ID in advance. + $user = drupal_anonymous_user(); + session_id(md5(uniqid('', TRUE))); + } +} + + +/** + * Counts how many users have sessions. Can count either anonymous sessions, authenticated sessions, or both. + * Would be insane slow with memcached as we would need to retrieve at least the stats of all object. + * Not implemented. + */ +function drupal_session_count($timestamp = 0, $anonymous = true) { +} + +/** + * Forcefully start a session, preserving already set session data. + * + * @ingroup php_wrappers + */ +function drupal_session_start() { + if (!drupal_session_started()) { + // Save current session data before starting it, as PHP will destroy it. + $session_data = isset($_SESSION) ? $_SESSION : NULL; + + session_start(); + drupal_session_started(TRUE); + + // Restore session data. + if (!empty($session_data)) { + $_SESSION += $session_data; + } + } +} + +/** + * Commit the current session, if necessary. + * + * If an anonymous user already have an empty session, destroy it. + */ +function drupal_session_commit() { + global $user; + + if (!drupal_save_session()) { + // We don't have anything to do if we are not allowed to save the session. + return; + } + + if (empty($user->uid) && empty($_SESSION)) { + // There is no session data to store, destroy the session if it was + // previously started. + if (drupal_session_started()) { + session_destroy(); + } + } + else { + // There is session data to store. Start the session if it is not already + // started. + if (!drupal_session_started()) { + drupal_session_start(); + } + // Write the session data. + session_write_close(); + } +} + +/** + * Return whether a session has been started. + */ +function drupal_session_started($set = NULL) { + static $session_started = FALSE; + if (isset($set)) { + $session_started = $set; + } + return $session_started && session_id(); +} + +/** + * Called when an anonymous user becomes authenticated or vice-versa. + * + * @ingroup php_wrappers + */ +function drupal_session_regenerate() { + global $user; + if (drupal_session_started()) { + $old_session_id = session_id(); + session_regenerate_id(); + $new_session_id = session_id(); + } + else { + // Start the session when it doesn't exist yet. + // Preserve the logged in user, as it will be reset to anonymous + // by _drupal_session_read. + $account = $user; + drupal_session_start(); + $user = $account; + } + + if (isset($old_session_id)) { + $info = dmemcache_get($old_session_id, 'session'); + $info->sid = $new_session_id; + dmemcache_set($new_session_id, $info, ini_get('session.gc_maxlifetime'), 'session'); + // Clear the old data from the cache. + dmemcache_delete($old_session_id, 'session'); + } +} + + +/** + * End a specific user's session. + */ +function drupal_session_destroy_uid($uid) { + $user = dmemcache_get($uid, 'users'); + if (is_object($user) && isset($user->sid)) { + dmemcache_delete($user->sid, 'session'); + } + dmemcache_delete($uid, 'users'); +} + + +/** + * Determine whether to save session data of the current request. + * + * This function allows the caller to temporarily disable writing of session data, + * should the request end while performing potentially dangerous operations, such as + * manipulating the global $user object. See http://drupal.org/node/218104 for usage + * + * @param $status + * Disables writing of session data when FALSE, (re-)enables writing when TRUE. + * @return + * FALSE if writing session data has been disabled. Otherwise, TRUE. + */ +function drupal_save_session($status = NULL) { + $save_session = &drupal_static(__FUNCTION__, TRUE); + if (isset($status)) { + $save_session = $status; + } + return ($save_session); +} + +/** + * Create the user object. + * + * @param $session + * The session object (see sess_write() for the structure). + * @return $user + * The user object. + */ +function _memcache_session_user_load($session) { + // We found the client's session record and they are an authenticated user. + if ($session && $session->uid != 0) { + $user = dmemcache_get($session->uid, 'users'); + // If the 'users' memcache bin is unavailable, $user will be NULL. + // If the cached user was not found in the 'users' memcache bin, $user will + // be FALSE. + // In either of these cases, the user must be retrieved from the database. + if (!$user && isset($session->uid) && $session->uid != 0) { + $user = db_query('SELECT u.* FROM {users} u WHERE u.uid = :uid', array('uid' => $session->uid))->fetchObject(); + + if (!$user->status) { + $user = drupal_anonymous_user($session->session); + } + else { + $user = drupal_unpack($user); + // Normally we would join the session and user tables. But we already + // have the session information. So add that in. + $user->sid = $session->sid; + $user->cache = $session->cache; + $user->hostname = $session->hostname; + $user->timestamp = $session->timestamp; + $user->session = empty($session->session) ? '' : $session->session; + + // Add roles element to $user + $user->roles = array(); + $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + $result = db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid)); + while ($role = $result->fetchObject()) { + $user->roles[$role->rid] = $role->name; + } + } + } + else if ($user->uid) { + // Got a user object from 'users' memcache bin. Mark it in case modules + // want to know that this user was created from memcache. + $user->from_cache = TRUE; + $user->session = empty($session->session) ? '' : $session->session; + } + else { + // We will only get here when the session has a nonzero uid, a user object + // was successfully retrieved from the 'users' bin, and that user + // object's uid is 0. Not sure why this would ever happen. Leaving former + // comment in: + // This is a rare case that we have a session cached, but no session user object cached. + // This usually only happens if you kill memcached and restart it. + $user = drupal_anonymous_user($session->session); + } + } + // We didn't find the client's record (session has expired), or they are an + // anonymous user. + else { + $session = isset($session->session) ? $session->session : ''; + $user = drupal_anonymous_user($session); + } + + return $user; +} + +/** + * Deletes the session cookie. + * + * @param $name + * Name of session cookie to delete. + * @param $force_insecure + * Fornce cookie to be insecure. + */ +function _drupal_session_delete_cookie($name, $force_insecure = FALSE) { + if (isset($_COOKIE[$name])) { + $params = session_get_cookie_params(); + setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']); + unset($_COOKIE[$name]); + } +} + diff --git a/sites/all/modules/memcache/memcache.inc b/sites/all/modules/memcache/memcache.inc new file mode 100644 index 0000000000000000000000000000000000000000..7718880d2c6499ad2be9ce1ccba3dbea73a26bfe --- /dev/null +++ b/sites/all/modules/memcache/memcache.inc @@ -0,0 +1,284 @@ +<?php + +require_once 'dmemcache.inc'; + +/** + * Defines the period after which wildcard clears are not considered valid. + */ +define('MEMCACHE_WILDCARD_INVALIDATE', 86400 * 28); + +/** Implementation of cache.inc with memcache logic included **/ + +class MemCacheDrupal implements DrupalCacheInterface { + function __construct($bin) { + $this->memcache = dmemcache_object($bin); + $this->bin = $bin; + + $this->wildcard_flushes = variable_get('memcache_wildcard_flushes', array()); + $this->invalidate = variable_get('memcache_wildcard_invalidate', MEMCACHE_WILDCARD_INVALIDATE); + $this->cache_lifetime = variable_get('cache_lifetime', 0); + $this->cache_flush = variable_get('cache_flush_' . $this->bin); + $this->flushed = min($this->cache_flush, REQUEST_TIME - $this->cache_lifetime); + } + function get($cid) { + $cache = dmemcache_get($cid, $this->bin, $this->memcache); + return $this->valid($cid, $cache) ? $cache : FALSE; + } + + function getMultiple(&$cids) { + $results = dmemcache_get_multi($cids, $this->bin, $this->memcache); + foreach ($results as $cid => $result) { + if (!$this->valid($result->cid, $result)) { + // This object has expired, so don't return it. + unset($results[$cid]); + } + else { + // Remove items from the referenced $cids array that we are returning, + // per the comment in cache_get_multiple() in includes/cache.inc. + unset($cids[$result->cid]); + } + } + return $results; + } + + protected function valid($cid, $cache) { + if (!isset($cache) || !is_object($cache)) { + return FALSE; + } + + // The wildcard_valid() function has overhead due to a call to + // dmemcache_get_multi() to fetch possible wildcard flushes. Since some + // bins never have wildcard clears with a cid, we can shortcut these checks. + if (!empty($this->wildcard_flushes[$this->bin]) && + max($this->wildcard_flushes[$this->bin]) >= (REQUEST_TIME - $this->invalidate) && + !$this->wildcard_valid($cid, $cache)) { + return FALSE; + } + + // Determine when the current bin was last flushed. + $item_flushed_globally = $cache->created && $this->cache_flush && + $this->cache_lifetime && + ($cache->created <= $this->flushed); + + // Look in the session to determine if this item was flushed for the + // current user (ie, if they posted a comment and are viewing a cached page) + $cache_bins = isset($_SESSION['cache_flush']) ? $_SESSION['cache_flush'] : NULL; + $item_flushed_for_user = !empty($cache_bins) && + isset($cache_bins[$this->bin]) && + ($cache->created < $cache_bins[$this->bin]); + if ($item_flushed_for_user) { + return FALSE; + } + + // The item can be expired if: + // - A liftetime is set and the item is older than both the lifetime and + // the global flush. + // - The item has been create before the bin was flushed for this user. + // - The item could simply expire. + // + // For the two global cases we try and grab a lock. If we get the lock, we + // return FALSE instead of the cached object which should cause it to be + // rebuilt. If we do not get the lock, we return the cached object despite + // it's expired The goal here is to avoid cache stampedes. + // By default the cache stampede semaphore is held for 15 seconds. This + // can be adjusted by setting the memcache_stampede_semaphore variable. + $item_expired = isset($cache->expire) && + $cache->expire !== CACHE_PERMANENT && + $cache->expire <= REQUEST_TIME; + if ($item_flushed_globally || $item_expired) { + // To avoid a stampede, return TRUE despite the item being expired if + // a previous process set the stampede semaphore already. However only + // do this if the data is less than 30 minutes stale. + if ((REQUEST_TIME - $cache->expire) >= variable_get('memcache_max_staleness', 1800) || + dmemcache_add($cid . '_semaphore', '', variable_get('memcache_stampede_semaphore', 15), $this->bin)) { + return FALSE; + } + } + return TRUE; + } + + function set($cid, $data, $expire = CACHE_PERMANENT, array $headers = NULL) { + $created = REQUEST_TIME; + + // Create new cache object. + $cache = new stdClass; + $cache->cid = $cid; + $cache->data = is_object($data) ? clone $data : $data; + $cache->created = $created; + $cache->headers = $headers; + // Record the previous number of wildcard flushes affecting our cid. + $cache->flushes = $this->wildcard_flushes($cid); + if ($expire == CACHE_TEMPORARY) { + // Convert CACHE_TEMPORARY (-1) into something that will live in memcache + // until the next flush. + $cache->expire = REQUEST_TIME + 2591999; + } + // Expire time is in seconds if less than 30 days, otherwise is a timestamp. + else if ($expire != CACHE_PERMANENT && $expire < 2592000) { + // Expire is expressed in seconds, convert to the proper future timestamp + // as expected in dmemcache_get(). + $cache->expire = REQUEST_TIME + $expire; + } + else { + $cache->expire = $expire; + } + + // We manually track the expire time in $cache->expire. When the object + // expires, we only allow one request to rebuild it to avoid cache + // stampedes. Other requests for the expired object while it is still being + // rebuilt get the expired object. + dmemcache_set($cid, $cache, 0, $this->bin, $this->memcache); + } + + function clear($cid = NULL, $wildcard = FALSE) { + if (empty($cid) || $wildcard === TRUE) { + // system_cron() flushes all cache bins returned by hook_flush_caches() + // with cache_clear_all(NULL, $bin); This is for garbage collection with + // the database cache, but serves no purpose with memcache. So return + // early here. + if (!isset($cid)) { + return; + } + elseif ($cid == '*') { + $cid = ''; + } + if ($this->cache_lifetime && empty($cid)) { + // Update the timestamp of the last global flushing of this bin. When + // retrieving data from this bin, we will compare the cache creation + // time minus the cache_flush time to the cache_lifetime to determine + // whether or not the cached item is still valid. + variable_set("cache_flush_$this->bin", REQUEST_TIME); + $this->flushed = REQUEST_TIME; + + // We store the time in the current user's session which is saved into + // the sessions table by sess_write(). We then simulate that the cache + // was flushed for this user by not returning cached data to this user + // that was cached before the timestamp. + if (isset($_SESSION['cache_flush']) && is_array($_SESSION['cache_flush'])) { + $cache_bins = $_SESSION['cache_flush']; + } + else { + $cache_bins = array(); + } + $cache_bins[$this->bin] = REQUEST_TIME; + $_SESSION['cache_flush'] = $cache_bins; + } + else { + // Register a wildcard flush for current cid + $this->wildcards($cid, TRUE); + } + } + else { + $cids = is_array($cid) ? $cid : array($cid); + foreach ($cids as $cid) { + dmemcache_delete($cid, $this->bin, $this->memcache); + } + } + } + + /** + * Sum of all matching wildcards. Checking any single cache item's flush + * value against this single-value sum tells us whether or not a new wildcard + * flush has affected the cached item. + */ + private function wildcard_flushes($cid) { + return array_sum($this->wildcards($cid)); + } + + /** + * Utilize multiget to retrieve all possible wildcard matches, storing + * statically so multiple cache requests for the same item on the same page + * load doesn't add overhead. + */ + private function wildcards($cid, $flush = FALSE) { + static $wildcards = array(); + $matching = array(); + + if (isset($this->wildcard_flushes[$this->bin]) && + is_array($this->wildcard_flushes[$this->bin])) { + // Determine which lookups we need to perform to determine whether or not + // our cid was impacted by a wildcard flush. + $lookup = array(); + + // Find statically cached wildcards, and determine possibly matching + // wildcards for this cid based on a history of the lengths of past + // valid wildcard flushes in this bin. + foreach ($this->wildcard_flushes[$this->bin] as $length => $timestamp) { + if ($timestamp >= (REQUEST_TIME - $this->invalidate)) { + $key = '.wildcard-' . substr($cid, 0, $length); + $wildcard = dmemcache_key($key, $this->bin); + if (isset($wildcards[$this->bin][$wildcard])) { + $matching[$wildcard] = $wildcards[$this->bin][$wildcard]; + } + else { + $lookup[$wildcard] = $key; + } + } + } + + // Do a multi-get to retrieve all possibly matching wildcard flushes. + if (!empty($lookup)) { + $values = dmemcache_get_multi($lookup, $this->bin, $this->memcache); + if (is_array($values)) { + // Prepare an array of matching wildcards. + $matching = array_merge($matching, $values); + // Store matches in the static cache. + if (isset($wildcards[$this->bin])) { + $wildcards[$this->bin] = array_merge($wildcards[$this->bin], $values); + } + else { + $wildcards[$this->bin] = $values; + } + $lookup = array_diff_key($lookup, $values); + } + + // Also store failed lookups in our static cache, so we don't have to + // do repeat lookups on single page loads. + foreach ($lookup as $wildcard => $key) { + $wildcards[$this->bin][$wildcard] = 0; + } + } + } + if ($flush) { + // Avoid too many calls to variable_set() by only recording a flush for + // a fraction of the wildcard invalidation variable, per cid length. + // Defaults to 28 / 4, or one week. + $length = strlen($cid); + if (!isset($this->wildcard_flushes[$this->bin][$length]) || + ($_SERVER['REQUEST_TIME'] - $this->wildcard_flushes[$this->bin][$length] > $this->invalidate / 4)) { + $this->wildcard_flushes = variable_get('memcache_wildcard_flushes', array()); + $this->wildcard_flushes[$this->bin][$length] = $_SERVER['REQUEST_TIME']; + variable_set('memcache_wildcard_flushes', $this->wildcard_flushes); + } + $wildcard = dmemcache_key('.wildcard-' . $cid, $this->bin); + if (isset($wildcards[$this->bin][$wildcard]) && $wildcards[$this->bin][$wildcard] != 0) { + $this->memcache->increment($wildcard); + $wildcards[$this->bin][$wildcard]++; + } + else { + $wildcards[$this->bin][$wildcard] = 1; + dmemcache_set('.wildcard-' . $cid, '1', 0, $this->bin); + } + } + return $matching; + } + + /** + * Check if a wildcard flush has invalidated the current cached copy. + */ + private function wildcard_valid($cid, $cache) { + // Previously cached content won't have ->flushes defined. We could + // force flush, but instead leave this up to the site admin. + $flushes = isset($cache->flushes) ? (int)$cache->flushes : 0; + if ($flushes < (int)$this->wildcard_flushes($cid)) { + return FALSE; + } + return TRUE; + } + + function isEmpty() { + // We do not know so err on the safe side? + return FALSE; + } +} + diff --git a/sites/all/modules/memcache/memcache.info b/sites/all/modules/memcache/memcache.info new file mode 100644 index 0000000000000000000000000000000000000000..b04fbcbe95aa3ee265fa14f2cd02650dbd29a802 --- /dev/null +++ b/sites/all/modules/memcache/memcache.info @@ -0,0 +1,14 @@ +name = Memcache +description = High performance integration with memcache. +package = Caching +core = 7.x +files[] = memcache.inc +files[] = memcache.module +files[] = memcache.test + +; Information added by drupal.org packaging script on 2011-05-27 +version = "7.x-1.0-beta4" +core = "7.x" +project = "memcache" +datestamp = "1306500116" + diff --git a/sites/all/modules/memcache/memcache.test b/sites/all/modules/memcache/memcache.test new file mode 100644 index 0000000000000000000000000000000000000000..89abd6b7f25a6f6472210be91512227af831fd3e --- /dev/null +++ b/sites/all/modules/memcache/memcache.test @@ -0,0 +1,541 @@ +<?php + +class MemcacheTestCase extends DrupalWebTestCase { + protected $default_bin = 'cache'; + protected $default_cid = 'test_temporary'; + protected $default_value = 'MemcacheTest'; + + function setUp() { + include_once drupal_get_path('module', 'memcache') . '/memcache.inc'; + variable_set('cache_default_class', 'MemCacheDrupal'); + parent::setUp(); + } + + /** + * Check whether or not a cache entry exists. + * + * @param $cid + * The cache id. + * @param $var + * The variable the cache should contain. + * @param $bin + * The bin the cache item was stored in. + * @return + * TRUE on pass, FALSE on fail. + */ + protected function checkCacheExists($cid, $var, $bin = NULL) { + if ($bin == NULL) { + $bin = $this->default_bin; + } + + $cache = cache_get($cid, $bin); + + return isset($cache->data) && $cache->data == $var; + } + + /** + * Assert or a cache entry exists. + * + * @param $message + * Message to display. + * @param $var + * The variable the cache should contain. + * @param $cid + * The cache id. + * @param $bin + * The bin the cache item was stored in. + */ + protected function assertCacheExists($message, $var = NULL, $cid = NULL, $bin = NULL) { + if ($bin == NULL) { + $bin = $this->default_bin; + } + if ($cid == NULL) { + $cid = $this->default_cid; + } + if ($var == NULL) { + $var = $this->default_value; + } + + $this->assertTrue($this->checkCacheExists($cid, $var, $bin), $message); + } + + /** + * Assert or a cache entry has been removed. + * + * @param $message + * Message to display. + * @param $cid + * The cache id. + * @param $bin + * The bin the cache item was stored in. + */ + function assertCacheRemoved($message, $cid = NULL, $bin = NULL) { + if ($bin == NULL) { + $bin = $this->default_bin; + } + if ($cid == NULL) { + $cid = $this->default_cid; + } + + $cache = cache_get($cid, $bin); + $this->assertFalse($cache, $message); + } + + /** + * Perform the general wipe. + * @param $bin + * The bin to perform the wipe on. + */ + protected function generalWipe($bin = NULL) { + if ($bin == NULL) { + $bin = $this->default_bin; + } + + cache_clear_all(NULL, $bin); + } + + /** + * Setup the lifetime settings for caching. + * + * @param $time + * The time in seconds the cache should minimal live. + */ + protected function setupLifetime($time) { + variable_set('cache_lifetime', $time); + variable_set('cache_flush', 0); + } +} + +class MemCacheSavingCase extends MemcacheTestCase { + public static function getInfo() { + return array( + 'name' => 'Memcache saving test', + 'description' => 'Check our variables are saved and restored the right way.', + 'group' => 'Memcache' + ); + } + + /** + * Test the saving and restoring of a string. + */ + function testString() { + $this->checkVariable($this->randomName(100)); + } + + /** + * Test the saving and restoring of an integer. + */ + function testInteger() { + $this->checkVariable(100); + } + + /** + * Test the saving and restoring of a double. + */ + function testDouble() { + $this->checkVariable(1.29); + } + + /** + * Test the saving and restoring of an array. + */ + function testArray() { + $this->checkVariable(array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6'))); + } + + /** + * Test the saving and restoring of an object. + */ + function testObject() { + $test_object = new stdClass(); + $test_object->test1 = $this->randomName(100); + $test_object->test2 = 100; + $test_object->test3 = array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6')); + + cache_set('test_object', $test_object, 'cache'); + $cache = cache_get('test_object', 'cache'); + $this->assertTrue(isset($cache->data) && $cache->data == $test_object, t('Object is saved and restored properly.')); + } + + /* + * Check or a variable is stored and restored properly. + **/ + function checkVariable($var) { + cache_set('test_var', $var, 'cache'); + $cache = cache_get('test_var', 'cache'); + $this->assertTrue(isset($cache->data) && $cache->data === $var, t('@type is saved and restored properly.', array('@type' => ucfirst(gettype($var))))); + } +} + +/** + * Test cache_get_multiple(). + */ +class MemCacheGetMultipleUnitTest extends MemcacheTestCase { + + public static function getInfo() { + return array( + 'name' => 'Fetching multiple cache items', + 'description' => 'Confirm that multiple records are fetched correctly.', + 'group' => 'Memcache', + ); + } + + function setUp() { + $this->default_bin = 'cache_page'; + parent::setUp(); + } + + /** + * Test cache_get_multiple(). + */ + function testCacheMultiple() { + $item1 = $this->randomName(10); + $item2 = $this->randomName(10); + cache_set('item1', $item1, $this->default_bin); + cache_set('item2', $item2, $this->default_bin); + $this->assertTrue($this->checkCacheExists('item1', $item1), t('Item 1 is cached.')); + $this->assertTrue($this->checkCacheExists('item2', $item2), t('Item 2 is cached.')); + + // Fetch both records from the database with cache_get_multiple(). + $item_ids = array('item1', 'item2'); + $items = cache_get_multiple($item_ids, $this->default_bin); + $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.')); + $this->assertEqual($items['item2']->data, $item2, t('Item was returned from cache successfully.')); + + // Remove one item from the cache. + cache_clear_all('item2', $this->default_bin); + + // Confirm that only one item is returned by cache_get_multiple(). + $item_ids = array('item1', 'item2'); + $items = cache_get_multiple($item_ids, $this->default_bin); + $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.')); + $this->assertFalse(isset($items['item2']), t('Item was not returned from the cache.')); + $this->assertTrue(count($items) == 1, t('Only valid cache entries returned.')); + } +} + +/** + * Test cache clearing methods. + */ +class MemCacheClearCase extends MemcacheTestCase { + public static function getInfo() { + return array( + 'name' => 'Cache clear test', + 'description' => 'Check our clearing is done the proper way.', + 'group' => 'Memcache' + ); + } + + function setUp() { + $this->default_bin = 'cache_page'; + $this->default_value = $this->randomName(10); + + parent::setUp(); + } + + /** + * Test clearing using a cid. + */ + function testClearCid() { + cache_set('test_cid_clear', $this->default_value, $this->default_bin); + + $this->assertCacheExists(t('Cache was set for clearing cid.'), $this->default_value, 'test_cid_clear'); + cache_clear_all('test_cid_clear', $this->default_bin); + + $this->assertCacheRemoved(t('Cache was removed after clearing cid.'), 'test_cid_clear'); + + cache_set('test_cid_clear1', $this->default_value, $this->default_bin); + cache_set('test_cid_clear2', $this->default_value, $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches were created for checking cid "*" with wildcard false.')); + cache_clear_all('*', $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches still exists after clearing cid "*" with wildcard false.')); + } + + /** + * Test clearing using wildcard. + */ + function testClearWildcard() { + cache_set('test_cid_clear1', $this->default_value, $this->default_bin); + cache_set('test_cid_clear2', $this->default_value, $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches were created for checking cid "*" with wildcard true.')); + cache_clear_all('*', $this->default_bin, TRUE); + $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) + || $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches removed after clearing cid "*" with wildcard true.')); + + cache_set('test_cid_clear1', $this->default_value, $this->default_bin); + cache_set('test_cid_clear2', $this->default_value, $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches were created for checking cid substring with wildcard true.')); + cache_clear_all('test_', $this->default_bin, TRUE); + $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) + || $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two caches removed after clearing cid substring with wildcard true.')); + } + + /** + * Test clearing using an array. + */ + function testClearArray() { + // Create three cache entries. + cache_set('test_cid_clear1', $this->default_value, $this->default_bin); + cache_set('test_cid_clear2', $this->default_value, $this->default_bin); + cache_set('test_cid_clear3', $this->default_value, $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value) + && $this->checkCacheExists('test_cid_clear3', $this->default_value), + t('Three cache entries were created.')); + + // Clear two entries using an array. + cache_clear_all(array('test_cid_clear1', 'test_cid_clear2'), $this->default_bin); + $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) + || $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two cache entries removed after clearing with an array.')); + + $this->assertTrue($this->checkCacheExists('test_cid_clear3', $this->default_value), + t('Entry was not cleared from the cache')); + + // Set the cache clear threshold to 2 to confirm that the full bin is cleared + // when the threshold is exceeded. + variable_set('cache_clear_threshold', 2); + cache_set('test_cid_clear1', $this->default_value, $this->default_bin); + cache_set('test_cid_clear2', $this->default_value, $this->default_bin); + $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) + && $this->checkCacheExists('test_cid_clear2', $this->default_value), + t('Two cache entries were created.')); + cache_clear_all(array('test_cid_clear1', 'test_cid_clear2', 'test_cid_clear3'), $this->default_bin); + $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) + || $this->checkCacheExists('test_cid_clear2', $this->default_value) + || $this->checkCacheExists('test_cid_clear3', $this->default_value), + t('All cache entries removed when the array exceeded the cache clear threshold.')); + } +} + +/** + * @file + * Provides SimpleTests for core session handling functionality. + */ +class MemcacheSessionTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Session tests', + 'description' => 'Memcache session handling tests.', + 'group' => 'Memcache' + ); + } + + function setUp() { + parent::setUp('session_test'); + } + + /** + * Tests for drupal_save_session() and drupal_session_regenerate(). + */ + function testSessionSaveRegenerate() { + $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE (inside of testing framework) when initially called with no arguments.'), t('Session')); + $this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session')); + $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session')); + $this->assertTrue(drupal_save_session(TRUE), t('drupal_save_session() correctly returns TRUE when called with TRUE.'), t('Session')); + $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when saving has been enabled.'), t('Session')); + + // Test session hardening code from SA-2008-044. + $user = $this->drupalCreateUser(array('access content')); + + // Enable sessions. + $this->sessionReset($user->uid); + + // Make sure the session cookie is set as HttpOnly. + $this->drupalLogin($user); + $this->assertTrue(preg_match('/HttpOnly/i', $this->drupalGetHeader('Set-Cookie', TRUE)), t('Session cookie is set as HttpOnly.')); + $this->drupalLogout(); + + // Verify that the session is regenerated if a module calls exit + // in hook_user_login(). + user_save($user, array('name' => 'session_test_user')); + $user->name = 'session_test_user'; + $this->drupalGet('session-test/id'); + $matches = array(); + preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); + $this->assertTrue(!empty($matches[1]) , t('Found session ID before logging in.')); + $original_session = $matches[1]; + + // We cannot use $this->drupalLogin($user); because we exit in + // session_test_user_login() which breaks a normal assertion. + $edit = array( + 'name' => $user->name, + 'pass' => $user->pass_raw + ); + $this->drupalPost('user', $edit, t('Log in')); + $this->drupalGet('user'); + $pass = $this->assertText($user->name, t('Found name: %name', array('%name' => $user->name)), t('User login')); + $this->_logged_in = $pass; + + $this->drupalGet('session-test/id'); + $matches = array(); + preg_match('/\s*session_id:(.*)\n/', $this->drupalGetContent(), $matches); + $this->assertTrue(!empty($matches[1]) , t('Found session ID after logging in.')); + $this->assertTrue($matches[1] != $original_session, t('Session ID changed after login.')); + } + + /** + * Test data persistence via the session_test module callbacks. Also tests + * drupal_session_count() since session data is already generated here. + */ + function testDataPersistence() { + $user = $this->drupalCreateUser(array('access content')); + // Enable sessions. + $this->sessionReset($user->uid); + + $this->drupalLogin($user); + + $value_1 = $this->randomName(); + $this->drupalGet('session-test/set/' . $value_1); + $this->assertText($value_1, t('The session value was stored.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_1, t('Session correctly returned the stored data for an authenticated user.'), t('Session')); + + // Attempt to write over val_1. If drupal_save_session(FALSE) is working. + // properly, val_1 will still be set. + $value_2 = $this->randomName(); + $this->drupalGet('session-test/no-set/' . $value_2); + $this->assertText($value_2, t('The session value was correctly passed to session-test/no-set.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_1, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); + + // Switch browser cookie to anonymous user, then back to user 1. + $this->sessionReset(); + $this->sessionReset($user->uid); + $this->assertText($value_1, t('Session data persists through browser close.'), t('Session')); + + // Logout the user and make sure the stored value no longer persists. + $this->drupalLogout(); + + $this->sessionReset(); + $this->drupalGet('session-test/get'); + $this->assertNoText($value_1, t("After logout, previous user's session data is not available."), t('Session')); + + // Now try to store some data as an anonymous user. + $value_3 = $this->randomName(); + $this->drupalGet('session-test/set/' . $value_3); + $this->assertText($value_3, t('Session data stored for anonymous user.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_3, t('Session correctly returned the stored data for an anonymous user.'), t('Session')); + + // Try to store data when drupal_save_session(FALSE). + $value_4 = $this->randomName(); + $this->drupalGet('session-test/no-set/' . $value_4); + $this->assertText($value_4, t('The session value was correctly passed to session-test/no-set.'), t('Session')); + $this->drupalGet('session-test/get'); + $this->assertText($value_3, t('Session data is not saved for drupal_save_session(FALSE).'), t('Session')); + + // Login, the data should persist. + $this->drupalLogin($user); + $this->sessionReset($user->uid); + $this->drupalGet('session-test/get'); + $this->assertNoText($value_1, t('Session has persisted for an authenticated user after logging out and then back in.'), t('Session')); + + // Change session and create another user. + $user2 = $this->drupalCreateUser(array('access content')); + $this->sessionReset($user2->uid); + $this->drupalLogin($user2); + } + + /** + * Test that empty anonymous sessions are destroyed. + */ + function testEmptyAnonymousSession() { + // Verify that no session is automatically created for anonymous user. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + + // The same behavior is expected when caching is enabled. + variable_set('cache', CACHE_NORMAL); + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.')); + + // Start a new session by setting a message. + $this->drupalGet('session-test/set-message'); + $this->assertSessionCookie(TRUE); + $this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.')); + + // Display the message, during the same request the session is destroyed + // and the session cookie is unset. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(FALSE); + $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.')); + $this->assertText(t('This is a dummy message.'), t('Message was displayed.')); + $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.')); + + // Verify that session was destroyed. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertNoText(t('This is a dummy message.'), t('Message was not cached.')); + $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); + $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.')); + + // Verify that no session is created if drupal_save_session(FALSE) is called. + $this->drupalGet('session-test/set-message-but-dont-save'); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + + // Verify that no message is displayed. + $this->drupalGet(''); + $this->assertSessionCookie(FALSE); + $this->assertSessionEmpty(TRUE); + $this->assertNoText(t('This is a dummy message.'), t('The message was not saved.')); + } + + /** + * Reset the cookie file so that it refers to the specified user. + * + * @param $uid User id to set as the active session. + */ + function sessionReset($uid = 0) { + // Close the internal browser. + $this->curlClose(); + $this->loggedInUser = FALSE; + + // Change cookie file for user. + $this->cookieFile = file_directory_path('temporary') . '/cookie.' . $uid . '.txt'; + $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile; + $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE; + $this->drupalGet('session-test/get'); + $this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session')); + } + + /** + * Assert whether the SimpleTest browser sent a session cookie. + */ + function assertSessionCookie($sent) { + if ($sent) { + $this->assertNotNull($this->session_id, t('Session cookie was sent.')); + } + else { + $this->assertNull($this->session_id, t('Session cookie was not sent.')); + } + } + + /** + * Assert whether $_SESSION is empty at the beginning of the request. + */ + function assertSessionEmpty($empty) { + if ($empty) { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.')); + } + else { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.')); + } + } +} diff --git a/sites/all/modules/memcache/memcache_admin/memcache.js b/sites/all/modules/memcache/memcache_admin/memcache.js new file mode 100644 index 0000000000000000000000000000000000000000..c6b79a0a9ca34bbacaebfba71b77c1fb2e45b20f --- /dev/null +++ b/sites/all/modules/memcache/memcache_admin/memcache.js @@ -0,0 +1,7 @@ + +// Global Killswitch +if (Drupal.jsEnabled) { +$(document).ready(function() { + $("body").append($("#memcache-devel")); + }); +} diff --git a/sites/all/modules/memcache/memcache_admin/memcache_admin.info b/sites/all/modules/memcache/memcache_admin/memcache_admin.info new file mode 100644 index 0000000000000000000000000000000000000000..b9d8fcb66915a18065b28ad1033a309af0b77e40 --- /dev/null +++ b/sites/all/modules/memcache/memcache_admin/memcache_admin.info @@ -0,0 +1,12 @@ +name = Memcache Admin +description = Adds a User Interface to monitor the Memcache for this site. +package = Caching +core = 7.x +files[] = memcache_admin.module + +; Information added by drupal.org packaging script on 2011-05-27 +version = "7.x-1.0-beta4" +core = "7.x" +project = "memcache" +datestamp = "1306500116" + diff --git a/sites/all/modules/memcache/memcache_admin/memcache_admin.module b/sites/all/modules/memcache/memcache_admin/memcache_admin.module new file mode 100644 index 0000000000000000000000000000000000000000..ee84db5cbb0e32c51e8e9fc714485909e2b19fdd --- /dev/null +++ b/sites/all/modules/memcache/memcache_admin/memcache_admin.module @@ -0,0 +1,260 @@ +<?php + +/** + * For the collection of memcache stats. This small .js file makes sure that the + * HTML displaying the stats is inside of the <body> part of the HTML + * document. + */ +function memcache_admin_init() { + global $user; + if (($user->uid == 0) || strstr($_SERVER['PHP_SELF'], 'update.php') || strstr($_GET['q'], 'autocomplete')) { + // update.php relies on standard error handler + } + else { + if ($user->uid) { + drupal_add_js(drupal_get_path('module', 'memcache_admin'). '/memcache.js'); + } + register_shutdown_function('memcache_admin_shutdown'); + } +} + +/** + * Implementation of hook_perm(). + */ +function memcache_admin_permission() { + return array( + 'access memcache statistics' => array( + 'title' => t('Access memcache statistics'), + ), + ); +} + +/** + * Implementation of hook_menu(). + */ +function memcache_admin_menu() { + $items['admin/config/system/memcache'] = array( + 'title' => 'Memcache', + 'description' => 'Show or hide memcache statistics at the bottom of each page.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('memcache_admin_admin_settings'), + 'access arguments' => array('administer site configuration'), + ); + $items['admin/reports/memcache'] = array( + 'title' => 'Memcache status', + 'description' => "View the statistics for this site's memcache.", + 'page callback' => 'memcache_admin_stats', + 'access arguments' => array('access memcache statistics'), + 'weight' => 1, + ); + $memache_servers = variable_get('memcache_servers', array()); + $clusters = array(); + foreach($memache_servers as $server => $cluster) { + $clusters[$cluster]['servers'][] = $server; + $clusters[$cluster]['bin'] = _memcache_admin_get_bin_for_cluster($cluster); + } + + $count = 0; + foreach ($clusters as $cluster => $cluster_info) { + if ($cluster_info['bin']) { + if (empty($current_cluster)) { + $current_cluster = arg(3); + if (empty($current_cluster)) { + $current_cluster = $cluster; + } + } + + $items['admin/reports/memcache/'. $cluster] = array( + 'title' => $cluster, + 'type' => $count == 0 ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'page callback' => 'memcache_admin_stats', + 'page arguments' => array($cluster), + 'access arguments' => array('access memcache statistics'), + 'weight' => $count, + ); + $count++; + + $sub_count = 0; + foreach (array('default', 'malloc', 'maps', 'slabs', 'items', 'sizes') as $type) { + $items['admin/reports/memcache/'. $cluster .'/'. $type] = array( + 'type' => $type == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, + 'page callback' => 'memcache_admin_stats', + 'page arguments' => array($cluster, $type), + 'title' => $type, + 'access arguments' => array('access memcache statistics'), + 'weight' => $sub_count, + ); + $sub_count++; + } + } + } + + return $items; +} + +/** + * Settings form. + */ +function memcache_admin_admin_settings() { + $form['show_memcache_statistics'] = array('#type' => 'checkbox', + '#title' => t('Show memcache statistics at the bottom of each page'), + '#default_value' => variable_get('show_memcache_statistics', 1), + '#description' => t("These statistics will be visible to users with the 'access memcache statistics' permission."), + ); + return system_settings_form($form); +} + +/** + * Memcahe Stats page + * + * @param string $cluster - which cluster to view? + * @param string $type - which type of stat, eg: default, malloc, maps, cachedump, slabs, items or sizes + * @return string + */ +function memcache_admin_stats($cluster = 'default', $type = 'default') { + $bin = _memcache_admin_get_bin_for_cluster($cluster); + + if ($bin) { + $stats = dmemcache_stats($bin, $type); + + if (is_array($stats) && count($stats)) { + $output = ''; + + foreach ($stats as $server => $values) { + if (is_array($values)) { + // Do some custome value tweaks for specific stat page types. + switch ($type) { + case 'default' : + $values['uptime'] = format_interval($values['uptime']); + $values['time'] = format_date($values['time']); + $values['bytes'] = format_size($values['bytes']); + $values['bytes_read'] = format_size($values['bytes_read']); + $values['bytes_written'] = format_size($values['bytes_written']); + $values['limit_maxbytes'] = format_size($values['limit_maxbytes']); + + //Custom Entries + $values['hit_percentage'] = ($values['cmd_get'] > 0) + ? number_format(100.0 * $values['get_hits'] / $values['cmd_get'], 2) . '%' + : '0'; + + $mem_used = intval($values['bytes']) / (intval($values['limit_maxbytes']) * 1024); + $values['mem_used'] = number_format(100.0 * $mem_used, 2) . '%'; + break; + } + + $output .= theme('memcache_admin_stats_table', array('server' => $server, 'stats' => $values)); + } + else { + drupal_set_message(t('Unable to get statistic from server %server', array('%server' => $server))); + } + } + } + + else { + $output = ''; + drupal_set_message(t('No available statistics for this bin.')); + } + } + + return $output; +} + +/** + * Implementation of hook_theme(). + */ +function memcache_admin_theme() { + return array( + 'memcache_admin_stats_table' => array( + 'variables' => array('server' => NULL, 'stats' => NULL), + ) + ); +} + +/** + * Theme function for rendering the output from memcache_admin_stats + * + * @param string $server - Server name:port for caption for the table + * @param array $stats - array of key/value string pairs for the table results + * @return string + */ +function theme_memcache_admin_stats_table($variables) { + $server = $variables['server']; + $stats = $variables['stats']; + + $rows = array(); + + foreach ($stats as $key => $value) { + if (is_array($value)) { + $rs = array(); + foreach ($value as $k => $v) { + $rs[] = array($k, $v); + } + $rows[] = array($key, theme('table', array('header' => array(), 'rows' => $rs))); + } + else { + $rows[] = array($key, $value); + } + } + + return theme('table', array('header' => array(t('Property'), t('Value')), 'rows' => $rows, 'caption' => $server)); +} + + +/** + * Retrieve the cluster for any given bin + * + * @param string $cluster - Cluster ID + * @return string + */ +function _memcache_admin_get_bin_for_cluster($cluster) { + static $cluster_map = array(); + + if (!isset($cluster_map[$cluster])) { + $memache_bins = variable_get('memcache_bins', array()); + if ($mapping = array_search($cluster, $memache_bins)) { + $cluster_map[$cluster] = $mapping; + } + else { + $cluster_map[$cluster] = 'default'; + } + } + + return $cluster_map[$cluster]; +} + +/** + * See memcache_admin_init() which registers this function as a shutdown function. + * Displays memcache stats in the footer. + */ +function memcache_admin_shutdown() { + global $_memcache_statistics; + + // Try not to break non-HTML pages. + if (function_exists('drupal_get_http_header')) { + $header = drupal_get_http_header('content-type'); + if ($header) { + $formats = array('xml', 'javascript', 'json', 'plain', 'image', 'application', 'csv', 'x-comma-separated-values'); + foreach ($formats as $format) { + if (strstr($header, $format)) { + return; + } + } + } + } + + if (variable_get('show_memcache_statistics', TRUE) && function_exists('user_access') && user_access('access memcache statistics')) { + if (!empty($_memcache_statistics)) { + foreach ($_memcache_statistics as $row => $stats) { + $_memcache_statistics[$row][1] = check_plain($stats[1]); + $_memcache_statistics[$row][2] = check_plain($stats[2]); + } + + $variables = array('header' => array(t('Operation'), t('Bin'), t('Key'), t('Hit')), + 'rows' => $_memcache_statistics); + $output = theme('table', $variables); + + // this makes sure all of the HTML is within the <body> even though this <script> is outside it + print '<div id="memcache-devel"><h2>'. t('Memcache statistics'). '</h2>'. $output. '</div>'; + } + } +}