1*549b59edSchristos /* $NetBSD: epoch.h,v 1.2 2021/08/14 16:14:58 christos Exp $ */ 2e670fd5cSchristos 3e670fd5cSchristos /* epoch.h - epoch based memory reclamation */ 4e670fd5cSchristos /* $OpenLDAP$ */ 5e670fd5cSchristos /* This work is part of OpenLDAP Software <http://www.openldap.org/>. 6e670fd5cSchristos * 7e670fd5cSchristos * Copyright 2018-2021 The OpenLDAP Foundation. 8e670fd5cSchristos * All rights reserved. 9e670fd5cSchristos * 10e670fd5cSchristos * Redistribution and use in source and binary forms, with or without 11e670fd5cSchristos * modification, are permitted only as authorized by the OpenLDAP 12e670fd5cSchristos * Public License. 13e670fd5cSchristos * 14e670fd5cSchristos * A copy of this license is available in the file LICENSE in the 15e670fd5cSchristos * top-level directory of the distribution or, alternatively, at 16e670fd5cSchristos * <http://www.OpenLDAP.org/license.html>. 17e670fd5cSchristos */ 18e670fd5cSchristos 19e670fd5cSchristos #ifndef __LLOAD_EPOCH_H 20e670fd5cSchristos #define __LLOAD_EPOCH_H 21e670fd5cSchristos 22e670fd5cSchristos /** @file epoch.h 23e670fd5cSchristos * 24e670fd5cSchristos * Implementation of epoch based memory reclamation, in principle 25e670fd5cSchristos * similar to the algorithm presented in 26e670fd5cSchristos * https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf 27e670fd5cSchristos */ 28e670fd5cSchristos 29e670fd5cSchristos typedef uintptr_t epoch_t; 30e670fd5cSchristos 31e670fd5cSchristos /** @brief A callback function used to free object and associated data */ 32e670fd5cSchristos typedef void (dispose_cb)( void *object ); 33e670fd5cSchristos 34e670fd5cSchristos /** @brief Initiate global state */ 35e670fd5cSchristos void epoch_init( void ); 36e670fd5cSchristos 37e670fd5cSchristos /** @brief Finalise global state and free any objects still pending */ 38e670fd5cSchristos void epoch_shutdown( void ); 39e670fd5cSchristos 40e670fd5cSchristos /** @brief Register thread as active 41e670fd5cSchristos * 42e670fd5cSchristos * In order to safely access managed objects, a thread should call 43e670fd5cSchristos * this function or make sure no other thread is running (e.g. config 44e670fd5cSchristos * pause, late shutdown). After calling this, it is guaranteed that no 45e670fd5cSchristos * reachable objects will be freed before all threads have called 46e670fd5cSchristos * `epoch_leave( current_epoch + 1 )` so it is essential that there 47e670fd5cSchristos * is an upper limit to the amount of time between #epoch_join and 48e670fd5cSchristos * corresponding #epoch_leave or the number of unfreed objects might 49e670fd5cSchristos * grow without bounds. 50e670fd5cSchristos * 51e670fd5cSchristos * To simplify locking, memory is only freed when the current epoch 52e670fd5cSchristos * is advanced rather than on leaving it. 53e670fd5cSchristos * 54e670fd5cSchristos * Can be safely called multiple times by the same thread as long as 55e670fd5cSchristos * a matching #epoch_leave() call is made eventually. 56e670fd5cSchristos * 57e670fd5cSchristos * @return The observed epoch, to be passed to #epoch_leave() 58e670fd5cSchristos */ 59e670fd5cSchristos epoch_t epoch_join( void ); 60e670fd5cSchristos 61e670fd5cSchristos /** @brief Register thread as inactive 62e670fd5cSchristos * 63e670fd5cSchristos * A thread should call this after they are finished with work 64e670fd5cSchristos * performed since matching call to #epoch_join(). It is not safe 65e670fd5cSchristos * to keep a local reference to managed objects after this call 66e670fd5cSchristos * unless other precautions have been made to prevent it being 67e670fd5cSchristos * released. 68e670fd5cSchristos * 69e670fd5cSchristos * @param[in] epoch Epoch identifier returned by a previous call to 70e670fd5cSchristos * #epoch_join(). 71e670fd5cSchristos */ 72e670fd5cSchristos void epoch_leave( epoch_t epoch ); 73e670fd5cSchristos 74e670fd5cSchristos /** @brief Return an unreachable object to be freed 75e670fd5cSchristos * 76e670fd5cSchristos * The object should already be unreachable at the point of call and 77e670fd5cSchristos * cb will be invoked when no other thread that could have seen it 78e670fd5cSchristos * is active any more. This happens when we have advanced by two 79e670fd5cSchristos * epochs. 80e670fd5cSchristos * 81e670fd5cSchristos * @param[in] ptr Object to be released/freed 82e670fd5cSchristos * @param[in] cb Callback to invoke when safe to do so 83e670fd5cSchristos */ 84e670fd5cSchristos void epoch_append( void *ptr, dispose_cb *cb ); 85e670fd5cSchristos 86e670fd5cSchristos /** 87e670fd5cSchristos * \defgroup Reference counting helpers 88e670fd5cSchristos */ 89e670fd5cSchristos /**@{*/ 90e670fd5cSchristos 91e670fd5cSchristos /** @brief Acquire a reference if possible 92e670fd5cSchristos * 93e670fd5cSchristos * Atomically, check reference count is non-zero and increment if so. 94e670fd5cSchristos * Returns old reference count. 95e670fd5cSchristos * 96e670fd5cSchristos * @param[in] refp Pointer to a reference counter 97e670fd5cSchristos * @return 0 if reference was already zero, non-zero if reference 98e670fd5cSchristos * count was successfully incremented 99e670fd5cSchristos */ 100e670fd5cSchristos int acquire_ref( uintptr_t *refp ); 101e670fd5cSchristos 102e670fd5cSchristos /** @brief Check reference count and try to decrement 103e670fd5cSchristos * 104e670fd5cSchristos * Atomically, decrement reference count if non-zero and register 105e670fd5cSchristos * object if decremented to zero. Returning previous reference count. 106e670fd5cSchristos * 107e670fd5cSchristos * @param[in] refp Pointer to a reference counter 108e670fd5cSchristos * @param[in] object The managed object 109e670fd5cSchristos * @param[in] cb Callback to invoke when safe to do so 110e670fd5cSchristos * @return 0 if reference was already zero, non-zero if reference 111e670fd5cSchristos * count was non-zero at the time of call 112e670fd5cSchristos */ 113e670fd5cSchristos int try_release_ref( uintptr_t *refp, void *object, dispose_cb *cb ); 114e670fd5cSchristos 115e670fd5cSchristos /** @brief Read reference count 116e670fd5cSchristos * 117e670fd5cSchristos * @param[in] object Pointer to the managed object 118e670fd5cSchristos * @param[in] ref_field Member where reference count is stored in 119e670fd5cSchristos * the object 120e670fd5cSchristos * @return Current value of reference counter 121e670fd5cSchristos */ 122e670fd5cSchristos #define IS_ALIVE( object, ref_field ) \ 123e670fd5cSchristos __atomic_load_n( &(object)->ref_field, __ATOMIC_ACQUIRE ) 124e670fd5cSchristos 125e670fd5cSchristos /** @brief Release reference 126e670fd5cSchristos * 127e670fd5cSchristos * A cheaper alternative to #try_release_ref(), safe only when we know 128e670fd5cSchristos * reference count was already non-zero. 129e670fd5cSchristos * 130e670fd5cSchristos * @param[in] object The managed object 131e670fd5cSchristos * @param[in] ref_field Member where reference count is stored in 132e670fd5cSchristos * the object 133e670fd5cSchristos * @param[in] cb Callback to invoke when safe to do so 134e670fd5cSchristos */ 135e670fd5cSchristos #define RELEASE_REF( object, ref_field, cb ) \ 136e670fd5cSchristos do { \ 137e670fd5cSchristos assert( IS_ALIVE( (object), ref_field ) ); \ 138e670fd5cSchristos if ( !__atomic_sub_fetch( \ 139e670fd5cSchristos &(object)->ref_field, 1, __ATOMIC_ACQ_REL ) ) { \ 140e670fd5cSchristos epoch_append( object, (dispose_cb *)cb ); \ 141e670fd5cSchristos } \ 142e670fd5cSchristos } while (0) 143e670fd5cSchristos 144e670fd5cSchristos /**@}*/ 145e670fd5cSchristos 146e670fd5cSchristos #endif /* __LLOAD_EPOCH_H */ 147