File Coverage

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