LRU Cache Routines

    #include <mplib1/dl_lru.h>
The cache routines consist of six functions which provide the following functions
initialisation
setup
search
add
notify
report

The normal program use would be to call the initialisation routine, then to call the setup routine to tell it what the name of the list will be, what fields it contains, and how many entries to have.

Then there would be multiple calls to the search routine, and probably a number of calls to the add routine when the search fails and the data was found by another method, and optionally a call to the notify routine if the data was not found externally.

After the program has performed its main job, the report routine can be called to give statistics on adds, hits & misses.

This can be best illustrated by the following pseudo-code.

    inititalise the cache routines
    create the list required
    while  user gives input
	search for required item in cache
	if not found in cache
	    find item in database
	    if found in database
		add to cache
		print details
	    otherwise
		notify cache
		inform user
	otherwise
	    print details (as returned by cache)
    end while
    print cache statistics

In fact slightly more variations are possible, but we shall examine these after looking at the actual functions in some detail.


Cache_Init

    int Cache_Init( int default_flags );
This function should be called once at startup. The value supplied will be used to provide the defaults flags for Create_Cache_List where not included.

If any value other than CACHE_USE_DEFAULT is supplied it should have only one bit set per mask group. For example, in the cache mode group either CACHE_CACHE_ON or CACHE_STAT_ONLY should be supplied.

The groups and their members are:

    Mode Group          Cache or statistics
	CACHE_CACHE_ON      Cache items
	CACHE_STAT_ONLY     Statistics only

    Case Group          Should key values distinguish
			between upper and lower case.
	CACHE_IGNORE_CASE   Ignore case in key values
	CACHE_CHECK_CASE    Check case in key values

    Allocate Group      Should the list be pre-allocated.
	CACHE_PRE_ALLOCATE  Preallocate space for items
	CACHE_DEMAND_ALLOC  Allocate space for items on demand

    Miss Group          Should complete miss value be stored.
	CACHE_MISS_IGNORE   Ignore complete miss finds
	CACHE_MISS_CACHE    Cache and return complete misses
			    on a find
Return: 0 on failure, 1 on success.


Create_Cache_List

    int Create_Cache_List( const char *list_name,
			   int max_items,
			   int list_flags,
			   int key_size,
			   const char *format_string,
			   const char *field_names );
The list_name is a unique string to identify which chain to put the data on.

The cache_max size specifies the maximum number of items that will be created on the list before re-use occurs. If this is 0 then the list will expand until it either feels like it, or is unable to.

The list_flags specify how the cache should operate. If the supplied value does not have a bit set in a particular mask, the value will default to that provided to Cache_Init.

The key_size parameter if non-zero specifies the maximum length of the key. This is used when pre-allocating list items, and also allows for faster re-use when the cache is full.

The format_string list is a string, similar to that used as a format string in printf, which specifies the field types. The current formats supported are string, character, integer, double and varchar.

	
    %d	decimal integer
    %x	hexadecimal integer
    %c	character
    %f	double
    %s	string
    %S	varchar
The string and varchar types may be preceded by a width specifier which if not present will default to 40.

The field_names parameter is a comma seperated list of fields, which will be used to identify the fields in the field type list.

    e.g. "stn_id,stn_name,gwy_live_stn"
This function should be called once for each type of data set that requires caching.

Return: 0 on failure, 1 on success.


Add_Cache_Item

    int Add_Cache_Item( char *list_name,
			char *list_key,
			... );
This function should be called to add a single set of data items to a given cache list.

The list_name is the same string as specified in the Create_Cache_List call.

The list_key value is a unique string to identify this item in the list. It can usually be produced by performing an sprintf of the actual key fields.

The list key value is then followed by a list of pointers to the actual data fields. These data pointers should be in exactly the same order as the fields specified in the Create_Cache_List call, and should have the same number of fields.

Return: 0 on failure, 1 on success.


Find_Cache_Item

    int Find_Cache_Item( const char *list_name,
			 const char *list_key,
			 const char *field_list,
			 ... );
This function should be called to search (and return data if found) a list for a given key value.

The list_name is the same string as specified in the Create_Cache_List call.

The list_key value is the unique string to identify a given item, generated in the same manner as would be done by the Add_Cache_Item call.

The field_list, is a comma separated list of fields to be returned. This list may contain any subset of the fields specified in field_names in the Create_Cache_List call.

The field list is followed by a list of pointers to the fields to filled in.

Return:

    CACHE_COMPLETE_MISS	if not in cache and not in
			    external data source
    CACHE_MISSED	if not in the cache

    CACHE_FOUND_ITEM	if the item was found :-)

    CACHE_FOUND_MANY	if more than one item with
			    this key value was found.
These values are in ascending sequence so that the return check can be coded as simply or a complex as desired.


Cache_Complete_Miss

    int Cache_Complete_Miss( const char *list_name,
			     const char *list_key );
This function should be called when a given value being searched for, is not found either in the cache list (Find_Cache_Item) or in the external data source.

The list_name is the same string as specified in the Create_Cache_List call.

The list key value is the unique string to identify a given item, generated in the same manner as would be done by the Add_Cache_Item call.

Return: 0 on failure, 1 on success.


Cache_Flush

    int Cache_Flush( const char *list_name );
This function causes all stored values, together with all complete miss values to be freed.

The list_name is the same string as specified in the Create_Cache_List call.

Return: 0 on failure, 1 on success.


Cache_Stats

    int Cache_Stats( FILE *file_handle,
		     const char *list_name,
		     int contents );
This function provides details of the data sizes, and cache hit statistics.

The file_handle is that of the stream where the report output is are desired.

The list_name is that for which statistics are desired, or NULL if statistics are required for all lists.

Return: 0 on failure, 1 on success.


Notes

The way the routines are designed, the only return value that must be checked is that from Find_Cache_Item. If Cache_Init or Create_Cache_List failed the only consequence of any given call to Find_Cache_Item will be that CACHE_MISSED will always be returned.

The return codes from Find_Cache_Item are so arranged in ascending order because it makes it possible to code the subsequent checking in a variety of ways, but allowing simple code to work as well. For example, the relevant section of the pseudo-code at the start of the document would normally be coded as follows;

    rv=Find_Cache_Item( list_name, key_value, field_list, &rf1, &rf2 );

    if ( rv <= CACHE_MISSED )
    {
	rv = Database_Check( key_value, &rf1, &rf2 );

	if ( rv == FOUND )
	{
	    Add_Cache_Item( list_name, key_value, &rf1, &rf2 );
	    /* display details .... */
	}else
	{
	    Cache_Complete_Miss( list_name, key_value );
	    /* inform user of error ... */
	}
    }
    else
    {
	/* display details ... */
    }
But consider the case where the key value is one that will not be found either in the cache or the database, and that this value has been checked before. We would end up checking the cache and the database to gain the same result, whereas if the cache knows something about complete failures we can recode the above as follows;
    rv=Find_Cache_Item( list_name, key_value, field_list, &rf1, &rf2 );

    if ( rv <= CACHE_COMPLETE_MISS )
    {
	Cache_Complete_Miss( list_name, key_value );
	/* inform user of error ... */
    }
    else if ( rv <= CACHE_MISSED )
    {
	rv = Database_Check( key_value, &rf1, &rf2 );

	if ( rv == FOUND )
	{
	    Add_Cache_Item( list_name, key_value, &rf1, &rf2 );
	    /* display details .... */
	}else
	{
	    Cache_Complete_Miss( list_name, key_value );
	    /* inform user of error ... */
	}
    }
    else
    {
	/* display details ... */
    }
The only item that should seem strange is the call to Cache_Complete_Miss when Find_Cache_Item has just returned CACHE_COMPLETE_MISS. This is because there may be some other action required, or this code may be like the first example, in which case a complete miss would be recorded. Let's just call it consistency.

Because of the nature of caching, and the design of the code it should not be assumed that, just because an item has just been added that it will be found on an immediately following search. The following example contains this bad assumption.

    Add_Cache_Item( list_name, key_value, &rf1, &rf2 );

    /* fiddle with values of rf1 & rf2 for some reason */

    Find_Cache_Item( list_name, key_value, field_list, &rf1, &rf2 );

    /* use rf1, rf2,  WRONG !!! */
The Add_Cache_Item call may have failed, the list may not exist, or it may be running in statistics only mode.


Additional Notes

Note:
  1. Currently the cache code does not support multiple items for a single key value. The overheads of providing this are still under consideration.


"OK. So I'll go with the sales pitch. But how do I actually use the thing ?"

Your code should include dl_lru.h, this file contains the function definitions for the cache routines, along with all the flag definitions.