diff options
Diffstat (limited to 'flow/gsl/gsldatacache.c')
-rw-r--r-- | flow/gsl/gsldatacache.c | 633 |
1 files changed, 633 insertions, 0 deletions
diff --git a/flow/gsl/gsldatacache.c b/flow/gsl/gsldatacache.c new file mode 100644 index 0000000..c1df215 --- /dev/null +++ b/flow/gsl/gsldatacache.c @@ -0,0 +1,633 @@ +/* GSL - Generic Sound Layer + * Copyright (C) 2001-2002 Tim Janik + * Copyright (C) 2004 Stefan Westerfeld + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include "gsldatacache.h" + +#include "gslcommon.h" +#include "gsldatahandle.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <time.h> + + +/* --- macros --- */ +#define NODEP_INDEX(dcache, node_p) ((node_p) - (dcache)->nodes) +#define UPPER_POWER2(n) (gsl_alloc_upper_power2 (MAX (n, 4))) +#define CONFIG_NODE_SIZE() (gsl_get_config ()->dcache_block_size) +#define AGE_EPSILON (3) /* must be < smallest sweep */ +#define LOW_PERSISTENCY_SWEEP (5) + +/* we use one global lock to protect the dcache list, the list + * count (length) and the number of aged (unused) nodes. + * also, each dcache has its own mutext to protect updates in + * the reference count, nodes or node data blocks. + * in order to avoid deadlocks, if both locks need + * to be held, they always have to be acquired in the order + * 1) global lock, 2) dcache lock. + * asyncronous data block filling for a new node occours without + * the dcache lock being held (as most calls to GslDataHandle + * functions). + * however, assignment of the newly acquired data is again + * protected by the dcache lock. concurrent API entries + * which require demand loading of such data will wait on + * a global condition which is always signaled once a new data + * block read has been completed. using one global condition + * is considered sufficient until shown otherwise by further + * profiling/debugging measures. + */ + + +/* --- prototypes --- */ +static void dcache_free (GslDataCache *dcache); +static GslDataCacheNode* data_cache_new_node_L (GslDataCache *dcache, + gsize offset, + guint pos, + gboolean demand_load); + + +/* --- variables --- */ +static GslMutex global_dcache_mutex = { 0, }; +static GslCond global_dcache_cond_node_filled = { 0, }; +static GslRing *global_dcache_list = NULL; +static guint global_dcache_count = 0; +static guint global_dcache_n_aged_nodes = 0; + + +/* --- functions --- */ +void +_gsl_init_data_caches (void) +{ + static gboolean initialized = FALSE; + + g_assert (initialized == FALSE); + initialized++; + + g_assert (AGE_EPSILON < LOW_PERSISTENCY_SWEEP); + gsl_cond_init (&global_dcache_cond_node_filled); + gsl_mutex_init (&global_dcache_mutex); +} + +GslDataCache* +gsl_data_cache_new (GslDataHandle *dhandle, + guint padding) +{ + guint node_size = CONFIG_NODE_SIZE () / sizeof (GslDataType); + GslDataCache *dcache; + + g_return_val_if_fail (dhandle != NULL, NULL); + g_return_val_if_fail (padding > 0, NULL); + g_return_val_if_fail (dhandle->name != NULL, NULL); + g_assert (node_size == gsl_alloc_upper_power2 (node_size)); + g_return_val_if_fail (padding < node_size / 2, NULL); + + /* allocate new closed dcache if necessary */ + dcache = gsl_new_struct (GslDataCache, 1); + dcache->dhandle = gsl_data_handle_ref (dhandle); + dcache->open_count = 0; + gsl_mutex_init (&dcache->mutex); + dcache->ref_count = 1; + dcache->node_size = node_size; + dcache->padding = padding; + dcache->max_age = 0; + dcache->low_persistency = FALSE; /* FIXME: !gsl_data_handle_needs_cache (dcache->dhandle); */ + dcache->n_nodes = 0; + dcache->nodes = g_renew (GslDataCacheNode*, NULL, UPPER_POWER2 (dcache->n_nodes)); + + GSL_SPIN_LOCK (&global_dcache_mutex); + global_dcache_list = gsl_ring_append (global_dcache_list, dcache); + global_dcache_count++; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + + return dcache; +} + +void +gsl_data_cache_open (GslDataCache *dcache) +{ + g_return_if_fail (dcache != NULL); + g_return_if_fail (dcache->ref_count > 0); + + GSL_SPIN_LOCK (&dcache->mutex); + if (!dcache->open_count) + { + GslErrorType error; + + error = gsl_data_handle_open (dcache->dhandle); + if (error) + { + /* FIXME: this is pretty fatal, throw out zero blocks now? */ + gsl_message_send (GSL_MSG_DATA_CACHE, "Open", + error, + "failed to open \"%s\": %s", + dcache->dhandle->name, + gsl_strerror (error)); + } + else + { + dcache->open_count = 1; + dcache->ref_count++; + } + } + else + dcache->open_count++; + GSL_SPIN_UNLOCK (&dcache->mutex); +} + +void +gsl_data_cache_close (GslDataCache *dcache) +{ + gboolean need_unref; + + g_return_if_fail (dcache != NULL); + g_return_if_fail (dcache->ref_count > 0); + g_return_if_fail (dcache->open_count > 0); + + GSL_SPIN_LOCK (&dcache->mutex); + dcache->open_count--; + need_unref = !dcache->open_count; + if (!dcache->open_count) + gsl_data_handle_close (dcache->dhandle); + GSL_SPIN_UNLOCK (&dcache->mutex); + if (need_unref) + gsl_data_cache_unref (dcache); +} + +GslDataCache* +gsl_data_cache_ref (GslDataCache *dcache) +{ + g_return_val_if_fail (dcache != NULL, NULL); + g_return_val_if_fail (dcache->ref_count > 0, NULL); + + /* we might get invoked with global_dcache_mutex locked */ + GSL_SPIN_LOCK (&dcache->mutex); + dcache->ref_count++; + GSL_SPIN_UNLOCK (&dcache->mutex); + + return dcache; +} + +static void +dcache_free (GslDataCache *dcache) +{ + guint i; + + g_return_if_fail (dcache->ref_count == 0); + g_return_if_fail (dcache->open_count == 0); + + gsl_data_handle_unref (dcache->dhandle); + gsl_mutex_destroy (&dcache->mutex); + for (i = 0; i < dcache->n_nodes; i++) + { + GslDataCacheNode *node = dcache->nodes[i]; + guint size; + + size = dcache->node_size + (dcache->padding << 1); + gsl_delete_structs (GslDataType, size, node->data - dcache->padding); + gsl_delete_struct (GslDataCacheNode, node); + } + g_free (dcache->nodes); + gsl_delete_struct (GslDataCache, dcache); +} + +void +gsl_data_cache_unref (GslDataCache *dcache) +{ + g_return_if_fail (dcache != NULL); + restart: + g_return_if_fail (dcache->ref_count > 0); + + if (dcache->ref_count == 1) /* possible destruction, need global lock */ + { + g_return_if_fail (dcache->open_count == 0); + + GSL_SPIN_LOCK (&global_dcache_mutex); + GSL_SPIN_LOCK (&dcache->mutex); + if (dcache->ref_count != 1) + { + /* damn, some other thread trapped in, restart */ + GSL_SPIN_UNLOCK (&dcache->mutex); + GSL_SPIN_UNLOCK (&global_dcache_mutex); + goto restart; + } + dcache->ref_count = 0; + global_dcache_list = gsl_ring_remove (global_dcache_list, dcache); + GSL_SPIN_UNLOCK (&dcache->mutex); + global_dcache_count--; + global_dcache_n_aged_nodes -= dcache->n_nodes; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + dcache_free (dcache); + } + else + { + GSL_SPIN_LOCK (&dcache->mutex); + if (dcache->ref_count < 2) + { + /* damn, some other thread trapped in, restart */ + GSL_SPIN_UNLOCK (&dcache->mutex); + goto restart; + } + dcache->ref_count--; + GSL_SPIN_UNLOCK (&dcache->mutex); + } +} + +static inline GslDataCacheNode** +data_cache_lookup_nextmost_node_L (GslDataCache *dcache, + gsize offset) +{ + if (dcache->n_nodes > 0) + { + GslDataCacheNode **check, **nodes = dcache->nodes; + guint n_nodes = dcache->n_nodes, node_size = dcache->node_size; + + /* caller has to figure himself whether we return nextmost vs. exact match */ + nodes -= 1; + do + { + register gint cmp; + register guint i; + + i = (n_nodes + 1) >> 1; + check = nodes + i; + cmp = offset < (*check)->offset ? -1 : offset >= (*check)->offset + node_size; + if (cmp == 0) + return check; /* exact match */ + else if (cmp > 0) + { + n_nodes -= i; + nodes = check; + } + else /* if (cmp < 0) */ + n_nodes = i - 1; + } + while (n_nodes); + + return check; /* nextmost */ + } + return NULL; +} + +static inline GslDataCacheNode* +data_cache_new_node_L (GslDataCache *dcache, + gsize offset, + guint pos, + gboolean demand_load) +{ + GslDataCacheNode **node_p, *dnode; + GslDataCacheNode *left_node; + GslDataType *data, *node_data; + guint new_node_array_size, old_node_array_size = UPPER_POWER2 (dcache->n_nodes); + GslLong dhandle_length; + guint i, size; + gint result; + + i = dcache->n_nodes++; + new_node_array_size = UPPER_POWER2 (dcache->n_nodes); + if (old_node_array_size != new_node_array_size) + dcache->nodes = g_renew (GslDataCacheNode*, dcache->nodes, new_node_array_size); + node_p = dcache->nodes + pos; + g_memmove (node_p + 1, node_p, (i - pos) * sizeof (*node_p)); + dnode = gsl_new_struct (GslDataCacheNode, 1); + (*node_p) = dnode; + dnode->offset = offset & ~(dcache->node_size - 1); + dnode->ref_count = 1; + dnode->age = 0; + dnode->data = NULL; + GSL_SPIN_UNLOCK (&dcache->mutex); + + size = dcache->node_size + (dcache->padding << 1); + data = gsl_new_struct (GslDataType, size); + node_data = data + dcache->padding; + offset = dnode->offset; + if (dcache->padding > offset) /* pad out bytes before data start */ + { + guint short_pad = dcache->padding - offset; + + memset (data, 0, short_pad * sizeof (GslDataType)); + size -= short_pad; + data += short_pad; + offset -= (dcache->padding - short_pad); /* should always result in offset=0 */ + } + else + offset -= dcache->padding; + if (!demand_load) + g_message (G_STRLOC ":FIXME: lazy data loading not yet supported"); + + /* if we have a left node, and it contains data that we need, copy it */ + left_node = pos ? dcache->nodes[pos - 1] : NULL; + if (left_node) + { + guint left_node_size = dcache->node_size; + gint left_node_offset = left_node->offset; + GslDataType *left_node_data = left_node->data; + + /* padding around left_node */ + left_node_size += (dcache->padding << 1); + left_node_offset -= dcache->padding; + left_node_data -= dcache->padding; + + if (offset < left_node_offset + left_node_size) + { + guint left_node_copy = left_node_offset + left_node_size - offset; + + memcpy (data, left_node_data + offset - left_node_offset, + left_node_copy * sizeof (GslDataType)); + + offset += left_node_copy; + size -= left_node_copy; + data += left_node_copy; + } + } + dhandle_length = gsl_data_handle_length (dcache->dhandle); + do + { + if (offset >= dhandle_length) + break; + size = MIN (size, dhandle_length - offset); + result = gsl_data_handle_read (dcache->dhandle, offset, size, data); + if (result < 0) + { + gsl_message_send (GSL_MSG_DATA_CACHE, "ReadAhead", + GSL_ERROR_READ_FAILED, + "reading from \"%s\"", dcache->dhandle->name); + break; + } + else + { + offset += result; + size -= result; + data += result; + } + } + while (size && result > 0); + memset (data, 0, size * sizeof (data[0])); + + GSL_SPIN_LOCK (&dcache->mutex); + dnode->data = node_data; + gsl_cond_broadcast (&global_dcache_cond_node_filled); + + return dnode; +} + +GslDataCacheNode* +gsl_data_cache_ref_node (GslDataCache *dcache, + gsize offset, + GslDataCacheRequest load_request) +{ + GslDataCacheNode **node_p, *node; + guint insertion_pos; + + g_return_val_if_fail (dcache != NULL, NULL); + g_return_val_if_fail (dcache->ref_count > 0, NULL); + g_return_val_if_fail (dcache->open_count > 0, NULL); + g_return_val_if_fail (offset < gsl_data_handle_length (dcache->dhandle), NULL); + + GSL_SPIN_LOCK (&dcache->mutex); + node_p = data_cache_lookup_nextmost_node_L (dcache, offset); + if (node_p) + { + node = *node_p; + if (offset >= node->offset && offset < node->offset + dcache->node_size) + { + gboolean rejuvenate_node = !node->ref_count; + + if (load_request == GSL_DATA_CACHE_PEEK) + { + if (node->data) + node->ref_count++; + else + node = NULL; + GSL_SPIN_UNLOCK (&dcache->mutex); + if (node && rejuvenate_node) + { + GSL_SPIN_LOCK (&global_dcache_mutex); /* different lock */ + global_dcache_n_aged_nodes--; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + } + return node; + } + + node->ref_count++; + if (load_request == GSL_DATA_CACHE_DEMAND_LOAD) + while (!node->data) + gsl_cond_wait (&global_dcache_cond_node_filled, &dcache->mutex); + GSL_SPIN_UNLOCK (&dcache->mutex); + /* g_printerr ("hit: %d :%d: %d\n", node->offset, offset, node->offset + dcache->node_size); */ + + if (rejuvenate_node) + { + GSL_SPIN_LOCK (&global_dcache_mutex); /* different lock */ + global_dcache_n_aged_nodes--; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + } + + return node; /* exact match */ + } + insertion_pos = NODEP_INDEX (dcache, node_p); /* insert before neighbour */ + if (offset > node->offset) /* insert after neighbour */ + insertion_pos += 1; + /* g_printerr ("mis: %d :%d: %d\n", node->offset, offset, node->offset + dcache->node_size); */ + } + else + insertion_pos = 0; /* insert at start */ + + if (load_request != GSL_DATA_CACHE_PEEK) + node = data_cache_new_node_L (dcache, offset, insertion_pos, load_request == GSL_DATA_CACHE_DEMAND_LOAD); + else + node = NULL; + + GSL_SPIN_UNLOCK (&dcache->mutex); + + return node; +} + +static gboolean /* still locked */ +data_cache_free_olders_Lunlock (GslDataCache *dcache, + guint max_lru) /* how many lru nodes to keep */ +{ + GslDataCacheNode **slot_p; + guint i, rejuvenate, size; + guint n_freed = 0; + + g_return_val_if_fail (dcache != NULL, TRUE); + + /* it doesn't make sense to free nodes below the jitter that + * AGE_EPSILON attempts to prevent. + */ + max_lru = MAX (AGE_EPSILON, max_lru); + if (max_lru >= dcache->max_age) + return TRUE; + + rejuvenate = dcache->max_age - max_lru; + if (0) + g_print ("start sweep: dcache (%p) with %u nodes, max_age: %u, rejuvenate: %u (max_lru: %u)\n", + dcache, dcache->n_nodes, dcache->max_age, rejuvenate, max_lru); + size = dcache->node_size + (dcache->padding << 1); + slot_p = NULL; + for (i = 0; i < dcache->n_nodes; i++) + { + GslDataCacheNode *node = dcache->nodes[i]; + + if (!node->ref_count && node->age <= rejuvenate) + { + gsl_delete_structs (GslDataType, size, node->data - dcache->padding); + gsl_delete_struct (GslDataCacheNode, node); + if (!slot_p) + slot_p = dcache->nodes + i; + n_freed++; + } + else + { + node->age -= MIN (rejuvenate, node->age); + if (slot_p) + { + *slot_p = node; + slot_p++; + } + } + } + dcache->max_age = max_lru; + if (slot_p) + dcache->n_nodes = NODEP_INDEX (dcache, slot_p); + GSL_SPIN_UNLOCK (&dcache->mutex); + + if (n_freed) + { + GSL_SPIN_LOCK (&global_dcache_mutex); + global_dcache_n_aged_nodes -= n_freed; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + } + + if (0) + g_printerr ("freed %u nodes (%u bytes) remaining %u bytes\n", + n_freed, n_freed * CONFIG_NODE_SIZE (), + global_dcache_n_aged_nodes * CONFIG_NODE_SIZE ()); + return FALSE; +} + +void +gsl_data_cache_unref_node (GslDataCache *dcache, + GslDataCacheNode *node) +{ + GslDataCacheNode **node_p; + gboolean check_cache; + + g_return_if_fail (dcache != NULL); + g_return_if_fail (node != NULL); + g_return_if_fail (node->ref_count > 0); + + GSL_SPIN_LOCK (&dcache->mutex); + node_p = data_cache_lookup_nextmost_node_L (dcache, node->offset); + g_assert (node_p && *node_p == node); /* paranoid check lookup, yeah! */ + node->ref_count -= 1; + check_cache = !node->ref_count; + if (!node->ref_count && + (node->age + AGE_EPSILON <= dcache->max_age || + dcache->max_age < AGE_EPSILON)) + node->age = ++dcache->max_age; + GSL_SPIN_UNLOCK (&dcache->mutex); + + if (check_cache) + { + guint node_size = CONFIG_NODE_SIZE (); + guint cache_mem = gsl_get_config ()->dcache_cache_memory; + guint current_mem; + + /* FIXME: cache sweeping should not be done from _unref_node for high persistency caches */ + GSL_SPIN_LOCK (&global_dcache_mutex); + global_dcache_n_aged_nodes++; + current_mem = node_size * global_dcache_n_aged_nodes; + if (current_mem > cache_mem) + { + guint dcache_count, needs_unlock; + dcache = gsl_ring_pop_head (&global_dcache_list); + GSL_SPIN_LOCK (&dcache->mutex); + dcache->ref_count++; + global_dcache_list = gsl_ring_append (global_dcache_list, dcache); + dcache_count = global_dcache_count; + GSL_SPIN_UNLOCK (&global_dcache_mutex); + if (dcache->low_persistency) + needs_unlock = data_cache_free_olders_Lunlock (dcache, LOW_PERSISTENCY_SWEEP); + else + { + guint max_lru; + /* try to free the actual cache overflow from the + * dcache we just picked, but don't free more than + * 25% of its nodes yet. + * overflow is actual overhang + ~6% of cache size, + * so cache sweeps are triggered less frequently. + */ + current_mem -= cache_mem; /* overhang */ + current_mem += cache_mem >> 4; /* overflow = overhang + 6% */ + current_mem /= node_size; /* n_nodes to free */ + current_mem = MIN (current_mem, dcache->n_nodes); + max_lru = dcache->n_nodes >> 1; + max_lru += max_lru >> 1; /* 75% of n_nodes */ + max_lru = MAX (max_lru, dcache->n_nodes - current_mem); + needs_unlock = data_cache_free_olders_Lunlock (dcache, MAX (max_lru, LOW_PERSISTENCY_SWEEP)); + } + if (needs_unlock) + GSL_SPIN_UNLOCK (&dcache->mutex); + } + else + GSL_SPIN_UNLOCK (&global_dcache_mutex); + } +} + +void +gsl_data_cache_free_olders (GslDataCache *dcache, + guint max_age) +{ + gboolean needs_unlock; + g_return_if_fail (dcache != NULL); + + GSL_SPIN_LOCK (&dcache->mutex); + needs_unlock = data_cache_free_olders_Lunlock (dcache, max_age); + if (needs_unlock) + GSL_SPIN_UNLOCK (&dcache->mutex); +} + +GslDataCache* +gsl_data_cache_from_dhandle (GslDataHandle *dhandle, + guint min_padding) +{ + GslRing *ring; + + g_return_val_if_fail (dhandle != NULL, NULL); + + GSL_SPIN_LOCK (&global_dcache_mutex); + for (ring = global_dcache_list; ring; ring = gsl_ring_walk (global_dcache_list, ring)) + { + GslDataCache *dcache = ring->data; + + if (dcache->dhandle == dhandle && dcache->padding >= min_padding) + { + gsl_data_cache_ref (dcache); + GSL_SPIN_UNLOCK (&global_dcache_mutex); + return dcache; + } + } + GSL_SPIN_UNLOCK (&global_dcache_mutex); + + return gsl_data_cache_new (dhandle, min_padding); +} |