diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/libsvn_subr/object_pool.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/libsvn_subr/object_pool.c')
-rw-r--r-- | subversion/libsvn_subr/object_pool.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/object_pool.c b/subversion/libsvn_subr/object_pool.c new file mode 100644 index 0000000..782ffa2 --- /dev/null +++ b/subversion/libsvn_subr/object_pool.c @@ -0,0 +1,398 @@ +/* + * config_pool.c : pool of configuration 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 *wrapper; + + /* 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; + + /* extractor and updater for the user object wrappers */ + svn_object_pool__getter_t getter; + svn_object_pool__setter_t setter; +}; + + +/* 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 */ + apr_pool_cleanup_register(pool, object_ref, object_ref_cleanup, + apr_pool_cleanup_null); +} + +/* 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, + void *baton, + 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_pool->getter(object_ref->wrapper, baton, result_pool); + 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 *wrapper, + void *baton, + apr_pool_t *wrapper_pool, + apr_pool_t *result_pool) +{ + object_ref_t *object_ref + = apr_hash_get(object_pool->objects, key->data, key->size); + if (object_ref) + { + /* entry already exists (e.g. race condition) */ + svn_error_t *err = object_pool->setter(&object_ref->wrapper, + wrapper, baton, + object_ref->pool); + if (err) + { + /* if we had an issue in the setter, then OBJECT_REF is in an + * unknown state now. Keep it around for the current users + * (i.e. don't clean the pool) but remove it from the list of + * available ones. + */ + apr_hash_set(object_pool->objects, key->data, key->size, NULL); + svn_atomic_dec(&object_pool->object_count); + + /* for the unlikely case that the object got created _and_ + * already released since we last checked: */ + if (svn_atomic_read(&object_ref->ref_count) == 0) + svn_atomic_dec(&object_pool->unused_count); + + /* cleanup the new data as well because it's not safe to use + * either. + */ + svn_pool_destroy(wrapper_pool); + + /* propagate error */ + return svn_error_trace(err); + } + + /* 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(wrapper_pool); + } + else + { + /* add new index entry */ + object_ref = apr_pcalloc(wrapper_pool, sizeof(*object_ref)); + object_ref->object_pool = object_pool; + object_ref->wrapper = wrapper; + object_ref->pool = wrapper_pool; + + svn_membuf__create(&object_ref->key, key->size, wrapper_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_pool->getter(object_ref->wrapper, baton, result_pool); + 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; +} + +/* Implement svn_object_pool__getter_t as no-op. + */ +static void * +default_getter(void *object, + void *baton, + apr_pool_t *pool) +{ + return object; +} + +/* Implement svn_object_pool__setter_t as no-op. + */ +static svn_error_t * +default_setter(void **target, + void *source, + void *baton, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + + +/* API implementation */ + +svn_error_t * +svn_object_pool__create(svn_object_pool__t **object_pool, + svn_object_pool__getter_t getter, + svn_object_pool__setter_t setter, + 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); + result->getter = getter ? getter : default_getter; + result->setter = setter ? setter : default_setter; + + /* 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_wrapper_pool(svn_object_pool__t *object_pool) +{ + return svn_pool_create(object_pool->pool); +} + +svn_mutex__t * +svn_object_pool__mutex(svn_object_pool__t *object_pool) +{ + return object_pool->mutex; +} + +unsigned +svn_object_pool__count(svn_object_pool__t *object_pool) +{ + return svn_atomic_read(&object_pool->object_count); +} + +svn_error_t * +svn_object_pool__lookup(void **object, + svn_object_pool__t *object_pool, + svn_membuf_t *key, + void *baton, + apr_pool_t *result_pool) +{ + *object = NULL; + SVN_MUTEX__WITH_LOCK(object_pool->mutex, + lookup(object, object_pool, key, baton, 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 *wrapper, + void *baton, + apr_pool_t *wrapper_pool, + apr_pool_t *result_pool) +{ + *object = NULL; + SVN_MUTEX__WITH_LOCK(object_pool->mutex, + insert(object, object_pool, key, wrapper, baton, + wrapper_pool, result_pool)); + return SVN_NO_ERROR; +} |