File Coverage

lib/Redis/CappedCollection.pm
Criterion Covered Total %
statement 56 648 8.6
branch 0 406 0.0
condition 0 262 0.0
subroutine 19 77 24.6
pod 21 21 100.0
total 96 1414 6.7


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