blob: 35916e04889761a266ec893337d3b3941fc21915 [file] [log] [blame]
/*
* object_pool.c : generic pool of reference-counted objects
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <assert.h>
#include "svn_error.h"
#include "svn_hash.h"
#include "svn_pools.h"
#include "private/svn_atomic.h"
#include "private/svn_object_pool.h"
#include "private/svn_subr_private.h"
#include "private/svn_dep_compat.h"
/* A reference counting wrapper around the user-provided object.
*/
typedef struct object_ref_t
{
/* reference to the parent container */
svn_object_pool__t *object_pool;
/* identifies the bucket in OBJECT_POOL->OBJECTS in which this entry
* belongs. */
svn_membuf_t key;
/* User provided object. Usually a wrapper. */
void *object;
/* private pool. This instance and its other members got allocated in it.
* Will be destroyed when this instance is cleaned up. */
apr_pool_t *pool;
/* Number of references to this data struct */
volatile svn_atomic_t ref_count;
} object_ref_t;
/* Core data structure. All access to it must be serialized using MUTEX.
*/
struct svn_object_pool__t
{
/* serialization object for all non-atomic data in this struct */
svn_mutex__t *mutex;
/* object_ref_t.KEY -> object_ref_t* mapping.
*
* In shared object mode, there is at most one such entry per key and it
* may or may not be in use. In exclusive mode, only unused references
* will be put here and they form chains if there are multiple unused
* instances for the key. */
apr_hash_t *objects;
/* same as objects->count but allows for non-sync'ed access */
volatile svn_atomic_t object_count;
/* Number of entries in OBJECTS with a reference count 0.
Due to races, this may be *temporarily* off by one or more.
Hence we must not strictly depend on it. */
volatile svn_atomic_t unused_count;
/* the root pool owning this structure */
apr_pool_t *pool;
};
/* Pool cleanup function for the whole object pool.
*/
static apr_status_t
object_pool_cleanup(void *baton)
{
svn_object_pool__t *object_pool = baton;
/* all entries must have been released up by now */
SVN_ERR_ASSERT_NO_RETURN( object_pool->object_count
== object_pool->unused_count);
return APR_SUCCESS;
}
/* Remove entries from OBJECTS in OBJECT_POOL that have a ref-count of 0.
*
* Requires external serialization on OBJECT_POOL.
*/
static void
remove_unused_objects(svn_object_pool__t *object_pool)
{
apr_pool_t *subpool = svn_pool_create(object_pool->pool);
/* process all hash buckets */
apr_hash_index_t *hi;
for (hi = apr_hash_first(subpool, object_pool->objects);
hi != NULL;
hi = apr_hash_next(hi))
{
object_ref_t *object_ref = apr_hash_this_val(hi);
/* note that we won't hand out new references while access
to the hash is serialized */
if (svn_atomic_read(&object_ref->ref_count) == 0)
{
apr_hash_set(object_pool->objects, object_ref->key.data,
object_ref->key.size, NULL);
svn_atomic_dec(&object_pool->object_count);
svn_atomic_dec(&object_pool->unused_count);
svn_pool_destroy(object_ref->pool);
}
}
svn_pool_destroy(subpool);
}
/* Cleanup function called when an object_ref_t gets released.
*/
static apr_status_t
object_ref_cleanup(void *baton)
{
object_ref_t *object = baton;
svn_object_pool__t *object_pool = object->object_pool;
/* If we released the last reference to object, there is one more
unused entry.
Note that unused_count does not need to be always exact but only
needs to become exact *eventually* (we use it to check whether we
should remove unused objects every now and then). I.e. it must
never drift off / get stuck but always reflect the true value once
all threads left the racy sections.
*/
if (svn_atomic_dec(&object->ref_count) == 0)
svn_atomic_inc(&object_pool->unused_count);
return APR_SUCCESS;
}
/* Handle reference counting for the OBJECT_REF that the caller is about
* to return. The reference will be released when POOL gets cleaned up.
*
* Requires external serialization on OBJECT_REF->OBJECT_POOL.
*/
static void
add_object_ref(object_ref_t *object_ref,
apr_pool_t *pool)
{
/* Update ref counter.
Note that this is racy with object_ref_cleanup; see comment there. */
if (svn_atomic_inc(&object_ref->ref_count) == 0)
svn_atomic_dec(&object_ref->object_pool->unused_count);
/* Make sure the reference gets released automatically.
Since POOL might be a parent pool of OBJECT_REF->OBJECT_POOL,
to the reference counting update before destroying any of the
pool hierarchy. */
apr_pool_pre_cleanup_register(pool, object_ref, object_ref_cleanup);
}
/* Actual implementation of svn_object_pool__lookup.
*
* Requires external serialization on OBJECT_POOL.
*/
static svn_error_t *
lookup(void **object,
svn_object_pool__t *object_pool,
svn_membuf_t *key,
apr_pool_t *result_pool)
{
object_ref_t *object_ref
= apr_hash_get(object_pool->objects, key->data, key->size);
if (object_ref)
{
*object = object_ref->object;
add_object_ref(object_ref, result_pool);
}
else
{
*object = NULL;
}
return SVN_NO_ERROR;
}
/* Actual implementation of svn_object_pool__insert.
*
* Requires external serialization on OBJECT_POOL.
*/
static svn_error_t *
insert(void **object,
svn_object_pool__t *object_pool,
const svn_membuf_t *key,
void *item,
apr_pool_t *item_pool,
apr_pool_t *result_pool)
{
object_ref_t *object_ref
= apr_hash_get(object_pool->objects, key->data, key->size);
if (object_ref)
{
/* Destroy the new one and return a reference to the existing one
* because the existing one may already have references on it.
*/
svn_pool_destroy(item_pool);
}
else
{
/* add new index entry */
object_ref = apr_pcalloc(item_pool, sizeof(*object_ref));
object_ref->object_pool = object_pool;
object_ref->object = item;
object_ref->pool = item_pool;
svn_membuf__create(&object_ref->key, key->size, item_pool);
object_ref->key.size = key->size;
memcpy(object_ref->key.data, key->data, key->size);
apr_hash_set(object_pool->objects, object_ref->key.data,
object_ref->key.size, object_ref);
svn_atomic_inc(&object_pool->object_count);
/* the new entry is *not* in use yet.
* add_object_ref will update counters again.
*/
svn_atomic_inc(&object_ref->object_pool->unused_count);
}
/* return a reference to the object we just added */
*object = object_ref->object;
add_object_ref(object_ref, result_pool);
/* limit memory usage */
if (svn_atomic_read(&object_pool->unused_count) * 2
> apr_hash_count(object_pool->objects) + 2)
remove_unused_objects(object_pool);
return SVN_NO_ERROR;
}
/* API implementation */
svn_error_t *
svn_object_pool__create(svn_object_pool__t **object_pool,
svn_boolean_t thread_safe,
apr_pool_t *pool)
{
svn_object_pool__t *result;
/* construct the object pool in our private ROOT_POOL to survive POOL
* cleanup and to prevent threading issues with the allocator
*/
result = apr_pcalloc(pool, sizeof(*result));
SVN_ERR(svn_mutex__init(&result->mutex, thread_safe, pool));
result->pool = pool;
result->objects = svn_hash__make(result->pool);
/* make sure we clean up nicely.
* We need two cleanup functions of which exactly one will be run
* (disabling the respective other as the first step). If the owning
* pool does not cleaned up / destroyed explicitly, it may live longer
* than our allocator. So, we need do act upon cleanup requests from
* either side - owning_pool and root_pool.
*/
apr_pool_cleanup_register(pool, result, object_pool_cleanup,
apr_pool_cleanup_null);
*object_pool = result;
return SVN_NO_ERROR;
}
apr_pool_t *
svn_object_pool__new_item_pool(svn_object_pool__t *object_pool)
{
return svn_pool_create(object_pool->pool);
}
svn_error_t *
svn_object_pool__lookup(void **object,
svn_object_pool__t *object_pool,
svn_membuf_t *key,
apr_pool_t *result_pool)
{
*object = NULL;
SVN_MUTEX__WITH_LOCK(object_pool->mutex,
lookup(object, object_pool, key, result_pool));
return SVN_NO_ERROR;
}
svn_error_t *
svn_object_pool__insert(void **object,
svn_object_pool__t *object_pool,
const svn_membuf_t *key,
void *item,
apr_pool_t *item_pool,
apr_pool_t *result_pool)
{
*object = NULL;
SVN_MUTEX__WITH_LOCK(object_pool->mutex,
insert(object, object_pool, key, item,
item_pool, result_pool));
return SVN_NO_ERROR;
}