PaxHeader/Cache-Memcached-Managed-0.27000755 000765 000024 00000000210 15000423521 020332 xustar00lnationstaff000000 000000 30 mtime=1744971601.284514468 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/000755 000765 000024 00000000000 15000423521 016442 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/PaxHeader/CHANGELOG000644 000765 000024 00000000373 15000423467 021641 xustar00lnationstaff000000 000000 30 mtime=1744971575.120904284 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjczNzs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022737;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/CHANGELOG000644 000765 000024 00000016152 15000423467 017672 0ustar00lnationstaff000000 000000 0.27 18 April 2025 - Attempt to fix failing tests 0.26 17 November 2019 - Fix spelling errors in manpage - Nick Morrott 0.25 9 November 2019 Adoption 0.24 24 May 2012 Removed forced die from Makefile.PL if memcached is not available. All relevant tests will be skipped, allowing installs on machines where memcached itself is not installed. Your Mileage May Vary. RT #52678 Added clarification about using version as floating points, and that it is better to use it as a string. RT #29224 Fixed problem with expiration just specified as number of seconds. Added tests for expiration specification, just to be sure. RT #49913 0.23 24 May 2012 Fixed thinko reported by James M. Added tests to test for thinko. Also fixed specifying a blessed object as data / directory source, although this may well break with non-hashref based implemenations. But it didn't work before anyway, so it at least now matches the documentation. RT #77389. Ensured it runs with 5.16.0, threaded and unthreaded. 0.22 22 April 2012 Some more spelling fixes. Added license information + adapted copyright message to reflect that I've taken it back in maintenance in my own time again. RT #75346 0.21 31 March 2012 Added spelling fixes as suggested by Nicholas Bamber, as suggested by Gregor Herrmann. RT #69648 Finally released to CPAN, thanks to the impetus given by the QA Hackathon 2012 in Paris. 24 May 2008 Shielded some code from using Cache::Memcached specific internal methods, which do not appear to exist in Cache::Memcached::Fast. This at least let the test-suite complete, albeit with errors if the Cache::Memcached::Fast class is being used. This probably will need some more work and some direct interaction with the developers of Cache::Memcached::Fast. Testing with a class other than Cache::Memcached can be accomplished by setting the environment variable CACHE_MEMCACHE to the intended class, e.g. $ CACHE_MEMCACHED=Cache::Memcached::Fast make test 0.20 19 May 2008 Added preliminary "memcached_class" parameter to "new", to allow other implementations of Cache::Memcached (such as Cache::Memcached::Fast) to be used. 0.19 8 October 2007 Argh, 0.18 broke on 5.005 because of new style open(). Now rephrased to old-style open() using globs. 0.18 7 October 2007 Changed Makefile.PL to quit before attempting to install to kill all cpan-tester messages where Cache::Memcached::Managed is being tested without "memcached" being available and executable. No changes made to the code. 0.17 3 October 2007 Methods "add", "decr", "incr", "replace" and "set" now accept a third unnamed parameter for expiration. Suggested by John Goulah. Fixed test-suite: most problems occurred because "localhost" apparently doesn't resolve on most systems anymore, so it couldn't find a port to work on. Now using 127.0.0.1. Several other problems occurred because of additional statistics in newer versions of memcached. Marked tests as TODO for now. Apparently the handling of dead servers has been improved, so the fetch on a dead servers wasn't failing, which was failing the test and subsequent statistics tests. Also marked tests as TODO for now. Added "Incompatibility with Cache module" section to CAVEATS. 0.16 2 February 2007 Updated the test-suite to support version 1.2.1 of memcached. Brought code of test-suite up to PBP standards (code itself will follow in a later version). Load testing is no longer skipped on OS X, as the latest libevent / memcached combo seems to compile and install cleanly with all necessary workarounds installed automagically. Instead a warning is displayed. Changed documentation of "flush_all" to point out that the "flush_interval.patch" is no longer necessary when using memcached 1.2.1. 0.15 11 June 2006 Cache::Memcached changed some of its internals with version 1.16. This version now supports both versions before and after. Spotted by islue. 0.14 19 October 2005 Update VERSION file (forgotten to do that in 0.13) Added fix for missing Kwalitee point for not using 'strict' 0.13 23 August 2005 Added support for Cache::Memcached::Managed::Multi, to allow accessing multiple managed servers at the same time. 0.12 18 July 2005 Added "inactive" parameter to "new". Added Cache::Memcached::Managed::Inactive module for the "inactive" functionality. This allows you to make your code in such a way that it doesn't make a difference whether or not there is an actual memcached server available. 0.11 8 July 2005 Fixed problem in "group" and "get_multi" when using a non-standard delimiter. Spotted by Torbjørn Køhle 0.10 6 July 2005 Changed return value of "flush_all" to boolean rather than number of servers flushed. Added support for "flush_interval". Added file "flush_interval.patch" to distribution to add time flush_all support to memcached itself. 0.09 5 July 2005 Further refined the fork checking / dead server logic: connection attempts are now made again after 20..30 seconds, instead of never in the current process. Fixed problem that would cause an empty list to be returned in case of failure in list context: this affected "set", "add", "replace", "incr" and "decr". A false value is now returned regardless of context. 4 July 2005 Completely reworked the fork checking logic. Child processes of parent processes that have been disconnected, now attempt to reconnect. 0.08 4 July 2005 Fixed many documentation problems. Added default data server functionality to "new". Added "W" to allow expirations to be specified by week. 0.07 3 July 2005 Fixed problem with return value of "start". Optimized "stats". Added stress test related to groups, which unfortunately seems to fail on Tiger (with its crippled libevent support). 0.06 1 July 2005 Fixed problems with relative keys and version support. 0.05 30 June 2005 Added initial tests for checking behaviour after fork(). 0.04 29 June 2005 Added methods "start", "stop" and "version". Removed starting / stopping logic from testlib. Adapted test-suite to account for these new methods. Basic testing should now cover all methods (albeit not with all parameter combinations yet. 0.03 28 June 2005 Tweaks and fixes and more tests. 0.02 27 June 2005 Tweaks and fixes and a much more expanded test-suite. 0.01 26 June 2005 First public version of Cache::Memcached::Managed. Cache-Memcached-Managed-0.27/PaxHeader/flush_interval.patch000644 000765 000024 00000000335 10263057135 024474 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/flush_interval.patch000644 000765 000024 00000005067 10263057135 022532 0ustar00lnationstaff000000 000000 --- memcached.c.1.1.12 2005-04-05 02:10:26.000000000 +0200 +++ memcached.c 2005-07-06 20:01:02.000000000 +0200 @@ -619,8 +619,8 @@ if (it && (it->it_flags & ITEM_DELETED)) { it = 0; } - if (settings.oldest_live && it && - it->time <= settings.oldest_live) { + if (settings.oldest_live && settings.oldest_live <= now && + it && it->time <= settings.oldest_live) { item_unlink(it); it = 0; } @@ -707,8 +707,23 @@ return; } - if (strcmp(command, "flush_all") == 0) { - settings.oldest_live = time(0); + if (strncmp(command, "flush_all", 9) == 0) { + time_t exptime = 0; + int res; + + if (strcmp(command, "flush_all") == 0) { + settings.oldest_live = time(0); + out_string(c, "OK"); + return; + } + + res = sscanf(command, "%*s %ld", &exptime); + if (res != 1) { + out_string(c, "ERROR"); + return; + } + + settings.oldest_live = realtime(exptime); out_string(c, "OK"); return; } --- doc/protocol.txt.1.1.12 2004-04-26 23:26:48.000000000 +0200 +++ doc/protocol.txt 2005-07-06 20:09:24.000000000 +0200 @@ -359,16 +359,17 @@ Other commands -------------- -"flush_all" is a command with no arguments. It always succeeds, -and the server sends "OK\r\n" in response. Its effect is to immediately -invalidate all existing items: none of them will be returned in -response to a retrieval command (unless it's stored again under the -same key *after* flush_all has been executed). flush_all doesn't +"flush_all" is a command with an optional numeric argument. It always +succeeds, and the server sends "OK\r\n" in response. Its effect is to +invalidate all existing items immediately (by default) or after the +expiration specified. After invalidation none of the items will be returned +in response to a retrieval command (unless it's stored again under the +same key *after* flush_all has invalidated the items). flush_all doesn't actually free all the memory taken up by existing items; that will happen gradually as new items are stored. The most precise definition of what flush_all does is the following: it causes all items whose -update time is earlier than the time at which flush_all was executed -to be ignored for retrieval purposes. +update time is earlier than the time at which flush_all was set to be +executed to be ignored for retrieval purposes. "version" is a command with no arguments: Cache-Memcached-Managed-0.27/PaxHeader/MANIFEST000644 000765 000024 00000000152 11757416713 021566 xustar00lnationstaff000000 000000 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/MANIFEST000644 000765 000024 00000000711 11757416713 017616 0ustar00lnationstaff000000 000000 MANIFEST CHANGELOG COPYING GNUGPL README TODO Makefile.PL flush_interval.patch lib/Cache/Memcached/Managed.pm lib/Cache/Memcached/Managed/Inactive.pm lib/Cache/Memcached/Managed/Multi.pm t/001basic.t t/002inactive.t t/003multi.t t/004expiration.t t/010fork.t t/020grab.t t/030configs.t testlib META.yml Module meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Cache-Memcached-Managed-0.27/PaxHeader/t000755 000765 000024 00000000210 15000423521 020575 xustar00lnationstaff000000 000000 30 mtime=1744971601.183462682 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/000755 000765 000024 00000000000 15000423521 016705 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/PaxHeader/README000644 000765 000024 00000000335 11757407143 021315 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/README000644 000765 000024 00000001644 11757407143 017350 0ustar00lnationstaff000000 000000 README for Cache::Memcached::Managed Copyright (c) 2005, 2006 BOOKINGS Copyright (c) 2007, 2008 BOOKING.COM Copyright (c) 2012 Elizabeth Mattijsen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Version: 0.24 Required Modules: Cache::Memcached (any) perl Makefile.PL make make test make install Cache-Memcached-Managed-0.27/PaxHeader/TODO000644 000765 000024 00000000335 11016046405 021111 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/TODO000644 000765 000024 00000000400 11016046405 017131 0ustar00lnationstaff000000 000000 Add examples Test for ":unique". Test for ":key" Test for default data server Test for get_group in list context Test all possible expirations Test for non-standard delimiters Test for "memcached_class" parameter to "new" Bring code more up to PBP Cache-Memcached-Managed-0.27/PaxHeader/COPYING000644 000765 000024 00000000335 10257534421 021462 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/COPYING000644 000765 000024 00000001304 10257534421 017506 0ustar00lnationstaff000000 000000 Copyright (C) 2005 BOOKINGS This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Cache-Memcached-Managed-0.27/PaxHeader/META.yml000644 000765 000024 00000000210 15000423521 021655 xustar00lnationstaff000000 000000 30 mtime=1744971601.223855896 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/META.yml000644 000765 000024 00000001127 15000423521 017714 0ustar00lnationstaff000000 000000 --- abstract: 'provide API for managing cached information' author: - 'Elizabeth Mattijsen (liz@dijkmat.nl)' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Cache-Memcached-Managed no_index: directory: - t - inc requires: Cache::Memcached: '0' version: '0.27' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Cache-Memcached-Managed-0.27/PaxHeader/testlib000644 000765 000024 00000000335 10700745751 022023 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/testlib000644 000765 000024 00000004107 10700745751 020053 0ustar00lnationstaff000000 000000 # make sure we can do sockets use IO::Socket (); use Data::Dumper (); # some local lexicals my $text; # satisy -require- 1; #------------------------------------------------------------------------ # anyport # # Return a free port for listening # # IN: 1 servername or IP address (defaults to "localhost") # OUT: 1 random port number sub anyport { # attempt to obtain a port to work on my $port = ''; if (my $socket = IO::Socket::INET->new( Listen => 5, LocalAddr => (shift || '127.0.0.1'), ) ) { $port = $socket->sockport; } # make sure the system's freed up the port sleep 1; return $port; } #anyport #------------------------------------------------------------------------ # ft # # Helper sub for doing tests inside a forked process # # a. called without parameter in void context: initialize # b. called with parameter in void context: add test result + comment # c. called without parameter in scalar context: return result sub ft { # completed, return what we got if ( defined wantarray ) { return $text; } # we're getting a test, return its result elsif (@_) { $text .= ($_[0] || '')."#$_[1]\n"; } # we're initializing else { $text = ''; } } #ft #------------------------------------------------------------------------ # pft # # Process forked test results # # IN: 1 filename to read sub pft { # open the file open my $handle, $_[0] or die "Could not open '$_[0]': $!"; # process all lines chomp,&ok( split "#" ) while <$handle>; # deny all further knowledge close $handle; unlink $_[0]; } #pft #------------------------------------------------------------------------ # slurp # # Slurp the contents of a file # # IN: 1 filename # OUT: 1 contents of file sub slurp { open my $handle,$_[0]; local $/; <$handle> } #slurp #------------------------------------------------------------------------ # splat # # Splat contents to a file # # IN: 1 filename # 2 contents of file sub splat { open my $handle,">$_[0]"; print $handle $_[1] } #splat Cache-Memcached-Managed-0.27/PaxHeader/lib000755 000765 000024 00000000210 15000423521 021100 xustar00lnationstaff000000 000000 30 mtime=1744971601.179874602 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/000755 000765 000024 00000000000 15000423521 017210 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/PaxHeader/Makefile.PL000644 000765 000024 00000000335 11757407141 022405 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/Makefile.PL000644 000765 000024 00000001513 11757407141 020433 0ustar00lnationstaff000000 000000 use strict; BEGIN { if ( open MEMCACHED, "memcached -i |" ) { chomp( my $header = ); print "Using $header for tests!\n"; } else { print "No executable memcached found: $!\n"; print "Many tests will fail, install can only be forced!\n"; } } use ExtUtils::MakeMaker; eval "use Devel::Required"; eval "use Devel::Required pod => [ qw( lib/Cache/Managed.pm lib/Cache/Managed/Inactive.pm lib/Cache/Managed/Multi.pm ) ]"; WriteMakefile ( NAME => "Cache::Memcached::Managed", AUTHOR => 'Elizabeth Mattijsen (liz@dijkmat.nl)', ABSTRACT => 'provide API for managing cached information', VERSION_FROM => 'lib/Cache/Memcached/Managed.pm', LICENSE => 'gpl', PREREQ_PM => { qw( Cache::Memcached 0 ) }, ); Cache-Memcached-Managed-0.27/PaxHeader/GNUGPL000644 000765 000024 00000000335 10050423644 021341 xustar00lnationstaff000000 000000 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjU5MTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022591;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/GNUGPL000644 000765 000024 00000043131 10050423644 017371 0ustar00lnationstaff000000 000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. Cache-Memcached-Managed-0.27/PaxHeader/META.json000644 000765 000024 00000000210 15000423521 022025 xustar00lnationstaff000000 000000 30 mtime=1744971601.283585511 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/META.json000644 000765 000024 00000001713 15000423521 020065 0ustar00lnationstaff000000 000000 { "abstract" : "provide API for managing cached information", "author" : [ "Elizabeth Mattijsen (liz@dijkmat.nl)" ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Cache-Memcached-Managed", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Cache::Memcached" : "0" } } }, "release_status" : "stable", "version" : "0.27", "x_serialization_backend" : "JSON::PP version 4.16" } Cache-Memcached-Managed-0.27/lib/PaxHeader/Cache000755 000765 000024 00000000210 15000423521 022103 xustar00lnationstaff000000 000000 30 mtime=1744971601.179918602 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/000755 000765 000024 00000000000 15000423521 020213 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/lib/Cache/PaxHeader/Memcached000755 000765 000024 00000000210 15000423521 023751 xustar00lnationstaff000000 000000 30 mtime=1744971601.182037266 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/Memcached/000755 000765 000024 00000000000 15000423521 022061 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/lib/Cache/Memcached/PaxHeader/Managed000755 000765 000024 00000000210 15000423521 025305 xustar00lnationstaff000000 000000 30 mtime=1744971601.180452643 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed/000755 000765 000024 00000000000 15000423521 023415 5ustar00lnationstaff000000 000000 Cache-Memcached-Managed-0.27/lib/Cache/Memcached/PaxHeader/Managed.pm000644 000765 000024 00000000373 15000423475 025737 xustar00lnationstaff000000 000000 30 mtime=1744971581.653741171 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjczZDs7 53 SCHILY.xattr.com.apple.quarantine=0281;6802273d;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed.pm000644 000765 000024 00000251271 15000423475 023773 0ustar00lnationstaff000000 000000 package Cache::Memcached::Managed; # Make sure we have version info for this module $VERSION= '0.27'; # Make sure we're as strict as possible # With as much feedback that we can get use strict; use warnings; # Use the external modules that we need use Scalar::Util qw(blessed reftype); # Initialize default expiration # Initialize the hash with specific expirations # Initialize the delimiter to be used # Initialize the counter for unique ID's # Initialize the seconds to wait after a delete of a directory key is done # Initialize the default timeout for ping # Initialize number of values to be fetched from memcached at a time # Initialize the server we're running on my $expiration = '1D'; my %expiration; my $default_del = '#'; my $unique = 0; my $deadtime = 0; my $pingtime = 10; my $atatime = 256; my $server = eval { `uname -n` } || 'unknown'; chomp $server; my $_oneline; # At compile time # Create simple accessors BEGIN { eval "sub $_ { shift->{'$_'} }" foreach qw( data delimiter directory expiration flush_interval namespace ); } #BEGIN # Satisfy -require- 1; #--------------------------------------------------------------------------- # # Class methods # #--------------------------------------------------------------------------- # new # # Return instantiated object # # IN: 1 class # 2..N hash with parameters # OUT: 1 instantiated object sub new { my $class = shift; my %self = @_ < 2 ? (data => (shift || '127.0.0.1:11211')) : @_; # want to force an inactive object if (delete $self{'inactive'}) { require Cache::Memcached::Managed::Inactive; return Cache::Memcached::Managed::Inactive->new; } # set defaults $self{expiration} = $expiration if !$self{expiration}; $self{delimiter} = $default_del if !length( $self{delimiter} || '' ); $self{namespace} = $> if !defined $self{namespace}; # set group names $self{group_names} = [ $self{group_names} ? sort @{ $self{group_names} } : 'group' ]; $self{_group_names} = { map { $_ => undef } @{ $self{group_names} } }; # obtain client class my $memcached_class = $self{memcached_class} ||= 'Cache::Memcached'; die $@ if !eval "require $memcached_class; 1"; # check both backends my @all_servers; BACKEND: foreach ( qw( data directory ) ) { # nothing to do my $spec = $self{$_}; next BACKEND if !$spec; # giving an existing object if ( blessed $spec ) { # unfortunately, there does not seem to be an API for this if ( my $servers = $spec->{servers} ) { push @all_servers, @{$servers}; next BACKEND; } } # assume a single server spec my $parameters; my $type = reftype $spec; if ( !$type ) { my @servers = split ',', $spec; push @all_servers, @servers; $parameters = { servers => \@servers }; } # list ref of servers elsif ( $type eq 'ARRAY' ) { push @all_servers, @{$spec}; $parameters = { servers => $spec }; } # ready made parameter hash elsif ( $type eq 'HASH' ) { $parameters = $spec; # attempt to find server spec in there $spec = $parameters->{servers}; $type = reftype $spec; # also need to fixup config if ( !$type ) { my @servers = split ',', $spec; push @all_servers, @servers; $parameters->{servers} = \@servers; } # regular array spec already elsif ( $type eq 'ARRAY' ) { push @all_servers, @{$spec}; } # huh? else { undef $parameters; } } # huh? die "Don't know how to handle '$spec' as server specification" if !$parameters; # create the object for the backend $self{$_} = $memcached_class->new($parameters); } # huh? die "No valid data server specification found" if !blessed $self{data}; # set directory server as data server if there was no data server $self{directory} = $self{data} if !blessed $self{directory}; # remember the pid for fork checking $self{_last_pid} = $$; # set server specification $self{servers} = [ sort @all_servers ]; return bless \%self,$class; } #new #--------------------------------------------------------------------------- # # Instance methods # #--------------------------------------------------------------------------- # add # # Add ID + value, only done if not yet in the cache # # IN: 1 instantiated object # 2 value # 3 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub add { shift->_do( 'add',@_ ) } #add #--------------------------------------------------------------------------- # dead # # Cycles through all of the available memcached servers and checks whether # they are alive or not. Returns all of the memcached servers that seem to # be inactive. # # IN: 1 instantiated object # 2 timeout to apply (default: 10 seconds) # OUT: 1..N memcached servers that did not reply (in time) # # or: # # OUT: 1 hash ref with dead servers sub dead { # Obtain the class # Obtain the timeout # Create key to be used # Create value to be used my $self = shift; my $timeout = shift || $pingtime; my $key = $self->_unique_key; my $value = time; # Initialize list of problem servers # For all of the servers to be checked (in alphabetical order) # Create new memcached server object for this server only # Obtain value from which # Makes sure alarm() will do a die() # Set the alarm # Set the value in the server # Attempt to get it back # Delete the key # Return the value obtained my @dead; foreach ($self->servers) { my $server = $self->{memcached_class}->new( {servers => [$_]} ); my $fetched = eval { local $SIG{ALRM} = sub { die "timed out\n" }; alarm $timeout; $server->set( $key,$value ); my $result = $server->get( $key ); $server->delete( $key ) if $result; $result; } || 0; # Reset the alarm # Mark server as problem if value obtained not equal to value stored alarm 0; push @dead,$_ if $fetched != $value; } # Return list of problem servers (sorted) or as a hash ref return wantarray ? @dead : {map {$_ => undef} @dead}; } #dead #--------------------------------------------------------------------------- # decr # # Decrement an existing ID, only done if not yet in the cache # # IN: 1 instantiated object # 2 value # 3 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub decr { shift->_do( 'decr',@_ ) } #decr #--------------------------------------------------------------------------- # delete # # Delete an existing ID # # IN: 1 instantiated object # 2 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub delete { # Obtain the object # Check the socket my $self = shift; return unless $self->_check_socket; # Obtain the parameters # Perform the delete my %param = @_ == 1 ? (id => shift) : @_; $self->data->delete( $self->_data_key( @param{qw(id key namespace version)} ) ); } #delete #--------------------------------------------------------------------------- # delete_group # # Delete all information about a group, given by group name and ID # # IN: 1 instantiated object # 2..N hash with group names and ID's to delete info of # OUT: 1 number of items deleted sub delete_group { # Obtain the object # Obtain the parameter hash # Obtain local copies of stuff we need fast access to here # Obtain the namespace my $self = shift; my %param = @_; my ($data,$directory) = map {$self->$_} qw(data directory); my ($namespace) = $self->_lexicalize( \%param,qw(namespace) ); # Initialize number of items deleted # Obtain reference to the group names # While there are group name / id pairs to process # Obtain groupname and ID # Make sure group name is fully qualified # Reloop if not a valid group name my $deleted = 0; my $group_names = $self->{'_group_names'}; while (my ($group_name,$group_id) = each %param) { $self->_group_id( $group_name ); die "'$group_name' is not a valid group name" unless exists $group_names->{$group_name}; # Obtain the directory key # Obtain the index keys # Obtain the backend keys for these index keys my $directory_key = $self->_directory_key( $namespace,$group_name,$group_id ); my @index_key = $self->_index_keys( $directory_key ); my @data_key = $self->_data_keys( $directory_key,0,@index_key ); # Delete the lowest index key # Delete the directory key # Delete all of the index keys # Delete all of the backend keys # Add the number of entries deleted $directory->delete( $self->_lowest_index_key($directory_key),$deadtime); $directory->delete( $directory_key,$deadtime ); $directory->delete( $_ ) foreach @index_key; $data->delete( $_ ) foreach @data_key; $deleted += @data_key; } # Return the result $deleted; } #delete_group #--------------------------------------------------------------------------- # errors # # Cycles through all of the available memcached servers and returns the # number of errors recorded. # # IN: 1 instantiated object # 2 flag: reset error counters # OUT: 1 reference to hash with number of errors for each server sub errors { # Obtain the parameters # Return with error counters if we don't want to reset the error counters my ($self,$reset) = @_; return $self->directory->get_multi( $self->servers ) unless $reset; # Obtain the directory backend # Obtain the error counters # Delete all the error counters that were returned # Return the hash ref with errors my $directory = $self->directory; my $errors = $directory->get_multi( $self->servers ); $directory->delete( $_ ) foreach keys %{$errors}; $errors; } #errors #--------------------------------------------------------------------------- # flush_all # # Flush the contents of all servers (without rebooting them) # # IN: 1 instantiated object # 2 number of seconds between flushes (default: flush_interval) # OUT: 1 number of servers successfully flushed sub flush_all { # Obtain the object # Obtain the data server # Obtain the servers my ($self,$interval) = @_; my $data = $self->data; my @server = $self->servers; # Use default interval if none specified # Initialize number of servers flushed # Initialize amount of time to wait $interval = $self->flush_interval unless defined $interval; my $flushed = 0; my $time = 0; # For all of the servers minus the directory server # Create the action # Increment flushed if flush was successful # Increment time if we need to foreach (0..$#server) { my $action = $interval ? "flush_all $time" : "flush_all"; $flushed++ if $self->_oneline( $data,$action,$_,"OK" ); $time += $interval if $interval; } # Return whether all servers successfully flushed $flushed = @server; } #flush_all #--------------------------------------------------------------------------- # get # # Get a single value from the cache # # IN: 1 instantiated object # 2 id # OUT: 1 value if found or undef # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 value if found or undef sub get { # Obtain the object # Check the socket my $self = shift; return unless $self->_check_socket; # Obtain the parameters # Perform the actual getting of the value my %param = @_ == 1 ? (id => shift) : @_; my $data_key = $self->_data_key( @param{qw(id key namespace version)} ); $self->data->get( $data_key ); } #get #--------------------------------------------------------------------------- # get_group # # Return the contents of the group, optionally deleting it # # IN: 1 instantiated object # 2..N parameter hash (group / delete / namespace) # OUT: 1 hash reference with result # # The structure of the hash is: # # $result # |--- key # |-- version # |-- id # |-- value sub get_group { # Obtain the object # Obtain the parameters # Obtain local copies of stuff we need my $self = shift; my %param = @_; my ($data,$delimiter,$directory) = map {$self->$_} qw(data delimiter directory); # Obtain delete flag # Obtain namespace to be used my $delete = delete $param{'delete'}; my ($namespace) = $self->_lexicalize( \%param,qw(namespace) ); # Quit now if more than 1 group specified # Obtain group name and ID # Make sure groupname is fully qualified # Die now if not a valid group die "Can only fetch one group at a time" if keys %param > 1; my ($group_name,$group_id) = each %param; $self->_group_id( $group_name,!!$delete ); die "'$group_name' is not a valid group name" unless exists $self->{'_group_names'}->{$group_name}; # Obtain the directory key # Obtain the index keys # Obtain the data keys for these index keys my $directory_key = $self->_directory_key( $namespace,$group_name,$group_id ); my @index_key = $self->_index_keys( $directory_key ); my @data_key = $self->_data_keys( $directory_key,$delete,@index_key ); # If we're deleting # Delete the lowest index key # Delete the directory key # Delete all of the index keys if ($delete) { $directory->delete( $self->_lowest_index_key($directory_key),$deadtime); $directory->delete( $directory_key,$deadtime ); $directory->delete( $_ ) foreach @index_key; } # Initialize result hash # Obtain all of the data in one fell swoop # For all of the backend keys for this group # Split out uid, version, key and ID # Remove the entry from the cache if deleting # Move the value out of the gotten hash into the result hash if right namespace my %result; while (my @todo = splice @data_key,0,$atatime) { my $gotten = $data->get_multi( @todo ); foreach my $data_key (keys %{$gotten}) { my (undef,$version,$key,$id) = split $delimiter,$data_key,4; $data->delete( $data_key ) if $delete; $result{$key}->{$version}->{$id||''} = delete $gotten->{$data_key}; } } # Return the result as a hash ref if in scalar context # Return only the values if in list context return \%result unless wantarray; map {values %{$_}} map {values %{$_}} values %result; } #get_group #--------------------------------------------------------------------------- # get_multi # # Get a multiple values from the cache, sharing the same key, version and # namespace # # IN: 1 instantiated object # 2 reference to list of ID's # OUT: 1 hash ref of ID's and values found # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 hash ref of ID's and values found sub get_multi { # Obtain the object # Check the socket my $self = shift; return {} unless $self->_check_socket; # Obtain the parameters # Obtain the key # Obtain the version # Obtain the namespace my %param = @_ == 1 ? (id => shift) : @_; my $key = $self->_create_key( $param{'key'} ); my $version = $param{'version'}; my ($namespace) = $self->_lexicalize( \%param,qw(namespace) ); # Obtain the data keys # Create result hash my @data_key = map {$self->_data_key( $_,$key,$namespace,$version )} @{$param{'id'}}; my %result; # Obtain the data server backend # Make sure we use the right delimiter # While we have a batch of data to fetch # Perform the actual getting of the values # For all of the values obtained this time # Move the value to the result hash with just the ID as the key my $data = $self->data; my $delimiter = $self->delimiter; while (my @todo = splice @data_key,0,$atatime) { my $hash = $data->get_multi( @todo ); foreach (keys %{$hash}) { $result{(split $delimiter,$_,4)[3]} = delete $hash->{$_}; } } # Return the reference to the resulting hash \%result; } #get_multi #--------------------------------------------------------------------------- # grab_group # # IN: 1 instantiated object # 2..N parameter hash (group / namespace) # OUT: 1 hash reference with result # # The structure of the hash is: # # $result # |--- key # |-- version # |-- id # |-- value sub grab_group { shift->get_group( delete => 1,@_ ) } #grab_group #--------------------------------------------------------------------------- # group # # Return the ID's of a group, ordered by key. # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 hash reference with result # # The structure of the hash is: # # $result # |--- key # |--- [id1,id2..idN] sub group { # Obtain the parameters # Check the socket my $self = shift; return {} unless $self->_check_socket; # Obtain the parameter hash # Obtain the namespace # Quit now if more than one group specified my %param = @_; my ($namespace) = $self->_lexicalize( \%param,qw(namespace) ); die "Can only fetch one group at a time" if keys %param > 1; # Obtain the group name and group ID # Make sure group name is fully qualified # Return now if not a valid group my ($group_name,$group_id) = each %param; $self->_group_id( $group_name ); return {} unless exists $self->{'_group_names'}->{$group_name}; # Initialize result hash # Make sure we use the right delimiter # For all of the backend keys for this group # Split out the parts # Save the ID in the list for the key my %result; my $delimiter = $self->delimiter; foreach ($self->_data_keys( $self->_directory_key( $namespace,$group_name,$group_id ) )) { my ($key,$id) = (split $delimiter)[2,3]; push @{$result{$key}},$id; } # Make sure the ID's are listed in order # Return the result $_ = [sort @$_] foreach values %result; \%result; } #group #--------------------------------------------------------------------------- # group_names # # Return the specifications of all groups defined in alphabetical order in # list context, or as a hash ref in scalar context # # IN: 1 instantiated object # OUT: 1..N group names specifications in alphabetical order # # or: # # OUT: 1 hash ref with group names sub group_names { # Obtain the object # Return the group names sorted or as a hash ref my $self = shift; return wantarray ? @{$self->{'group_names'}} : $self->{'_group_names'}; } #group_names #--------------------------------------------------------------------------- # inactive # # IN: 1 instantiated object # OUT: 1 false sub inactive { undef } #inactive #--------------------------------------------------------------------------- # incr # # Decrement an existing ID # # IN: 1 instantiated object # 2 value # 3 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub incr { shift->_do( 'incr',@_ ) } #incr #--------------------------------------------------------------------------- # replace # # Replace an existing ID # # IN: 1 instantiated object # 2 value # 3 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub replace { shift->_do( 'replace',@_ ) } #replace #--------------------------------------------------------------------------- # reset # # Reset the client side of the cache system # # IN: 1 instantiated object # OUT: 1 returns true sub reset { my $self = shift; # obtain local copy of data and directory object my ( $data, $directory ) = ( $self->data, $self->directory ); # all of the Cache::Memcached objects we need to handle foreach ( $data == $directory ? ($data) : ( $data, $directory ) ) { # disconnect all sockets $_->disconnect_all if $_->can('disconnect_all');; # kickstart connection logic $_->forget_dead_hosts if $_->can('forget_dead_hosts'); } # make sure we try to connect again $self->_mark_connected; # set last pid used flag $self->{'_last_pid'} = $$; return 1; } #reset #--------------------------------------------------------------------------- # set # # Set an ID, create if doesn't exist yet # # IN: 1 instantiated object # 2 value # 3 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2..N parameter hash # OUT: 1 true if successful sub set { shift->_do( 'set',@_ ) } #set #--------------------------------------------------------------------------- # servers # # Return the specifications of all memcached servers being used in # alphabetical order in list context, or as a hash ref in scalar context # # IN: 1 instantiated object # OUT: 1..N server specifications in alphabetical order # # or: # # OUT: 1 hash ref with server configs sub servers { return wantarray ? @{ shift->{servers} } : { map { $_ => undef } @{ shift->{servers} } }; } #servers #--------------------------------------------------------------------------- # start # # Start the indicated memcached backend servers # # IN: 1 instantiated object # 2..N config of memcached servers to start (default: all) # OUT: 1 whether all indicated memcached servers started sub start { # Obtain the object # Obtain the servers to start my $self = shift; @_ = $self->servers unless @_; # Initialize started counter # For all of the servers to start # Obtain IP and port # Increment counter if start was successful my $started = 0; foreach (@_) { my ($ip,$port) = split ':'; $started++ unless system 'memcached', '-d','-u',(scalar getpwuid $>),'-l',$ip,'-p',$port; } # Return whether all servers started $started == @_; } #start #--------------------------------------------------------------------------- # stats # # Return a hash ref with simple statistics for each server # # IN: 1 instantiated object # 2..N config specifications of servers (default: all) # OUT: 1 hash reference # # $stats # |-- server # |-- key # |-- value sub stats { # Obtain the object # Return now if no active servers anymore my $self = shift; return {} unless $self->_check_socket; # Create hash with configs to be done # Initialize the result ref # For all of the objects that we have # For all of the servers we want to do this # Reloop if not to be done # Obtain STATS info my %todo = @_ ? map {$_ => undef} @_ : %{$self->servers}; my %result; foreach my $cache ($self->data,$self->directory) { foreach my $host ( $self->servers ) { next unless exists $todo{$host} and not exists $result{$host}; $result{$host} = { map {s#^STAT ##; split m#\s+#} $self->_morelines( $cache,$host,"stats" ) }; } } # Return the result hash as a ref \%result; } #stats #--------------------------------------------------------------------------- # stop # # Stop the indicated memcached backend servers # # IN: 1 instantiated object # 2..N config of memcached servers to stop (default: all) # OUT: 1 whether all indicated memcached servers stopped sub stop { # Obtain the object # Obtain the pid's to kill # Return whether all were killed my $self = shift; my @pid = map {$_->{'pid'}} grep {$_->{'pid'}} values %{$self->stats( @_ )}; @pid == kill 15,@pid; } #stop #--------------------------------------------------------------------------- # version # # Return version information of running memcached backend servers # # IN: 1 instantiated object # 2..N config of memcached servers to obtain version of (default: all) # OUT: 1 hash ref with version info, keyed to config sub version { # Obtain the object # Obtain the basic info to work with # Normalize to version information my $self = shift; my $stats = $self->stats( @_ ); $_ = $_->{'version'} foreach values %{$stats}; # Return the resulting hash reference $stats; } #version #--------------------------------------------------------------------------- # # Internal methods # #--------------------------------------------------------------------------- # _data_key # # Expand the given id # # IN: 1 instantiated object # 2 id to expand (default: none) # 3 key to use (default: caller sub) # 4 namespace to use (default: object->namespace) # 5 version to use (default: key's $package::VERSION) # 6 number of levels to go back in caller stack (default: 2 ) # OUT: 1 expanded key sub _data_key { # Obtain the parameters # Obtain key # Obtain the delimiter my ($self,$id,$key,$namespace,$version,$levels) = @_; $key = $self->_create_key( $key,($levels ||= 2) + 1 ); my $delimiter = $self->delimiter; # If we don't have a version yet # Allow for non strict references # Adapt the version information # Make sure we have a namespace # Prefix the version information unless ($version) { no strict 'refs'; $version = ($key =~ m#^(.*)::# ? ${$1.'::VERSION'} : '') || ($key =~ m#^/# ? $main::VERSION : '') || $Cache::Memcached::Managed::VERSION; } $namespace = $self->namespace unless defined $namespace; $key = $namespace.$delimiter.$version.$delimiter.$key; # If some type of ref was specified for the ID # If it was a list ref # Join the elements # Elseif it was a hash ref # Join the sorted key/value pairs # Elseif it was a scalar ref # Just deref it if (my $type = ref $id) { if ($type eq 'ARRAY') { $id = join $delimiter,@{$id}; } elsif ($type eq 'HASH') { $id = join $delimiter,map {$_ => $id->{$_}} sort keys %{$id}; } elsif ($type eq 'SCALAR') { $id = $$id; # Else (unexpected type of ref) # Let the world know we didn't expect this } else { die "Don't know how to handle key of type '$type': $id"; } } # Expand the ID as appropriate and return the result $self->{'_data_key'} = $key.(defined $id and length $id ? $delimiter.$id : ''); } #_data_key #--------------------------------------------------------------------------- # _data_keys # # Return the backend keys for a given directory_key # # IN: 1 instantiated object (ignored) # 2 directory key # 3 flag: don't perform cleanup (default: perform cleanup) # 4..N index keys, highest first (default: _index_keys) # OUT: 1..N unordered list with backend keys sub _data_keys { # Obtain the main parameters # Make sure we have index keys # Obtain backend keys my ($self,$directory_key,$nocleanup) = splice @_,0,3; @_ = $self->_index_keys( $directory_key ) unless @_; # Obtain shortcut to the directory backend # Initialize lowest index number found # Initialize list of index keys with duplicate backend keys found # Initialize backend key hash my $directory = $self->directory; my $lowest = 1; my @double; my %data_key; # While there are data keys to be fetched # If successful in obtaining next slice of the backend keys # Copy them into the final result hash # If we don't want to cleanup # Just put all of the values as keys while (@_) { if (my $result = $directory->get_multi( splice @_,0,$atatime )) { if ($nocleanup) { $data_key{$_} = undef foreach values %{$result}; #************************************************************************** # Note that we're using the side effect of Perl taking the digits at the # start of a string as the numerical value: this allows us to quickly # check the index number of the index keys, and to calculate the lowest # possible free index number that should be checked later. That's why # we're switching off warnings for this section here. #************************************************************************** # Else (we want to cleanup) # For all of the index keys obtained # If this backend key was already found # Mark this index key as double # Else # Set this backend key # Save this as the lowest value } else { no warnings; foreach (sort {$b <=> $a} keys %{$result}) { my $data_key = $result->{$_}; if (exists $data_key{$data_key}) { push @double,$_; } else { $data_key{$data_key} = undef; $lowest = $_; } } } # Else (failed,the directory backend has died: this is REALLY bad) # Invalidate all backend servers # Invalidate all cache access for this process from now on # Return emptyhanded } else { $self->flush_all( $self->flush_interval ); $self->_mark_disconnected; return; } } # If we want to cleanup # Remove the index keys that we don't need anymore # Make sure we're silent about numifying the lowest key # Set the lowest index to be checked later if lowest was found unless ($nocleanup) { $directory->delete( $_ ) foreach @double; no warnings; $directory->set( $self->_lowest_index_key( $directory_key ),0+$lowest ); } # Return the result keys %data_key; } #_data_keys #--------------------------------------------------------------------------- # _check_socket # # Check whether the socket has been used in this process, disconnect if not # yet used in this process # # IN: 1 class or object (ignored) # OUT: 1 whether successful sub _check_socket { # Quickest way out in the most common case return 1 if $$ == $_[0]->{'_last_pid'} and !exists $_[0]->{'_disconnected'}; # Obtain the object # Return result of reset if we're in a different process now my $self = shift; return $self->reset if $$ != $self->{'_last_pid'}; # Mark object as connected if waited long enough # Return (possibly changed) status $self->_mark_connected if $self->{'_disconnected'} and time > $self->{'_disconnected'}; return !$self->{'_disconnected'}; } #_check_socket; #--------------------------------------------------------------------------- # _create_key # # Expand the given key # # IN: 1 instantiated object # 2 key to expand (default: caller sub) # 3 number of levels to go back in caller stack (default: 2 ) # OUT: 1 fully qualified key sub _create_key { # Obtain the parameters # Return now if we already have a fully qualified key my ($self,$key,$levels) = @_; return $key if $key and ($key =~ m#.+::# or $key =~ m#^/#); # Set levels if not set yet # Obtain caller info $levels ||= 2; my $caller = (caller($levels))[3] || ($0 =~ m#^/# ? $0 : do {my $pwd = `pwd`; chomp $pwd; $pwd}."/$0"); # Set the default key if no key specified yet # If we have a package relative key, removing prefix on the fly # Remove caller info's relative part # Prefix the caller info # Return the resulting key $key ||= $caller; if ($key =~ s#^::##) { $caller =~ s#[^:]+$##; $key = $caller.$key; } $key; } #_create_key #--------------------------------------------------------------------------- # _directory_key # # Return the directory key for a given group name, ID and namespace # # IN: 1 instantiated object # 2 namespace # 3 group name # 4 ID sub _directory_key { # Obtain the delimiter (lose the object on the fly # Create key and return that my $delimiter = shift->delimiter; __PACKAGE__.$delimiter.(join $delimiter,@_); } #_directory_key #--------------------------------------------------------------------------- # _do # # Perform one of the basic cache actions # # IN: 1 instantiated object # 2 method name # 3 value # 4 id # OUT: 1 true if successful # # or: # # IN: 1 instantiated object # 2 method name # 3..N parameter hash # OUT: 1 true if successful sub _do { # Obtain object and method # Check the socket my ($self,$method) = splice @_,0,2; return undef unless $self->_check_socket; # Obtain the parameter hash # Create the key, removing key specification on the fly my %param = @_ > 3 ? @_ : ( value => shift, id => shift, expiration => shift ); my $key = $self->_create_key( delete( $param{'key'} ),3 ); # Obtain the ID, removing it on the fly # Set unique ID if so requested # Obtain the value, removing it on the fly # Make sure there is a valid value for increment and decrement # Obtain the lexicals for parameters # Convert the expiration to seconds my $id = delete $param{'id'}; $id = $self->_unique_key if $id and $id eq ':unique'; my $value = delete $param{'value'}; $value = 1 if !defined $value and $method =~ m#(?:decr|incr)$#; my ($expiration,$namespace) = $self->_lexicalize( \%param,qw(expiration namespace) ); $expiration = $self->_expiration2seconds( $expiration ); # Obtain the data server # Obtain the data key, remove version from parameter hash on the fly # Perform the named method my $data = $self->data; my $data_key = $self->_data_key( $id,$key,$namespace,delete $param{'version'} ); my $result = $data->$method( $data_key,$value,$expiration ); # If action was successful # Return now if replace, decr or incr (assume always same groups) # Elseif we're trying to increment (and action failed) # Add an entry with the indicated value or 1 # Elsif we're not doing a set (so: add|decr|replace and failed) # Just return with whatever we got if ($result) { return $result if $method =~ m#^(?:decr|incr|replace)$#; } elsif ($method eq 'incr') { $result = $data->add( $data_key,$value || 1,$expiration); } elsif ($method ne 'set') { return $result; } # still don't have a good result my $directory = $self->directory; if ( !$result ) { # can get the bucket if ( $data->can('get_sock') ) { if ( my $bucket = $data->get_sock($data_key) ) { # can lose prefix, increment error on server if ( $bucket =~ s#^Sock_## ) { $directory->add( $bucket, 1 ) if !$directory->incr($bucket); } } } # block all access for this process $self->{'_disconnected'} = 1; # return indicating error return undef; } # Obtain hash ref to valid group names # For all group name links to be set (remaining pairs in parameter hash) # Normalize group ID if necessary # Obtain directory key # Obtain an index my $group_names = $self->{'_group_names'}; while (my ($group_name,$group_id) = each %param) { $group_id =~ s#^:key#$key#; my $directory_key = $self->_directory_key( $namespace,$group_name,$group_id ); my $index = $directory->incr( $directory_key ); # If we don't have a valid index # If not successful in initializing the directory key # Block all access for this process # Return indicating error unless (defined $index) { unless (defined $directory->add( $directory_key,$index = 1 )) { $self->{'_disconnected'} = 1; return undef; } } # If not successful in storing the data key # Block all access for this process # Return indicating error unless ($directory->set( $self->_index_key( $directory_key,$index ),$data_key,$expiration )) { $self->{'_disconnected'} = 1; return undef; } } # Return the original result $result; } #_do #--------------------------------------------------------------------------- # _expiration2seconds # # Convert given expiration to number of seconds # # IN: 1 instantiated object (ignored) # 2 expiration # OUT: 1 number of seconds sub _expiration2seconds { # Obtain the initial expiration # Return now if nothing to check # Return now if invalid characters found my $expiration = $_[1]; return if !defined $expiration; return if $expiration !~ m#^[sSmMhHdDwW\d]+$#; # Just a second specification return $expiration if $expiration !~ m#\D#; # Convert seconds into seconds # Convert minutes into seconds # Convert hours into seconds # Convert days into seconds # Convert weeks into seconds my $seconds = 0; $seconds += $1 if $expiration =~ m#(\d+)[sS]#; $seconds += (60 * $1) if $expiration =~ m#(\d+)[mM]#; $seconds += (3600 * $1) if $expiration =~ m#(\+?\d+)[hH]#; $seconds += (86400 * $1) if $expiration =~ m#(\+?\d+)[dD]#; $seconds += (604800 * $1) if $expiration =~ m#(\+?\d+)[wW]#; # Return the resulting sum $seconds; } #_expiration2seconds #--------------------------------------------------------------------------- # _index_key # # Return the index key for a given directory_key and ordinal number # # IN: 1 instantiated object (ignored) # 2 directory key # 3 ordinal number # OUT: 1 index key sub _index_key { $_[2].$_[0]->delimiter.$_[1] } #_index_key #--------------------------------------------------------------------------- # _index_keys # # Return the index keys for a given directory_key # # IN: 1 instantiated object (ignored) # 2 directory key # OUT: 1 list with index keys (highest first) sub _index_keys { # Obtain the parameters # Return emtyhanded if no index keys available my ($self,$directory_key) = @_; return unless my $found = $self->directory->get( $directory_key ); # Obtain the lowest possible index # Create the index keys and return them my $lowest = $self->directory->get( $self->_lowest_index_key( $directory_key ) ) || 1; reverse map {$self->_index_key( $directory_key,$_ )} $lowest..$found; } #_index_keys #--------------------------------------------------------------------------- # _group_id # # Fully qualify a group name if relative name indicated # # IN: 1 instantiated object (ignored) # 2 group name to check (directly updated, must be left value) # 3 number of extra levels to go up sub _group_id { # Prefix package name of relative group name indicated $_[1] = (caller(1 + ($_[2] || 0)))[0].$_[1] if $_[1] =~ m#^::#; } #_group_id #--------------------------------------------------------------------------- # _lexicalize # # Return values associated with the given method names, allowing for # overrides from a parameter hash. Removes these values from the parameter # hash. # # IN: 1 instantiated object # 2 reference to parameter hash # 3..N method names to check # OUT: 1..N values associated with method names sub _lexicalize { # Obtain object and parameter hash # Create temporary value holder # Map the method names to the appropriate value my ($self,$param) = splice @_,0,2; my $v; map {$v = delete $param->{$_}; defined $v ? $v : $self->$_ } @_; } #_lexicalize #--------------------------------------------------------------------------- # _lowest_index_key # # Return the index key for the lowest possible index # # IN: 1 instantiated object (ignored) # 2 directory key # OUT: 1 index key of lowest index sub _lowest_index_key { $_[1].$_[0]->delimiter.'_lowest' } #_lowest_index_key #--------------------------------------------------------------------------- # _mark_connected # # Mark the object as connected # # IN: 1 instantiated object sub _mark_connected { delete $_[0]->{'_disconnected'} } #_mark_connected #--------------------------------------------------------------------------- # _mark_disconnected # # Mark the object as disconnected: all actions will fail for a random # amount of time. # # IN: 1 instantiated object # 2 amount of time to mark as disconnected (default: 20..30) sub _mark_disconnected { # Mark the object as disconnected $_[0]->{'_disconnected'} = time + ($_[1] || 20 + int rand 10) } #_mark_disconnected #--------------------------------------------------------------------------- # _morelines # # Handle non-API request that returns multiple lines # # IN: 1 instantiated object (ignored) # 2 Cache::Memcached object # 3 host to send to # 4 line to send (no newline, default: just return next response) # 5 bucket (default: 0) # OUT: 1..N response lines sub _morelines { my ( $self, $cache, $host, $send, $bucket ) = @_; # don't have any sock to host mapping, so quit return if !$cache->can('sock_to_host'); # couldn't get a socket for given host return unless my $socket = $cache->sock_to_host($host); return map { s#[\r\n]+$##; m#^(?:END|ERROR)# ? () : ($_) } $cache->run_command( $socket, $send. "\r\n" ); } #_morelines #--------------------------------------------------------------------------- # _oneline # # IN: 1 instantiated object (ignored) # 2 Cache::Memcached object # 3 line to send (no newline, default: just return next response) # 4 bucket (default: 0) # 5 response string to check with (no newline, default: return response) # OUT: 1 response or whether expected response returned sub _oneline { my ( $self, $cache, $send, $bucket, $expect ) = @_; # can't get any socket, so quit return if !$cache->can('get_sock'); # couldn't get a socket for the indicated bucket return unless my $socket = $cache->get_sock( [$bucket || 0,0] ); # make sure we can call a "_oneline" compatible method $_oneline ||= $cache->can( '_oneline' ) || $cache->can( '_write_and_read' ) or die "Unsupported version of " . ( blessed $cache ) . "\n"; # obtain response my $response = defined $send ? $_oneline->( $cache, $socket, $send . "\r\n" ) : $_oneline->( $cache, $socket ); # nothing to check against, just give back what we got return $response if !defined $expect; return ( $response and $expect."\r\n" eq $response ); } #_oneline #--------------------------------------------------------------------------- # _unique_key # # Return a unique key # # IN: 1 class or object (ignored) # OUT: 1 guaranteed unique key sub _unique_key { # Create unique key and return that join $_[0]->delimiter,$server,$$,time,++$unique; } #_unique_key #--------------------------------------------------------------------------- # _spec2servers # # Converts server spec to list ref of servers # # IN: 1 server spec # 2 recursing flag (only used internally) # OUT: 1 list ref of servers sub _spec2servers { my ( $spec, $recursing ) = @_; # assume scalar definition if not a ref my $type = reftype $spec; if ( !defined $type ) { return [ split ',', $spec ]; } # list ref of servers elsif ( $type eq 'ARRAY' ) { return $spec; } # huh? die "Don't know how to handle '$spec' as server specification"; } #_spec2servers #--------------------------------------------------------------------------- __END__ =head1 NAME Cache::Memcached::Managed - provide API for managing cached information =head1 SYNOPSIS use Cache::Memcached::Managed; my $cache = Cache::Memcached::Managed->new( '127.0.0.1:12345' ); $cache->set( $value ); $cache->set( $value,$id ); $cache->set( value => $value, id => $id, key => $key, version => "1.1", namespace => 'foo', expiration => '1D', ); my $value = $cache->get( $id ); my $value = $cache->get( id => $id, key => $key ); =head1 VERSION This documentation describes version 0.27. =head1 DIFFERENCES FROM THE Cache::Memcached API The Cache::Memcached::Managed module provides an API to values, cached in one or more memcached servers. Apart from being very similar to the API of L, the Cached::Memcached::Managed API allows for management of groups of values, for simplified key generation and expiration, as well as version and namespace management and a few other goodies. These are the main differences between this module and the L module. =head2 automatic key generation The calling subroutine provides the key (by default). Whenever the "get" and "set" operations occur in the same subroutine, you don't need to think up an identifying key that will have to be unique across the entire cache. =head2 ID refinement An ID can be added to the (automatically) generated key (none is by default), allowing easy identification of similar data objects (e.g. the primary key of a Class::DBI object). If necessary, a unique ID can be created automatically (useful when logging events). =head2 version management The caller's package provides an identifying version (by default), allowing differently formatted data-structures caused by source code changes, to live separately from each other in the cache. =head2 namespace support A namespace identifier allows different realms to co-exist in the same cache (the uid by default). This e.g. allows a group of developers to all use the same cache without interfering with each other. =head2 group management A piece of cached data can be assigned to any number of groups. Cached data can be retrieved and removed by specifying the group to which the data belongs. This can be used to selectively remove cached data that has been invalidated by a database change, or to obtain logged events of which the identification is not known (but the group name is). =head2 easy (default) expiration specification A default expiration per Cache::Memcached::Managed object can be specified. Expirations can be used by using mnemonics D, H, M, S, (e.g. '2D3H' would be 2 days and 3 hours). =head2 automatic fork() detection Sockets are automatically reset in forked processes, no manual reset needed. This allows the module to be used to access cached data during the server start phase in a mod_perl environment. =head2 magical increment Counters are automagically created with L if they don't exist yet. =head2 instant invalidation Support for the new "flush_all" memcached action to invalidate all data in a cache in one fell swoop. =head2 dead memcached server detection An easy way to check whether all related memcached servers are still alive. =head2 starting/stopping memcached servers Easy start / stop of indicated memcached servers, mainly intended for development and testing environments. =head2 extensive test-suite An extensive test-suite is included (which is sadly lacking in the Cache::Memcached distribution). =head1 BASIC PREMISES The basic premise is that each piece of information that is to be cached, can be identified by a L, an optional L, a L and a L. The L determines the basic identification of the value to be cached. The L specifies a refinement on the basic identification. The L ensures that differently formatted values with the same key and ID do not interfere with each other. The L ensures that different realms of information (for instance, for different users) do not interfere with each other. =head2 key The default for the key is the fully qualified subroutine name from which the cached value is accessed. For instance, if a cached value is to be accessed from subroutine "bar" in the Foo package, then the key is "Foo::bar". Explicit keys can be specified and may contain any characters except the L. A special case is applicable if the cache is being accessed from the lowest level in a script. In that case the default key will be created consisted of the server name (as determined by C) and the absolute path of the executing script. =head2 ID If no ID is specified for a piece of information, then just the L will be assumed. The ID can be any string. It can for instance be the primary key of a Class::DBI object. ID's can be specified as a scalar value, or as list ref, or as a hash ref (for instance, for multi-keyed Class::DBI objects). Some examples: my $value = $cache->get( $id ); my $value = $cache->get( [$id,$checkin,$checkout] ); my $value = $cache->get( {id => $id,checkin => $checkin,checkout => $checkout} ); If the ID should be something unique, and you're not interested in the ID per se (for instance, if you're only interested in the L to which the information will be linked), you can specify the string C<:unique> to have a unique ID automatically generated. =head2 version management The version indicates which version (generation) of the data is to be fetched or stored. By default, it takes the value of the C<$VERSION> variable of the package to which the L is associated. This allows new modules that cache information to be easily installed in a server park without having to fear data format changes. A specific version can be specified with each of the L, L, L, L, L, L and L to indicate the link with the group of the information being cached. Please always use a string as the version indicator. Using floating point values may yield unexpected results, where B<1.0> would actually use B<1> as the version. =head2 namespace management The namespace indicates the realm to which the data belongs. By default, the effective user id of the process (as known by $>) is assumed. This allows several users to share the same L<"data server"> and L<"directory server">, while each still having their own set of cached data. A specific namespace can be specified with each of the L, L, L, L, L, L and L to indicate the link with the group of the information being cached. =head2 data server The data server is a Cache::Memcached (compatible) object in which all data (keyed to a L<"data key">) is stored. It uses one or more memcached servers. The data server can be obtained with the L object. =head2 data key The data key identifies a piece of data in the L<"data server">. It is formed by appending the namespace (by default the user id of the process), L, L and L, separated by the L. If a scalar value is specified as an ID, then that value is used. If the ID is specified as a list ref, then the values are concatenated with the L. If the ID is specified as a hash ref, then the sorted key and value pairs are concatenated with the L. =head2 group management The group concept was added to allow easier management of cached information. Since it is impossible to delete cached information from the L<"data server"> by a matching a wildcard key value (because you can only access cached information if you know the exact key), another way was needed to access groups of cached data. Another way that would not need another (database) backend or be dependent on running on a single hardware. This is achieved by using a L<"directory server">, which is basically just another memcached server dedicated to keeping a directory of data kept in the L<"data server">. The group concept allows you to associate a given L<"data key"> to a named group and an group ID value (e.g. the group named "group" and the name of an SQL table). This information is then stored in the L<"directory server">, from which it is possible to obtain a list of L<"data keys"> associated with the group name and the ID value. In the current implementation, the only one group name is recognized by default: =over 2 =item group Intended for generic data without specific keys. =back You can specify your own set of group names with the "group_names" parameter in L. Group names and ID's can be specified with each of the L, L, L, L and L to indicate the link with the group of the information being cached. The pseudo group ID 'C<:key>' can be specified to indicate that the key should be used for the group ID. This is usually used in conjunction with the generic 'C' group name A list of valid group names can be obtained with the L method. =head2 directory server The directory server is a Cache::Memcached (compatible) object that is being used to store L<"data key">s (as opposed to the data itself) used in L<"group management">. If no L server was specified, then the data server will be assumed. If there are multiple memcached servers used for the L<"data server">, then it is advised to use a separate directory server (as a failure in one of the memcached backend servers will leave you with an incomplete directory otherwise). Should the directory server fail, and it is vital that there is no stale data in the data server, then a L would need to be executed to ensure that no stale data remains behind. Of course, this will also delete all non-stale data from the data server, so your mileage may vary. =head2 expiration specification Expiration can be specified in seconds, but, for convenience, can also be specified in days, hours and minutes (and seconds). This is indicated by a number, immediately followed by a letter B (for days) or B (for hours) or B (for minutes) or B (for seconds). For example: 2D3H means 2 days and 3 hours, which means B<183600> seconds. =head2 transparent fork handling Using this module, you do not have to worry if everything will still work after a fork(). As soon as it is detected that the process has forked, new handles will be opened to the memcached servers in the child process (so the meticulous calling of "disconnect_all" of L is no longer needed). Transparent thread handling is still on the todo list. =head1 CLASS METHODS =head2 new my $cache = Cache::Memcached::Managed->new; my $cache = Cache::Memcached::Managed->new( '127.0.0.1:11311' ); my $cache = Cache::Memcached::Managed->new( data => '127.0.0.1:11311', # default: '127.0.0.1:11211' directory => '127.0.0.1:11411', # default: data delimiter => ';', # default: '#' expiration => '1H', # default: '1D' flush_interval => 10, # default: none namespace => 'foo', # default: $> ($EUID) group_names => [qw(foo bar)], # default: ['group'] memcached_class => 'Cached::Memcached::Fast', # default: 'Cache::Memcached' ); my $cache = Cache::Memcached::Managed->new( inactive => 1 ); Create a new Cache::Memcached::Managed object. If there are less than two input parameters, then the input parameter is assumed to be the value of the "data" field, with a default of '127.0.0.1:11211'. If there are more than one input parameter, the parameters are assumed to be a hash with the following fields: =over 2 =item data data => '127.0.0.1:11211,127.0.0.1:11212', data => ['127.0.0.1:11211','127.0.0.1:11212'], data => { servers => ['127.0.0.1:11211','127.0.0.1:11212'], debug => 1, }, data => $memcached, The specification of the memcached server backend(s) for the L<"data server">. It should either be: - string with comma separated memcached server specification - list ref with memcached server specification - hash ref with Cache::Memcached object specification - blessed object adhering to the Cache::Memcached API There is no default for this field, it B be specified. The blessed object can later be obtained with the L method. =item delimiter delimiter => ';', # default: '#' Specify the delimiter to be used in key generation. Should only be specified if you expect L, L, L or L values to contain the character '#'. Can be any character that will not be part of L, L, L or L values. The current delimiter can be obtained with the L method. Using the null byte (I<\\0>) is not advised at this moment, as there are some encoding issues within L regarding null bytes. =item directory directory => '127.0.0.1:11311,127.0.0.1:11312', directory => ['127.0.0.1:11311','127.0.0.1:11312'], directory => { servers => ['127.0.0.1:11311','127.0.0.1:11312'], debug => 1, }, directory => $memcached, The specification of the memcached server backend(s) for the L<"directory server">. It should either be: - string with comma separated memcached server specification - list ref with memcached server specification - hash ref with Cache::Memcached object specification - blessed object adhering to the Cache::Memcached API If this field is not specified, the L<"data server"> object will be assumed. The blessed object can later be obtained with the L method. =item expiration expiration => '1H', # default: '1D' The specification of the default L. The following postfixes can be specified: - S seconds - M minutes - H hours - D days - W weeks The default default expiration is one day ('1D'). The default expiration will be used whenever no expiration has been specified with L, L, L, L or L. The default expiration can be obtained with the L method. =item flush_interval flush_interval => 10, # default: none The specification of the default interval between which memcached servers will be flushed with L. No interval will be used by default if not specified. =item group_names group_names => [qw(foo bar)], # default: ['group'] The specification of allowable group names. Should be specified as a list reference to the allowable group names. Defaults to one element list reference with 'group' only. Any group name can be specified, as long it consists of alphanumeric characters and does not interfere with other functions. Currently disallowed name are: - data - delete - directory - expiration - id - group_names - namespace There is hardly any penalty for using a lot of different group names in itself. However, linking cached information to a lot of different groups B have a penalty. =item inactive inactive => 1, Indicate that the object is inactive. If this is specified, an instantiated object is returned with the same API as Cache::Memcached::Managed, but which will not do anything. Intended to be uses in situations where no active memcached servers can be reached: all code will then function as if there are no cached values in the cache. =item memcached_class memcached_class => 'Cached::Memcached::Fast', By default, this module uses the L class as a C client. Recently, other implementations have been developed, such as L, that are considered to be API compatible. To be able to use these other implementation of the memcached client, you can specify the name of the class to be used. By default, C will be assumed: the module will be loaded automatically if not loaded already. =item namespace namespace => 'foo', # default: $> ($EUID) The specification of the default L to be used with L, L, L, L, L, L, L, L, L and L. Defaults to the effective user ID of the process, as indicated by $> ($EUID). =back =head1 OBJECT METHODS The following object methods are available (in alphabetical order): =head2 add $cache->add( $value ); $cache->add( $value, $id ); $cache->add( $value, $id, $expiration ); $cache->add( value => $value, id => $id, # optional key => $key, # optional group => 'foo', # optional expiration => '3H', # optional version => '1.0', # optional namespace => 'foo', # optional ); Add a value to the cache, but only if it doesn't exist yet. Otherwise the same as L. =head2 data my $data = $cache->data; Returns the data server object as specified with L. =head2 dead my @dead = $cache->dead; my $dead = $cache->dead; # hash ref Returns the memcached backend L that appear to be non-functional. In list context returns the specifications of the servers in alphabetical order. Returns a hash reference in scalar context, where the unresponsive servers are the keys. Call L to obtain the number of errors that were found for each memcached server. =head2 decr $cache->decr; $cache->decr( $value ); $cache->decr( $value, $id, $expiration ); $cache->decr( value => $value, # default: 1 id => $id, # default: key only key => $key, # default: caller environment expiration => '3H', # default: $cache->expiration version => '1.0', # default: key environment namespace => 'foo', # default: $cache->namespace ); Decrement a value to the cache, but only if it already exists. Otherwise the same as L. Default for value is B<1>. Please note that any L associations will B be honoured: it is assumed they would be all the same for all calls to this counter and are therefore set only with L, L or L. =head2 delete $cache->delete; $cache->delete( $id ); $cache->delete( id => $id, # optional key => $key, # optional version => '1.0', # optional namespace => 'foo', # optional ); Delete a value, associated with the specified L<"data key">, from the cache. Can be called with unnamed and named parameters (assumed if two or more input parameters given). If called with unnamed parameters, then they are: =over 2 =item 1 id The L to be used to identify the value to be deleted. Defaults to no ID (then uses L only). =back When using named parameters, the following names can be specified: =over 2 =item id The L to be used to identify the value to be deleted. Defaults to no ID (then uses L only). =item key The L to be used to identify the value to be deleted. Defaults to the default key (as determined by the caller environment). =item version The L to be used to identify the value to be deleted. Defaults to the version associated with the L. =item namespace The L to be used to identify the value to be deleted. Defaults to the default namespace associated with the object. =back =head2 delete_group my $deleted = $cache->delete_group( group => 'foo' ); Deletes all cached information related to one or more given groups (specified as name and ID value pairs) and returns how many items were actually deleted. =head2 delimiter my $delimiter = $cache->delimiter; Returns the delimiter as (implicitly) specified with L. =head2 directory my $directory = $cache->directory; Returns the directory cache object as (implicitly) specified with L. =head2 errors my $errors = $cache->errors( "reset" ); foreach ($cache->servers) { print "Found $errors->{$_} errors for $_\n" if exists $errors->{$_}; } Return a hash reference with the number of errors when storing data values in a memcached backend server. Use L to find out whether a server is not responding. A true value for the input parameter indicates that the error counts should be reset. =head2 expiration $expiration = $cache->expiration; Returns the default expiration as (implicitly) specified with L. =head2 flush_all my $flushed = $cache->flush_all; my $flushed = $cache->flush_all( 30 ); # flush with 30 second intervals Initialize contents of all of the memcached backend servers of the L<"data server">. The input parameter specifies interval between flushes of backend memcached servers, default is the L value implicitly) specified with L. Returns whether all memcached L were successfully flushed. Please note that this method returns immediately after instructing each of the memcached servers. Also note that the timed flush_all functionality has only recently become part of the standard memcached API (starting from publicly released version C<1.2.1>). See the file "flush_interval.patch" for a patch for release 1.1.12 of the memcached software that implements timed flush_all functionality. =head2 flush_interval my $interval = $cache->flush_interval; Returns the default flush interval values used with L, as (implicitly) specified with L. =head2 get my $value = $cache->get; my $value = $cache->get( $id ); my $value = $cache->get( id => $id, # optional key => $key, # optional version => '1.1', # optional namespace => 'foo', # optional ); Obtain a value, associated with a L<"data key">, from the cache. Can be called with unnamed and named parameters. If called with unnamed parameters, then these are: =over 2 =item 1 id The L to be used to identify the value to be fetched. Defaults to no ID (then uses the default L only). =back When using named parameters, the following names can be specified: =over 2 =item id The L to be used to identify the value to be fetched. Defaults to no ID (then uses L only). =item key The L to be used to identify the value to be fetched. Defaults to the default key (as determined by the caller environment). =item version The L to be used to identify the value to be deleted. Defaults to the version associated with the L. =item namespace The L to be used to identify the value to be deleted. Defaults to the default namespace associated with the object. =back =head2 get_group my $group = $cache->get_group( group => $groupname, namespace => $namespace, # default: $cache->namespace ); foreach my $key (sort keys %{$group}) { print "key: $key\n" my $versions = $group->{$key}; foreach my $version (sort keys %{$versions}) { print " version: $version\n"; my $ids = $versions->{$version}; foreach my $id (sort keys %{$ids}) { print " id: $ids->{$id}\n"; } } } my @value = $cache->get_group( group => $groupname, namespace => $namespace, # default: $cache->namespace ); Either returns a reference to a multi level hash for the given group name and ID (containing the group's data) in scalar context, or a list with values (regardless of key, version or id) in list context. The input parameters are a hash that should contain the group name and associated ID, with an optional namespace specification. The structure of the returned hash reference is: $result |--- key |-- version |-- id |-- value See L<"group management"> for more information about groups. See L for obtaining the group and deleting it at the same time. =head2 get_multi my $hash = $cache->get_multi( \@id ); my $hash = $cache->get_multi( id => \@id, key => $key, namespace => $namespace, ); Optimized way of obtaining multiple values, associated with the same key, from the cache. Returns a hash reference with values found, keyed to the associated L. Can be called with named and unnamed parameters. If called with unnamed parameters, the parameters are: =over 2 =item 1 id A list reference of L's to be used to identify the values to be fetched. Must be specified. =back When using named parameters, the following names can be specified: =over 2 =item id A list reference of L's to be used to identify the values to be fetched. Must be specified. =item key The L to be used to identify the values to be fetched. Defaults to the default key (as determined by the caller environment). =item namespace The L for which to fetch values. Defaults to the namespace that was (implicitly) specified with L. =back =head2 grab_group my $group = $cache->grab_group( group => $groupname, namespace => $namespace, # default: $cache->namespace ); Same as L, but removes the returned data from the cache at the same time. =head2 group my $group = $cache->group( group => $groupname, namespace => $namespace, # default: $cache->namespace ); foreach my $key (sort keys %{$group}) { print "key: $key\n" print " ids: @{$group->{$key}}\n"; } Return a reference to a multi level hash for the given group name and ID. The input parameters are a hash that should contain the group name and associated ID, with an optional namespace specification. The structure of the hash is: $result |--- key |--- [id1,id2..idN] See L<"group management"> for more information about groups. =head2 group_names my @group_name = $cache->group_names; my $group_names = $cache->group_names; # hash ref Returns the valid group names as (implicitly) specified with L. Returns them in alphabetical order if called in a list context, or as a hash ref if called in scalar context. =head2 inactive print "Inactive!\n" if $cache->inactive; Returns whether the cache object is inactive. This happens if a true value is specified with L. =head2 incr $cache->incr; $cache->incr( $value ); $cache->incr( $value, $id ); $cache->incr( $value, $id, $expiration ); $cache->incr( value => $value, # default: 1 id => $id, # default: key only key => $key, # default: caller environment expiration => '3H', # default: $cache->expiration version => '1.1', # default: key environment namespace => 'foo', # default: $cache->namespace group => 'bar', # default: none ); Increment a value to the cache. Otherwise the same as L. Default for value is B<1>. Differently from the incr() of L, this increment function is magical in the sense that it automagically will L the counter if it doesn't exist yet. Please note that any L associations will only be set when the counter is created (and will be ignored in any subsequent increments of the same counter). =head2 namespace my $namespace = $cache->namespace; Obtain the default namespace, as (implicitly) specified with L. =head2 replace $cache->replace( $value ); $cache->replace( $value, $id ); $cache->replace( $value, $id, $expiration ); $cache->replace( value => $value, # undef id => $id, # default: key only key => $key, # default: caller environment expiration => '3H', # default: $cache->expiration version => '1.1', # default: key environment namespace => 'foo', # default: $cache->namespace ); Replace a value to the cache, but only if it already exists. Otherwise the same as L. Please note that any L associations will B be honoured: it is assumed they would be all the same for all calls to this counter and are therefore set only with L, L or L. =head2 reset $cache->reset; Resets the client side of the cache system. Mainly for internal usage only. Always returns true. =head2 servers my @backend = $cache->servers; my $backend = $cache->servers; # hash ref Returns the configuration details of the memcached backend servers that are currently configured to be used. Returns a list in alphabetical order in list context, and a hash ref in scalar context. See also L to find out if any of the memcached backend servers are not responding. =head2 set $cache->set; $cache->set( $value ); $cache->set( $value,$id ); $cache->set( $value, $id, $expiration ); $cache->set( value => $value, # default: undef id => $id, # default: key only key => $key, # default: caller environment expiration => '3H', # default: $cache->expiration version => '1.1', # default: key environment namespace => 'foo', # default: $cache->namespace group => 'bar', # default: none ); Set a value in the cache, regardless of whether it exists already or not. Can be called with named or unnamed parameters (if called with two input parameters or less). If called with unnamed parameters, then the input parameters are: =over 2 =item 1 value The value to set in the cache. Defaults to C. =item 2 id The L to be used to identify the value. Defaults to no ID (then uses L only). =item 3 expiration The expiration of the value. Defaults to the value as specified with L for the L. =back With named input parameters, the following names and values can be specified as a hash (in alphabetical order). =over 2 =item expiration The expiration time in seconds of the given value. Defaults to the value as specified with L for the L. Values below 30*24*60*60 (30 days) will be considered to be relative to the current time. Other values will be assumed to be absolute epoch times (seconds since 1 Jan. 1970 GMT). See L<"expiration specification"> for more ways to set expiration. =item id The L to be used to identify the value. Defaults to no ID (then uses L only). =item key The L to be used to identify the value. Defaults to the default key (as determined by the caller environment). Can be specified as a relative key when prefixed with "::", so that "::bar" would refer to the key "Foo::bar" if called from the package "Foo". =item namespace The L to which to associate the value. Defaults to the namespace that was (implicitly) specified with L. =item value The value to set in the cache. Defaults to C. =item version The L to be used to identify the value to be set. Defaults to the version associated with the L. =back Other than these named parameters, any number of group name and ID pairs can be specified to indicate a link to that group. =head2 start my $started_ok = $cache->start; my $started_ok = $cache->start( $config ); Attempts to start the memcached servers that have been configured with L (and which can be find out with L) by default, or the servers with the specified configs. Returns whether all servers (implicitly) specified have been started successfully. This only works if the memcached server(s) will be running on the same physical hardware as the script is running (which will generally not be the case in a production environment). It is therefore of limited usage generally, but it is a handy feature to have if you're developing or testing. See also L. =head2 stats my $stats = $cache->stats; Return a hash ref with simple statistics of all of the memcached backend L. The structure of the hash ref is as follows: $stats |-- server specification |-- key |-- value See the memcached server documentation on possible keys and values. =head2 stop my $stopped = $cache->stop; my $stopped = $cache->stop( $config ); Attempts to stop the specified memcached L (as specified by config value), returns whether all servers have actually stopped. Defaults to stopping all servers as initially specified with L. This only works if the memcached server(s) are running on the same physical hardware as the script is running (which will generally not be the case in a production environment). It is therefore of limited usage generally, but it is a handy feature to have if you're developing or testing. See also L. =head2 version my $version = $cache->version; # hash ref my $version = $cache->version( $config ); # hash ref Obtain the version information of the specified memcached servers, or all memcached servers being used if no input parameters are specified. Returns a hash reference in which the keys are the config information of the servers used (as returned by L) and the values are the version information of the associated memcached server. =head1 EXAMPLES =head2 generic grouped event logging $cache->set( group => 'event1', id => ':unique', value => $value ); This would put the value C<$value> into the cache, linked to the group 'event1'. Since we're not interested in the id of the event, but want to make sure it is always unique, the pseudo id ':unique' is specified. A recurring process, usually a cron job, would then need to do the following to grab all of the values cached: my @value = $cache->grab_group( group => 'event1' ); foreach (@value) { # perform whatever you want to do with the value in C<$_> } Please not that only the values are returned because L is called in list context. =head2 generic content logging my $cache = Cache::Memcached::Managed->new( data => $servers, group_names => [qw(hotel_id room_id)], expiration => '1H', ); package Foo; sub available { my ($cache,$hotel_id,$room_id,$checkin,$checkout) = @_; my $available; unless ($available = $cache->get( id => [$room_id,$checkin,$checkout] )) { # perform complicated calculations setting C<$available> $cache->set( id => [$room_id,$checkin,$checkout], value => $available, room_id => $room_id, hotel_id => $hotel_id, ); } return $available; } #available This example shows availability caching in a specific subroutine. Because the L and the L are located in de same subroutine, it is not necessary to specify the L (which will be automatically set to "Foo::available"). Please also not the absence of a L specification. Since each user of the "available" subroutine should have its "realm" depending on the cache object, no namespace specification is done. Now, whenever something related to the hotel_id is changed, a simple: $cache->delete_group( hotel_id => $hotel_id ); would be enough to also remove any availability cached in the above example (for the same value of C<$hotel_id>). The same would apply when something related to the room_id is changed: a simple: $cache->delete_group( room_id => $room_id ); would be enough to also remove any availability cached in the above example (for the same value of C<$room_id>). =head1 CAVEATS =head2 Race Conditions Several race conditions exists that can not be fixed because of a lack of semaphores when using memcached. Most important race condition is when a group is deleted: between the moment the main pointer ("directory key") is reset and all of the index keys are removed, it is possible for another process to be adding information to the same directory key already. In a worst case scenario, this means that a data key can get lost. To prevent this, a delay of B<2> seconds is applied to each time a group is deleted. This should give some time for the cleaning process to clean up before other processes start accessing again, but it is no way a guarantee that other processes wouldn't be able to add information if the cleaning process needs more than 2 seconds to clean up. =head2 Cron jobs Because the L<"data key">s by default includes the user id (uid) of the process as the L with which the entry was stored in the cache, cron jobs (which usually run under a different user id) will need to set the namespace to the user id of the process storing information into the cache. =head2 Incompatibility with Cache module John Goulah pointed out to me that there is an inconsistency with unnamed parameter passing in respect to the L module. Specifically, the C method: $c->set( $key, $data, [ $expiry ] ); is incompatible with this module's C method: $cache->set; $cache->set( $value ); $cache->set( $value, $id ); $cache->set( $value, $id, $expiration ); The reason for this simple: in this module, B parameters are optional. So you can specify just a value: the key will be generated for you from the caller environment. Since I felt at the time that you would more likely specify a value than a key, I made that the first parameter (as opposed to the C method of L. Changing to the format as imposed by the L module, is not an option at this moment in the lifetime of this module, as it would break existing code (the same way as it breaks the test-suite). =head1 THEORY OF OPERATION The group management is implemented by keeping a type of directory information in a (separate) directory memcached server. For each L one directory key is maintained in the directory memcached server. This key consists of the string "Cache::Memcached::Managed::", appended with the L, group name, the L and the ID of the group. For instance, the directory key for the group group => 'foo' when running as user "500" would be: Cache::Memcached::Managed#500#group#foo The value of the directory key of a group is used as a counter. Each time a some content is added that is linked to the group, that counter will be incremented and its value prepended to create an C<"index key">. So the first index key of the above example, would be: 1#Cache::Memcached::Managed#500#group#foo This index key is then also stored in the directory memcached server, with the original L<"data key"> as its value, and with the same expiration as used for the data key. Whenever the index keys are needed of a group (e.g. for fetching all of its members, or for deleting all of its members), the value of the directory key of the group is inspected, and that is used to generate a list of index keys. Suppose the value of the directory key is 5, then then following index keys would be generated (essentially mapping 1..5): 1#Cache::Memcached::Managed#500#group#foo 2#Cache::Memcached::Managed#500#group#foo 3#Cache::Memcached::Managed#500#group#foo 4#Cache::Memcached::Managed#500#group#foo 5#Cache::Memcached::Managed#500#group#foo If the group is to be deleted or fetched, then all possible values for these index keys are obtained. For instance, this would fetch: 1#Cache::Memcached::Managed#500#group#foo => 500#1.0#Foo::zip#23 2#Cache::Memcached::Managed#500#group#foo => 500#1.1#Bar::pod#47 3#Cache::Memcached::Managed#500#group#foo => 500#1.0#Foo::zip#23 4#Cache::Memcached::Managed#500#group#foo => 500#1.1#Bar::pid#12 5#Cache::Memcached::Managed#500#group#foo => 500#1.1#Bar::pid#14 Note that index key 1 and 3 return the same backend key. This can be caused by doing multiple sets with the same key / id combination. The final list of backend keys then becomes: 500#1.0#Foo::zip#23 500#1.1#Bar::pod#47 500#1.1#Bar::pid#12 500#1.1#Bar::pid#14 If the group is to be deleted (L), then the index keys are removed from the directory memcached server. And the associated data keys are removed from the data memcached server. If the group (data) is to be fetched (L or L), then the superfluous index keys are removed from the directory memcached server. In this example, that would be: 1#Cache::Memcached::Managed#500#group#foo because: 3#Cache::Memcached::Managed#500#group#foo also refers to the data key 500#1.0#Foo::zip#23 Because of this, the lowest index key with a valid data key has become: 2#Cache::Memcached::Managed#500#group#foo making "2" the lowest ordinal number of the index keys. In that case a special key, the lowest index key, is saved in the directory memcached server. The name of the keys is the same as the directory key for the group, postfixed with the L and the string "_lowest". In this example, this would be: Cache::Memcached::Managed#500#group#foo#_lowest Whenever index keys are fetched, the value of this key is used to determine the start point for the generation of index keys. If, in the above example another fetch of that group would be done, then these index_keys would be generated (essentially mapping 2..5): 2#Cache::Memcached::Managed#500#group#foo 3#Cache::Memcached::Managed#500#group#foo 4#Cache::Memcached::Managed#500#group#foo 5#Cache::Memcached::Managed#500#group#foo Since 32 bit counters are being used, about 4 billion items can be linked to a group, before a group should be deleted to completely restart. In most live situation, this overflow condition will not occur, since this mechanism was mainly intended to be able to delete groups of information from the cache. And a deletion will remove the counter and all of its associated keys, essentially starting again at 1. =head1 REQUIRED MODULES Cache::Memcached (any) Scalar::Util (any) =head1 AUTHOR Elizabeth Mattijsen maintained by LNATION, =head1 HISTORY This module started life as an internal module at BOOKINGS Online Hotel Reservation, the foremost European on-line hotel booking portal. With approval and funding of Bookings, this module was generalized and put on CPAN, for which Elizabeth Mattijsen would like to express her gratitude. =head1 COPYRIGHT (C) 2005, 2006 BOOKINGS (C) 2007, 2008 BOOKING.COM (C) 2012 Elizabeth Mattijsen (C) 2025 LNATION This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =cut Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed/PaxHeader/Multi.pm000644 000765 000024 00000000373 15000422663 027027 xustar00lnationstaff000000 000000 30 mtime=1744971187.423438726 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjViMzs7 53 SCHILY.xattr.com.apple.quarantine=0281;680225b3;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed/Multi.pm000644 000765 000024 00000011624 15000422663 025057 0ustar00lnationstaff000000 000000 package Cache::Memcached::Managed::Multi; # Make sure we have version info for this module $VERSION= '0.27'; BEGIN { # We're fooling the Kwalitee checker into thinking we're strict use strict; } #-------------------------------------------------------------------------- # No, we're NOT using strict here. There are several reasons, the most # important is that we're doing a lot of nasty stuff here. # If you _do_ want stricture as a developer of this module, simply activate # the line below here #-------------------------------------------------------------------------- #use strict; # At compile time # Create list with method names BEGIN { my @method = qw( add data dead decr delete delete_group delimiter directory errors expiration flush_all flush_interval get get_group get_multi grab_group group group_names inactive incr namespace replace reset servers set start stats stop version ); # Create a method for each of the methods which # Obtains the list of objects # If called in list context # Call the methods in list context and return list of list refs # Elseif called in scalar context # Call the methods in scalar context and return list ref of values # Call the methods in void context eval <$_( \@_ )] } \@{\$objects}; } elsif (defined wantarray) { return [map { scalar \$_->$_( \@_ ) } \@{\$objects}]; } \$_->$_( \@_ ) foreach \@{\$objects}; } #$_ SUB } #BEGIN # Satisfy -require- 1; #--------------------------------------------------------------------------- # # Class methods # #--------------------------------------------------------------------------- # new # # Return instantiated object # # IN: 1 class # 2..N list of Cache::Memcached::Managed (compatible) objects # OUT: 1 instantiated object sub new { my $class = shift; bless \@_,$class } #new #--------------------------------------------------------------------------- # # Object methods # #--------------------------------------------------------------------------- # objects # # IN: 1 instantiated object # OUT: 1..N objects of which this object consists sub objects { @{$_[0]} } #objects #--------------------------------------------------------------------------- __END__ =head1 NAME Cache::Memcached::Managed::Multi - multiple Cache::Memcache::Managed objects =head1 SYNOPSIS use Cache::Memcached::Managed::Multi; my $multi = Cache::Memcached::Managed::Multi->new( @managed ); =head1 DESCRIPTION Provides the same API as L, but applies all methods called to all of the objects specified, except for L and L. =head1 CONTEXT All methods are called on all of the L objects in the same context (list, scalar or void) in which the method is called on the L object. The return value differs in format depending on the context also: =over 2 =item scalar my $listref = $multi->method; print "Result: @{$listref}\n"; When called in scalar context, a list ref with scalar values is returned in the same order in which the objects are used (which is determined by the order in which they were supplied with L and returned by L.. =item list my @listref = $multi->method; print "Result $_: @{$listref[$_]}\n" foreach 0..$#listref; When called in list context, a list of list references is returned in the same order in which the objects are used (which is determined by the order in which they were supplied with L and returned by L. =item void $multi->method; When called in void context, nothing is returned (not strangely enough ;-). =back =head1 SPECIFIC CLASS METHODS There is only one specific class method. =head2 new my $multi = Cache::Memcached::Managed::Multi->new( @managed ); Create an object containing multiple L objects. Returns the instantiated object. =head1 SPECIFIC INSTANCE METHODS =head2 objects my @managed = $multi->objects; Returns the list of instantiated L objects that the object is a proxy for. =head1 AUTHOR Elizabeth Mattijsen =head1 COPYRIGHT (C) 2005 - 2006 BOOKINGS (C) 2007 BOOKING.COM This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =cut Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed/PaxHeader/Inactive.pm000644 000765 000024 00000000373 15000422653 027476 xustar00lnationstaff000000 000000 30 mtime=1744971179.351020109 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjVhYjs7 53 SCHILY.xattr.com.apple.quarantine=0281;680225ab;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/lib/Cache/Memcached/Managed/Inactive.pm000644 000765 000024 00000005764 15000422653 025536 0ustar00lnationstaff000000 000000 package Cache::Memcached::Managed::Inactive; # Make sure we have version info for this module $VERSION= '0.27'; #-------------------------------------------------------------------------- BEGIN { # We're fooling the Kwalitee checker into thinking we're strict use strict; } #-------------------------------------------------------------------------- # No, we're NOT using strict here. There are several reasons, the most # important is that we're doing a lot of nasty stuff here. # If you _do_ want stricture as a developer of this module, simply activate # the line below here #-------------------------------------------------------------------------- #use strict; # Singleton object my $self; # At compile time # Create accessors returning undef BEGIN { *$_ = sub { undef } foreach qw( add data decr delete delete_group delimiter directory expiration flush_all flush_interval get incr namespace replace reset set start stop ); # Create accessors returning hash ref *$_ = sub { {} } foreach qw( errors get_group get_multi grab_group group stats version ); # Create accessors returning list or hash ref *$_ = sub { wantarray ? () : {} } foreach qw( dead group_names servers ); } #BEGIN # Satisfy -require- 1; #--------------------------------------------------------------------------- # # Class methods # #--------------------------------------------------------------------------- # new # # Return instantiated object # # IN: 1 class # 2..N hash with parameters # OUT: 1 instantiated object sub new { $self ||= bless {},shift } #new #--------------------------------------------------------------------------- # # Object methods # #--------------------------------------------------------------------------- # inactive # # IN: 1 instantiated object # OUT: 1 true sub inactive { 1 } #inactive #--------------------------------------------------------------------------- __END__ =head1 NAME Cache::Memcached::Managed::Inactive - inactive Cache::Memcache::Managed object =head1 SYNOPSIS use Cache::Memcached::Managed::Inactive; my $cache = Cache::Memcached::Managed::Inactive->new; =head1 DESCRIPTION Provides the same API as L, but doesn't do anything. =head1 AUTHOR Elizabeth Mattijsen =head1 COPYRIGHT (C) 2005 - 2006 BOOKINGS (C) 2007 BOOKING.COM This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =cut Cache-Memcached-Managed-0.27/t/PaxHeader/004expiration.t000644 000765 000024 00000000372 15000423167 023461 xustar00lnationstaff000000 000000 29 mtime=1744971383.82557945 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjY3Nzs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022677;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/004expiration.t000644 000765 000024 00000002526 15000423167 021513 0ustar00lnationstaff000000 000000 # set up strictures use strict; use warnings; use lib '.'; # set up tests my @tests; BEGIN { # some initializations my $second= 1; my $minute= 60 * $second; my $hour= 60 * $minute; my $day= 24 * $hour; my $week= 7 * $day; # set up @tests= ( [ undef, undef ], [ 'foo', undef ], [ 1, 1 * $second ], [ '0s', 0 * $second ], [ '2s', 2 * $second ], [ '3S', 3 * $second ], [ '0m', 0 * $minute ], [ '4m', 4 * $minute ], [ '5M', 5 * $minute ], [ '0h', 0 * $hour ], [ '6h', 6 * $hour ], [ '7H', 7 * $hour ], [ '0d', 0 * $day ], [ '8d', 8 * $day ], [ '9D', 9 * $day ], [ '0w', 0 * $week ], [ '1w', 1 * $week ], [ '2W', 2 * $week ], [ '3W4d5H6m7S', 3 * $week + 4 * $day + 5 * $hour + 6 * $minute + 7 * $second ], ); } #BEGIN # Set up tests use Test::More tests => scalar(@tests); # get the stuff we need use Cache::Memcached::Managed; *e2s= \&Cache::Memcached::Managed::_expiration2seconds; # do the tests! foreach (@tests) { my ( $expiration, $seconds )= @{$_}; # expecting undef result if ( !defined $seconds ) { ok( !defined( e2s( undef, $expiration ) ), "checking " . ( defined $expiration ? $expiration : 'undef' ) ); } # expecting real result else { is( e2s( undef, $expiration ), $seconds, "checking $expiration" ); } } Cache-Memcached-Managed-0.27/t/PaxHeader/030configs.t000644 000765 000024 00000000373 15000423342 022722 xustar00lnationstaff000000 000000 30 mtime=1744971490.768112133 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjZlMjs7 53 SCHILY.xattr.com.apple.quarantine=0281;680226e2;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/030configs.t000644 000765 000024 00000004536 15000423342 020756 0ustar00lnationstaff000000 000000 # Setup test and strictness use Test::More tests => 1 + ( 4 * 5 ) + 1 + 5; use strict; use warnings; use lib '.'; # initializations my $class= 'Cache::Memcached::Managed'; my $memcached_class= $ENV{CACHE_MEMCACHED} || 'Cache::Memcached'; my $base_config= "127.0.0.1"; # Make sure we have all the support routines require 'testlib'; # Make sure class is loaded use_ok($class); # simple string config my $port= anyport(); check_config( $port, "$base_config:$port", 'simple string config' ); # simple listref config $port= anyport(); check_config( $port, [ "$base_config:$port" ], 'simple listref config' ); # hashref/string config $port= anyport(); check_config( $port, { servers => "$base_config:$port" }, 'hashref/string config' ); # hashref/listref config $port= anyport(); check_config( $port, { servers => [ "$base_config:$port" ] }, 'hashref/listref config' ); # object config $port= anyport(); my $memcached= $memcached_class->new( servers => [ "$base_config:$port" ], ); isa_ok( $memcached, $memcached_class, "Check whether memcached object ok" ); check_config( $port, $memcached, 'object config' ); #------------------------------------------------------------------------------- # check_config # # Check given port / config. Good for 5 tests. # # IN: 1 port # 2 config # 3 test message sub check_config { my ( $port, $config, $message )= @_; ok( $port, "Check whether we have a port to work on for $message" ); # Create a cache object my $cache= $class->new( data => $config, memcached_class => $memcached_class, ); isa_ok( $cache, $class, "Check whether object ok for $message" ); # Start the server, skip further tests if failed SKIP: { skip( "Memcached server not started", 3 ) if !$cache->start; sleep 2; # let the server warm up diag("\nStarted memcached server for $message"); # Set/Get simple value here my $value= 'value'; ok( $cache->set($value), "Check if simple set is ok for $message" ); is( $cache->get,$value, "Check if simple get is ok for $message" ); # Stop the server ok( $cache->stop, "Check if all servers have stopped for $message" ); diag("\nStopped memcached server for $message"); } #SKIP } #check_config #------------------------------------------------------------------------------- Cache-Memcached-Managed-0.27/t/PaxHeader/003multi.t000644 000765 000024 00000000373 15000423162 022424 xustar00lnationstaff000000 000000 30 mtime=1744971378.720407934 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjY3Mjs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022672;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/003multi.t000644 000765 000024 00000003574 15000423162 020461 0ustar00lnationstaff000000 000000 # Set up tests and strictness use Test::More tests => 37; use strict; use warnings; use lib '.'; # Make sure we have all the support routines require 'testlib'; my $class = 'Cache::Memcached::Managed::Inactive'; (my $multi = $class) =~ s#::Inactive#::Multi#; # Make sure we can load the module, both active and inactive require_ok( $_ ) foreach $class,$multi; # Create inactive cache objects directly my @cache = map {$class->new} 0..2; isa_ok( $cache[$_],$class,"Check whether object #$_ ok" ) foreach 0..2; # Create a multi object my $self = $multi->new( @cache ); isa_ok( $self,$multi,"Check whether multi object ok" ); check_methods( $self ); #------------------------------------------------------------------------- # check_methods # # Check whether all the methods are indeed inactive. Good for 32 tests. # # IN: 1 instantiated object sub check_methods { my ($self) = @_; # Check methods returning undef always is_deeply( scalar( $self->$_ ), [ undef, undef, undef ], "Check result of inactive method $_" ) foreach qw( add data decr delete delete_group delimiter directory expiration flush_all flush_interval get incr namespace replace reset set start stop ); # Check all methods that always return a hash ref is_deeply( scalar( $self->$_ ), [ {}, {}, {} ], , "Check result of hash ref method $_" ) foreach qw( errors get_group get_multi grab_group group stats version ); # Check all methods returning a list in array context is_deeply( [ $self->$_ ], [ [], [], [] ], "Check result of list list method $_" ) foreach qw( dead group_names servers ); # Check all methods returning a hash ref in scalar context is_deeply( scalar( $self->$_ ), [ {}, {}, {} ], "Check result of scalar inactive method $_") foreach qw( dead group_names servers ); } #check_methods Cache-Memcached-Managed-0.27/t/PaxHeader/020grab.t000644 000765 000024 00000000373 15000423321 022201 xustar00lnationstaff000000 000000 30 mtime=1744971473.683205849 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjZkMTs7 53 SCHILY.xattr.com.apple.quarantine=0281;680226d1;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/020grab.t000644 000765 000024 00000015660 15000423321 020235 0ustar00lnationstaff000000 000000 # Warn for possible excessive timing when on OS X diag( <<"DIAG" ) if $^O eq 'darwin'; This test may take very long on OS X (some 10 hours) if you use an older version of libevent and/or memcached. Please upgrade to the latest libevent if this test is taking more than a few minutes. DIAG use lib '.'; # Make sure we have a version for the subroutine based checks $Foo::VERSION = 'Foo::VERSION'; # Set up test and strictness use Test::More tests => 36018; use strict; use warnings; # Load modules that we need use List::Util qw(shuffle); # Add stopping code my $cache; END { diag( "\nStopped memcached server(s)" ) if $cache and ok( $cache->stop, "Check if all servers have stopped" ); } #END # Make sure we have all the support routines require 'testlib'; my $class = 'Cache::Memcached::Managed'; use_ok( $class ); # Obtain port and create config my $port = anyport(); ok( $port,"Check whether we have a port to work on" ); my $config = "127.0.0.1:$port"; # Create a cache object my $memcached_class = $ENV{CACHE_MEMCACHED} || 'Cache::Memcached'; $cache = $class->new( data => $config, memcached_class => $memcached_class, ); isa_ok( $cache,$class,"Check whether object ok" ); # Start the server, skip further tests if failed SKIP: { skip( "Memcached server not started",36014 ) if !$cache->start; sleep 2; # let the server warm up diag("\nStarted memcached server"); # Set the number of items to check my @item = (1,2,255,256,257,511,512,513,1023,1024,1025,4095,4096,4097); # Set them all in random order Foo::set($_) foreach shuffle @item; # Check them all in random order Foo::check($_) foreach shuffle @item; # Obtain final stats my $got = $cache->stats->{$config}; # Remove stuff that we cannot check reliably delete @$got{ qw( bytes_read bytes_written connection_structures curr_connections limit_maxbytes pid pointer_size rusage_user rusage_system time total_connections uptime version ) }; # Set up the expected stats for the rest my $expected = { bytes => 0, cmd_get => 88764, cmd_set => 35936, curr_items => 0, get_hits => 88722, get_misses => 42, total_items => 35968, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got,$expected, "Check if final stats with one server correct" ); } #TODO # Stop the single memcached server setup ok( $cache->stop, "Check if single server has stopped" ); diag("\nStopped memcached server"); # Obtain ports and create config my @port = map { anyport() } 0 .. 1; ok( $port[$_], "Check whether we have a port to work on for $_" ) foreach 0 .. 1; my @config = map { "127.0.0.1:$_" } @port; # Create a cache object $cache = $class->new( data => $config[1], directory => $config[0], memcached_class => $memcached_class, ); isa_ok( $cache, $class, "Check whether object ok" ); # Start the server, give it time to warm up diag( "\nStarted memcached servers" ) if ok( $cache->start, "Check if memcached servers started" ); sleep 2; # Set them all in random order Foo::set($_) foreach shuffle @item; # Check them all in random order Foo::check($_) foreach shuffle @item; # Obtain final stats for directory server my $stats = $cache->stats; # Remove stuff that we cannot check reliably $got = $stats->{$config[0]}; delete @$got{ qw( bytes_read bytes_written connection_structures curr_connections limit_maxbytes pid pointer_size rusage_user rusage_system time total_connections uptime version ) }; # Set up the expected stats for the rest $expected = { bytes => 0, cmd_get => 53393, cmd_set => 17989, curr_items => 0, get_hits => 53351, get_misses => 42, total_items => 18021, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got,$expected ) ) if !is_deeply( $got,$expected, "Check if final stats with two servers correct" ); } #TODO # Obtain final stats for data server $got = $stats->{ $config[1] }; # Remove stuff that we cannot check reliably delete @$got{ qw( bytes_read bytes_written connection_structures curr_connections limit_maxbytes pid pointer_size rusage_user rusage_system time total_connections uptime version ) }; # Set up the expected stats for the rest $expected = { bytes => 0, cmd_get => 35371, cmd_set => 17947, curr_items => 0, get_hits => 35371, get_misses => 0, total_items => 17947, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got,$expected ) ) if !is_deeply( $got, $expected, "Check if final stats with two servers correct" ); } #TODO } #SKIP #--------------------------------------------------------------------- # Foo::set # # Set information for group setting check # # IN: 1 number of items sub Foo::set { my ($items) = @_; # Set up items and group name my $group = "group$items"; # Fill the group foreach ( shuffle 1 .. $items ) { ok( $cache->set( key => "::$items", id => $_, value => $items - $_ + 1, group => $group ), "Check if group$_ set ok for $_" ); } } #Foo::set #--------------------------------------------------------------------- # Foo::check # # Check group information # # IN: 1 number of items sub Foo::check { my ($items) = @_; # Set up items and group name my $group = "group$items"; my $key = "Foo::$items"; # Obtain the group key and associated IDs my $got = $cache->group( group => $group ); my $expected = { $key => [ sort 1 .. $items ] }; # need alpha sorting diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got, $expected, "Check if group key and IDs correct for $_" ); # Fetch the group and data $got = $cache->get_group( group => $group ); $expected = { $key => { $Foo::VERSION => { map { $_ => ( $items - $_ + 1 ) } 1..$items } } }; diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got,$expected, "Check if fetch group and data structure correct for $_" ); my $values = $expected->{$key}->{$Foo::VERSION}; foreach ( shuffle 1 .. 20 ) { ok( $cache->set( key => $key, id => $_, value => ($values->{$_} = $_), group => $group ), "Check if group$_ override ok for $_" ); } # Grab the group and data $got = $cache->grab_group( group => $group ); diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got,$expected, "Check if grab group and data structure correct for $_" ); # Grab the now empty group and data $got = $cache->grab_group( group => $group ); diag( Data::Dumper::Dumper( $got, {} ) ) if !is_deeply( $got, {}, "Check if second grab group and data structure fails for $_" ); } #Foo::check Cache-Memcached-Managed-0.27/t/PaxHeader/010fork.t000644 000765 000024 00000000373 15000423205 022227 xustar00lnationstaff000000 000000 30 mtime=1744971397.094887877 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjY4NTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022685;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/010fork.t000644 000765 000024 00000012132 15000423205 020252 0ustar00lnationstaff000000 000000 # Setup test and strictness use Test::More tests => 23; use strict; use warnings; use lib '.'; # Add stopping code, only to be executed in main process my $cache; my $filename; my $pid = $$; END { if ( $pid == $$ ) { diag( "\nStopped memcached server" ) if $cache and ok( $cache->stop, "Check if all servers have stopped" ); } } #END # Make sure we have all the support routines require 'testlib'; # Make sure class is loaded my $class = 'Cache::Memcached::Managed'; use_ok($class); # Obtain port and create config my $port = anyport(); ok( $port, "Check whether we have a port to work on" ); my $config = "127.0.0.1:$port"; # Create a cache object my $memcached_class = $ENV{CACHE_MEMCACHED} || 'Cache::Memcached'; $cache = $class->new( data => $config, memcached_class => $memcached_class, ); isa_ok( $cache, $class, "Check whether object ok" ); # Start the server, skip further tests if failed SKIP: { skip( "Memcached server not started", 19 ) if !$cache->start; sleep 2; # let the server warm up diag("\nStarted memcached server"); # Set/Get simple value here my $value = 'value'; ok( $cache->set($value),"Check if simple set is ok" ); is( $cache->get,$value,"Check if simple get is ok" ); # Fork, get and set value there $filename = 'forked'; if ( !fork ) { ft(); ft( $cache->get eq $value,"Check if simple get in fork is ok" ); ft( $cache->set( 'foo' ),"Check if simple set in fork is ok" ); splat( $filename,ft() ); exit; } # Process test results from fork sleep 3; pft($filename); # Check whether the value from the fork is ok is( $cache->get, 'foo', "Check if simple get after fork is ok" ); ok( $cache->delete, "Check if simple delete after fork is ok" ); # Obtain final stats my $got = $cache->stats->{$config}; =for Explanation: For some reason, version 1.2.1 of memcached sometimes doesn't return cmd_get, cmd_set, get_hits and get_misses statistics if there are no items in the cache. This seems platform related as the test result differs between Fedora Core 6 and OS X 10.4. Previous versions of memcached *did* always return this information. As it is unclear whether this is a bug or a feature, we're only not checking these fields anymore until further notice. =cut # Remove stuff that we can not check reliably delete @$got{ qw( bytes_read bytes_written cmd_get cmd_set connection_structures curr_connections get_hits get_misses limit_maxbytes pid pointer_size rusage_user rusage_system time total_connections uptime version ) }; # cmd_get => 3, # cmd_set => 2, # get_hits => 3, # get_misses => 0, # Set up the expected stats for the rest my $expected = { bytes => 0, curr_items => 0, total_items => 2, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got,$expected, "Check if final stats correct" ); } #TODO # Stop the server ok( $cache->stop, "Check if all servers have stopped" ); diag("\nStopped memcached server"); # Obtain another port and recreate config $port = anyport(); ok( $port, "Check whether we have a port to work on" ); $config = "127.0.0.1:$port"; # Create a new cache object $cache = $class->new( data => $config, memcached_class => $memcached_class, ); isa_ok( $cache, $class, "Check whether object ok" ); ok( !$cache->set($value),"Check if simple set fails" ); ok( $cache->start, "Check if servers have started again" ); sleep 2; diag("\nStarted memcached server"); TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; ok( !$cache->set($value), "Check if simple set still fails" ); } #TODO if ( !fork ) { ft(); ft( $cache->set($value), "Check if simple set in 2nd fork is ok" ); ft( $cache->get eq $value, "Check if simple get in 2nd fork is ok" ); splat( $filename, ft() ); exit; } # Process test results from fork sleep 3; pft($filename); # Check failing get TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; ok( !$cache->get, "Check if simple get still fails" ); } #TODO diag("\nWaiting 30 seconds for server to become eligible again"); sleep 30; is( $cache->get, $value, "Check if simple get now successful" ); # Delete the value ok( $cache->delete, "Check if simple delete after 2nd fork is ok" ); # Obtain final stats $got = $cache->stats->{$config}; # Remove stuff that we can not check reliably delete @$got{ qw( bytes_read bytes_written cmd_get cmd_set connection_structures curr_connections get_hits get_misses limit_maxbytes pid pointer_size rusage_user rusage_system time total_connections uptime version ) }; # cmd_get => 2, # cmd_set => 1, # get_hits => 2, # get_misses => 0, # Set up the expected stats for the rest $expected = { bytes => 0, curr_items => 0, total_items => 1, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got,$expected ) ) if !is_deeply( $got,$expected, "Check if final stats correct" ); } #TODO } #SKIP Cache-Memcached-Managed-0.27/t/PaxHeader/001basic.t000644 000765 000024 00000000373 15000423061 022347 xustar00lnationstaff000000 000000 30 mtime=1744971313.378425833 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjYzMTs7 53 SCHILY.xattr.com.apple.quarantine=0281;68022631;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/001basic.t000644 000765 000024 00000031466 15000423061 020405 0ustar00lnationstaff000000 000000 # Version number initializations $VERSION = 'main::VERSION'; $Foo::VERSION = 'Foo::VERSION'; # Set up tests and strictness use Test::More tests => 176; use strict; use warnings; use lib '.'; # Add the termination code my $cache; END { my $stopped_ok; $stopped_ok = $cache->stop if $cache; diag( "\nStopped memcached server" ) if ok( $stopped_ok, "Check if all servers have stopped" ); } #END # Make sure we have all the support routines require 'testlib'; my $class = 'Cache::Memcached::Managed'; # For both active and inactive version foreach ($class,$class.'::Inactive') { # check loading and methods require_ok( $_ ); can_ok( $_,qw( add data dead decr delete delete_group delimiter directory errors expiration flush_all flush_interval get get_group get_multi grab_group group group_names incr namespace new replace reset servers set start stats stop version ) ); } # Obtain port and create config my $port = anyport(); ok( $port, "Check whether we have a port to work on" ); my $config = "127.0.0.1:$port"; # Create a cache object my $memcached_class = $ENV{CACHE_MEMCACHED} || 'Cache::Memcached'; $cache = $class->new( data => $config, memcached_class => $memcached_class, ); isa_ok( $cache,$class, "Check whether object ok" ); # Start the server, skip further tests if failed SKIP: { skip( "Memcached server not started", 169 ) if !$cache->start; sleep 2; # let the server warm up diag("\nStarted memcached server"); # Check version info my $versions = $cache->version; my $version = $versions->{$config}; ok( $version, "Check whether version information available" ); # Show warning if memcached version questionable my $pid = $cache->stats->{$config}->{pid}; diag( <servers; is_deeply( \@server, [$config], "Check if all memcached backend servers accounted for from a list" ); my $servers = $cache->servers; is_deeply( $servers, { $config => undef }, "Check if all memcached backend servers accounted for from a hash ref" ); # Check whether backend servers all alive my @dead = $cache->dead; is( scalar @dead, 0, "Check that all servers are alive from a list" ); my $dead = $cache->dead; is_deeply( $dead, {}, "Check that all servers are alive from a hash ref" ); # Check group names my @group_name = $cache->group_names; is_deeply( \@group_name, ['group'], "Check that all group names accounted for from a list" ); my $group_names = $cache->group_names; is_deeply( $group_names, { group => undef }, "Check that all group names accounted for from a hash ref" ); # No key, no ID my $value = 'value'; ok( $cache->set($value), "Check if simple setting works" ); is( $cache->get,$value, "Check if simple getting works" ); ok( $cache->delete, "Check if simple delete works" ); ok( !defined $cache->get, "Check if simple getting fails" ); # No key, but ID given foreach my $param ( [ [ qw(foo foo) ], [qw(bar bar) ] ], [ [ qw(id foo value foo) ], [ qw(id bar value bar) ] ], ) { ok( $cache->set( @{ $param->[0] } ), "Check if setting with ID works" ); ok( $cache->set( @{ $param->[1] } ), "Check if setting with ID works" ); my $got = $cache->get_multi( [ qw(foo bar) ] ); diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,{ foo => 'foo', bar => 'bar' }, "Check whether get_multi with ID's works" ); is( $cache->flush_all, 1, "Check if flushing works" ); sleep 1; # give flush time to work through $got = $cache->get_multi( qw(foo bar) ); diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,{}, "Check whether get_multi with ID's fails" ); # Remove flushed elements anyway for final stats $cache->delete($_) foreach qw(foo bar); } # Check version dependency my $version = do { no strict; $VERSION }; ok( $version, "Check whether there was a version for the module itself" ); ok( $cache->set($value), "Simple value for version / namespace check" ); is( $cache->get( version => $version ), $value, "Check if simple getting with version works" ); ok( !defined $cache->get( version => 'foo' ), "Check if simple getting with version fails" ); # Check namespace dependency my $namespace = $cache->namespace; is( $namespace, $>, "Check whether there was a default namespace" ); is( $cache->get( namespace => $namespace ), $value, "Check if simple getting with namespace works" ); ok( !defined $cache->get( namespace => 'foo' ), "Check if simple getting with namespace fails" ); # Check expiration ok( $cache->set( value => $value, expiration => '3' ), "Simple value for expiration check" ); is( $cache->get, $value, "Check if simple getting before expiration works" ); sleep 5; ok( !defined $cache->get, "Check if simple getting after expiration fails" ); # Check (magical) in/decrement is( $cache->incr, 1, "Check initial simple increment" ); is( $cache->incr, 2, "Check additional simple increment" ); is( $cache->incr(7), 9, "Check additional increment with specific value" ); is( $cache->decr, 8, "Check additional simple decrement" ); is( $cache->decr(6),2, "Check additional decrement with specific value" ); ok( $cache->delete, "Check whether deletion successful" ); ok( !defined $cache->get, "Check if simple getting after increment + deletion fails" ); ok( !$cache->decr( 1, 1 ), "Check if simple decrement fails" ); # Check add/replace ok( $cache->add($value), "Check if simple add works" ); is( $cache->get, $value, "Check if get after add works" ); ok( !$cache->add($value), "Check if additional add fails" ); is( $cache->get,$value, "Check if get after add still works" ); ok( $cache->replace(22), "Check if simple replace works" ); is( $cache->get, 22, "Check if get after replace works" ); ok( $cache->replace(33), "Check if additional replace works" ); is( $cache->get, 33, "Check if get after additional replace works" ); ok( $cache->delete, "Check whether deletion successful" ); ok( !$cache->replace($value), "Check if replace after delete fails" ); # determine unique key my $key = $0 =~ m#^/# ? $0 : do { my $pwd = `pwd`; chomp $pwd; $pwd } . "/$0"; # Check simple group management ok( $cache->set( value => $value, group => 'group' ), "Simple value with group" ); is( $cache->get, $value, "Check if simple get with group works" ); my $expected = { $key => { $version => { '' => $value } } }; my $got = $cache->get_group( group => 'group' ); diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,$expected, "Check if simple get_group with group works" ); is( $cache->get, $value, "Check if simple get with group works" ); # Repeat simple group management, now with grab_group $got = $cache->get_group( group => 'group' ); diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,$expected, "Check if simple get_group with group works still" ); is( $cache->get, $value, "Check if simple get with group works" ); $got = $cache->grab_group( group => 'group' ); diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,$expected, "Check if simple grab_group with group works" ); ok( !defined $cache->get, "Check if simple getting with grabbed group fails" ); # Check simple group deletion ok( $cache->set( value => $value, group => 'group' ), "Simple value with group" ); is( $cache->get, $value, "Check if simple get with group works" ); ok( $cache->delete_group( group => 'group' ), "Delete group" ); ok( !defined $cache->get, "Check if simple getting with deleted group fails" ); # Check stats fetching $got = $cache->stats; foreach ( values %{$got} ) { $_ = undef foreach values %{$_}; } $expected = { $config => { map { $_ => undef } qw( bytes bytes_read bytes_written cmd_get cmd_set connection_structures curr_items curr_connections get_hits get_misses limit_maxbytes pid rusage_system rusage_user time total_connections total_items uptime version ) } }; # pointer_size introduced in memcached 1.2.1 $expected->{$config}->{pointer_size} = undef if $version ge "1.2.1"; TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper($got) ) if !is_deeply( $got,$expected, "Check if simple stats works" ); } #TODO # Check inside subroutine Foo::bar(); # Done now if we did a reset already last TEST if $reset; # Reset so we can do it again with a clean slate ok( $cache->reset, "Check if client side reset ok" ); } # Obtain final stats my $got = $cache->stats->{$config}; # Remove stuff that we cannot check reliably delete @$got{qw( bytes_read bytes_written connection_structures curr_connections limit_maxbytes rusage_user rusage_system time total_connections uptime )}; # Set up the expected stats for the rest my $expected = { bytes => 0, cmd_get => 108, cmd_set => 56, curr_items => 0, get_hits => 74, get_misses => 34, pid => $pid, pointer_size => 32, total_items => 52, version => $version, }; # Check if it is what we expected TODO: { local $TODO = 'Need to look up changes in memcached for different versions'; diag( Data::Dumper::Dumper( $got, $expected ) ) if !is_deeply( $got, $expected, "Check if final stats correct" ); } #TODO } #SKIP #-------------------------------------------------------------------------- # Foo::bar # # A subroutine for checking subroutine relative keys sub Foo::bar { # One set, many different gets ok( $cache->set('foo1'), "Check simple set inside a subroutine" ); is( $cache->get, 'foo1', "Check simple get inside a subroutine" ); is( $cache->get( key => '::bar' ), 'foo1', "Check simple get with relative key inside a subroutine" ); is( $cache->get( key => 'Foo::bar' ), 'foo1', "Check simple get with absolute key inside a subroutine" ); # Simple delete, many gets ok( $cache->delete, "Check simple delete inside a subroutine" ); ok( !$cache->get, "Check whether simple get inside a subroutinei fails" ); ok( !$cache->get( key => '::bar' ), "Check whether get with relative key inside a subroutine fails" ); ok( !$cache->get( key => 'Foo::bar' ), "Check whether get with absolute key inside a subroutine fails" ); # Relative key set and delete ok( $cache->set( key => '::bar', value => 'foo2' ), "Check simple set with relative key inside a subroutine" ); is( $cache->get, 'foo2', "Check simple get inside a subroutine after set with relative key" ); ok( $cache->delete( key => '::bar' ), "Check delete with relative key inside a subroutine" ); ok( !$cache->get( key => '::bar' ), "Check whether get with relative key inside a subroutine fails" ); # Absolute key set and delete ok( $cache->set( key => 'Foo::bar', value => 'foo3' ), "Check simple set with absolute key inside a subroutine" ); is( $cache->get, 'foo3', "Check simple get inside a subroutine after set with absolute key" ); ok( $cache->delete( key => 'Foo::bar' ), "Check delete with absolute key inside a subroutine" ); ok( !$cache->get( key => 'Foo::bar' ), "Check whether get with absolute key inside a subroutine fails" ); # Check version support ok( $cache->set('foo4'), "Check simple set for version info" ); is( $cache->get( version => $Foo::VERSION ), 'foo4', "Check if get with version info ok" ); ok( $cache->delete( version => $Foo::VERSION ), "Check if delete with version info ok" ); ok( !$cache->get( version => $Foo::VERSION ), "Check whether get with version inside a subroutine fails" ); ok( !$cache->get( version => $main::VERSION ), "Check whether get with main version inside a subroutine fails" ); ok( !$cache->get( version => $Cache::Memcached::Managed::VERSION ), "Check whether get with module version inside a subroutine fails" ); } #Foo::bar Cache-Memcached-Managed-0.27/t/PaxHeader/002inactive.t000644 000765 000024 00000000372 15000423155 023074 xustar00lnationstaff000000 000000 29 mtime=1744971373.32778363 62 LIBARCHIVE.xattr.com.apple.quarantine=MDI4MTs2ODAyMjY2ZDs7 53 SCHILY.xattr.com.apple.quarantine=0281;6802266d;; 57 LIBARCHIVE.xattr.com.apple.provenance=AQIAJs5OR7WSpMg 49 SCHILY.xattr.com.apple.provenance=&ÎNGµ’¤È Cache-Memcached-Managed-0.27/t/002inactive.t000644 000765 000024 00000003723 15000423155 021126 0ustar00lnationstaff000000 000000 # Set up tests and strictness use Test::More tests => 68; use strict; use warnings; use lib '.'; # Make sure we have all the support routines require 'testlib'; my $class = 'Cache::Memcached::Managed'; my $inactive = $class.'::Inactive'; # Make sure we can load the module, both active and inactive require_ok( $_ ) foreach 'Cache::Memcached',$class,$inactive; # Create inactive cache object indirectly my $cache = $class->new( inactive => 1 ); isa_ok( $cache,$inactive,"Check whether object #1 ok" ); check_methods($cache); # Create inactive cache object directly $cache = $inactive->new; isa_ok( $cache,$inactive,"Check whether object #2 ok" ); check_methods( $cache ); # Create a cache object with default memcached servers $cache = $class->new; isa_ok( $cache, $class, "Check whether object #3 ok" ); #check_methods( $cache ); #------------------------------------------------------------------------- # check_methods # # Check whether all the methods are indeed inactive. Good for 32 tests. # # IN: 1 instantiated object sub check_methods { my ($cache) = @_; # Check methods returning undef always ok( !defined( $cache->$_ ), "Check result of inactive method $_" ) foreach qw( add data decr delete delete_group delimiter directory expiration flush_all flush_interval get incr namespace replace reset set start stop ); # Check all methods that always return a hash ref is_deeply( $cache->$_, {}, "Check result of inactive method $_" ) foreach qw( errors get_group get_multi grab_group group stats version ); # Check all methods returning a list in array context is_deeply( [$cache->$_], [], "Check result of list inactive method $_" ) foreach qw( dead group_names servers ); # Check all methods returning a hash ref in scalar context is_deeply( scalar $cache->$_, {}, "Check result of scalar inactive method $_") foreach qw( dead group_names servers ); } #check_methods