File Coverage

lib/Redis/CappedCollection.pm
Criterion Covered Total %
statement 50 577 8.6
branch 0 384 0.0
condition 0 227 0.0
subroutine 17 76 22.3
pod 21 21 100.0
total 88 1285 6.8


line stmt bran cond sub pod time code
1             package Redis::CappedCollection;
2              
3             =head1 NAME
4              
5             Redis::CappedCollection - Provides fixed size (determined by 'maxmemory'
6             Redis server setting) collections with FIFO data removal.
7              
8             =head1 VERSION
9              
10             This documentation refers to C version 1.08
11              
12             =cut
13              
14             #-- Pragmas --------------------------------------------------------------------
15              
16 98     98   8263042 use 5.010;
  98         238  
17 98     98   308 use strict;
  98         110  
  98         1492  
18 98     98   264 use warnings;
  98         117  
  98         1879  
19 98     98   12562 use bytes;
  98         265  
  98         343  
20              
21             # ENVIRONMENT ------------------------------------------------------------------
22              
23             our $VERSION = '1.08';
24              
25 98         4755 use Exporter qw(
26             import
27 98     98   3124 );
  98         106  
28              
29             our @EXPORT_OK = qw(
30             $DATA_VERSION
31             $DEFAULT_CONNECTION_TIMEOUT
32             $DEFAULT_OPERATION_TIMEOUT
33             $DEFAULT_SERVER
34             $DEFAULT_PORT
35             $NAMESPACE
36             $MIN_MEMORY_RESERVE
37             $MAX_MEMORY_RESERVE
38             $DEFAULT_MIN_CLEANUP_ITEMS
39              
40             $E_NO_ERROR
41             $E_MISMATCH_ARG
42             $E_DATA_TOO_LARGE
43             $E_NETWORK
44             $E_MAXMEMORY_LIMIT
45             $E_MAXMEMORY_POLICY
46             $E_COLLECTION_DELETED
47             $E_REDIS
48             $E_DATA_ID_EXISTS
49             $E_OLDER_THAN_ALLOWED
50             $E_NONEXISTENT_DATA_ID
51             $E_INCOMP_DATA_VERSION
52             $E_REDIS_DID_NOT_RETURN_DATA
53             $E_UNKNOWN_ERROR
54             );
55              
56             #-- load the modules -----------------------------------------------------------
57              
58 98     98   319 use Carp;
  98         91  
  98         3684  
59 98     98   32370 use Const::Fast;
  98         159355  
  98         494  
60 98         4510 use Digest::SHA1 qw(
61             sha1_hex
62 98     98   39918 );
  98         42067  
63 98         6559 use List::Util qw(
64             min
65 98     98   473 );
  98         147  
66 98     98   1764 use Mouse;
  98         41565  
  98         594  
67 98     98   28967 use Mouse::Util::TypeConstraints;
  98         98  
  98         571  
68 98         6089 use Params::Util qw(
69             _ARRAY
70             _ARRAY0
71             _HASH0
72             _CLASSISA
73             _INSTANCE
74             _NONNEGINT
75             _NUMBER
76             _STRING
77 98     98   27488 );
  98         86363  
78 98     98   53567 use Redis '1.976';
  98         1629878  
  98         2706  
79 98         4471 use Redis::CappedCollection::Util qw(
80             format_message
81 98     98   27015 );
  98         188  
82 98     98   421 use Try::Tiny;
  98         82  
  98         787853  
83              
84             class_type 'Redis';
85             class_type 'Test::RedisServer';
86              
87             #-- declarations ---------------------------------------------------------------
88              
89             =head1 SYNOPSIS
90              
91             use 5.010;
92             use strict;
93             use warnings;
94              
95             #-- Common
96             use Redis::CappedCollection qw(
97             $DEFAULT_SERVER
98             $DEFAULT_PORT
99             );
100              
101             my $server = $DEFAULT_SERVER.':'.$DEFAULT_PORT;
102             my $coll = Redis::CappedCollection->create( redis => { server => $server } );
103              
104             # Insert new data into collection
105             my $list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' );
106              
107             # Change the element of the list with the ID $list_id
108             $updated = $coll->update( $list_id, $data_id, 'New data' );
109              
110             # Get data from a list with the ID $list_id
111             @data = $coll->receive( $list_id );
112             # or to obtain the data ordered from the oldest to the newest
113             while ( my ( $list_id, $data ) = $coll->pop_oldest ) {
114             say "List '$list_id' had '$data'";
115             }
116              
117             A brief example of the C usage is provided in
118             L section.
119              
120             The data structures used by C on Redis server
121             are explained in L section.
122              
123             =head1 ABSTRACT
124              
125             Redis::CappedCollection module provides fixed sized collections that have
126             a auto-FIFO age-out feature.
127              
128             The collection consists of multiple lists containing data items ordered
129             by time. Each list must have an unique ID within the collection and each
130             data item has unique ID within its list.
131              
132             Automatic data removal (when size limit is reached) may remove the oldest
133             item from any list.
134              
135             Collection size is determined by 'maxmemory' Redis server setting.
136              
137             =head1 DESCRIPTION
138              
139             Main features of the package are:
140              
141             =over 3
142              
143             =item *
144              
145             Support creation of capped collection, status monitoring,
146             updating the data set, obtaining consistent data from the collection,
147             automatic data removal, error reporting.
148              
149             =item *
150              
151             Simple API for inserting and retrieving data and for managing collection.
152              
153             =back
154              
155             Capped collections are fixed-size collections that have an auto-FIFO
156             age-out feature based on the time of the inserted data. When collection
157             size reaches memory limit, the oldest data elements are removed automatically
158             to provide space for the new elements.
159              
160             The lists in capped collection store their data items ordered by item time.
161              
162             To insert a new data item into the capped collection, provide list ID, data ID,
163             data and optional data time (current time is used if not specified).
164             If there is a list with the given ID, the data is inserted into the existing list,
165             otherwise the new list is created automatically.
166              
167             You may update the existing data in the collection, providing list ID, data ID and
168             optional data time. If no time is specified, the updated data will keep
169             its existing time.
170              
171             Once the space is fully utilized, newly added data will replace
172             the oldest data in the collection.
173              
174             Limits are specified when the collection is created.
175             Collection size is determined by 'maxmemory' redis server settings.
176              
177             The package includes the utilities to dump and restore the collection:
178             F, F .
179              
180             =head2 EXPORT
181              
182             None by default.
183              
184             Additional constants are available for import, which can be used
185             to define some type of parameters.
186              
187             These are the defaults:
188              
189             =head3 $DEFAULT_SERVER
190              
191             Default Redis local server: C<'localhost'>.
192              
193             =cut
194             const our $DEFAULT_SERVER => 'localhost';
195              
196             =head3 $DEFAULT_PORT
197              
198             Default Redis server port: 6379.
199              
200             =cut
201             const our $DEFAULT_PORT => 6379;
202              
203             =head3 $DEFAULT_CONNECTION_TIMEOUT
204              
205             Default socket timeout for connection, number of seconds: 0.1 .
206              
207             =cut
208             const our $DEFAULT_CONNECTION_TIMEOUT => 0.1;
209              
210             =head3 $DEFAULT_OPERATION_TIMEOUT
211              
212             Default socket timeout for read and write operations, number of seconds: 1.
213              
214             =cut
215             const our $DEFAULT_OPERATION_TIMEOUT => 1;
216              
217             =head3 $NAMESPACE
218              
219             Namespace name used keys on the Redis server: C<'C'>.
220              
221             =cut
222             const our $NAMESPACE => 'C';
223              
224             =head3 $MIN_MEMORY_RESERVE, $MAX_MEMORY_RESERVE
225              
226             Minimum and maximum memory reserve limits based on 'maxmemory'
227             configuration of the Redis server.
228              
229             Not used when C<'maxmemory'> = 0 (not set in the F).
230              
231             The following values are used by default:
232              
233             $MIN_MEMORY_RESERVE = 0.05; # 5%
234             $MAX_MEMORY_RESERVE = 0.5; # 50%
235              
236             =cut
237             const our $MIN_MEMORY_RESERVE => 0.05; # 5% memory reserve coefficient
238             const our $MAX_MEMORY_RESERVE => 0.5; # 50% memory reserve coefficient
239              
240              
241             =head3 $DEFAULT_MIN_CLEANUP_ITEMS
242              
243             Number of additional elements to delete from collection during cleanup procedure when collection size
244             exceeds 'maxmemory'.
245              
246             Default 100 elements. 0 means no minimal cleanup required,
247             so memory cleanup will be performed only to free up sufficient amount of memory.
248              
249             =cut
250             const our $DEFAULT_MIN_CLEANUP_ITEMS => 100;
251              
252             =head3 $DATA_VERSION
253              
254             Current data structure version - 3.
255              
256             =cut
257             const our $DATA_VERSION => 3; # incremented for each incompatible data structure change
258              
259             =over
260              
261             =item Error codes
262              
263             More details about error codes are provided in L section.
264              
265             =back
266              
267             Possible error codes:
268              
269             =cut
270              
271             =over 3
272              
273             =item C<$E_NO_ERROR>
274              
275             0 - No error
276              
277             =cut
278             const our $E_NO_ERROR => -1000;
279              
280             =item C<$E_MISMATCH_ARG>
281              
282             1 - Invalid argument.
283              
284             Thrown by methods when there is a missing required argument or argument value is invalid.
285              
286             =cut
287             const our $E_MISMATCH_ARG => -1001;
288              
289             =item C<$E_DATA_TOO_LARGE>
290              
291             2 - Data is too large.
292              
293             =cut
294             const our $E_DATA_TOO_LARGE => -1002;
295              
296             =item C<$E_NETWORK>
297              
298             3 - Error in connection to Redis server.
299              
300             =cut
301             const our $E_NETWORK => -1003;
302              
303             =item C<$E_MAXMEMORY_LIMIT>
304              
305             4 - Command not allowed when used memory > 'maxmemory'.
306              
307             This means that the command is not allowed when used memory > C
308             in the F file.
309              
310             =cut
311             const our $E_MAXMEMORY_LIMIT => -1004;
312              
313             =item C<$E_MAXMEMORY_POLICY>
314              
315             5 - Redis server have incompatible C setting.
316              
317             Thrown when Redis server have incompatible C setting in F.
318              
319             =cut
320             const our $E_MAXMEMORY_POLICY => -1005;
321              
322             =item C<$E_COLLECTION_DELETED>
323              
324             6 - Collection elements was removed prior to use.
325              
326             This means that the system part of the collection was removed prior to use.
327              
328             =cut
329             const our $E_COLLECTION_DELETED => -1006;
330              
331             =item C<$E_REDIS>
332              
333             7 - Redis error message.
334              
335             This means that other Redis error message detected.
336              
337             =cut
338             const our $E_REDIS => -1007;
339              
340             =item C<$E_DATA_ID_EXISTS>
341              
342             8 - Attempt to add data with an existing ID
343              
344             This means that you are trying to insert data with an ID that is already in
345             the data list.
346              
347             =cut
348             const our $E_DATA_ID_EXISTS => -1008;
349              
350             =item C<$E_OLDER_THAN_ALLOWED>
351              
352             9 - Attempt to add outdated data
353              
354             This means that you are trying to insert the data with the time older than
355             the time of the oldest element currently stored in collection.
356              
357             =cut
358             const our $E_OLDER_THAN_ALLOWED => -1009;
359              
360             =item C<$E_NONEXISTENT_DATA_ID>
361              
362             10 - Attempt to access the elements missing in the collection.
363              
364             This means that you are trying to update data which does not exist.
365              
366             =cut
367             const our $E_NONEXISTENT_DATA_ID => -1010;
368              
369             =item C<$E_INCOMP_DATA_VERSION>
370              
371             11 - Attempt to access the collection with incompatible data structure, created
372             by an older or newer version of this module.
373              
374             =cut
375             const our $E_INCOMP_DATA_VERSION => -1011;
376              
377             =item C<$E_REDIS_DID_NOT_RETURN_DATA>
378              
379             12 - The Redis server did not return data.
380              
381             Check the settings in the file F.
382              
383             =cut
384             const our $E_REDIS_DID_NOT_RETURN_DATA => -1012;
385              
386             =item C<$E_UNKNOWN_ERROR>
387              
388             13 - Unknown error.
389              
390             Possibly you should modify the constructor parameters for more intense automatic memory release.
391              
392             =back
393              
394             =cut
395             const our $E_UNKNOWN_ERROR => -1013;
396              
397             our %ERROR = (
398             $E_NO_ERROR => 'No error',
399             $E_MISMATCH_ARG => 'Invalid argument',
400             $E_DATA_TOO_LARGE => 'Data is too large',
401             $E_NETWORK => 'Error in connection to Redis server',
402             $E_MAXMEMORY_LIMIT => "Command not allowed when used memory > 'maxmemory'",
403             $E_MAXMEMORY_POLICY => "Redis server have incompatible 'maxmemory-policy' setting. Use 'noeviction' only.",
404             $E_COLLECTION_DELETED => 'Collection elements was removed prior to use',
405             $E_REDIS => 'Redis error message',
406             $E_DATA_ID_EXISTS => 'Attempt to add data to an existing ID',
407             $E_OLDER_THAN_ALLOWED => 'Attempt to add data over outdated',
408             $E_NONEXISTENT_DATA_ID => 'Non-existent data id',
409             $E_INCOMP_DATA_VERSION => 'Incompatible data version',
410             $E_REDIS_DID_NOT_RETURN_DATA => 'The Redis server did not return data',
411             $E_UNKNOWN_ERROR => 'Unknown error',
412             );
413              
414             const our $REDIS_ERROR_CODE => 'ERR';
415             const our $REDIS_MEMORY_ERROR_CODE => 'OOM';
416             const our $REDIS_MEMORY_ERROR_MSG => "$REDIS_MEMORY_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }.";
417             const our $MAX_DATASIZE => 512*1024*1024; # A String value can be at max 512 Megabytes in length.
418             const my $MAX_REMOVE_RETRIES => 2; # the number of remove retries when memory limit is near
419             const my $USED_MEMORY_POLICY => 'noeviction';
420              
421             # status field names
422             const my $_LISTS => 'lists';
423             const my $_ITEMS => 'items';
424             const my $_OLDER_ALLOWED => 'older_allowed';
425             const my $_MIN_CLEANUP_BYTES => 'min_cleanup_bytes';
426             const my $_MIN_CLEANUP_ITEMS => 'min_cleanup_items';
427             const my $_MEMORY_RESERVE => 'memory_reserve';
428             const my $_DATA_VERSION => 'data_version';
429             const my $_LAST_REMOVED_TIME => 'last_removed_time';
430             const my $_LAST_CLEANUP_BYTES => 'last_cleanup_bytes';
431             # information fields
432             const my $_LAST_CLEANUP_ITEMS => 'last_cleanup_items';
433             const my $_LAST_CLEANUP_MAXMEMORY => 'last_cleanup_maxmemory';
434             const my $_LAST_CLEANUP_USED_MEMORY => 'last_cleanup_used_memory';
435             const my $_LAST_CLEANUP_BYTES_MUST_BE_DELETED => 'last_bytes_must_be_deleted';
436             const my $_INSERTS_SINCE_CLEANING => 'inserts_since_cleaning';
437             const my $_UPDATES_SINCE_CLEANING => 'updates_since_cleaning';
438              
439             my $_lua_namespace = "local NAMESPACE = '".$NAMESPACE."'";
440             my $_lua_queue_key = "local QUEUE_KEY = NAMESPACE..':Q:'..coll_name";
441             my $_lua_status_key = "local STATUS_KEY = NAMESPACE..':S:'..coll_name";
442             my $_lua_data_keys = "local DATA_KEYS = NAMESPACE..':D:'..coll_name";
443             my $_lua_time_keys = "local TIME_KEYS = NAMESPACE..':T:'..coll_name";
444             my $_lua_data_key = "local DATA_KEY = DATA_KEYS..':'..list_id";
445             my $_lua_time_key = "local TIME_KEY = TIME_KEYS..':'..list_id";
446              
447             my %lua_script_body;
448              
449             my $_lua_clean_data = <<"END_CLEAN_DATA";
450             -- remove the control structures of the collection
451             if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then
452             ret = ret + redis.call( 'DEL', QUEUE_KEY )
453             end
454              
455             -- each element of the list are deleted separately, as total number of items may be too large to send commands 'DEL'
456             $_lua_data_keys
457             $_lua_time_keys
458              
459             local arr = redis.call( 'KEYS', DATA_KEYS..':*' )
460             if #arr > 0 then
461              
462             -- remove structures store data lists
463             for i = 1, #arr do
464             ret = ret + redis.call( 'DEL', arr[i] )
465             end
466              
467             -- remove structures store time lists
468             arr = redis.call( 'KEYS', TIME_KEYS..':*' )
469             for i = 1, #arr do
470             ret = ret + redis.call( 'DEL', arr[i] )
471             end
472              
473             end
474              
475             return ret
476             END_CLEAN_DATA
477              
478             my $_lua_cleaning = <<"END_CLEANING";
479             local REDIS_USED_MEMORY = 0
480             local REDIS_MAXMEMORY = 0
481             local ROLLBACK = {}
482             local TOTAL_BYTES_DELETED = 0
483             local LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0
484             local LAST_CLEANUP_BYTES = 0
485             local LAST_CLEANUP_ITEMS = 0
486             local LAST_OPERATION = ''
487             local INSERTS_SINCE_CLEANING = 0
488             local UPDATES_SINCE_CLEANING = 0
489              
490             local _DEBUG, _DEBUG_ID, _FUNC_NAME
491              
492             local table_merge = function ( t1, t2 )
493             for key, val in pairs( t2 ) do
494             t1[ key ] = val
495             end
496             end
497              
498             local _debug_log = function ( values )
499             table_merge( values, {
500             _DEBUG_ID = _DEBUG_ID,
501             _FUNC_NAME = _FUNC_NAME,
502             REDIS_USED_MEMORY = REDIS_USED_MEMORY,
503             list_id = list_id,
504             data_id = data_id,
505             data_len = #data,
506             ROLLBACK = ROLLBACK
507             } )
508              
509             redis.log( redis.LOG_NOTICE, _FUNC_NAME..': '..cjson.encode( values ) )
510             end
511              
512             local _setup = function ( argv_idx, func_name, extra_data_len )
513             LAST_OPERATION = func_name
514              
515             REDIS_MAXMEMORY = tonumber( redis.call( 'CONFIG', 'GET', 'maxmemory' )[2] )
516             local memory_reserve_coefficient = 1 + tonumber( redis.call( 'HGET', STATUS_KEY, '$_MEMORY_RESERVE' ) )
517              
518             local redis_used_memory = string.match(
519             redis.call( 'INFO', 'memory' ),
520             'used_memory:(%d+)'
521             )
522             REDIS_USED_MEMORY = tonumber( redis_used_memory )
523             LAST_CLEANUP_BYTES_MUST_BE_DELETED = REDIS_USED_MEMORY + extra_data_len - math.floor( REDIS_MAXMEMORY / memory_reserve_coefficient )
524             if LAST_CLEANUP_BYTES_MUST_BE_DELETED < 0 or REDIS_MAXMEMORY == 0 then
525             LAST_CLEANUP_BYTES_MUST_BE_DELETED = 0
526             end
527              
528             LAST_CLEANUP_BYTES = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_CLEANUP_BYTES' ) )
529             if LAST_CLEANUP_BYTES == nil then
530             LAST_CLEANUP_BYTES = 0
531             end
532              
533             INSERTS_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING' ) )
534             if INSERTS_SINCE_CLEANING == nil then
535             INSERTS_SINCE_CLEANING = 0
536             end
537             UPDATES_SINCE_CLEANING = tonumber( redis.call( 'HGET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING' ) )
538             if UPDATES_SINCE_CLEANING == nil then
539             UPDATES_SINCE_CLEANING = 0
540             end
541              
542             _FUNC_NAME = func_name
543             _DEBUG_ID = tonumber( ARGV[ argv_idx ] )
544             if _DEBUG_ID ~= 0 then
545             _DEBUG = true
546             _debug_log( {
547             _STEP = '_setup',
548             maxmemory = REDIS_MAXMEMORY,
549             redis_used_memory = REDIS_USED_MEMORY,
550             bytes_must_be_deleted = LAST_CLEANUP_BYTES_MUST_BE_DELETED
551             } )
552             else
553             _DEBUG = false
554             end
555             end
556              
557             local cleaning_error = function ( error_msg )
558             if _DEBUG then
559             _debug_log( {
560             _STEP = 'cleaning_error',
561             error_msg = error_msg
562             } )
563             end
564              
565             for _, rollback_command in ipairs( ROLLBACK ) do
566             redis.call( unpack( rollback_command ) )
567             end
568             -- Level 2 points the error to where the function that called error was called
569             error( error_msg, 2 )
570             end
571              
572             local table_val_to_str = function ( v )
573             if "string" == type( v ) then
574             v = string.gsub( v, "\\n", "\\\\n" )
575             if string.match( string.gsub( v, "[^'\\"]", "" ), '^"+\$' ) then
576             return "'" .. v .. "'"
577             end
578             return '"' .. string.gsub( v, '"', '\\\\"' ) .. '"'
579             else
580             return "table" == type( v ) and table_tostring( v ) or tostring( v )
581             end
582             end
583              
584             local table_key_to_str = function ( k )
585             if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*\$" ) then
586             return k
587             else
588             return "[" .. table_val_to_str( k ) .. "]"
589             end
590             end
591              
592             local table_tostring = function ( tbl )
593             local result, done = {}, {}
594             for k, v in ipairs( tbl ) do
595             table.insert( result, table_val_to_str( v ) )
596             done[ k ] = true
597             end
598             for k, v in pairs( tbl ) do
599             if not done[ k ] then
600             table.insert( result, table_key_to_str( k ) .. "=" .. table_val_to_str( v ) )
601             end
602             end
603             return "{" .. table.concat( result, "," ) .. "}"
604             end
605              
606             -- deleting old data to make room for new data
607             local cleaning = function ( list_id, data_id, is_cleaning_needed )
608             local coll_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_ITEMS' ) )
609              
610             if coll_items == 0 then
611             return
612             end
613              
614             local min_cleanup_bytes = tonumber( redis.call( 'HGET', STATUS_KEY, '$_MIN_CLEANUP_BYTES' ) )
615             local min_cleanup_items = tonumber( redis.call( 'HGET', STATUS_KEY, '$_MIN_CLEANUP_ITEMS' ) )
616              
617             local cleanup_bytes = math.max( min_cleanup_bytes, LAST_CLEANUP_BYTES_MUST_BE_DELETED )
618              
619             if not ( is_cleaning_needed or LAST_CLEANUP_BYTES_MUST_BE_DELETED > 0 ) then
620             return
621             end
622              
623             if _DEBUG then
624             _debug_log( {
625             _STEP = 'Before cleanings',
626             coll_items = coll_items,
627             min_cleanup_items = min_cleanup_items,
628             min_cleanup_bytes = min_cleanup_bytes,
629             } )
630             end
631              
632             local items_deleted = 0
633             local bytes_deleted = 0
634             local lists_deleted = 0
635              
636             repeat
637             if redis.call( 'EXISTS', QUEUE_KEY ) ~= 1 then
638             -- Level 2 points the error to where the function that called error was called
639             error( 'Queue key does not exist', 2 )
640             end
641              
642             -- continue to work with the to_delete (requiring removal) data and for them using the prefix 'to_delete_'
643             local to_delete_list_id, last_removed_time = unpack( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' ) )
644             last_removed_time = tonumber( last_removed_time )
645             -- key data structures
646             local to_delete_data_key = DATA_KEYS..':'..to_delete_list_id
647             local to_delete_time_key = TIME_KEYS..':'..to_delete_list_id
648              
649             -- looking for the oldest data
650             local to_delete_data_id
651             local to_delete_data
652             local items = redis.call( 'HLEN', to_delete_data_key )
653             -- #FIXME: to_delete_data -> to_delete_data_len
654             -- HSTRLEN key field
655             -- Available since 3.2.0.
656             if items == 1 then
657             to_delete_data_id, to_delete_data = unpack( redis.call( 'HGETALL', to_delete_data_key ) )
658             else
659             to_delete_data_id = redis.call( 'ZRANGE', to_delete_time_key, 0, 0 )[1]
660             to_delete_data = redis.call( 'HGET', to_delete_data_key, to_delete_data_id )
661             end
662             local to_delete_data_len = #to_delete_data
663             to_delete_data = nil -- free memory
664              
665             if _DEBUG then
666             _debug_log( {
667             _STEP = 'Before real cleaning',
668             items = items,
669             to_delete_list_id = to_delete_list_id,
670             to_delete_data_id = to_delete_data_id,
671             to_delete_data_len = to_delete_data_len
672             } )
673             end
674              
675             if to_delete_list_id == list_id and to_delete_data_id == data_id then
676             if items_deleted == 0 then
677             -- Its first attempt to clean the conflicting data, for which the primary operation executed.
678             -- In this case, we are roll back operations that have been made before, and immediately return an error,
679             -- shifting the handling of such errors on the client.
680             cleaning_error( "$REDIS_MEMORY_ERROR_MSG" )
681             end
682             break
683             end
684              
685             if _DEBUG then
686             _debug_log( {
687             _STEP = 'Why it is cleared?',
688             coll_items = coll_items,
689             min_cleanup_bytes = min_cleanup_bytes,
690             min_cleanup_items = min_cleanup_items,
691             items_deleted = items_deleted,
692             bytes_deleted = bytes_deleted,
693             } )
694             end
695              
696             -- actually remove the oldest item
697             redis.call( 'HDEL', to_delete_data_key, to_delete_data_id )
698             items = items - 1
699             coll_items = coll_items - 1
700              
701             redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time )
702              
703             if items > 0 then
704             -- If the list has more data
705             redis.call( 'ZREM', to_delete_time_key, to_delete_data_id )
706             local oldest_item_time = tonumber( redis.call( 'ZRANGE', to_delete_time_key, 0, 0, 'WITHSCORES' )[2] )
707             redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, to_delete_list_id )
708              
709             if items == 1 then
710             redis.call( 'DEL', to_delete_time_key )
711             end
712             else
713             -- If the list does not have data
714             -- remove the name of the list from the queue collection
715             redis.call( 'ZREM', QUEUE_KEY, to_delete_list_id )
716             lists_deleted = lists_deleted + 1
717             end
718              
719             -- amount of data collection decreased
720             items_deleted = items_deleted + 1
721             bytes_deleted = bytes_deleted + to_delete_data_len
722              
723             if _DEBUG then
724             _debug_log( {
725             _STEP = 'After real cleaning',
726             to_delete_data_key = to_delete_data_key,
727             to_delete_data_id = to_delete_data_id,
728             items = items,
729             items_deleted = items_deleted,
730             bytes_deleted = bytes_deleted,
731             } )
732             end
733              
734             until
735             coll_items <= 0
736             or (
737             items_deleted >= min_cleanup_items
738             and bytes_deleted >= cleanup_bytes
739             )
740              
741             if items_deleted > 0 then
742             -- reduce the number of items in the collection
743             redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -items_deleted )
744             end
745             if lists_deleted > 0 then
746             -- reduce the number of lists stored in a collection
747             redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -lists_deleted )
748             end
749              
750             if _DEBUG then
751             _debug_log( {
752             _STEP = 'Cleaning finished',
753             items_deleted = items_deleted,
754             bytes_deleted = bytes_deleted,
755             lists_deleted = lists_deleted,
756             min_cleanup_bytes = min_cleanup_bytes,
757             min_cleanup_items = min_cleanup_items,
758             coll_items = coll_items,
759             } )
760             end
761              
762             if bytes_deleted > 0 then
763             if TOTAL_BYTES_DELETED == 0 then -- first cleaning
764             LAST_CLEANUP_BYTES = bytes_deleted
765             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', LAST_CLEANUP_BYTES )
766             INSERTS_SINCE_CLEANING = 0
767             redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING )
768             UPDATES_SINCE_CLEANING = 0
769             redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING )
770             else
771             LAST_CLEANUP_BYTES = LAST_CLEANUP_BYTES + bytes_deleted
772             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', LAST_CLEANUP_BYTES )
773             end
774              
775             TOTAL_BYTES_DELETED = TOTAL_BYTES_DELETED + bytes_deleted
776             LAST_CLEANUP_ITEMS = LAST_CLEANUP_ITEMS + items_deleted
777             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS', LAST_CLEANUP_ITEMS )
778              
779             -- information values
780             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_ITEMS', LAST_CLEANUP_ITEMS )
781             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_MAXMEMORY', REDIS_MAXMEMORY )
782             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_USED_MEMORY', REDIS_USED_MEMORY )
783             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES_MUST_BE_DELETED', LAST_CLEANUP_BYTES_MUST_BE_DELETED )
784             end
785             end
786              
787             local call_with_error_control = function ( list_id, data_id, ... )
788             local retries = $MAX_REMOVE_RETRIES
789             local ret
790             local error_msg = ''
791             repeat
792             ret = redis.pcall( ... )
793             if type( ret ) == 'table' and ret.err ~= nil then
794             error_msg = "$REDIS_MEMORY_ERROR_MSG - " .. ret.err .. " (call = " .. table_tostring( { ... } ) .. ")"
795             if _DEBUG then
796             _debug_log( {
797             _STEP = 'call_with_error_control',
798             error_msg = error_msg,
799             retries = retries
800             } )
801             end
802              
803             cleaning( list_id, data_id, true )
804             else
805             break
806             end
807             retries = retries - 1
808             until retries == 0
809              
810             if retries == 0 then
811             -- Operation returned an error related to insufficient memory.
812             -- Start cleaning process and then re-try operation.
813             -- Repeat the cycle of operation + memory cleaning a couple of times and return an error / fail,
814             -- if it still did not work.
815             cleaning_error( error_msg )
816             end
817              
818             return ret
819             end
820             END_CLEANING
821              
822             $lua_script_body{insert} = <<"END_INSERT";
823             -- adding data to a list of collections
824              
825             local coll_name = ARGV[1]
826             local list_id = ARGV[2]
827             local data_id = ARGV[3]
828             local data = ARGV[4]
829             local data_time = tonumber( ARGV[5] )
830              
831             -- key data storage structures
832             $_lua_namespace
833             $_lua_queue_key
834             $_lua_status_key
835             $_lua_data_keys
836             $_lua_time_keys
837             $_lua_data_key
838             $_lua_time_key
839              
840             -- determine whether there is a list of data and a collection
841             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
842             return { $E_COLLECTION_DELETED, 0, 0, 0 }
843             end
844              
845             -- verification of the existence of old data with new data identifier
846             if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then
847             return { $E_DATA_ID_EXISTS, 0, 0, 0 }
848             end
849              
850             -- Validating the time of new data, if required
851             local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) )
852              
853             if redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ~= '1' then
854             if redis.call( 'EXISTS', QUEUE_KEY ) == 1 then
855             if data_time < last_removed_time then
856             return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 }
857             end
858             end
859             end
860              
861             -- deleting obsolete data, if it is necessary
862             $_lua_cleaning
863             local data_len = #data
864             _setup( 6, 'insert', data_len ) -- 6 -> is the index of ARGV[6]
865             cleaning( list_id, data_id, false )
866              
867             -- add data to the list
868             -- Remember that the list and the collection can be automatically deleted after the "crowding out" old data
869              
870             -- the existing data
871             local items = redis.call( 'HLEN', DATA_KEY )
872             local existing_id, existing_time
873             if items == 1 then
874             existing_id = redis.call( 'HGETALL', DATA_KEY )[1]
875             existing_time = tonumber( redis.call( 'ZSCORE', QUEUE_KEY, list_id ) )
876             end
877              
878             -- actually add data to the list
879             call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data )
880             data = nil -- free memory
881             table.insert( ROLLBACK, 1, { 'HDEL', DATA_KEY, data_id } )
882              
883             if redis.call( 'HLEN', DATA_KEY ) == 1 then -- list recreated after cleaning
884             redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', 1 )
885             table.insert( ROLLBACK, 1, { 'HINCRBY', STATUS_KEY, '$_LISTS', -1 } )
886             call_with_error_control( list_id, data_id, 'ZADD', QUEUE_KEY, data_time, list_id )
887             else
888             if items == 1 then
889             call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, existing_time, existing_id )
890             table.insert( ROLLBACK, 1, { 'ZREM', TIME_KEY, existing_id } )
891             end
892             call_with_error_control( list_id, data_id, 'ZADD', TIME_KEY, data_time, data_id )
893             local oldest_item_time = redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2]
894             redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id )
895             end
896              
897             -- reflect the addition of new data
898             redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', 1 )
899             if data_time < last_removed_time then
900             redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 )
901             end
902              
903             redis.call( 'HSET', STATUS_KEY, '$_LAST_CLEANUP_BYTES', LAST_CLEANUP_BYTES )
904             INSERTS_SINCE_CLEANING = INSERTS_SINCE_CLEANING + 1
905             redis.call( 'HSET', STATUS_KEY, '$_INSERTS_SINCE_CLEANING', INSERTS_SINCE_CLEANING )
906              
907             return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED }
908             END_INSERT
909              
910             $lua_script_body{update} = <<"END_UPDATE";
911             -- update the data in the list of collections
912              
913             local coll_name = ARGV[1]
914             local list_id = ARGV[2]
915             local data_id = ARGV[3]
916             local data = ARGV[4]
917             local new_data_time = tonumber( ARGV[5] )
918              
919             -- key data storage structures
920             $_lua_namespace
921             $_lua_queue_key
922             $_lua_status_key
923             $_lua_data_keys
924             $_lua_time_keys
925             $_lua_data_key
926             $_lua_time_key
927              
928             -- determine whether there is a list of data and a collection
929             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
930             return { $E_COLLECTION_DELETED, 0, 0, 0 }
931             end
932             if redis.call( 'EXISTS', DATA_KEY ) ~= 1 then
933             return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 }
934             end
935             local extra_data_len
936             local data_len = #data
937             if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then
938             -- #FIXME: existed_data -> existed_data_len
939             -- HSTRLEN key field
940             -- Available since 3.2.0.
941             local existed_data = redis.call( 'HGET', DATA_KEY, data_id )
942             extra_data_len = data_len - #existed_data
943             existed_data = nil -- free memory
944             else
945             return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 }
946             end
947              
948             local last_removed_time = tonumber( redis.call( 'HGET', STATUS_KEY, '$_LAST_REMOVED_TIME' ) )
949             if redis.call( 'HGET', STATUS_KEY, '$_OLDER_ALLOWED' ) ~= '1' then
950             if new_data_time ~= 0 and new_data_time < last_removed_time then
951             return { $E_OLDER_THAN_ALLOWED, 0, 0, 0 }
952             end
953             end
954              
955             -- deleting obsolete data, if it can be necessary
956             $_lua_cleaning
957             _setup( 6, 'update', extra_data_len ) -- 6 is the index of ARGV[6]
958             cleaning( list_id, data_id, false )
959              
960             -- data change
961             -- Remember that the list and the collection can be automatically deleted after the "crowding out" old data
962             if redis.call( 'HEXISTS', DATA_KEY, data_id ) ~= 1 then
963             return { $E_NONEXISTENT_DATA_ID, 0, 0, 0 }
964             end
965              
966             -- data to be changed were not removed
967              
968             -- actually change
969             call_with_error_control( list_id, data_id, 'HSET', DATA_KEY, data_id, data )
970             data = nil -- free memory
971              
972             if new_data_time ~= 0 then
973             if redis.call( 'HLEN', DATA_KEY ) == 1 then
974             redis.call( 'ZADD', QUEUE_KEY, new_data_time, list_id )
975             else
976             redis.call( 'ZADD', TIME_KEY, new_data_time, data_id )
977             local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] )
978             redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id )
979             end
980              
981             if new_data_time < last_removed_time then
982             redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', 0 )
983             end
984             end
985              
986             UPDATES_SINCE_CLEANING = UPDATES_SINCE_CLEANING + 1
987             redis.call( 'HSET', STATUS_KEY, '$_UPDATES_SINCE_CLEANING', UPDATES_SINCE_CLEANING )
988              
989             return { $E_NO_ERROR, LAST_CLEANUP_ITEMS, REDIS_USED_MEMORY, TOTAL_BYTES_DELETED }
990             END_UPDATE
991              
992             $lua_script_body{upsert} = <<"END_UPSERT";
993             -- update or insert the data in the list of collections
994              
995             local coll_name = ARGV[1]
996             local list_id = ARGV[2]
997             local data_id = ARGV[3]
998             local data_time = tonumber( ARGV[5] )
999             local start_time = ARGV[7]
1000              
1001             -- key data storage structures
1002             $_lua_namespace
1003             $_lua_data_keys
1004             $_lua_data_key
1005              
1006             -- verification of the existence of old data with new data identifier
1007             if redis.call( 'HEXISTS', DATA_KEY, data_id ) == 1 then
1008             if data_time == -1 then
1009             ARGV[5] = '0'
1010             end
1011             $lua_script_body{update}
1012             else
1013             if data_time == -1 then
1014             ARGV[5] = start_time
1015             end
1016             $lua_script_body{insert}
1017             end
1018             END_UPSERT
1019              
1020             $lua_script_body{receive} = <<"END_RECEIVE";
1021             -- returns the data from the list
1022              
1023             local coll_name = ARGV[1]
1024             local list_id = ARGV[2]
1025             local mode = ARGV[3]
1026             local data_id = ARGV[4]
1027              
1028             -- key data storage structures
1029             $_lua_namespace
1030             $_lua_status_key
1031             $_lua_data_keys
1032             $_lua_data_key
1033              
1034             -- determine whether there is a list of data and a collection
1035             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1036             -- sort of a mistake
1037             return nil
1038             end
1039              
1040             if mode == 'val' then
1041             -- returns the specified element of the data list
1042             return redis.call( 'HGET', DATA_KEY, data_id )
1043             elseif mode == 'len' then
1044             -- returns the length of the data list
1045             return redis.call( 'HLEN', DATA_KEY )
1046             elseif mode == 'vals' then
1047             -- returns all the data from the list
1048             return redis.call( 'HVALS', DATA_KEY )
1049             elseif mode == 'all' then
1050             -- returns all data IDs and data values of the data list
1051             return redis.call( 'HGETALL', DATA_KEY )
1052             else
1053             -- sort of a mistake
1054             return nil
1055             end
1056              
1057             END_RECEIVE
1058              
1059             $lua_script_body{pop_oldest} = <<"END_POP_OLDEST";
1060             -- retrieve the oldest data stored in the collection
1061              
1062             local coll_name = ARGV[1]
1063              
1064             -- key data storage structures
1065             $_lua_namespace
1066             $_lua_queue_key
1067             $_lua_status_key
1068              
1069             -- determine whether there is a list of data and a collection
1070             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1071             -- sort of a mistake
1072             return { $E_COLLECTION_DELETED, nil, nil, nil }
1073             end
1074             if redis.call( 'EXISTS', QUEUE_KEY ) ~= 1 then
1075             return { $E_NO_ERROR, false, nil, nil }
1076             end
1077              
1078             -- initialize the data returned from the script
1079             local list_exist = 0
1080             local list_id = false
1081             local data = false
1082              
1083             -- identifier of the list with the oldest data
1084             list_id = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0 )[1]
1085              
1086             -- key data storage structures
1087             $_lua_data_keys
1088             $_lua_time_keys
1089             $_lua_data_key
1090             $_lua_time_key
1091              
1092             -- determine whether there is a list of data and a collection
1093             if redis.call( 'EXISTS', DATA_KEY ) ~= 1 then
1094             return { $E_COLLECTION_DELETED, nil, nil, nil }
1095             end
1096              
1097             -- Features the oldest data
1098             local items = redis.call( 'HLEN', DATA_KEY )
1099             local data_id
1100             if items == 1 then
1101             data_id = redis.call( 'HGETALL', DATA_KEY )[1]
1102             else
1103             data_id = redis.call( 'ZRANGE', TIME_KEY, 0, 0 )[1]
1104             end
1105             local last_removed_time = tonumber( redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2] )
1106              
1107             -- get data
1108              
1109             -- actually get data
1110             data = redis.call( 'HGET', DATA_KEY, data_id )
1111              
1112             -- delete the data from the list
1113             redis.call( 'HDEL', DATA_KEY, data_id )
1114             items = items - 1
1115              
1116             -- obtain information about the data that has become the oldest
1117             local oldest_item_time = tonumber( redis.call( 'ZRANGE', TIME_KEY, 0, 0, 'WITHSCORES' )[2] )
1118              
1119             if items > 0 then
1120             -- If the list has more data
1121              
1122             -- delete the information about the time of the data
1123             redis.call( 'ZREM', TIME_KEY, data_id )
1124              
1125             redis.call( 'ZADD', QUEUE_KEY, oldest_item_time, list_id )
1126              
1127             if items == 1 then
1128             -- delete the list data structure 'zset'
1129             redis.call( 'DEL', TIME_KEY )
1130             end
1131             else
1132             -- if the list is no more data
1133             -- delete the list data structure 'zset'
1134             redis.call( 'DEL', TIME_KEY )
1135              
1136             -- reduce the number of lists stored in a collection
1137             redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 )
1138             -- remove the name of the list from the queue collection
1139             redis.call( 'ZREM', QUEUE_KEY, list_id )
1140             end
1141              
1142             redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -1 )
1143             redis.call( 'HSET', STATUS_KEY, '$_LAST_REMOVED_TIME', last_removed_time )
1144              
1145             return { $E_NO_ERROR, true, list_id, data }
1146             END_POP_OLDEST
1147              
1148             $lua_script_body{collection_info} = <<"END_COLLECTION_INFO";
1149             -- to obtain information on the status of the collection
1150              
1151             local coll_name = ARGV[1]
1152              
1153             -- key data storage structures
1154             $_lua_namespace
1155             $_lua_queue_key
1156             $_lua_status_key
1157              
1158             -- determine whether there is a collection
1159             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1160             return { $E_COLLECTION_DELETED, false, false, false, false, false, false, false }
1161             end
1162              
1163             local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2]
1164             local lists, items, older_allowed, min_cleanup_bytes, min_cleanup_items, memory_reserve, data_version, last_removed_time = unpack( redis.call( 'HMGET', STATUS_KEY,
1165             '$_LISTS',
1166             '$_ITEMS',
1167             '$_OLDER_ALLOWED',
1168             '$_MIN_CLEANUP_BYTES',
1169             '$_MIN_CLEANUP_ITEMS',
1170             '$_MEMORY_RESERVE',
1171             '$_DATA_VERSION',
1172             '$_LAST_REMOVED_TIME'
1173             ) )
1174              
1175             if type( data_version ) ~= 'string' then data_version = '0' end
1176              
1177             return {
1178             $E_NO_ERROR,
1179             lists,
1180             items,
1181             older_allowed,
1182             min_cleanup_bytes,
1183             min_cleanup_items,
1184             memory_reserve,
1185             data_version,
1186             last_removed_time,
1187             oldest_item_time
1188             }
1189             END_COLLECTION_INFO
1190              
1191             $lua_script_body{oldest_time} = <<"END_OLDEST_TIME";
1192             -- to obtain time corresponding to the oldest data in the collection
1193              
1194             local coll_name = ARGV[1]
1195              
1196             -- key data storage structures
1197             $_lua_namespace
1198             $_lua_queue_key
1199             $_lua_status_key
1200              
1201             -- determine whe, falther there is a collection
1202             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1203             return { $E_COLLECTION_DELETED, false }
1204             end
1205              
1206             local oldest_item_time = redis.call( 'ZRANGE', QUEUE_KEY, 0, 0, 'WITHSCORES' )[2]
1207             return { $E_NO_ERROR, oldest_item_time }
1208             END_OLDEST_TIME
1209              
1210             $lua_script_body{list_info} = <<"END_LIST_INFO";
1211             -- to obtain information on the status of the data list
1212              
1213             local coll_name = ARGV[1]
1214             local list_id = ARGV[2]
1215              
1216             -- key data storage structures
1217             $_lua_namespace
1218             $_lua_queue_key
1219             $_lua_status_key
1220             $_lua_data_keys
1221             $_lua_data_key
1222              
1223             -- determine whether there is a list of data and a collection
1224             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1225             return { $E_COLLECTION_DELETED, false, nil }
1226             end
1227             if redis.call( 'EXISTS', DATA_KEY ) ~= 1 then
1228             return { $E_NO_ERROR, false, nil }
1229             end
1230              
1231             -- the length of the data list
1232             local items = redis.call( 'HLEN', DATA_KEY )
1233              
1234             -- the second data
1235             local oldest_item_time = redis.call( 'ZSCORE', QUEUE_KEY, list_id )
1236              
1237             return { $E_NO_ERROR, items, oldest_item_time }
1238             END_LIST_INFO
1239              
1240             $lua_script_body{drop_collection} = <<"END_DROP_COLLECTION";
1241             -- to remove the entire collection
1242              
1243             local coll_name = ARGV[1]
1244              
1245             -- key data storage structures
1246             $_lua_namespace
1247             $_lua_queue_key
1248             $_lua_status_key
1249              
1250             -- initialize the data returned from the script
1251             local ret = 0 -- the number of deleted items
1252              
1253             if redis.call( 'EXISTS', STATUS_KEY ) == 1 then
1254             ret = ret + redis.call( 'DEL', STATUS_KEY )
1255             end
1256              
1257             $_lua_clean_data
1258             END_DROP_COLLECTION
1259              
1260             $lua_script_body{clear_collection} = <<"END_CLEAR_COLLECTION";
1261             -- to remove the entire collection data
1262              
1263             local coll_name = ARGV[1]
1264              
1265             -- key data storage structures
1266             $_lua_namespace
1267             $_lua_queue_key
1268             $_lua_status_key
1269              
1270             -- initialize the data returned from the script
1271             local ret = 0 -- the number of deleted items
1272              
1273             redis.call( 'HMSET', STATUS_KEY,
1274             '$_LISTS', 0,
1275             '$_ITEMS', 0,
1276             '$_LAST_REMOVED_TIME', 0
1277             );
1278              
1279             $_lua_clean_data
1280             END_CLEAR_COLLECTION
1281              
1282             $lua_script_body{drop_list} = <<"END_DROP_LIST";
1283             -- to remove the data_list
1284              
1285             local coll_name = ARGV[1]
1286             local list_id = ARGV[2]
1287              
1288             -- key data storage structures
1289             $_lua_namespace
1290             $_lua_queue_key
1291             $_lua_status_key
1292             $_lua_data_keys
1293             $_lua_data_key
1294              
1295             -- determine whether there is a list of data and a collection
1296             if redis.call( 'EXISTS', STATUS_KEY ) ~= 1 then
1297             return { $E_COLLECTION_DELETED, 0 }
1298             end
1299             if redis.call( 'EXISTS', DATA_KEY ) ~= 1 then
1300             return { $E_NO_ERROR, 0 }
1301             end
1302              
1303             -- initialize the data returned from the script
1304             local ret = 0 -- the number of deleted items
1305              
1306             -- key data storage structures
1307             $_lua_time_keys
1308             $_lua_time_key
1309              
1310             -- determine the size of the data in the list and delete the list structure
1311             local bytes_deleted = 0
1312             local vals = redis.call( 'HVALS', DATA_KEY )
1313             local list_items = #vals
1314             for i = 1, list_items do
1315             bytes_deleted = bytes_deleted + #vals[ i ]
1316             end
1317             redis.call( 'DEL', DATA_KEY, TIME_KEY )
1318              
1319             -- reduce the number of items in the collection
1320             redis.call( 'HINCRBY', STATUS_KEY, '$_ITEMS', -list_items )
1321             -- reduce the number of lists stored in a collection
1322             redis.call( 'HINCRBY', STATUS_KEY, '$_LISTS', -1 )
1323             -- remove the name of the list from the queue collection
1324             redis.call( 'ZREM', QUEUE_KEY, list_id )
1325              
1326             return { $E_NO_ERROR, 1 }
1327             END_DROP_LIST
1328              
1329             $lua_script_body{verify_collection} = <<"END_VERIFY_COLLECTION";
1330             -- creation of the collection and characterization of the collection by accessing existing collection
1331              
1332             local coll_name = ARGV[1];
1333             local older_allowed = ARGV[2];
1334             local min_cleanup_bytes = ARGV[3];
1335             local min_cleanup_items = ARGV[4];
1336             local memory_reserve = ARGV[5];
1337              
1338             local data_version = '$DATA_VERSION'
1339              
1340             -- key data storage structures
1341             $_lua_namespace
1342             $_lua_status_key
1343              
1344             -- determine whether there is a collection
1345             local status_exist = redis.call( 'EXISTS', STATUS_KEY );
1346              
1347             if status_exist == 1 then
1348             -- if there is a collection
1349             older_allowed, min_cleanup_bytes, min_cleanup_items, memory_reserve, data_version = unpack( redis.call( 'HMGET', STATUS_KEY,
1350             '$_OLDER_ALLOWED',
1351             '$_MIN_CLEANUP_BYTES',
1352             '$_MIN_CLEANUP_ITEMS',
1353             '$_MEMORY_RESERVE',
1354             '$_DATA_VERSION'
1355             ) );
1356              
1357             if type( data_version ) ~= 'string' then data_version = '0' end
1358             else
1359             -- if you want to create a new collection
1360             redis.call( 'HMSET', STATUS_KEY,
1361             '$_LISTS', 0,
1362             '$_ITEMS', 0,
1363             '$_OLDER_ALLOWED', older_allowed,
1364             '$_MIN_CLEANUP_BYTES', min_cleanup_bytes,
1365             '$_MIN_CLEANUP_ITEMS', min_cleanup_items,
1366             '$_MEMORY_RESERVE', memory_reserve,
1367             '$_DATA_VERSION', data_version,
1368             '$_LAST_REMOVED_TIME', 0,
1369             '$_LAST_CLEANUP_BYTES', 0,
1370             '$_LAST_CLEANUP_ITEMS', 0
1371             );
1372             end
1373              
1374             return {
1375             status_exist,
1376             older_allowed,
1377             min_cleanup_bytes,
1378             min_cleanup_items,
1379             memory_reserve,
1380             data_version
1381             };
1382             END_VERIFY_COLLECTION
1383              
1384             $lua_script_body{_long_term_operation} = <<"END_LONG_TERM_OPERATION";
1385             local coll_name = ARGV[1];
1386             local return_as_insert = tonumber( ARGV[2] );
1387              
1388             local STATUS_KEY = 'C:S:'..coll_name;
1389             local DATA_VERSION_KEY = '$_DATA_VERSION';
1390              
1391             local LIST = 'Test_list';
1392             local DATA = 'Data';
1393             local MAX_WORKING_CYCLES = 5000000;
1394              
1395             redis.call( 'DEL', LIST );
1396              
1397             local ret;
1398             local i = 1;
1399             while i < MAX_WORKING_CYCLES do
1400             -- simple active actions
1401             local data_version = redis.call( 'HGET', STATUS_KEY, DATA_VERSION_KEY );
1402             ret = redis.call( 'HSET', LIST, i, DATA );
1403              
1404             i = i + 1;
1405             end
1406              
1407             if return_as_insert == 1 then
1408             return { $E_NO_ERROR, 0, 0, 0 };
1409             else
1410             return { $E_NO_ERROR, ret, '_long_term_operation' };
1411             end
1412             END_LONG_TERM_OPERATION
1413              
1414             subtype __PACKAGE__.'::NonNegInt',
1415             as 'Int',
1416             where { $_ >= 0 },
1417             message { format_message( '%s is not a non-negative integer!', $_ ) }
1418             ;
1419              
1420             subtype __PACKAGE__.'::NonNegNum',
1421             as 'Num',
1422             where { $_ >= 0 },
1423             message { format_message( '%s is not a non-negative number!', $_ ) }
1424             ;
1425              
1426             subtype __PACKAGE__.'::NonEmptNameStr',
1427             as 'Str',
1428             where { $_ ne '' && $_ !~ /:/ },
1429             message { format_message( '%s is not a non-empty string!', $_ ) }
1430             ;
1431              
1432             subtype __PACKAGE__.'::DataStr',
1433             as 'Str',
1434             where { bytes::length( $_ ) <= $MAX_DATASIZE },
1435             message { format_message( "'%s' is not a valid data string!", $_ ) }
1436             ;
1437              
1438             #-- constructor ----------------------------------------------------------------
1439              
1440             =head2 CONSTRUCTOR
1441              
1442             =head3 create
1443              
1444             create( redis => $server, name => $name, ... )
1445              
1446             Create a new collection on the Redis server and return an C
1447             object to access it. Must be called as a class method only.
1448              
1449             The C creates and returns a C object that is configured
1450             to work with the default settings if the corresponding arguments were not given.
1451              
1452             C argument can be either an existing object of L class
1453             (which is then used for all communication with Redis server) or a hash reference used to create a
1454             new internal Redis object. See documentation of L module for details.
1455              
1456             C takes arguments in key-value pairs.
1457              
1458             This example illustrates a C call with all the valid arguments:
1459              
1460             my $coll = Redis::CappedCollection->create(
1461             redis => { server => "$server:$port" }, # Redis object
1462             # or hash reference to parameters to create a new Redis object.
1463             name => 'Some name', # Redis::CappedCollection collection name.
1464             min_cleanup_bytes => 50_000, # The minimum size, in bytes,
1465             # of the data to be released when performing memory cleanup.
1466             min_cleanup_items => 1_000, # The minimum number of the collection
1467             # elements to be realesed when performing memory cleanup.
1468             max_datasize => 1_000_000, # Maximum size, in bytes, of the data.
1469             # Default 512MB.
1470             older_allowed => 0, # Allow adding an element to collection that's older
1471             # than the last element removed from collection.
1472             # Default 0.
1473             check_maxmemory => 1, # Controls if collection should try to find out maximum
1474             # available memory from Redis.
1475             # In some cases Redis implementation forbids such request,
1476             # but setting 'check_maxmemory' to false can be used
1477             # as a workaround.
1478             memory_reserve => 0.05, # Reserve coefficient of 'maxmemory'.
1479             # Not used when 'maxmemory' == 0 (it is not set in the redis.conf).
1480             # When you add or modify the data trying to ensure
1481             # reserve of free memory for metadata and bookkeeping.
1482             reconnect_on_error => 0, # Controls ability to force re-connection with Redis on error.
1483             connection_timeout => $DEFAULT_CONNECTION_TIMEOUT, # Socket timeout for connection,
1484             # number of seconds (can be fractional).
1485             # NOTE: Changes external socket configuration.
1486             operation_timeout => $DEFAULT_OPERATION_TIMEOUT, # Socket timeout for read and write operations,
1487             # number of seconds (can be fractional).
1488             # NOTE: Changes external socket configuration.
1489             );
1490              
1491             The C and C arguments are required.
1492             Do not use the symbol C<':'> in C.
1493              
1494             The following examples illustrate other uses of the C method:
1495              
1496             my $redis = Redis->new( server => "$server:$port" );
1497             my $coll = Redis::CappedCollection->create( redis => $redis, name => 'Next collection' );
1498             my $next_coll = Redis::CappedCollection->create( redis => $coll, name => 'Some name' );
1499              
1500             An error exception is thrown (C) if an argument is not valid or the collection with
1501             same name already exists.
1502              
1503             =cut
1504             sub create {
1505 0 0   0 1   my $class = _CLASSISA( shift, __PACKAGE__ ) or confess 'Must be called as a class method only';
1506 0           return $class->new( @_, _create_from_naked_new => 0 );
1507             }
1508              
1509             sub BUILD {
1510 0     0 1   my $self = shift;
1511              
1512 0           my $redis = $self->redis;
1513 0 0         if ( _INSTANCE( $redis, 'Redis' ) ) {
    0          
    0          
1514             # have to look into the Redis object ...
1515 0           $self->_server( $redis->{server} );
1516 0           $self->_redis( $redis );
1517             } elsif ( _INSTANCE( $redis, 'Test::RedisServer' ) ) {
1518             # to test only
1519             # have to look into the Test::RedisServer object ...
1520 0           my $conf = $redis->conf;
1521 0 0         $conf->{server} = '127.0.0.1:'.$conf->{port} unless exists $conf->{server};
1522 0           $self->_server( $conf->{server} );
1523 0           $self->_redis( Redis->new( server => $conf->{server} ) );
1524             } elsif ( _INSTANCE( $redis, __PACKAGE__ ) ) {
1525 0           $self->_server( $redis->_server );
1526 0           $self->_redis( $self->_redis );
1527             } else { # $redis is hash ref
1528 0   0       $self->_server( $redis->{server} // "$DEFAULT_SERVER:$DEFAULT_PORT" );
1529              
1530             # defaults for the case when the Redis object we create
1531 0 0         $redis->{reconnect} = 0 unless exists $redis->{reconnect};
1532 0 0         $redis->{every} = 1000 unless exists $redis->{every}; # 1 ms
1533 0 0         $redis->{conservative_reconnect} = 0 unless exists $redis->{conservative_reconnect};
1534 0 0         $redis->{cnx_timeout} = $DEFAULT_CONNECTION_TIMEOUT unless exists $redis->{cnx_timeout};
1535 0 0         $redis->{read_timeout} = $DEFAULT_OPERATION_TIMEOUT unless exists $redis->{read_timeout};
1536 0 0         $redis->{write_timeout} = $DEFAULT_OPERATION_TIMEOUT unless exists $redis->{write_timeout};
1537              
1538 0           $self->_redis( $self->_redis_constructor( $redis ) );
1539 0           $self->_use_external_connection( 0 );
1540             }
1541              
1542 0           $self->_connection_timeout_trigger( $self->connection_timeout );
1543 0           $self->_operation_timeout_trigger( $self->operation_timeout );
1544              
1545 0 0         if ( $self->_create_from_naked_new ) {
1546 0           warn 'Redis::CappedCollection->new() is deprecated and will be removed in future. Please use either create() or open() instead.';
1547             } else {
1548 0 0 0       confess format_message( "Collection '%s' already exists", $self->name )
1549             if !$self->_create_from_open && $self->collection_exists( name => $self->name );
1550             }
1551              
1552 0           my $maxmemory;
1553 0 0         if ( $self->_check_maxmemory ) {
1554 0           ( undef, $maxmemory ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory' );
1555 0 0         defined( _NONNEGINT( $maxmemory ) )
1556             or $self->_throw( $E_NETWORK );
1557             } else {
1558             # 0 means all system memory
1559 0           $maxmemory = 0;
1560             }
1561              
1562 0           my ( $major, $minor ) = $self->_redis->info->{redis_version} =~ /^(\d+)\.(\d+)/;
1563 0 0 0       if ( $major < 2 || ( $major == 2 && $minor < 8 ) ) {
      0        
1564 0           $self->_set_last_errorcode( $E_REDIS );
1565 0           confess "Need a Redis server version 2.8 or higher";
1566             }
1567              
1568 0 0         $self->_throw( $E_MAXMEMORY_POLICY )
1569             unless $self->_maxmemory_policy_ok;
1570              
1571 0           $self->_maxmemory( $maxmemory );
1572 0 0         $self->max_datasize( min $self->_maxmemory, $self->max_datasize )
1573             if $self->_maxmemory;
1574              
1575 0           $self->_queue_key( $NAMESPACE.':Q:'.$self->name );
1576 0           $self->_status_key( _make_status_key( $self->name ) );
1577 0           $self->_data_keys( _make_data_key( $self->name ) );
1578 0           $self->_time_keys( $NAMESPACE.':T:'.$self->name );
1579              
1580 0 0         $self->_verify_collection unless $self->_create_from_open;
1581             }
1582              
1583             #-- public attributes ----------------------------------------------------------
1584              
1585             =head3 open
1586              
1587             open( redis => $server, name => $name, ... )
1588              
1589             Example:
1590              
1591             my $redis = Redis->new( server => "$server:$port" );
1592             my $coll = Redis::CappedCollection::open( redis => $redis, name => 'Some name' );
1593              
1594             Create a C object to work with an existing collection
1595             (created by L). It must be called as a class method only.
1596              
1597             C takes optional arguments. These arguments are in key-value pairs.
1598             Arguments description is the same as for L method.
1599              
1600             =over 3
1601              
1602             =item I
1603              
1604             =item I
1605              
1606             =item I
1607              
1608             =item I
1609              
1610             =item I
1611              
1612             =item I
1613              
1614             =item I
1615              
1616             =back
1617              
1618             The C and C arguments are mandatory.
1619              
1620             The C creates and returns a C object that is configured
1621             to work with the default settings if the corresponding arguments are not given.
1622              
1623             If C argument is not a L object, a new connection to Redis is established using
1624             passed hash reference to create a new L object.
1625              
1626             An error exception is thrown (C) if an argument is not valid.
1627              
1628             =cut
1629             my @_asked_parameters = qw(
1630             redis
1631             name
1632             max_datasize
1633             check_maxmemory
1634             reconnect_on_error
1635             connection_timeout
1636             operation_timeout
1637             );
1638             my @_status_parameters = qw(
1639             older_allowed
1640             min_cleanup_bytes
1641             min_cleanup_items
1642             memory_reserve
1643             );
1644              
1645             sub open {
1646 0 0   0 1   my $class = _CLASSISA( shift, __PACKAGE__ ) or confess 'Must be called as a class method only';
1647              
1648 0           my %params = @_;
1649 0           _check_arguments_acceptability( \%params, \@_asked_parameters );
1650              
1651 0 0         confess "'redis' argument is required" unless exists $params{redis};
1652 0 0         confess "'name' argument is required" unless exists $params{name};
1653              
1654 0           my $use_external_connection = ref( $params{redis} ) ne 'HASH';
1655 0           my $redis = $params{redis} = _get_redis( $params{redis} );
1656 0           my $name = $params{name};
1657 0 0         if ( collection_exists( redis => $redis, name => $name ) ) {
1658 0           my $info = collection_info( redis => $redis, name => $name );
1659 0 0         $info->{data_version} == $DATA_VERSION or confess $ERROR{ $E_INCOMP_DATA_VERSION };
1660 0           $params{ $_ } = $info->{ $_ } foreach @_status_parameters;
1661 0           return $class->new( %params,
1662             _create_from_naked_new => 0,
1663             _create_from_open => 1,
1664             _use_external_connection => $use_external_connection,
1665             );
1666             } else {
1667 0           confess format_message( "Collection '%s' does not exist", $name );
1668             };
1669             }
1670              
1671             =head2 METHODS
1672              
1673             An exception is thrown (C) if any method argument is not valid or
1674             if a required argument is missing.
1675              
1676             ATTENTION: In the L module the synchronous commands throw an
1677             exception on receipt of an error reply, or return a non-error reply directly.
1678              
1679             =cut
1680              
1681             =head3 name
1682              
1683             Get collection C attribute (collection ID).
1684             The method returns the current value of the attribute.
1685             The C attribute value is used in the L.
1686              
1687             =cut
1688             has name => (
1689             is => 'ro',
1690             clearer => '_clear_name',
1691             isa => __PACKAGE__.'::NonEmptNameStr',
1692             required => 1,
1693             );
1694              
1695             =head3 redis
1696              
1697             Existing L object or a hash reference with parameters to create a new one.
1698              
1699             =cut
1700             has redis => (
1701             is => 'ro',
1702             isa => 'Redis|Test::RedisServer|HashRef',
1703             required => 1,
1704             );
1705              
1706             =head3 reconnect_on_error
1707              
1708             Controls ability to force re-connection with Redis on error.
1709              
1710             =cut
1711             has reconnect_on_error => (
1712             is => 'rw',
1713             isa => 'Bool',
1714             default => 0,
1715             );
1716              
1717             =head3 connection_timeout
1718              
1719             Controls socket timeout for Redis server connection, number of seconds (can be fractional).
1720              
1721             NOTE: Changes external socket configuration.
1722              
1723             =cut
1724             has connection_timeout => (
1725             is => 'rw',
1726             isa => 'Maybe['.__PACKAGE__.'::NonNegNum]',
1727             default => undef,
1728             trigger => \&_connection_timeout_trigger,
1729             );
1730              
1731             sub _connection_timeout_trigger {
1732 0     0     my ( $self, $timeout, $old_timeout ) = @_;
1733              
1734 0 0 0       return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) );
      0        
1735              
1736 0 0         if ( my $redis = $self->_redis ) {
1737 0 0         my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or confess 'Bad socket object';
1738             # IO::Socket provides a way to set a timeout on the socket,
1739             # but the timeout will be used only for connection,
1740             # not for reading / writing operations.
1741 0           $socket->timeout( $redis->{cnx_timeout} = $timeout );
1742             }
1743             }
1744              
1745             =head3 operation_timeout
1746              
1747             Controls socket timeout for Redis server read and write operations, number of seconds (can be fractional).
1748              
1749             NOTE: Changes external socket configuration.
1750              
1751             =cut
1752             has operation_timeout => (
1753             is => 'rw',
1754             isa => 'Maybe['.__PACKAGE__.'::NonNegNum]',
1755             default => undef,
1756             trigger => \&_operation_timeout_trigger,
1757             );
1758              
1759             sub _operation_timeout_trigger {
1760 0     0     my ( $self, $timeout, $old_timeout ) = @_;
1761              
1762 0 0 0       return if scalar( @_ ) == 2 && ( !defined( $timeout ) && !defined( $old_timeout ) );
      0        
1763              
1764 0 0         if ( my $redis = $self->_redis ) {
1765 0 0         my $socket = _INSTANCE( $redis->{sock}, 'IO::Socket' ) or confess 'Bad socket object';
1766             # IO::Socket::Timeout provides a way to set a timeout
1767             # on read / write operations on an IO::Socket instance,
1768             # or any IO::Socket::* modules, like IO::Socket::INET.
1769 0 0         if ( defined $timeout ) {
1770 0           $redis->{write_timeout} = $redis->{read_timeout} = $timeout;
1771 0           $redis->_maybe_enable_timeouts( $socket );
1772 0           $socket->enable_timeout;
1773             } else {
1774 0           $redis->{write_timeout} = $redis->{read_timeout} = 0;
1775 0           $redis->_maybe_enable_timeouts( $socket );
1776 0           $socket->disable_timeout;
1777             }
1778             }
1779             }
1780              
1781             =head3 min_cleanup_bytes
1782              
1783             Accessor for C attribute - The minimum size, in bytes,
1784             of the data to be released when performing memory cleanup.
1785             Default 0.
1786              
1787             The C attribute is designed to reduce the release of memory
1788             operations with frequent data changes.
1789              
1790             The C attribute value can be provided to L.
1791             The method returns and sets the current value of the attribute.
1792              
1793             The C value must be less than or equal to C<'maxmemory'>. Otherwise
1794             an error exception is thrown (C).
1795              
1796             =cut
1797             has min_cleanup_bytes => (
1798             is => 'rw',
1799             writer => '_set_min_cleanup_bytes',
1800             isa => __PACKAGE__.'::NonNegInt',
1801             default => 0,
1802             trigger => sub {
1803             my $self = shift;
1804             !$self->_maxmemory || ( $self->min_cleanup_bytes <= $self->maxmemory || $self->_throw( $E_MISMATCH_ARG, 'min_cleanup_bytes' ) );
1805             },
1806             );
1807              
1808             =head3 min_cleanup_items
1809              
1810             The minimum number of the collection elements to be realesed
1811             when performing memory cleanup. Default 100.
1812              
1813             The C attribute is designed to reduce number of times collection cleanup takes place.
1814             Setting value too high may result in unwanted delays during operations with Redis.
1815              
1816             The C attribute value can be used in the L.
1817             The method returns and sets the current value of the attribute.
1818              
1819             =cut
1820             has min_cleanup_items => (
1821             is => 'rw',
1822             writer => '_set_min_cleanup_items',
1823             isa => __PACKAGE__.'::NonNegInt',
1824             default => 100,
1825             );
1826              
1827             =head3 max_datasize
1828              
1829             Accessor for the C attribute.
1830              
1831             The method returns the current value of the attribute if called without arguments.
1832              
1833             Non-negative integer value can be used to specify a new value to
1834             the maximum size of the data introduced into the collection
1835             (methods L and L).
1836              
1837             The C attribute value is used in the L
1838             and operations data entry on the Redis server.
1839              
1840             The L uses the smaller of the values of 512MB and
1841             C<'maxmemory'> limit from a F file.
1842              
1843             =cut
1844             has max_datasize => (
1845             is => 'rw',
1846             isa => __PACKAGE__.'::NonNegInt',
1847             default => $MAX_DATASIZE,
1848             lazy => 1,
1849             trigger => sub {
1850             my $self = shift;
1851             $self->max_datasize <= ( $self->_maxmemory ? min( $self->_maxmemory, $MAX_DATASIZE ) : $MAX_DATASIZE )
1852             || $self->_throw( $E_MISMATCH_ARG, 'max_datasize' );
1853             },
1854             );
1855              
1856             =head3 older_allowed
1857              
1858             Accessor for the C attribute which controls if adding an element
1859             that is older than the last element removed from collection is allowed.
1860             Default is C<0> (not allowed).
1861              
1862             The method returns the current value of the attribute.
1863             The C attribute value is used in the L.
1864              
1865             =cut
1866             has older_allowed => (
1867             is => 'rw',
1868             isa => 'Bool',
1869             default => 0,
1870             );
1871              
1872             =head3 memory_reserve
1873              
1874             Accessor for the C attribute which specifies the amount of additional
1875             memory reserved for metadata and bookkeeping.
1876             Default C<0.05> (5%) of 'maxmemory'.
1877             Not used when C<'maxmemory'> == 0 (it is not set in the F).
1878              
1879             Valid values must be between C<$MIN_MEMORY_RESERVE> and C<$MAX_MEMORY_RESERVE>.
1880              
1881             The method returns the current value of the attribute.
1882             The C attribute value is used in the L.
1883              
1884             =cut
1885             has memory_reserve => (
1886             is => 'rw',
1887             writer => '_set_memory_reserve',
1888             isa => 'Num',
1889             default => $MIN_MEMORY_RESERVE,
1890             trigger => sub {
1891             my $self = shift;
1892             my $memory_reserve = $self->memory_reserve;
1893             ( _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE )
1894             || $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' );
1895             },
1896             );
1897              
1898             =head3 last_errorcode
1899              
1900             Get code of the last error.
1901              
1902             See the list of supported error codes in L section.
1903              
1904             =cut
1905             has last_errorcode => (
1906             reader => 'last_errorcode',
1907             writer => '_set_last_errorcode',
1908             isa => 'Int',
1909             default => 0,
1910             );
1911              
1912             #-- public methods -------------------------------------------------------------
1913              
1914             =head3 insert
1915              
1916             insert( $list_id, $data_id, $data, $data_time )
1917              
1918             Example:
1919              
1920             $list_id = $coll->insert( 'Some List_id', 'Some Data_id', 'Some data' );
1921              
1922             $list_id = $coll->insert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() );
1923              
1924             Insert data into the capped collection on the Redis server.
1925              
1926             Arguments:
1927              
1928             =over 3
1929              
1930             =item C<$list_id>
1931              
1932             Mandatory, non-empty string: list ID. Must not contain C<':'>.
1933              
1934             The data will be inserted into the list with given ID, and the list
1935             is created automatically if it does not exist yet.
1936              
1937             =item C<$data_id>
1938              
1939             Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id>
1940             argument.
1941              
1942             =item C<$data>
1943              
1944             Data value: a string. Data length should not exceed value of L attribute.
1945              
1946             =item C<$data_time>
1947              
1948             Optional data time, a non-negative number. If not specified, the current
1949             value returned by C is used instead. Floating values (such as those
1950             returned by L module) are supported to have time
1951             granularity of less than 1 second and stored with 4 decimal places.
1952              
1953             =back
1954              
1955             If collection is set to C and C<$data_time> less than time of the last removed
1956             element (C - see C) then C is set to 0.
1957             The L attribute value is used in the L.
1958              
1959             The method returns the ID of the data list to which the data was inserted (value of
1960             the C<$list_id> argument).
1961              
1962             =cut
1963             sub insert {
1964 0     0 1   my $self = shift;
1965 0           my $list_id = shift;
1966 0           my $data_id = shift;
1967 0           my $data = shift;
1968 0   0       my $data_time = shift // time;
1969              
1970 0   0       $data // $self->_throw( $E_MISMATCH_ARG, 'data' );
1971 0 0 0       ( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' );
1972 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
1973 0 0         $list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' );
1974 0 0         defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' );
1975 0 0 0       ( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' );
1976              
1977 0           my $data_len = bytes::length( $data );
1978 0 0         ( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE );
1979              
1980 0           $self->_set_last_errorcode( $E_NO_ERROR );
1981              
1982 0           my @ret = $self->_call_redis(
1983             $self->_lua_script_cmd( 'insert' ),
1984             0,
1985             $self->name,
1986             $list_id,
1987             $data_id,
1988             $data,
1989             $data_time,
1990             # Recommend the inclusion of this option in the case of incomprehensible errors
1991             $self->_DEBUG,
1992             );
1993              
1994 0           my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret;
1995              
1996 0 0 0       if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) {
      0        
1997 0 0 0       if ( $error == $E_NO_ERROR ) {
    0          
    0          
1998             # Normal result: Nothing to do
1999             } elsif ( $error == $E_COLLECTION_DELETED ) {
2000 0           $self->_clear_sha1;
2001 0           $self->_throw( $error );
2002             } elsif (
2003             $error == $E_DATA_ID_EXISTS
2004             || $error == $E_OLDER_THAN_ALLOWED
2005             ) {
2006 0           $self->_throw( $error );
2007             } else {
2008 0           $self->_throw( $error, 'Unexpected error' );
2009             }
2010             } else {
2011 0           $self->_process_unknown_error( @ret );
2012             }
2013              
2014 0 0         return wantarray ? ( $list_id, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : $list_id;
2015             }
2016              
2017             =head3 update
2018              
2019             update( $list_id, $data_id, $data, $new_data_time )
2020              
2021             Example:
2022              
2023             if ( $coll->update( $list_id, $data_id, 'New data' ) ) {
2024             say "Data updated successfully";
2025             } else {
2026             say "The data is not updated";
2027             }
2028              
2029             Updates existing data item.
2030              
2031             Arguments:
2032              
2033             =over 3
2034              
2035             =item C<$list_id>
2036              
2037             Mandatory, non-empty string: list ID. Must not contain C<':'>.
2038              
2039             =item C<$data_id>
2040              
2041             Mandatory, non-empty string: data ID, unique within the list identified by C<$list_id>
2042             argument.
2043              
2044             =item C<$data>
2045              
2046             New data value: a string. Data length should not exceed value of L attribute.
2047              
2048             =item C<$new_data_time>
2049              
2050             Optional new data time, a non-negative number. If not specified, the existing
2051             data time is preserved.
2052              
2053             =back
2054              
2055             If the collection is set to C and C<$new_data_time> less than time of the last
2056             removed element (C - see L) then C is set to 0.
2057             The L attribute value is used in the L.
2058              
2059             Method returns true if the data is updated or false if the list with the given ID does not exist or
2060             is used an invalid data ID.
2061              
2062             Throws an exception on other errors.
2063              
2064             =cut
2065             sub update {
2066 0     0 1   my $self = shift;
2067 0           my $list_id = shift;
2068 0           my $data_id = shift;
2069 0           my $data = shift;
2070              
2071 0   0       $data // $self->_throw( $E_MISMATCH_ARG, 'data' );
2072 0 0 0       ( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' );
2073 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2074 0 0         defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' );
2075              
2076 0           my $new_data_time;
2077 0 0         if ( @_ ) {
2078 0           $new_data_time = shift;
2079 0 0 0       ( defined( _NUMBER( $new_data_time ) ) && $new_data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'new_data_time' );
2080             }
2081              
2082 0           my $data_len = bytes::length( $data );
2083 0 0         ( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE );
2084              
2085 0           $self->_set_last_errorcode( $E_NO_ERROR );
2086              
2087 0   0       my @ret = $self->_call_redis(
2088             $self->_lua_script_cmd( 'update' ),
2089             0,
2090             $self->name,
2091             $list_id,
2092             $data_id,
2093             $data,
2094             $new_data_time // 0,
2095             # Recommend the inclusion of this option in the case of incomprehensible errors
2096             $self->_DEBUG,
2097             );
2098              
2099 0           my ( $error, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) = @ret;
2100              
2101 0 0 0       if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $_last_cleanup_items ) ) ) {
      0        
2102 0 0 0       if ( $error == $E_NO_ERROR ) {
    0 0        
    0          
2103 0 0         return wantarray ? ( 1, $_last_cleanup_items, $_used_memory, $_total_bytes_deleted ) : 1;
2104             } elsif ( $error == $E_NONEXISTENT_DATA_ID ) {
2105 0           return 0;
2106             } elsif (
2107             $error == $E_COLLECTION_DELETED
2108             || $error == $E_DATA_ID_EXISTS
2109             || $error == $E_OLDER_THAN_ALLOWED
2110             ) {
2111 0           $self->_clear_sha1;
2112 0           $self->_throw( $error );
2113             } else {
2114 0           $self->_throw( $error, 'Unexpected error' );
2115             }
2116             } else {
2117 0           $self->_process_unknown_error( @ret );
2118             }
2119             }
2120              
2121             =head3 upsert
2122              
2123             upsert( $list_id, $data_id, $data, $data_time )
2124              
2125             Example:
2126              
2127             $list_id = $coll->upsert( 'Some List_id', 'Some Data_id', 'Some data' );
2128              
2129             $list_id = $coll->upsert( 'Another List_id', 'Data ID', 'More data', Time::HiRes::time() );
2130              
2131             If the list C<$list_id> does not contain data with C<$data_id>,
2132             then it behaves like an L,
2133             otherwise behaves like an L.
2134              
2135             The method returns the ID of the data list to which the data was inserted (value of
2136             the C<$list_id> argument) as the L method.
2137              
2138             =cut
2139             sub upsert {
2140 0     0 1   my $self = shift;
2141 0           my $list_id = shift;
2142 0           my $data_id = shift;
2143 0           my $data = shift;
2144 0           my $data_time = shift;
2145              
2146 0   0       $data // $self->_throw( $E_MISMATCH_ARG, 'data' );
2147 0 0 0       ( defined( _STRING( $data ) ) || $data eq '' ) || $self->_throw( $E_MISMATCH_ARG, 'data' );
2148 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2149 0 0         $list_id !~ /:/ || $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2150 0 0         defined( _STRING( $data_id ) ) || $self->_throw( $E_MISMATCH_ARG, 'data_id' );
2151 0 0 0       !defined( $data_time ) || ( defined( _NUMBER( $data_time ) ) && $data_time > 0 ) || $self->_throw( $E_MISMATCH_ARG, 'data_time' );
      0        
2152              
2153 0           my $data_len = bytes::length( $data );
2154 0 0         ( $data_len <= $self->max_datasize ) || $self->_throw( $E_DATA_TOO_LARGE );
2155              
2156 0           $self->_set_last_errorcode( $E_NO_ERROR );
2157              
2158 0   0       my @ret = $self->_call_redis(
2159             $self->_lua_script_cmd( 'upsert' ),
2160             0,
2161             $self->name,
2162             $list_id,
2163             $data_id,
2164             $data,
2165             $data_time // -1,
2166             # Recommend the inclusion of this option in the case of incomprehensible errors
2167             $self->_DEBUG,
2168             time,
2169             );
2170              
2171 0           my ( $error, $cleanings ) = @ret;
2172              
2173 0 0 0       if ( scalar( @ret ) == 4 && exists( $ERROR{ $error } ) && defined( _NONNEGINT( $cleanings ) ) ) {
      0        
2174 0 0 0       if ( $error == $E_NO_ERROR ) {
    0          
    0          
    0          
2175             # Normal result: Nothing to do
2176             } elsif ( $error == $E_COLLECTION_DELETED ) {
2177 0           $self->_clear_sha1;
2178 0           $self->_throw( $error );
2179             } elsif (
2180             $error == $E_DATA_ID_EXISTS
2181             || $error == $E_OLDER_THAN_ALLOWED
2182             ) {
2183 0           $self->_throw( $error );
2184             } elsif ( $error == $E_NONEXISTENT_DATA_ID ) {
2185             # Nothing to do
2186             } else {
2187 0           $self->_throw( $error, 'Unexpected error' );
2188             }
2189             } else {
2190 0           $self->_process_unknown_error( @ret );
2191             }
2192              
2193 0 0         return wantarray ? ( $list_id, $cleanings ) : $list_id; # as insert
2194             }
2195              
2196             =head3 receive
2197              
2198             receive( $list_id, $data_id )
2199              
2200             Example:
2201              
2202             my @data = $coll->receive( $list_id );
2203             say "List '$list_id' has '$_'" foreach @data;
2204             # or
2205             my $list_len = $coll->receive( $list_id );
2206             say "List '$list_id' has '$list_len' item(s)";
2207             # or
2208             my $data = $coll->receive( $list_id, $data_id );
2209             say "List '$list_id' has '$data_id'" if defined $data;
2210              
2211             If the C<$data_id> argument is not specified or is an empty string:
2212              
2213             =over 3
2214              
2215             =item *
2216              
2217             In a list context, the method returns all the data from the list given by
2218             the C<$list_id> identifier.
2219              
2220             Method returns an empty list if the list with the given ID does not exist.
2221              
2222             =item *
2223              
2224             In a scalar context, the method returns the length of the data list given by
2225             the C<$list_id> identifier.
2226              
2227             =back
2228              
2229             If the C<$data_id> argument is specified:
2230              
2231             =over 3
2232              
2233             =item *
2234              
2235             The method returns the specified element of the data list.
2236             If the data with C<$data_id> ID does not exist, C is returned.
2237              
2238             =back
2239              
2240             =cut
2241             sub receive {
2242 0     0 1   my ( $self, $list_id, $data_id ) = @_;
2243              
2244 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2245              
2246 0           $self->_set_last_errorcode( $E_NO_ERROR );
2247              
2248 0 0         return unless $self->list_exists( $list_id );
2249              
2250 0 0 0       if ( defined( $data_id ) && $data_id ne '' ) {
2251 0   0       _STRING( $data_id ) // $self->_throw( $E_MISMATCH_ARG, 'data_id' );
2252 0           return $self->_call_redis(
2253             $self->_lua_script_cmd( 'receive' ),
2254             0,
2255             $self->name,
2256             $list_id,
2257             'val',
2258             $data_id,
2259             );
2260             } else {
2261 0 0         if ( wantarray ) {
2262 0 0         return $self->_call_redis(
2263             $self->_lua_script_cmd( 'receive' ),
2264             0,
2265             $self->name,
2266             $list_id,
2267             defined( $data_id ) ? 'all' : 'vals',
2268             '',
2269             );
2270             } else {
2271 0           return $self->_call_redis(
2272             $self->_lua_script_cmd( 'receive' ),
2273             0,
2274             $self->name,
2275             $list_id,
2276             'len',
2277             '',
2278             );
2279             }
2280             }
2281             }
2282              
2283             =head3 pop_oldest
2284              
2285             The method retrieves the oldest data stored in the collection and removes it from
2286             the collection.
2287              
2288             Returns a list of two elements.
2289             The first element contains the identifier of the list from which the data was retrieved.
2290             The second element contains the extracted data.
2291              
2292             The returned data item is removed from the collection.
2293              
2294             Method returns an empty list if the collection does not contain any data.
2295              
2296             The following examples illustrate uses of the C method:
2297              
2298             while ( my ( $list_id, $data ) = $coll->pop_oldest ) {
2299             say "List '$list_id' had '$data'";
2300             }
2301              
2302             =cut
2303             sub pop_oldest {
2304 0     0 1   my ( $self ) = @_;
2305              
2306 0           $self->_set_last_errorcode( $E_NO_ERROR );
2307              
2308 0           my @ret = $self->_call_redis(
2309             $self->_lua_script_cmd( 'pop_oldest' ),
2310             0,
2311             $self->name,
2312             );
2313              
2314 0           my ( $error, $queue_exist, $to_delete_id, $to_delete_data ) = @ret;
2315              
2316 0 0         if ( exists $ERROR{ $error } ) {
2317 0 0         $self->_clear_sha1 if $error == $E_COLLECTION_DELETED;
2318 0 0         $self->_throw( $error ) if $error != $E_NO_ERROR;
2319             } else {
2320 0           $self->_process_unknown_error( @ret );
2321             }
2322              
2323 0 0         if ( $queue_exist ) {
2324 0           return( $to_delete_id, $to_delete_data );
2325             } else {
2326 0           return;
2327             }
2328             }
2329              
2330             =head3 redis_config_ok
2331              
2332             redis_config_ok( redis => $server )
2333              
2334             Example:
2335              
2336             say 'Redis server config ', $coll->redis_config_ok ? 'OK' : 'NOT OK';
2337             my $redis = Redis->new( server => "$server:$port" );
2338             say 'Redis server config ',
2339             Redis::CappedCollection::redis_config_ok( redis => $redis )
2340             ? 'OK'
2341             : 'NOT OK'
2342             ;
2343              
2344             Check whether there is a Redis server config correct,
2345             now that the 'maxmemory-policy' setting is 'noeviction'.
2346             Returns true if config correct and false otherwise.
2347              
2348             It can be called as either the existing C object method or a class function.
2349              
2350             If invoked as the object method, C uses the C
2351             attribute from the object as default.
2352              
2353             If invoked as the class function, C requires mandatory C
2354             argument.
2355              
2356             This argument are in key-value pair as described for L method.
2357              
2358             An error exception is thrown (C) if an argument is not valid.
2359              
2360             =cut
2361             sub redis_config_ok {
2362 0     0 1   return _maxmemory_policy_ok( @_ );
2363             }
2364              
2365             =head3 collection_info
2366              
2367             collection_info( redis => $server, name => $name )
2368              
2369             Example:
2370              
2371             my $info = $coll->collection_info;
2372             say 'An existing collection uses ', $info->{min_cleanup_bytes}, " byte of 'min_cleanup_bytes', ",
2373             $info->{items}, ' items are stored in ', $info->{lists}, ' lists';
2374             # or
2375             my $info = Redis::CappedCollection::collection_info(
2376             redis => $redis, # or redis => { server => "$server:$port" }
2377             name => 'Collection name',
2378             );
2379              
2380             Get collection information and status.
2381             It can be called as either an existing C object method or a class function.
2382              
2383             C arguments are in key-value pairs.
2384             Arguments description match the arguments description for L method:
2385              
2386             =over 3
2387              
2388             =item C
2389              
2390             =item C
2391              
2392             =back
2393              
2394             If invoked as the object method, C, arguments are optional and
2395             use corresponding object attributes as defaults.
2396              
2397             If called as a class methods, the arguments are mandatory.
2398              
2399             Returns a reference to a hash with the following elements:
2400              
2401             =over 3
2402              
2403             =item *
2404              
2405             C - Number of lists in a collection.
2406              
2407             =item *
2408              
2409             C - Number of data items stored in the collection.
2410              
2411             =item *
2412              
2413             C - Time of the oldest data in the collection.
2414             C if the collection does not contain data.
2415              
2416             =item *
2417              
2418             C - True if it is allowed to put data in collection that is older than the last element
2419             removed from collection.
2420              
2421             =item *
2422              
2423             C - Memory reserve coefficient.
2424              
2425             =item *
2426              
2427             C - The minimum size, in bytes,
2428             of the data to be released when performing memory cleanup.
2429              
2430             =item *
2431              
2432             C - The minimum number of the collection elements
2433             to be realesed when performing memory cleanup.
2434              
2435             =item *
2436              
2437             C - Data structure version.
2438              
2439             =item *
2440              
2441             C - time of the last removed element from collection
2442             or 0 if nothing was removed from collection yet.
2443              
2444             =back
2445              
2446             An error will cause the program to throw an exception (C) if an argument is not valid
2447             or the collection does not exist.
2448              
2449             =cut
2450             my @_collection_info_result_keys = qw(
2451             error
2452             lists
2453             items
2454             older_allowed
2455             min_cleanup_bytes
2456             min_cleanup_items
2457             memory_reserve
2458             data_version
2459             last_removed_time
2460             oldest_time
2461             );
2462              
2463             sub collection_info {
2464 0     0 1   my $results = {};
2465 0           my @ret;
2466 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
2467 0           my $self = shift;
2468              
2469 0           my %arguments = @_;
2470 0           _check_arguments_acceptability( \%arguments, [] );
2471              
2472 0           $self->_set_last_errorcode( $E_NO_ERROR );
2473              
2474 0           @ret = $self->_call_redis(
2475             $self->_lua_script_cmd( 'collection_info' ),
2476             0,
2477             $self->name,
2478             );
2479 0           $results = _lists2hash( \@_collection_info_result_keys, \@ret );
2480              
2481 0           my $error = $results->{error};
2482              
2483 0 0         if ( exists $ERROR{ $error } ) {
2484 0 0         $self->_clear_sha1 if $error == $E_COLLECTION_DELETED;
2485 0 0         $self->_throw( $error ) if $error != $E_NO_ERROR;
2486             } else {
2487 0           $self->_process_unknown_error( @ret );
2488             }
2489             } else {
2490 0 0         shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar
2491              
2492 0           my %arguments = @_;
2493 0           _check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] );
2494              
2495 0 0         confess "'redis' argument is required" unless defined $arguments{redis};
2496 0 0         confess "'name' argument is required" unless defined $arguments{name};
2497              
2498 0           my $redis = _get_redis( delete $arguments{redis} );
2499 0           my $name = delete $arguments{name};
2500              
2501 0 0         confess( 'Unknown arguments: ', join( ', ', keys %arguments ) ) if %arguments;
2502              
2503 0           @ret = _call_redis(
2504             $redis,
2505             _lua_script_cmd( $redis, 'collection_info' ),
2506             0,
2507             $name,
2508             );
2509 0           $results = _lists2hash( \@_collection_info_result_keys, \@ret );
2510              
2511 0           my $error = $results->{error};
2512              
2513 0 0         if ( exists $ERROR{ $error } ) {
2514 0 0         _confess( format_message( "Collection '%s' info not received (%s)", $name, $ERROR{ $error } ) )
2515             if $error != $E_NO_ERROR;
2516             } else {
2517 0           _unknown_error( @ret );
2518             }
2519             }
2520              
2521 0           my $oldest_time = $results->{oldest_time};
2522 0 0 0       !$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) );
2523              
2524 0           delete $results->{error};
2525 0           return $results;
2526             }
2527              
2528             =head3 list_info
2529              
2530             list_info( $list_id )
2531              
2532             Get data list information and status.
2533              
2534             C<$list_id> must be a non-empty string.
2535              
2536             Returns a reference to a hash with the following elements:
2537              
2538             =over 3
2539              
2540             =item *
2541              
2542             C - Number of data items stored in the data list.
2543              
2544             =item *
2545              
2546             C - The time of the oldest data in the list.
2547             C if the data list does not exist.
2548              
2549             =back
2550              
2551             =cut
2552             my @_list_info_result_keys = qw(
2553             error
2554             items
2555             oldest_time
2556             );
2557              
2558             sub list_info {
2559 0     0 1   my ( $self, $list_id ) = @_;
2560              
2561 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2562              
2563 0           $self->_set_last_errorcode( $E_NO_ERROR );
2564              
2565 0           my @ret = $self->_call_redis(
2566             $self->_lua_script_cmd( 'list_info' ),
2567             0,
2568             $self->name,
2569             $list_id,
2570             );
2571 0           my $results = _lists2hash( \@_list_info_result_keys, \@ret );
2572              
2573 0           my $error = $results->{error};
2574              
2575 0 0         if ( exists $ERROR{ $error } ) {
2576 0 0         $self->_clear_sha1 if $error == $E_COLLECTION_DELETED;
2577 0 0         $self->_throw( $error ) if $error != $E_NO_ERROR;
2578             } else {
2579 0           $self->_process_unknown_error( @ret );
2580             }
2581              
2582 0           my $oldest_time = $results->{oldest_time};
2583 0 0 0       !$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) );
2584              
2585 0           delete $results->{error};
2586 0           return $results;
2587             }
2588              
2589             =head3 oldest_time
2590              
2591             my $oldest_time = $coll->oldest_time;
2592              
2593             Get the time of the oldest data in the collection.
2594             Returns C if the collection does not contain data.
2595              
2596             An error exception is thrown (C) if the collection does not exist.
2597              
2598             =cut
2599             my @_oldest_time_result_keys = qw(
2600             error
2601             oldest_time
2602             );
2603              
2604             sub oldest_time {
2605 0     0 1   my $self = shift;
2606              
2607 0           $self->_set_last_errorcode( $E_NO_ERROR );
2608              
2609 0           my @ret = $self->_call_redis(
2610             $self->_lua_script_cmd( 'oldest_time' ),
2611             0,
2612             $self->name,
2613             );
2614 0           my $results = _lists2hash( \@_oldest_time_result_keys, \@ret );
2615              
2616 0           my $error = $results->{error};
2617              
2618 0 0         if ( exists $ERROR{ $error } ) {
2619 0 0         $self->_clear_sha1 if $error == $E_COLLECTION_DELETED;
2620 0 0         $self->_throw( $error ) if $error != $E_NO_ERROR;
2621             } else {
2622 0           $self->_process_unknown_error( @ret );
2623             }
2624              
2625 0           my $oldest_time = $results->{oldest_time};
2626 0 0 0       !$oldest_time || defined( _NUMBER( $oldest_time ) ) || warn( format_message( 'oldest_time is not a number: %s', $oldest_time ) );
2627              
2628 0           return $oldest_time;
2629             }
2630              
2631             =head3 list_exists
2632              
2633             list_exists( $list_id )
2634              
2635             Example:
2636              
2637             say "The collection has '$list_id' list" if $coll->list_exists( 'Some_id' );
2638              
2639             Check whether there is a list in the collection with given
2640             ID C<$list_id>.
2641              
2642             Returns true if the list exists and false otherwise.
2643              
2644             =cut
2645             sub list_exists {
2646 0     0 1   my ( $self, $list_id ) = @_;
2647              
2648 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2649              
2650 0           $self->_set_last_errorcode( $E_NO_ERROR );
2651              
2652 0           return $self->_call_redis( 'EXISTS', $self->_data_list_key( $list_id ) );
2653             }
2654              
2655             =head3 collection_exists
2656              
2657             collection_exists( redis => $server, name => $name )
2658              
2659             Example:
2660              
2661             say 'The collection ', $coll->name, ' exists' if $coll->collection_exists;
2662             my $redis = Redis->new( server => "$server:$port" );
2663             say "The collection 'Some name' exists"
2664             if Redis::CappedCollection::collection_exists( redis => $redis, name => 'Some name' );
2665              
2666             Check whether there is a collection with given name.
2667             Returns true if the collection exists and false otherwise.
2668              
2669             It can be called as either the existing C object method or a class function.
2670              
2671             If invoked as the object method, C uses C and C
2672             attributes from the object as defaults.
2673              
2674             If invoked as the class function, C requires mandatory C and C
2675             arguments.
2676              
2677             These arguments are in key-value pairs as described for L method.
2678              
2679             An error exception is thrown (C) if an argument is not valid.
2680              
2681             =cut
2682             sub collection_exists {
2683 0     0 1   my ( $self, $redis, $name );
2684 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
2685 0           $self = shift;
2686 0           $redis = $self->_redis;
2687 0           $name = $self->name;
2688             } else {
2689 0 0         shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar
2690             }
2691              
2692 0           my %arguments = @_;
2693 0           _check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] );
2694              
2695 0 0         unless ( $self ) {
2696 0 0         confess "'redis' argument is required" unless defined $arguments{redis};
2697 0 0         confess "'name' argument is required" unless defined $arguments{name};
2698             }
2699              
2700 0 0         $redis = _get_redis( $arguments{redis} ) unless $self;
2701 0 0         $name = $arguments{name} if exists $arguments{name};
2702              
2703 0 0         if ( $self ) {
2704 0           return $self->_call_redis( 'EXISTS', _make_status_key( $name ) );
2705             } else {
2706 0           return _call_redis( $redis, 'EXISTS', _make_status_key( $name ) );
2707             }
2708             }
2709              
2710             =head3 lists
2711              
2712             lists( $pattern )
2713              
2714             Example:
2715              
2716             say "The collection has '$_' list" foreach $coll->lists;
2717              
2718             Returns an array of list ID of lists stored in a collection.
2719             Returns all list IDs matching C<$pattern> if C<$pattern> is not empty.
2720             C<$patten> must be a non-empty string.
2721              
2722             Supported glob-style patterns:
2723              
2724             =over 3
2725              
2726             =item *
2727              
2728             C matches C, C and C
2729              
2730             =item *
2731              
2732             C matches C and C
2733              
2734             =item *
2735              
2736             C matches C and C, but not C
2737              
2738             =back
2739              
2740             Use C<'\'> to escape special characters if you want to match them verbatim.
2741              
2742             Warning: consider C as a command that should only be used in production
2743             environments with extreme care. Its performance is not optimal for large collections.
2744             This command is intended for debugging and special operations.
2745             Don't use C in your regular application code.
2746              
2747             In addition, it may cause an exception (C) if
2748             the collection contains a very large number of lists
2749             (C<'Error while reading from Redis server'>).
2750              
2751             =cut
2752             sub lists {
2753 0     0 1   my $self = shift;
2754 0   0       my $pattern = shift // '*';
2755              
2756 0   0       _STRING( $pattern ) // $self->_throw( $E_MISMATCH_ARG, 'pattern' );
2757              
2758 0           $self->_set_last_errorcode( $E_NO_ERROR );
2759              
2760 0           my @keys;
2761             try {
2762 0     0     @keys = $self->_call_redis( 'KEYS', $self->_data_list_key( $pattern ) );
2763             } catch {
2764 0     0     my $error = $_;
2765 0 0         _confess( $error ) unless $self->last_errorcode == $E_REDIS_DID_NOT_RETURN_DATA;
2766 0           };
2767              
2768 0           return map { ( $_ =~ /:([^:]+)$/ )[0] } @keys;
  0            
2769             }
2770              
2771             =head3 resize
2772              
2773             resize( redis => $server, name => $name, ... )
2774              
2775             Example:
2776              
2777             $coll->resize( min_cleanup_bytes => 100_000 );
2778             my $redis = Redis->new( server => "$server:$port" );
2779             Redis::CappedCollection::resize( redis => $redis, name => 'Some name', older_allowed => 1 );
2780              
2781             Use the C to change the values of the parameters of the collection.
2782             It can be called as either the existing C object method or a class function.
2783              
2784             If invoked as the object method, C uses C and C attributes
2785             from the object as defaults.
2786             If invoked as the class function, C requires mandatory C and C
2787             arguments.
2788              
2789             These arguments are in key-value pairs as described for L method.
2790              
2791             It is possible to change the following parameters: C, C,
2792             C, C. One or more parameters are required.
2793              
2794             Returns the number of completed changes.
2795              
2796             An error exception is thrown (C) if an argument is not valid or the
2797             collection does not exist.
2798              
2799             =cut
2800             sub resize {
2801 0     0 1   my ( $self, $redis, $name );
2802 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
2803 0           $self = shift;
2804 0           $redis = $self->_redis;
2805 0           $name = $self->name;
2806             } else {
2807 0 0         shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar
2808             }
2809              
2810 0           my %arguments = @_;
2811 0           _check_arguments_acceptability( \%arguments, [ 'redis', 'name', @_status_parameters ] );
2812              
2813 0 0         unless ( $self ) {
2814 0 0         confess "'redis' argument is required" unless defined $arguments{redis};
2815 0 0         confess "'name' argument is required" unless defined $arguments{name};
2816             }
2817              
2818 0 0         $redis = _get_redis( $arguments{redis} ) unless $self;
2819 0 0         $name = $arguments{name} if $arguments{name};
2820              
2821 0           my $requested_changes = 0;
2822 0           foreach my $parameter ( @_status_parameters ) {
2823 0 0         ++$requested_changes if exists $arguments{ $parameter };
2824             }
2825 0 0         unless ( $requested_changes ) {
2826 0           my $error = 'One or more parameters are required';
2827 0 0         if ( $self ) {
2828 0           $self->_throw( $E_MISMATCH_ARG, $error );
2829             } else {
2830 0           confess format_message( '%s : %s', $error, $ERROR{ $E_MISMATCH_ARG } );
2831             }
2832             }
2833              
2834 0           my $resized = 0;
2835 0           foreach my $parameter ( @_status_parameters ) {
2836 0 0         if ( exists $arguments{ $parameter } ) {
2837 0 0 0       if ( $parameter eq 'min_cleanup_bytes' || $parameter eq 'min_cleanup_items' ) {
    0          
    0          
2838             confess "'$parameter' must be nonnegative integer"
2839 0 0         unless _NONNEGINT( $arguments{ $parameter } );
2840             } elsif ( $parameter eq 'memory_reserve' ) {
2841 0           my $memory_reserve = $arguments{ $parameter };
2842 0 0 0       confess format_message( "'%s' must have a valid value", $parameter )
      0        
2843             unless _NUMBER( $memory_reserve ) && $memory_reserve >= $MIN_MEMORY_RESERVE && $memory_reserve <= $MAX_MEMORY_RESERVE;
2844             } elsif ( $parameter eq 'older_allowed' ) {
2845 0 0         $arguments{ $parameter } = $arguments{ $parameter } ? 1 :0;
2846             }
2847              
2848 0           my $ret = 0;
2849 0           my $new_val = $arguments{ $parameter };
2850 0 0         if ( $self ) {
2851 0           $ret = $self->_call_redis( 'HSET', _make_status_key( $self->name ), $parameter, $new_val );
2852             } else {
2853 0           $ret = _call_redis( $redis, 'HSET', _make_status_key( $name ), $parameter, $new_val );
2854             }
2855              
2856 0 0         if ( $ret == 0 ) { # 0 if field already exists in the hash and the value was updated
2857 0 0         if ( $self ) {
2858 0 0         if ( $parameter eq 'min_cleanup_bytes' ) {
    0          
    0          
2859 0           $self->_set_min_cleanup_bytes( $new_val );
2860             } elsif ( $parameter eq 'min_cleanup_items' ) {
2861 0           $self->_set_min_cleanup_items( $new_val );
2862             } elsif ( $parameter eq 'memory_reserve' ) {
2863 0           $self->_set_memory_reserve( $new_val );
2864             } else {
2865 0           $self->$parameter( $new_val );
2866             }
2867             }
2868 0           ++$resized;
2869             } else {
2870 0           my $msg = format_message( "Parameter %s not updated to '%s' for collection '%s'", $parameter, $new_val, $name );
2871 0 0         if ( $self ) {
2872 0           $self->_throw( $E_COLLECTION_DELETED, $msg );
2873             } else {
2874 0           _confess( "$msg (".$ERROR{ $E_COLLECTION_DELETED }.')' );
2875             }
2876             }
2877             }
2878             }
2879              
2880 0           return $resized;
2881             }
2882              
2883             =head3 drop_collection
2884              
2885             drop_collection( redis => $server, name => $name )
2886              
2887             Example:
2888              
2889             $coll->drop_collection;
2890             my $redis = Redis->new( server => "$server:$port" );
2891             Redis::CappedCollection::drop_collection( redis => $redis, name => 'Some name' );
2892              
2893             Use the C to remove the entire collection from the redis server,
2894             including all its data and metadata.
2895              
2896             Before using this method, make sure that the collection is not being used by other customers.
2897              
2898             It can be called as either the existing C object method or a class function.
2899             If invoked as the class function, C requires mandatory C and C
2900             arguments.
2901             These arguments are in key-value pairs as described for L method.
2902              
2903             Warning: consider C as a command that should only be used in production
2904             environments with extreme care. Its performance is not optimal for large collections.
2905             This command is intended for debugging and special operations.
2906             Avoid using C in your regular application code.
2907              
2908             C mat throw an exception (C) if
2909             the collection contains a very large number of lists
2910             (C<'Error while reading from Redis server'>).
2911              
2912             An error exception is thrown (C) if an argument is not valid.
2913              
2914             =cut
2915             sub drop_collection {
2916 0     0 1   my $ret;
2917 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
2918 0           my $self = shift;
2919              
2920 0           my %arguments = @_;
2921 0           _check_arguments_acceptability( \%arguments, [] );
2922              
2923 0           $self->_set_last_errorcode( $E_NO_ERROR );
2924              
2925 0           $ret = $self->_call_redis(
2926             $self->_lua_script_cmd( 'drop_collection' ),
2927             0,
2928             $self->name,
2929             );
2930              
2931 0           $self->_clear_name;
2932 0           $self->_clear_sha1;
2933             } else {
2934 0 0         shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar
2935              
2936 0           my %arguments = @_;
2937 0           _check_arguments_acceptability( \%arguments, [ 'redis', 'name' ] );
2938              
2939 0 0         confess "'redis' argument is required" unless defined $arguments{redis};
2940 0 0         confess "'name' argument is required" unless defined $arguments{name};
2941              
2942 0           my $redis = _get_redis( $arguments{redis} );
2943 0           my $name = $arguments{name};
2944              
2945 0           $ret = _call_redis(
2946             $redis,
2947             _lua_script_cmd( $redis, 'drop_collection' ),
2948             0,
2949             $name,
2950             );
2951             }
2952              
2953 0           return $ret;
2954             }
2955              
2956             =head3 drop_list
2957              
2958             drop_list( $list_id )
2959              
2960             Use the C method to remove the entire specified list.
2961             Method removes all the structures on the Redis server associated with
2962             the specified list.
2963              
2964             C<$list_id> must be a non-empty string.
2965              
2966             Method returns true if the list is removed, or false otherwise.
2967              
2968             =cut
2969             my @_drop_list_result_keys = qw(
2970             error
2971             list_removed
2972             );
2973              
2974             sub drop_list {
2975 0     0 1   my ( $self, $list_id ) = @_;
2976              
2977 0   0       _STRING( $list_id ) // $self->_throw( $E_MISMATCH_ARG, 'list_id' );
2978              
2979 0           $self->_set_last_errorcode( $E_NO_ERROR );
2980              
2981 0           my @ret = $self->_call_redis(
2982             $self->_lua_script_cmd( 'drop_list' ),
2983             0,
2984             $self->name,
2985             $list_id,
2986             );
2987 0           my $results = _lists2hash( \@_drop_list_result_keys, \@ret );
2988              
2989 0           my $error = $results->{error};
2990              
2991 0 0         if ( exists $ERROR{ $error } ) {
2992 0 0         $self->_clear_sha1 if $error == $E_COLLECTION_DELETED;
2993 0 0         $self->_throw( $error ) if $error != $E_NO_ERROR;
2994             } else {
2995 0           $self->_process_unknown_error( @ret );
2996             }
2997              
2998 0           return $results->{list_removed};
2999             }
3000              
3001             =head3 clear_collection
3002              
3003             $coll->clear_collection;
3004              
3005             Use the C to remove the entire collection data from the redis server,
3006              
3007             Before using this method, make sure that the collection is not being used by other customers.
3008              
3009             Warning: consider C as a command that should only be used in production
3010             environments with extreme care. Its performance is not optimal for large collections.
3011             This command is intended for debugging and special operations.
3012             Avoid using C in your regular application code.
3013              
3014             C mat throw an exception (C) if
3015             the collection contains a very large number of lists
3016             (C<'Error while reading from Redis server'>).
3017              
3018             =cut
3019             sub clear_collection {
3020 0     0 1   my $self = shift;
3021              
3022 0           my $ret;
3023              
3024 0           $self->_set_last_errorcode( $E_NO_ERROR );
3025              
3026 0           $ret = $self->_call_redis(
3027             $self->_lua_script_cmd( 'clear_collection' ),
3028             0,
3029             $self->name,
3030             );
3031              
3032 0           return $ret;
3033             }
3034              
3035             =head3 ping
3036              
3037             $is_alive = $coll->ping;
3038              
3039             This command is used to test if a connection is still alive.
3040              
3041             Returns 1 if a connection is still alive or 0 otherwise.
3042              
3043             External connections to the server object (eg, C <$redis = Redis->new( ... );>),
3044             and the collection object can continue to work after calling ping only if the method returned 1.
3045              
3046             If there is no connection to the Redis server (methods return 0), the connection to the server closes.
3047             In this case, to continue working with the collection,
3048             you must re-create the C object with the L method.
3049             When using an external connection to the server,
3050             to check the connection to the server you can use the C<$redis->echo( ... )> call.
3051             This is useful to avoid closing the connection to the Redis server unintentionally.
3052              
3053             =cut
3054             sub ping {
3055 0     0 1   my ( $self ) = @_;
3056              
3057 0           $self->_set_last_errorcode( $E_NO_ERROR );
3058              
3059 0           my $ret;
3060             try {
3061 0     0     $ret = $self->_redis->ping;
3062             } catch {
3063 0     0     $self->_redis_exception( $_ );
3064 0           };
3065              
3066 0 0 0       return( ( $ret // '' ) eq 'PONG' ? 1 : 0 );
3067             }
3068              
3069             =head3 quit
3070              
3071             $coll->quit;
3072              
3073             Close the connection with the redis server.
3074              
3075             It does not close the connection to the Redis server if it is an external connection provided
3076             to collection constructor as existing L object.
3077             When using an external connection (eg, C<$redis = Redis-> new (...);>),
3078             to close the connection to the Redis server, call C<$redis->quit> after calling this method.
3079              
3080             =cut
3081             sub quit {
3082 0     0 1   my ( $self ) = @_;
3083              
3084 0 0 0       return if $] >= 5.14 && ${^GLOBAL_PHASE} eq 'DESTRUCT';
3085              
3086 0           $self->_set_last_errorcode( $E_NO_ERROR );
3087 0           $self->_clear_sha1;
3088 0 0         unless ( $self->_use_external_connection ) {
3089             try {
3090 0     0     $self->_redis->quit;
3091             } catch {
3092 0     0     $self->_redis_exception( $_ );
3093 0           };
3094             }
3095              
3096 0           return;
3097             }
3098              
3099             #-- private attributes ---------------------------------------------------------
3100              
3101             has _DEBUG => (
3102             is => 'rw',
3103             init_arg => undef,
3104             isa => 'Num',
3105             default => 0,
3106             );
3107              
3108             has _check_maxmemory => (
3109             is => 'ro',
3110             init_arg => 'check_maxmemory',
3111             isa => 'Bool',
3112             default => 1,
3113             );
3114              
3115             has _create_from_naked_new => (
3116             is => 'ro',
3117             isa => 'Bool',
3118             default => 1,
3119             );
3120              
3121             has _create_from_open => (
3122             is => 'ro',
3123             isa => 'Bool',
3124             default => 0,
3125             );
3126              
3127             has _use_external_connection => (
3128             is => 'rw',
3129             isa => 'Bool',
3130             default => 1,
3131             );
3132              
3133             has _server => (
3134             is => 'rw',
3135             isa => 'Str',
3136             default => $DEFAULT_SERVER.':'.$DEFAULT_PORT,
3137             trigger => sub {
3138             my $self = shift;
3139             $self->_server( $self->_server.':'.$DEFAULT_PORT )
3140             unless $self->_server =~ /:/;
3141             },
3142             );
3143              
3144             has _redis => (
3145             is => 'rw',
3146             # 'Maybe[Test::RedisServer]' to test only
3147             isa => 'Maybe[Redis] | Maybe[Test::RedisServer]',
3148             );
3149              
3150             has _maxmemory => (
3151             is => 'rw',
3152             isa => __PACKAGE__.'::NonNegInt',
3153             init_arg => undef,
3154             );
3155              
3156             foreach my $attr_name ( qw(
3157             _queue_key
3158             _status_key
3159             _data_keys
3160             _time_keys
3161             ) ) {
3162             has $attr_name => (
3163             is => 'rw',
3164             isa => 'Str',
3165             init_arg => undef,
3166             );
3167             }
3168              
3169             my $_lua_scripts = {};
3170              
3171             #-- private functions ----------------------------------------------------------
3172              
3173             sub _check_arguments_acceptability {
3174 0     0     my ( $received_arguments, $acceptable_arguments ) = @_;
3175              
3176 0           my ( %legal_arguments, @unlegal_arguments );
3177 0           $legal_arguments{ $_ } = 1 foreach @$acceptable_arguments;
3178 0           foreach my $argument ( keys %$received_arguments ) {
3179 0 0         push @unlegal_arguments, $argument unless exists $legal_arguments{ $argument };
3180             }
3181              
3182 0 0         confess( format_message( 'Unknown arguments: %s', \@unlegal_arguments ) ) if @unlegal_arguments;
3183              
3184 0           return;
3185             }
3186              
3187             sub _maxmemory_policy_ok {
3188 0     0     my ( $self, $redis );
3189 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
3190 0           $self = shift;
3191 0           $redis = $self->_redis;
3192             } else {
3193 0 0         shift if _CLASSISA( $_[0], __PACKAGE__ ); # allow calling Foo->bar as well as Foo::bar
3194             }
3195              
3196 0           my %arguments = @_;
3197 0           _check_arguments_acceptability( \%arguments, [ 'redis' ] );
3198              
3199 0           my $maxmemory_policy;
3200 0 0         if ( $self ) {
3201 0           ( undef, $maxmemory_policy ) = $self->_call_redis( 'CONFIG', 'GET', 'maxmemory-policy' );
3202             } else {
3203 0           my $redis_argument = $arguments{redis};
3204 0 0         confess "'redis' argument is required" unless defined( $redis_argument );
3205 0           ( undef, $maxmemory_policy ) = _call_redis( _get_redis( $redis_argument ), 'CONFIG', 'GET', 'maxmemory-policy' )
3206             }
3207              
3208 0   0       return( defined( $maxmemory_policy ) && $maxmemory_policy eq $USED_MEMORY_POLICY );
3209             }
3210              
3211             sub _lists2hash {
3212 0     0     my ( $keys, $vals ) = @_;
3213              
3214 0 0 0       confess $ERROR{ $E_MISMATCH_ARG }." for internal function '_lists2hash'"
      0        
3215             unless _ARRAY( $keys ) && _ARRAY0( $vals ) && scalar( @$keys ) >= scalar( @$vals );
3216              
3217 0           my %hash;
3218 0           for ( my $idx = 0; $idx < @$keys; $idx++ ) {
3219 0           $hash{ $keys->[ $idx ] } = $vals->[ $idx ];
3220             }
3221              
3222 0           return \%hash;
3223             }
3224              
3225             sub _process_unknown_error {
3226 0     0     my ( $self, @args ) = @_;
3227              
3228 0           $self->_set_last_errorcode( $E_UNKNOWN_ERROR );
3229 0 0         _unknown_error( @args, $self->reconnect_on_error ? $self->_reconnect( $E_UNKNOWN_ERROR ) : () );
3230             }
3231              
3232             sub _unknown_error {
3233 0     0     my @args = @_;
3234              
3235 0           _confess( format_message( '%s: %s', $ERROR{ $E_UNKNOWN_ERROR }, \@args ) );
3236             }
3237              
3238             sub _confess {
3239 0     0     my @args = @_;
3240              
3241 0           confess @args;
3242             }
3243              
3244             sub _make_data_key {
3245 0     0     my ( $name ) = @_;
3246 0           return( $NAMESPACE.':D:'.$name );
3247             }
3248              
3249             sub _make_time_key {
3250 0     0     my ( $name ) = @_;
3251 0           return( $NAMESPACE.':T:'.$name );
3252             }
3253              
3254             sub _make_status_key {
3255 0     0     my ( $name ) = @_;
3256 0           return( $NAMESPACE.':S:'.$name );
3257             }
3258              
3259             sub _get_redis {
3260 0     0     my ( $redis ) = @_;
3261              
3262 0 0         $redis = _redis_constructor( $redis )
3263             unless _INSTANCE( $redis, 'Redis' );
3264              
3265 0           return $redis;
3266             }
3267              
3268             #-- private methods ------------------------------------------------------------
3269              
3270             # for testing only
3271             sub _long_term_operation {
3272 0     0     my ( $self, $return_as_insert ) = @_;
3273              
3274 0           $self->_set_last_errorcode( $E_NO_ERROR );
3275              
3276 0 0         my @ret = $self->_call_redis(
3277             $self->_lua_script_cmd( '_long_term_operation' ),
3278             0,
3279             $self->name,
3280             $return_as_insert ? 1 : 0,
3281             );
3282              
3283 0           my ( $error ) = @ret;
3284              
3285 0 0         if ( $return_as_insert ) {
3286 0 0 0       if ( scalar( @ret ) == 1 && exists( $ERROR{ $error } ) ) {
3287 0 0 0       if ( $error == $E_NO_ERROR ) {
    0          
    0          
3288             # Normal result: Nothing to do
3289             } elsif ( $error == $E_COLLECTION_DELETED ) {
3290 0           $self->_clear_sha1;
3291 0           $self->_throw( $error );
3292             } elsif (
3293             $error == $E_DATA_ID_EXISTS
3294             || $error == $E_OLDER_THAN_ALLOWED
3295             ) {
3296 0           $self->_throw( $error );
3297             } else {
3298 0           $self->_throw( $error, 'Unexpected error' );
3299             }
3300             } else {
3301 0           $self->_process_unknown_error( @ret );
3302             }
3303             } else {
3304 0 0 0       if ( scalar( @ret ) == 3 && exists( $ERROR{ $error } ) && $ret[2] eq '_long_term_operation' ) {
      0        
3305 0 0         if ( $error == $E_NO_ERROR ) {
3306             # Normal result: Nothing to do
3307             } else {
3308 0           $self->_throw( $error, 'Unexpected error' );
3309             }
3310             } else {
3311 0           $self->_process_unknown_error( @ret );
3312             }
3313             }
3314              
3315 0           return \@ret;
3316             }
3317              
3318             sub _data_list_key {
3319 0     0     my ( $self, $list_id ) = @_;
3320              
3321 0           return( $self->_data_keys.':'.$list_id );
3322             }
3323              
3324             sub _time_list_key {
3325 0     0     my ( $self, $list_id ) = @_;
3326              
3327 0           return( $self->_time_keys.':'.$list_id );
3328             }
3329              
3330             sub _verify_collection {
3331 0     0     my ( $self ) = @_;
3332              
3333 0           $self->_set_last_errorcode( $E_NO_ERROR );
3334              
3335 0 0 0       my ( $status_exist, $older_allowed, $min_cleanup_bytes, $min_cleanup_items, $memory_reserve, $data_version ) = $self->_call_redis(
      0        
      0        
3336             $self->_lua_script_cmd( 'verify_collection' ),
3337             0,
3338             $self->name,
3339             $self->older_allowed ? 1 : 0,
3340             $self->min_cleanup_bytes || 0,
3341             $self->min_cleanup_items || 0,
3342             $self->memory_reserve || $MIN_MEMORY_RESERVE,
3343             );
3344              
3345 0 0         if ( $status_exist ) {
3346 0 0         $self->min_cleanup_bytes( $min_cleanup_bytes ) unless $self->min_cleanup_bytes;
3347 0 0         $self->min_cleanup_items( $min_cleanup_items ) unless $self->min_cleanup_items;
3348 0 0         $older_allowed == $self->older_allowed or $self->_throw( $E_MISMATCH_ARG, 'older_allowed' );
3349 0 0         $min_cleanup_bytes == $self->min_cleanup_bytes or $self->_throw( $E_MISMATCH_ARG, 'min_cleanup_bytes' );
3350 0 0         $min_cleanup_items == $self->min_cleanup_items or $self->_throw( $E_MISMATCH_ARG, 'min_cleanup_items' );
3351 0 0         $memory_reserve == $self->memory_reserve or $self->_throw( $E_MISMATCH_ARG, 'memory_reserve' );
3352 0 0         $data_version == $DATA_VERSION or $self->_throw( $E_INCOMP_DATA_VERSION );
3353             }
3354             }
3355              
3356             sub _reconnect {
3357 0     0     my $self = shift;
3358 0   0       my $err = shift // 0;
3359 0           my $msg = shift;
3360              
3361 0           my $err_msg = '';
3362 0 0 0       if (
      0        
      0        
      0        
3363             !$err || (
3364             $err != $E_MISMATCH_ARG
3365             && $err != $E_DATA_TOO_LARGE
3366             && $err != $E_MAXMEMORY_LIMIT
3367             && $err != $E_MAXMEMORY_POLICY
3368             )
3369             ) {
3370             try {
3371 0     0     $self->_redis->connect;
3372             } catch {
3373 0     0     my $error = $_;
3374 0           $err_msg = "(Not reconnected: $error)";
3375 0           };
3376             }
3377              
3378 0 0         if ( $err_msg ) {
3379 0 0         $msg = defined( $msg )
    0          
3380             ? ( $msg ? "$msg " : '' )."($err_msg)"
3381             : $err_msg;
3382             }
3383              
3384 0           return $msg;
3385             }
3386              
3387             sub _throw {
3388 0     0     my ( $self, $err, $prefix ) = @_;
3389              
3390 0 0         $prefix = $self->_reconnect( $err, $prefix ) if $self->reconnect_on_error;
3391              
3392 0 0         if ( exists $ERROR{ $err } ) {
3393 0           $self->_set_last_errorcode( $err );
3394 0 0         _confess( format_message( '%s%s', ( $prefix ? "$prefix : " : '' ), $ERROR{ $err } ) );
3395             } else {
3396 0           $self->_set_last_errorcode( $E_UNKNOWN_ERROR );
3397 0 0         _confess( format_message( '%s: %s%s', $ERROR{ $E_UNKNOWN_ERROR }, ( $prefix ? "$prefix : " : '' ), format_message( '%s', $err ) ) );
3398             }
3399             }
3400              
3401             {
3402             my ( $_running_script_name, $_running_script_body );
3403              
3404             sub _lua_script_cmd {
3405 0     0     my ( $self, $redis );
3406 0 0         if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
3407 0           $self = shift;
3408 0           $redis = $self->_redis;
3409             } else { # allow calling Foo::bar
3410 0           $redis = shift;
3411             }
3412              
3413 0           $_running_script_name = shift;
3414 0           $_running_script_body = $lua_script_body{ $_running_script_name };
3415              
3416 0           my $sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name };
3417 0 0         unless ( $sha1 ) {
3418 0           $sha1 = $_lua_scripts->{ $redis }->{ $_running_script_name } = sha1_hex( $_running_script_body );
3419 0           my $ret;
3420 0 0         if ( $self ) {
3421 0           $ret = ( $self->_call_redis( 'SCRIPT', 'EXISTS', $sha1 ) )[0];
3422             } else {
3423 0           $ret = ( _call_redis( $redis, 'SCRIPT', 'EXISTS', $sha1 ) )[0];
3424             }
3425 0 0         return( 'EVAL', $_running_script_body )
3426             unless $ret;
3427             }
3428 0           return( 'EVALSHA', $sha1 );
3429             }
3430              
3431             sub _redis_exception {
3432 0     0     my $self;
3433 0 0         $self = shift if _INSTANCE( $_[0], __PACKAGE__ ); # allow calling $obj->bar
3434 0           my ( $error ) = @_; # allow calling Foo::bar
3435              
3436 0           my $err_msg = '';
3437 0 0         if ( $self ) {
3438             # Use the error messages from Redis.pm
3439 0 0 0       if (
    0 0        
    0 0        
      0        
      0        
      0        
3440             $error =~ /^Could not connect to Redis server at /
3441             || $error =~ /^Can't close socket: /
3442             || $error =~ /^Not connected to any server/
3443             # Maybe for pub/sub only
3444             || $error =~ /^Error while reading from Redis server: /
3445             || $error =~ /^Redis server closed connection/
3446             ) {
3447 0           $self->_set_last_errorcode( $E_NETWORK );
3448              
3449             # For connection problem
3450 0 0         $err_msg = $self->_reconnect( $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error;
3451             } elsif (
3452             $error =~ /^\[[^]]+\]\s+-?\Q$REDIS_MEMORY_ERROR_MSG\E/i
3453             || $error =~ /^\[[^]]+\]\s+-?\Q$REDIS_ERROR_CODE $ERROR{ $E_MAXMEMORY_LIMIT }\E/i
3454             || $error =~ /^\[[^]]+\]\s+-NOSCRIPT No matching script. Please use EVAL./
3455             ) {
3456 0           $self->_set_last_errorcode( $E_MAXMEMORY_LIMIT );
3457 0           $self->_clear_sha1;
3458              
3459             # No connection problem
3460             } elsif ( $error =~ /^\[[^]]+\]\s+BUSY Redis is busy running a script/ ){
3461 0           $self->_set_last_errorcode( $E_UNKNOWN_ERROR );
3462              
3463             # No connection problem - must wait...
3464             } else { # external ALRM processing here
3465 0           $self->_set_last_errorcode( $E_REDIS );
3466              
3467             # For possible connection problems
3468 0 0         $err_msg = $self->_reconnect( $E_UNKNOWN_ERROR, $err_msg ) if $self->reconnect_on_error;
3469             }
3470             } else {
3471             # nothing to do now
3472             }
3473              
3474 0 0         if ( $error =~ /\] ERR Error (?:running|compiling) script/ ) {
3475 0           $error .= "\nLua script '$_running_script_name':\n$_running_script_body";
3476             }
3477 0           _confess( format_message( '%s %s', $error, $err_msg ) );
3478             }
3479             }
3480              
3481             sub _clear_sha1 {
3482 0     0     my ( $self ) = @_;
3483              
3484 0 0         delete( $_lua_scripts->{ $self->_redis } ) if $self->_redis;
3485             }
3486              
3487             sub _redis_constructor {
3488 0     0     my ( $self, $redis, $redis_parameters );
3489 0 0 0       if ( @_ && _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
3490 0           $self = shift;
3491 0           $redis_parameters = shift;
3492              
3493 0 0         if ( _HASH0( $redis_parameters ) ) {
3494 0           $self->_set_last_errorcode( $E_NO_ERROR );
3495             $redis = try {
3496 0     0     Redis->new( %$redis_parameters );
3497             } catch {
3498 0     0     my $error = $_;
3499 0           $self->_redis_exception( format_message( '%s; (redis_parameters = %s)', $error, _parameters_2_str( $redis_parameters ) ) );
3500 0           };
3501             } else {
3502 0           $redis = $self->_redis;
3503             }
3504             } else { # allow calling Foo::bar
3505 0 0         $redis_parameters = _HASH0( shift ) or confess $ERROR{ $E_MISMATCH_ARG };
3506             $redis = try {
3507 0     0     Redis->new( %$redis_parameters );
3508             } catch {
3509 0     0     my $error = $_;
3510 0           confess format_message( "'Redis' exception: %s; (redis_parameters = %s)", $error, _parameters_2_str( $redis_parameters ) );
3511 0           };
3512             }
3513              
3514 0           return $redis;
3515             }
3516              
3517             sub _parameters_2_str {
3518 0     0     my ( $parameters_hash_ref ) = @_;
3519              
3520 0           my %parameters_hash = ( %$parameters_hash_ref );
3521 0 0         $parameters_hash{password} =~ s/./*/g if defined $parameters_hash{password};
3522              
3523 0           return format_message( '%s', \%parameters_hash );
3524             }
3525              
3526             # Keep in mind the default 'redis.conf' values:
3527             # Close the connection after a client is idle for N seconds (0 to disable)
3528             # timeout 300
3529              
3530             # Send a request to Redis
3531             sub _call_redis {
3532 0     0     my ( $self, $redis );
3533 0 0         if ( _INSTANCE( $_[0], __PACKAGE__ ) ) { # allow calling $obj->bar
3534 0           $self = shift;
3535              
3536 0 0 0       if ( $self->reconnect_on_error && !$self->ping ) {
3537 0           my $err_msg = $self->_reconnect();
3538 0 0         _confess( format_message( '%s: %s', $ERROR{$E_REDIS}, $err_msg ) ) if $err_msg;
3539             }
3540              
3541 0           $redis = $self->_redis;
3542             } else { # allow calling Foo::bar
3543 0           $redis = shift;
3544             }
3545 0           my $method = shift;
3546              
3547 0 0         $self->_set_last_errorcode( $E_NO_ERROR ) if $self;
3548              
3549 0           my @return;
3550 0           my @args = @_;
3551             try {
3552 0 0   0     @return = $redis->$method( map { ref( $_ ) ? $$_ : $_ } @args );
  0            
3553             } catch {
3554 0     0     my $error = $_;
3555 0 0         if ( $self ) {
3556 0           $self->_redis_exception( $error );
3557             } else {
3558 0           _redis_exception( $error );
3559             }
3560 0           };
3561              
3562 0 0         unless ( scalar @return ) {
3563 0 0         $self->_set_last_errorcode( $E_REDIS_DID_NOT_RETURN_DATA )
3564             if $self;
3565 0           confess $ERROR{ $E_REDIS_DID_NOT_RETURN_DATA };
3566             }
3567              
3568 0 0         return wantarray ? @return : $return[0];
3569             }
3570              
3571             sub DESTROY {
3572             my ( $self ) = @_;
3573              
3574             $self->clear_sha1;
3575             }
3576              
3577             #-- Closes and cleans up -------------------------------------------------------
3578              
3579 98     98   579 no Mouse::Util::TypeConstraints;
  98         154  
  98         924  
3580 98     98   11666 no Mouse; # keywords are removed from the package
  98         118  
  98         381  
3581             __PACKAGE__->meta->make_immutable();
3582              
3583             =head2 DIAGNOSTICS
3584              
3585             All recognizable errors in C set corresponding value
3586             into the L and throw an exception (C).
3587             Unidentified errors also throw exceptions but L is not set.
3588              
3589             In addition to errors in the L module, detected errors are
3590             L, L, L, L,
3591             L, L, L,
3592             L, L, L.
3593              
3594             The user has the choice:
3595              
3596             =over 3
3597              
3598             =item *
3599              
3600             Use the module methods and independently analyze the situation without the use
3601             of L.
3602              
3603             =item *
3604              
3605             Piece of code wrapped in C and analyze L
3606             (look at the L section).
3607              
3608             =back
3609              
3610             =head2 An Example
3611              
3612             An example of error handling.
3613              
3614             use 5.010;
3615             use strict;
3616             use warnings;
3617              
3618             #-- Common ---------------------------------------------------------
3619             use Redis::CappedCollection qw(
3620             $DEFAULT_SERVER
3621             $DEFAULT_PORT
3622              
3623             $E_NO_ERROR
3624             $E_MISMATCH_ARG
3625             $E_DATA_TOO_LARGE
3626             $E_NETWORK
3627             $E_MAXMEMORY_LIMIT
3628             $E_MAXMEMORY_POLICY
3629             $E_COLLECTION_DELETED
3630             $E_REDIS
3631             );
3632              
3633             # Error handling
3634             sub exception {
3635             my $coll = shift;
3636             my $err = shift;
3637              
3638             die $err unless $coll;
3639             if ( $coll->last_errorcode == $E_NO_ERROR ) {
3640             # For example, to ignore
3641             return unless $err;
3642             } elsif ( $coll->last_errorcode == $E_MISMATCH_ARG ) {
3643             # Necessary to correct the code
3644             } elsif ( $coll->last_errorcode == $E_DATA_TOO_LARGE ) {
3645             # Limit data length
3646             } elsif ( $coll->last_errorcode == $E_NETWORK ) {
3647             # For example, sleep
3648             #sleep 60;
3649             # and return code to repeat the operation
3650             #return 'to repeat';
3651             } elsif ( $coll->last_errorcode == $E_MAXMEMORY_LIMIT ) {
3652             # For example, return code to restart the server
3653             #return 'to restart the redis server';
3654             } elsif ( $coll->last_errorcode == $E_MAXMEMORY_POLICY ) {
3655             # Correct Redis server 'maxmemory-policy' setting
3656             } elsif ( $coll->last_errorcode == $E_COLLECTION_DELETED ) {
3657             # For example, return code to ignore
3658             #return "to ignore $err";
3659             } elsif ( $coll->last_errorcode == $E_REDIS ) {
3660             # Independently analyze the $err
3661             } elsif ( $coll->last_errorcode == $E_DATA_ID_EXISTS ) {
3662             # For example, return code to reinsert the data
3663             #return "to reinsert with new data ID";
3664             } elsif ( $coll->last_errorcode == $E_OLDER_THAN_ALLOWED ) {
3665             # Independently analyze the situation
3666             } else {
3667             # Unknown error code
3668             }
3669             die $err if $err;
3670             }
3671              
3672             my ( $list_id, $coll, @data );
3673              
3674             eval {
3675             $coll = Redis::CappedCollection->create(
3676             redis => $DEFAULT_SERVER.':'.$DEFAULT_PORT,
3677             name => 'Some name',
3678             );
3679             };
3680             exception( $coll, $@ ) if $@;
3681             say "'", $coll->name, "' collection created.";
3682              
3683             #-- Producer -------------------------------------------------------
3684             #-- New data
3685              
3686             eval {
3687             $list_id = $coll->insert(
3688             'Some List_id', # list id
3689             123, # data id
3690             'Some data',
3691             );
3692             say "Added data in a list with '", $list_id, "' id" );
3693              
3694             # Change the "zero" element of the list with the ID $list_id
3695             if ( $coll->update( $list_id, 0, 'New data' ) ) {
3696             say 'Data updated successfully';
3697             } else {
3698             say 'Failed to update element';
3699             }
3700             };
3701             exception( $coll, $@ ) if $@;
3702              
3703             #-- Consumer -------------------------------------------------------
3704             #-- Fetching the data
3705              
3706             eval {
3707             @data = $coll->receive( $list_id );
3708             say "List '$list_id' has '$_'" foreach @data;
3709             # or to obtain records in the order they were placed
3710             while ( my ( $list_id, $data ) = $coll->pop_oldest ) {
3711             say "List '$list_id' had '$data'";
3712             }
3713             };
3714             exception( $coll, $@ ) if $@;
3715              
3716             #-- Utility --------------------------------------------------------
3717             #-- Getting statistics
3718              
3719             my ( $lists, $items );
3720             eval {
3721             my $info = $coll->collection_info;
3722             say 'An existing collection uses ', $info->{min_cleanup_bytes}, " byte of 'min_cleanup_bytes', ",
3723             'in ', $info->{items}, ' items are placed in ',
3724             $info->{lists}, ' lists';
3725              
3726             say "The collection has '$list_id' list"
3727             if $coll->list_exists( 'Some_id' );
3728             };
3729             exception( $coll, $@ ) if $@;
3730              
3731             #-- Closes and cleans up -------------------------------------------
3732              
3733             eval {
3734             $coll->quit;
3735              
3736             # Before use, make sure that the collection
3737             # is not being used by other clients
3738             #$coll->drop_collection;
3739             };
3740             exception( $coll, $@ ) if $@;
3741              
3742             =head2 CappedCollection data structure
3743              
3744             Using currently selected database (default = 0).
3745              
3746             CappedCollection package creates the following data structures on Redis:
3747              
3748             #-- To store collection status:
3749             # HASH Namespace:S:Collection_id
3750             # For example:
3751             $ redis-cli
3752             redis 127.0.0.1:6379> KEYS C:S:*
3753             1) "C:S:Some collection name"
3754             # | | |
3755             # | +-------+ +------------+
3756             # | | |
3757             # Namespace | |
3758             # Fixed symbol of a properties hash |
3759             # Capped Collection id
3760             ...
3761             redis 127.0.0.1:6379> HGETALL "C:S:Some collection name"
3762             1) "lists" # hash key
3763             2) "1" # the key value
3764             3) "items" # hash key
3765             4) "1" # the key value
3766             5) "older_allowed" # hash key
3767             6) "0" # the key value
3768             7) "min_cleanup_bytes" # hash key
3769             8) "0" # the key value
3770             9) "min_cleanup_items" # hash key
3771             10) "100" # the key value
3772             11) "memory_reserve" # hash key
3773             12) "0.05" # the key value
3774             13) "data_version" # hash key
3775             14) "3" # the key value
3776             15) "last_removed_time" # hash key
3777             16) "0" # the key value
3778             ...
3779              
3780             #-- To store collection queue:
3781             # ZSET Namespace:Q:Collection_id
3782             # For example:
3783             redis 127.0.0.1:6379> KEYS C:Q:*
3784             1) "C:Q:Some collection name"
3785             # | | |
3786             # | +------+ +-----------+
3787             # | | |
3788             # Namespace | |
3789             # Fixed symbol of a queue |
3790             # Capped Collection id
3791             ...
3792             redis 127.0.0.1:6379> ZRANGE "C:Q:Some collection name" 0 -1 WITHSCORES
3793             1) "Some list id" ----------+
3794             2) "1348252575.6651001" |
3795             # | |
3796             # Score: oldest data_time |
3797             # Member: Data List id
3798             ...
3799              
3800             #-- To store CappedCollection data:
3801             # HASH Namespace:I:Collection_id:DataList_id
3802             # HASH Namespace:D:Collection_id:DataList_id
3803             # If the amount of data in the list is greater than 1
3804             # ZSET Namespace:T:Collection_id:DataList_id
3805             # For example:
3806             redis 127.0.0.1:6379> KEYS C:[DT]:*
3807             1) "C:D:Some collection name:Some list id"
3808             # If the amount of data in the list is greater than 1
3809             2) "C:T:Some collection name:Some list id"
3810             # | | | |
3811             # | +-----+ +-------+ + ---------+
3812             # | | | |
3813             # Namespace | | |
3814             # Fixed symbol of a list of data | |
3815             # Capped Collection id |
3816             # Data list id
3817             ...
3818             redis 127.0.0.1:6379> HGETALL "C:D:Some collection name:Some list id"
3819             1) "0" # hash key: Data id
3820             2) "Some stuff" # the key value: Data
3821             ...
3822             # If the amount of data in the list is greater than 1
3823             redis 127.0.0.1:6379> ZRANGE "C:T:Some collection name:Some list id" 0 -1 WITHSCORES
3824             1) "0" ---------------+
3825             2) "1348252575.5906" |
3826             # | |
3827             # Score: data_time |
3828             # Member: Data id
3829             ...
3830              
3831             =head1 DEPENDENCIES
3832              
3833             In order to install and use this package Perl version 5.010 or better is
3834             required. Redis::CappedCollection module depends on other packages
3835             that are distributed separately from Perl. We recommend the following packages
3836             to be installed before installing Redis::CappedCollection :
3837              
3838             Const::Fast
3839             Digest::SHA1
3840             Mouse
3841             Params::Util
3842             Redis
3843             Try::Tiny
3844              
3845             The Redis::CappedCollection module has the following optional dependencies:
3846              
3847             Data::UUID
3848             JSON::XS
3849             Net::EmptyPort
3850             Test::Exception
3851             Test::NoWarnings
3852             Test::RedisServer
3853              
3854             If the optional modules are missing, some "prereq" tests are skipped.
3855              
3856             The installation of the missing dependencies can either be accomplished
3857             through your OS package manager or through CPAN (or downloading the source
3858             for all dependencies and compiling them manually).
3859              
3860             =head1 BUGS AND LIMITATIONS
3861              
3862             Redis server version 2.8 or higher is required.
3863              
3864             The use of C in the F file could lead to
3865             a serious (and hard to detect) problem as Redis server may delete
3866             the collection element. Therefore the C does not work with
3867             mode C in the F.
3868              
3869             It may not be possible to use this module with the cluster of Redis servers
3870             because full name of some Redis keys may not be known at the time of the call
3871             the Redis Lua script (C<'EVAL'> or C<'EVALSHA'> command).
3872             So the Redis server may not be able to correctly forward the request
3873             to the appropriate node in the cluster.
3874              
3875             We strongly recommend setting C option in the F file.
3876              
3877             Old data with the same time will be forced out in no specific order.
3878              
3879             The collection API does not support deleting a single data item.
3880              
3881             UTF-8 data should be serialized before passing to C for storing in Redis.
3882              
3883             According to L documentation:
3884              
3885             =over 3
3886              
3887             =item *
3888              
3889             This module consider that any data sent to the Redis server is a raw octets string,
3890             even if it has utf8 flag set.
3891             And it doesn't do anything when getting data from the Redis server.
3892              
3893             TODO: implement tests for
3894              
3895             =over 3
3896              
3897             =item *
3898              
3899             memory errors (working with internal ROLLBACK commands)
3900              
3901             =item *
3902              
3903             working when maxmemory = 0 (in the F file)
3904              
3905             =back
3906              
3907             WARNING: According to C function in F :
3908              
3909             /* 32 bit instances are limited to 4GB of address space, so if there is
3910             * no explicit limit in the user provided configuration we set a limit
3911             * at 3 GB using maxmemory with 'noeviction' policy'. This avoids
3912             * useless crashes of the Redis instance for out of memory. */
3913              
3914             The C module was written, tested, and found working
3915             on recent Linux distributions.
3916              
3917             There are no known bugs in this package.
3918              
3919             Please report problems to the L.
3920              
3921             Patches are welcome.
3922              
3923             =back
3924              
3925             =head1 MORE DOCUMENTATION
3926              
3927             All modules contain detailed information on the interfaces they provide.
3928              
3929             =head1 SEE ALSO
3930              
3931             The basic operation of the Redis::CappedCollection package module:
3932              
3933             L - Object interface to create
3934             a collection, addition of data and data manipulation.
3935              
3936             L - String manipulation utilities.
3937              
3938             L - Perl binding for Redis database.
3939              
3940             =head1 SOURCE CODE
3941              
3942             Redis::CappedCollection is hosted on GitHub:
3943             L
3944              
3945             =head1 AUTHOR
3946              
3947             Sergey Gladkov, Esgladkov@trackingsoft.comE
3948              
3949             =head1 CONTRIBUTORS
3950              
3951             Alexander Solovey
3952              
3953             Jeremy Jordan
3954              
3955             Vlad Marchenko
3956              
3957             =head1 COPYRIGHT AND LICENSE
3958              
3959             Copyright (C) 2012-2016 by TrackingSoft LLC.
3960              
3961             This package is free software; you can redistribute it and/or modify it under
3962             the same terms as Perl itself. See I at
3963             L.
3964              
3965             This program is
3966             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
3967             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
3968             PARTICULAR PURPOSE.
3969              
3970             =cut
3971              
3972             __DATA__