/* epoch.h - epoch based memory reclamation */ /* $OpenLDAP$ */ /* This work is part of OpenLDAP Software . * * Copyright 2018-2022 The OpenLDAP Foundation. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #ifndef __LLOAD_EPOCH_H #define __LLOAD_EPOCH_H /** @file epoch.h * * Implementation of epoch based memory reclamation, in principle * similar to the algorithm presented in * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf */ typedef uintptr_t epoch_t; /** @brief A callback function used to free object and associated data */ typedef void (dispose_cb)( void *object ); /** @brief Initiate global state */ void epoch_init( void ); /** @brief Finalise global state and free any objects still pending */ void epoch_shutdown( void ); /** @brief Register thread as active * * In order to safely access managed objects, a thread should call * this function or make sure no other thread is running (e.g. config * pause, late shutdown). After calling this, it is guaranteed that no * reachable objects will be freed before all threads have called * `epoch_leave( current_epoch + 1 )` so it is essential that there * is an upper limit to the amount of time between #epoch_join and * corresponding #epoch_leave or the number of unfreed objects might * grow without bounds. * * To simplify locking, memory is only freed when the current epoch * is advanced rather than on leaving it. * * Can be safely called multiple times by the same thread as long as * a matching #epoch_leave() call is made eventually. * * @return The observed epoch, to be passed to #epoch_leave() */ epoch_t epoch_join( void ); /** @brief Register thread as inactive * * A thread should call this after they are finished with work * performed since matching call to #epoch_join(). It is not safe * to keep a local reference to managed objects after this call * unless other precautions have been made to prevent it being * released. * * @param[in] epoch Epoch identifier returned by a previous call to * #epoch_join(). */ void epoch_leave( epoch_t epoch ); /** @brief Return an unreachable object to be freed * * The object should already be unreachable at the point of call and * cb will be invoked when no other thread that could have seen it * is active any more. This happens when we have advanced by two * epochs. * * @param[in] ptr Object to be released/freed * @param[in] cb Callback to invoke when safe to do so */ void epoch_append( void *ptr, dispose_cb *cb ); /** * \defgroup Reference counting helpers */ /**@{*/ /** @brief Acquire a reference if possible * * Atomically, check reference count is non-zero and increment if so. * Returns old reference count. * * @param[in] refp Pointer to a reference counter * @return 0 if reference was already zero, non-zero if reference * count was successfully incremented */ int acquire_ref( uintptr_t *refp ); /** @brief Check reference count and try to decrement * * Atomically, decrement reference count if non-zero and register * object if decremented to zero. Returning previous reference count. * * @param[in] refp Pointer to a reference counter * @param[in] object The managed object * @param[in] cb Callback to invoke when safe to do so * @return 0 if reference was already zero, non-zero if reference * count was non-zero at the time of call */ int try_release_ref( uintptr_t *refp, void *object, dispose_cb *unlink_cb, dispose_cb *destroy_cb ); /** @brief Read reference count * * @param[in] object Pointer to the managed object * @param[in] ref_field Member where reference count is stored in * the object * @return Current value of reference counter */ #define IS_ALIVE( object, ref_field ) \ __atomic_load_n( &(object)->ref_field, __ATOMIC_ACQUIRE ) /** @brief Release reference * * A cheaper alternative to #try_release_ref(), safe only when we know * reference count was already non-zero. * * @param[in] object The managed object * @param[in] ref_field Member where reference count is stored in * the object * @param[in] cb Callback to invoke when safe to do so */ #define RELEASE_REF( object, ref_field, cb ) \ do { \ assert( IS_ALIVE( (object), ref_field ) ); \ if ( !__atomic_sub_fetch( \ &(object)->ref_field, 1, __ATOMIC_ACQ_REL ) ) { \ epoch_append( object, (dispose_cb *)cb ); \ } \ } while (0) /**@}*/ #endif /* __LLOAD_EPOCH_H */