| /* |
| * 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 destroing 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; |
| } |