MaxScale/server/core/listmanager.c
Johan Wikman e41589be10 Move headers from server/include to include/maxscale
- Headers now to be included as <maxscale/xyz.h>
- First step, no cleanup of headers has been made. Only moving
  from one place to another + necessary modifications.
2016-10-13 16:19:20 +03:00

452 lines
14 KiB
C

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl.
*
* Change Date: 2019-07-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
/**
* @file listmanager.c - Logic for list handling
*
* MaxScale contains a number of linked lists. This code attempts to provide
* standard functions for handling them. Initially, the main work has been
* on recyclable lists - lists of entries that use dynamically allocated
* memory but are reused rather than freed. Some functions are not fully
* tested - see comments.
*
* @verbatim
* Revision History
*
* Date Who Description
* 20/04/16 Martin Brampton Initial implementation
*
* @endverbatim
*/
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <maxscale/listmanager.h>
#include <maxscale/spinlock.h>
#include <maxscale/dcb.h>
#include <maxscale/log_manager.h>
#include <maxscale/alloc.h>
/**
* Initialise a list configuration.
*
* @param list_config Pointer to the configuration of the list to be initialised
* @param type_of_list Type of list to be initialised.
* @param entry_size The size of each list entry (typically from sizeof).
*
* @note This is only required if a list is configured at execution time
* rather than being declared and statically initialized.
*/
void
list_initialise(LIST_CONFIG *list_config, list_type_t type_of_list, size_t entry_size)
{
list_config->list_type = type_of_list;
list_config->all_entries = NULL;
list_config->last_entry = NULL;
list_config->last_free = NULL;
list_config->count = 0;
list_config->maximum = 0;
list_config->freecount = 0;
list_config->num_malloc = 0;
list_config->entry_size = entry_size;
spinlock_init(&list_config->list_lock);
}
/**
* Allocate memory for some initial list entries
*
* The caller must check the return value. If it is false, this most likely
* indicates a memory allocation failure, and the caller should act
* accordingly. (It could indicate that the requested number of entries
* was less than 1, but this is fairly unlikely).
*
* @param list_config Pointer to the configuration of the list to be initialised
* @param num_entries Number of list entries to be allocated
* @return true if memory was allocated, false if not (or num_entries < 1)
*
*/
bool
list_pre_alloc(LIST_CONFIG *list_config, int num_entries, void (*init_struct)(void *))
{
uint8_t *entry_space;
bool result;
spinlock_acquire(&list_config->list_lock);
if (num_entries <= 0)
{
MXS_ERROR("Attempt to preallocate space for recyclable list asked for no entries");
result = false;
}
else if ((entry_space = (uint8_t *)MXS_CALLOC(num_entries, list_config->entry_size)) == NULL)
{
result = false;
}
else
{
list_entry_t *first_new_entry = (list_entry_t *)entry_space;
list_entry_t *previous = first_new_entry;
for (int i = 1; i <= num_entries; i++)
{
if (init_struct)
{
init_struct((void *)previous);
}
previous->list_entry_chk_top = CHK_NUM_MANAGED_LIST;
previous->list_entry_chk_tail = CHK_NUM_MANAGED_LIST;
list_entry_t *next_entry = (list_entry_t *)((uint8_t *)previous + list_config->entry_size);
if (i < num_entries)
{
previous->next = next_entry;
previous = next_entry;
}
else
{
previous->next = NULL;
}
}
list_config->freecount += num_entries;
list_add_to_end(list_config, first_new_entry);
list_config->last_entry = previous;
list_config->last_free = first_new_entry;
result = true;
}
spinlock_release(&list_config->list_lock);
return result;
}
/**
* @brief Find a free list entry or allocate memory for a new one.
*
* This routine looks to see whether there are free entries.
* If not, new memory is allocated, if possible, and the new entry is added to
* the list of all entries.
*
* @param Pointer to the list configuration structure
* @return An available entry or NULL if none could be found or created.
*/
list_entry_t *
list_find_free(LIST_CONFIG *list_config, void (*init_struct)(void *))
{
list_entry_t *available_entry;
spinlock_acquire(&list_config->list_lock);
if (list_config->freecount <= 0)
{
/* No free entries, need to allocate a new one */
if ((available_entry = MXS_CALLOC(1, list_config->entry_size)) == NULL)
{
spinlock_release(&list_config->list_lock);
return NULL;
}
list_config->num_malloc++;
if (init_struct)
{
init_struct((void *)available_entry);
}
available_entry->list_entry_chk_top = CHK_NUM_MANAGED_LIST;
available_entry->list_entry_chk_tail = CHK_NUM_MANAGED_LIST;
available_entry->next = NULL;
list_add_to_end(list_config, available_entry);
}
/* Starting at the last place a free DCB was found, loop through the */
/* list of DCBs searching for one that is not in use. */
else
{
list_entry_t *next_in_list;
int loopcount = 0;
while (list_config->last_free->entry_is_in_use)
{
list_config->last_free = list_config->last_free->next;
if (NULL == list_config->last_free)
{
loopcount++;
ss_dassert(loopcount == 1);
if (loopcount > 1)
{
/* Shouldn't need to loop round more than once */
MXS_ERROR("Find free list entry failed to find when count positive");
spinlock_release(&list_config->list_lock);
return NULL;
}
list_config->last_free = list_config->all_entries;
}
}
list_config->freecount--;
available_entry = list_config->last_free;
/* Clear the old data, then reset the list forward link */
next_in_list = available_entry->next;
if (init_struct)
{
init_struct((void *)available_entry);
}
else
{
memset(available_entry, 0, list_config->entry_size);
}
available_entry->list_entry_chk_top = CHK_NUM_MANAGED_LIST;
available_entry->list_entry_chk_tail = CHK_NUM_MANAGED_LIST;
available_entry->next = next_in_list;
}
list_config->count++;
if (list_config->count > list_config->maximum)
{
list_config->maximum = list_config->count;
}
available_entry->entry_is_in_use = true;
spinlock_release(&list_config->list_lock);
return available_entry;
}
/**
* @brief Display information about a recyclable list
*
* Should be called from a module that handles one specific list, passing
* the name for the list as well as the print DCB and the list config.
*
* @param Pointer to the print DCB
* @param List configuration pointer, the list to be displayed
* @param Name for the list
*/
void
dprintListStats(DCB *pdcb, LIST_CONFIG *list_config, const char *listname)
{
dcb_printf(pdcb, "Recyclable list statistics\n");
dcb_printf(pdcb, "--------------------------\n");
dcb_printf(pdcb, "Name of list: %s\n", listname);
dcb_printf(pdcb, "Size of entries: %zu\n", list_config->entry_size);
dcb_printf(pdcb, "Currently in use: %d\n", list_config->count);
dcb_printf(pdcb, "Maximum ever used at once: %d\n", list_config->maximum);
dcb_printf(pdcb, "Currently free for reuse: %d\n", list_config->freecount);
dcb_printf(pdcb, "Total in use + free: %d\n",
list_config->freecount + list_config->count);
dcb_printf(pdcb, "Number of memory allocations: %d\n", list_config->num_malloc);
}
/**
* @brief Dispose of a list entry by making it available for reuse.
*
* Within spinlock control, the entry is marked not in use and the
* counts are adjusted.
*
* @param Pointer to the list configuration structure
* @param List entry pointer, the item to be "freed"
*/
void
list_free_entry(LIST_CONFIG *list_config, list_entry_t *to_be_freed)
{
spinlock_acquire(&list_config->list_lock);
to_be_freed->entry_is_in_use = false;
list_config->freecount++;
list_config->count--;
spinlock_release(&list_config->list_lock);
}
/**
* @brief Find out whether a pointer points to a valid list entry
*
* Search the list for the given entry, under spinlock control.
*
* @param Pointer to the list configuration structure
* @param Pointer to be searched for
* @return True if the pointer is in the list and it is in use
*/
bool
list_is_entry_in_use(LIST_CONFIG *list_config, list_entry_t *to_be_found)
{
list_entry_t *entry;
spinlock_acquire(&list_config->list_lock);
entry = list_config->all_entries;
while (entry && to_be_found != entry)
{
entry = entry->next;
}
spinlock_release(&list_config->list_lock);
return (entry && entry->entry_is_in_use);
}
/**
* @brief Invoke a callback for every active member of list
*
* The list is locked, list entries that are in use are successively
* submitted to the callback function. The list entry is supplied to the
* callback function as the first parameters, followed by whatever other
* parameters have been passed to list_map. The process will continue so
* long as the callback function returns true, and will terminate either
* at the end of the list or when the callback function returns false.
*
* Code to be developed.
*
* @param Pointer to the list configuration structure
* @param Pointer to the callback function
*/
void
list_map(LIST_CONFIG *list_config, bool (*callback)(void *, ...))
{
}
/**
* @brief Start to iterate over a list
*
* The list is locked, and the first entry returned to the caller
*
* @param Pointer to the list configuration structure
* @return Pointer to the first entry in the list
*/
list_entry_t *
list_start_iteration(LIST_CONFIG *list_config)
{
spinlock_acquire(&list_config->list_lock);
return list_config->all_entries;
}
/**
* @brief Iterate over a list from a given point
*
* The list is assumed locked through list_start_iteration having been
* called. The next entry that is currently in use is returned to the caller.
* If the end of the list is reached, the spinlock is freed.
*
* @param Pointer to the list configuration structure
* @param Pointer to the entry to move forward from
* @return The next item in the list, or NULL if reached the end
*/
list_entry_t *
list_iterate(LIST_CONFIG *list_config, list_entry_t *current_entry)
{
list_entry_t *next_entry = current_entry->next;
while (next_entry && !(next_entry->entry_is_in_use && next_entry->entry_is_ready))
{
next_entry = next_entry->next;
}
if (NULL == next_entry)
{
spinlock_release(&list_config->list_lock);
}
return next_entry;
}
/**
* @brief Terminate list iteration before reaching the end
*
* The list is assumed locked through list_start_iteration having been
* called, unless the last item is NULL, in which case it is assumed that
* the iteration had already reached the end of the list. If this is not
* the case, then the spinlock is released.
*
* @param Pointer to the list configuration structure
* @param Pointer to the entry last reached in the iteration
*/
void
list_terminate_iteration_early(LIST_CONFIG *list_config, list_entry_t *current_entry)
{
if (current_entry)
{
spinlock_release(&list_config->list_lock);
}
}
/**
* Add a new item to the end of a list.
*
* Must be called with the list lock held.
*
* A pointer, last_entry, is held to find the end of the list, and the new entry
* is linked to the end of the list. The pointer, last_free, that is used to
* search for a free entry is initialised if not already set. There cannot be
* any free entries until this routine has been called at least once.
*
* @param list_config The configuration of the list.
* @param new_entry The new entry to be added to the end of the list
*
* @note UNTESTED for simple or doubly linked lists, currently used
* internally for recyclable lists.
*/
void
list_add_to_end(LIST_CONFIG *list_config, list_entry_t *new_entry)
{
if (NULL == list_config->all_entries)
{
list_config->all_entries = new_entry;
if (LIST_TYPE_DOUBLE == list_config->list_type)
{
new_entry->previous = NULL;
}
}
else
{
list_config->last_entry->next = new_entry;
if (LIST_TYPE_DOUBLE == list_config->list_type)
{
new_entry->previous = list_config->last_entry;
}
}
list_config->last_entry = new_entry;
if (NULL == list_config->last_free)
{
list_config->last_free = new_entry;
}
}
/**
* @brief the list entry removed from the start of the list
*
* Must be called with the list lock held.
*
* @return The first list entry or NULL if the list is empty.
*
* @note UNTESTED! Intended for use on simple or doubly linked lists.
*/
list_entry_t *
list_remove_first(LIST_CONFIG *list_config)
{
list_entry_t *first_in_list = NULL;
if (list_config->all_entries)
{
first_in_list = list_config->all_entries;
list_config->all_entries = first_in_list->next;
}
return first_in_list;
}
/**
* @brief Return the list entry removed from the end of the list
*
* Must be called with the list lock held.
*
* @return The last list entry or NULL if the list is empty.
*
* @note UNTESTED! Intended for use only with doubly linked lists.
*/
list_entry_t *
list_remove_last(LIST_CONFIG *list_config)
{
list_entry_t *last_in_list = NULL;
if (list_config->list_type != LIST_TYPE_DOUBLE)
{
MXS_ERROR("Attempt to remove the last entry in a list that is not doubly linked");
return NULL;
}
if (list_config->all_entries)
{
last_in_list = list_config->last_entry;
list_config->last_entry = last_in_list->previous;
}
return last_in_list;
}