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