File lock_free.h¶
File List > base > lock_free.h
Go to the documentation of this file
/*
* Copyright 2008 Search Solution Corporation
* Copyright 2016 CUBRID Corporation
*
* Licensed 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.
*
*/
/*
* lock_free.h : Lock-free structures interface.
*/
#ifndef _LOCK_FREE_H_
#define _LOCK_FREE_H_
#include "dbtype_def.h"
#include "lockfree_bitmap.hpp"
#include "porting.h"
#include <cassert>
#if !defined (WINDOWS)
#include <pthread.h>
#endif
/*
* Some common hash, copy and compare functions
*/
extern unsigned int lf_callback_vpid_hash (void *vpid, int htsize);
extern int lf_callback_vpid_compare (void *vpid_1, void *vpid_2);
extern int lf_callback_vpid_copy (void *src, void *dest);
/*
* Volatile access to a variable
*/
#define VOLATILE_ACCESS(v,t) (*((t volatile *) &(v)))
/*
* Entry descriptor
*/
typedef void *(*LF_ENTRY_ALLOC_FUNC) ();
typedef int (*LF_ENTRY_FREE_FUNC) (void *);
typedef int (*LF_ENTRY_INITIALIZE_FUNC) (void *);
typedef int (*LF_ENTRY_UNINITIALIZE_FUNC) (void *);
typedef int (*LF_ENTRY_KEY_COPY_FUNC) (void *src, void *dest);
typedef int (*LF_ENTRY_KEY_COMPARE_FUNC) (void *key1, void *key2);
typedef unsigned int (*LF_ENTRY_HASH_FUNC) (void *key, int htsize);
typedef int (*LF_ENTRY_DUPLICATE_KEY_HANDLER) (void *key, void *existing);
#define LF_EM_NOT_USING_MUTEX 0
#define LF_EM_USING_MUTEX 1
typedef struct lf_entry_descriptor LF_ENTRY_DESCRIPTOR;
struct lf_entry_descriptor
{
/* offset of "next" pointer used in local lists */
unsigned int of_local_next;
/* offset of "next" pointer */
unsigned int of_next;
/* offset of transaction id of delete operation */
unsigned int of_del_tran_id;
/* offset of key */
unsigned int of_key;
/* offset of entry mutex */
unsigned int of_mutex;
/* does entry have mutex */
int using_mutex;
/* maximum alloc cnt */
int max_alloc_cnt;
/* allocation callback */
LF_ENTRY_ALLOC_FUNC f_alloc;
/* deallocation callback */
LF_ENTRY_FREE_FUNC f_free;
/* initialization callback; can be NULL */
LF_ENTRY_INITIALIZE_FUNC f_init;
/* uninitialization callback; can be NULL */
LF_ENTRY_UNINITIALIZE_FUNC f_uninit;
/* copy function for keys */
LF_ENTRY_KEY_COPY_FUNC f_key_copy;
/* compare function for keys */
LF_ENTRY_KEY_COMPARE_FUNC f_key_cmp;
/* hash function for keys */
LF_ENTRY_HASH_FUNC f_hash;
/* callback for lf_insert with existing key */
/* NOTE: when NULL, lf_insert will spin until existing entry is deleted */
LF_ENTRY_DUPLICATE_KEY_HANDLER f_duplicate;
};
#define LF_ENTRY_DESCRIPTOR_MAX_ALLOC 2147483647 /* MAX INT */
#define LF_ENTRY_DESCRIPTOR_INITIALIZER { 0, 0, 0, 0, 0, 0, LF_ENTRY_DESCRIPTOR_MAX_ALLOC, NULL, NULL, NULL, \
NULL, NULL, NULL, NULL, NULL}
/*
* Lock free transaction based memory garbage collector
*/
#define LF_NULL_TRANSACTION_ID ULONG_MAX
typedef struct lf_tran_system LF_TRAN_SYSTEM;
typedef struct lf_tran_entry LF_TRAN_ENTRY;
struct lf_tran_entry
{
/* last ID for which a cleanup of retired_list was performed */
UINT64 last_cleanup_id;
/* id of current transaction */
UINT64 transaction_id;
/* list of retired node for attached thread */
void *retired_list;
/* temp entry - for find_and_insert operations, to avoid unnecessary ops */
void *temp_entry;
/* attached transaction system */
LF_TRAN_SYSTEM *tran_system;
/* entry in transaction system */
int entry_idx;
/* Was transaction ID incremented? */
bool did_incr;
#if defined (UNITTEST_LF)
/* Debug */
pthread_mutex_t *locked_mutex;
int locked_mutex_line;
#endif /* UNITTEST_LF */
};
#define LF_TRAN_ENTRY_INITIALIZER { 0, LF_NULL_TRANSACTION_ID, NULL, NULL, NULL, -1, false }
struct lf_tran_system
{
/* pointer array to thread dtran entries */
LF_TRAN_ENTRY *entries;
/* capacity */
int entry_count;
/* lock-free bitmap */
LF_BITMAP lf_bitmap;
/* global delete ID for all delete operations */
UINT64 global_transaction_id;
/* minimum curr_delete_id of all used LF_DTRAN_ENTRY entries */
UINT64 min_active_transaction_id;
/* number of transactions between computing min_active_transaction_id */
int mati_refresh_interval;
/* current used count */
int used_entry_count;
/* entry descriptor */
LF_ENTRY_DESCRIPTOR *entry_desc;
};
#define LF_TRAN_SYSTEM_INITIALIZER \
{ NULL, 0, {}, 0, 0, 100, 0, NULL }
#define LF_TRAN_CLEANUP_NECESSARY(e) ((e)->tran_system->min_active_transaction_id > (e)->last_cleanup_id)
extern int lf_tran_system_init (LF_TRAN_SYSTEM * sys, int max_threads);
extern void lf_tran_system_destroy (LF_TRAN_SYSTEM * sys);
extern LF_TRAN_ENTRY *lf_tran_request_entry (LF_TRAN_SYSTEM * sys);
extern void lf_tran_return_entry (LF_TRAN_ENTRY * entry);
extern void lf_tran_destroy_entry (LF_TRAN_ENTRY * entry);
extern void lf_tran_compute_minimum_transaction_id (LF_TRAN_SYSTEM * sys);
extern void lf_tran_start (LF_TRAN_ENTRY * entry, bool incr);
extern void lf_tran_end (LF_TRAN_ENTRY * entry);
/* TODO: Investigate memory barriers. First of all, I need to check if it breaks the inlining of lf_tran_start and
* lf_tran_end functions. Second of all, full memory barriers might not be necessary.
*/
#define lf_tran_start_with_mb(entry, incr) lf_tran_start (entry, incr); MEMORY_BARRIER ()
#define lf_tran_end_with_mb(entry) MEMORY_BARRIER (); lf_tran_end (entry)
/*
* Global lock free transaction system declarations
*/
extern LF_TRAN_SYSTEM spage_saving_Ts;
extern LF_TRAN_SYSTEM obj_lock_res_Ts;
extern LF_TRAN_SYSTEM obj_lock_ent_Ts;
extern LF_TRAN_SYSTEM catalog_Ts;
extern LF_TRAN_SYSTEM sessions_Ts;
extern LF_TRAN_SYSTEM free_sort_list_Ts;
extern LF_TRAN_SYSTEM global_unique_stats_Ts;
extern LF_TRAN_SYSTEM hfid_table_Ts;
extern LF_TRAN_SYSTEM xcache_Ts;
extern LF_TRAN_SYSTEM fpcache_Ts;
extern LF_TRAN_SYSTEM dwb_slots_Ts;
extern int lf_initialize_transaction_systems (int max_threads);
extern void lf_destroy_transaction_systems (void);
/*
* Lock free stack
*/
extern int lf_stack_push (void **top, void *entry, LF_ENTRY_DESCRIPTOR * edesc);
extern void *lf_stack_pop (void **top, LF_ENTRY_DESCRIPTOR * edesc);
/*
* Lock free freelist
*/
typedef struct lf_freelist LF_FREELIST;
struct lf_freelist
{
/* available stack (i.e. entries that can be safely reclaimed) */
void *available;
/* allocation block size */
int block_size;
/* entry counters */
int alloc_cnt;
int available_cnt;
int retired_cnt;
/* entry descriptor */
LF_ENTRY_DESCRIPTOR *entry_desc;
/* transaction system */
LF_TRAN_SYSTEM *tran_system;
};
#define LF_FREELIST_INITIALIZER \
{ NULL, 0, 0, 0, 0, NULL, NULL }
extern int lf_freelist_init (LF_FREELIST * freelist, int initial_blocks, int block_size, LF_ENTRY_DESCRIPTOR * edesc,
LF_TRAN_SYSTEM * tran_system);
extern void lf_freelist_destroy (LF_FREELIST * freelist);
extern void *lf_freelist_claim (LF_TRAN_ENTRY * tran_entry, LF_FREELIST * freelist);
extern int lf_freelist_retire (LF_TRAN_ENTRY * tran_entry, LF_FREELIST * freelist, void *entry);
extern int lf_freelist_transport (LF_TRAN_ENTRY * tran_entry, LF_FREELIST * freelist);
/*
* Lock free insert-only list based dictionary
* NOTE: This list does not use a LF_TRAN_SYSTEM nor a LF_FREELIST.
*/
extern int lf_io_list_find (void **list_p, void *key, LF_ENTRY_DESCRIPTOR * edesc, void **entry);
extern int lf_io_list_find_or_insert (void **list_p, void *new_entry, LF_ENTRY_DESCRIPTOR * edesc, void **entry);
/*
* Lock free linked list based dictionary
*/
#define LF_LIST_BF_NONE 0x0
/* flags that can be given to lf_list_* functions */
#define LF_LIST_BF_RETURN_ON_RESTART ((int) 0x01)
#define LF_LIST_BF_RESTART_ON_DUPLICATE ((int) 0x02) /* Not used for now. */
#define LF_LIST_BF_INSERT_GIVEN ((int) 0x04)
#define LF_LIST_BF_FIND_OR_INSERT ((int) 0x08)
#define LF_LIST_BF_LOCK_ON_DELETE ((int) 0x10)
#define LF_LIST_BF_IS_FLAG_SET(bf, flag) ((*(bf) & (flag)) != 0)
#define LF_LIST_BF_SET_FLAG(bf, flag) (*(bf) = *(bf) | (flag))
/* responses to flags from lf_list_* functions */
#define LF_LIST_BR_RESTARTED ((int) 0x100)
#define LF_LIST_BR_DUPLICATE ((int) 0x200) /* Not used for now. */
#define LF_LIST_BR_IS_FLAG_SET(br, flag) ((*(br) & (flag)))
#define LF_LIST_BR_SET_FLAG(br, flag) (*(br) = *(br) | (flag))
extern int lf_list_find (LF_TRAN_ENTRY * tran, void **list_p, void *key, int *behavior_flags,
LF_ENTRY_DESCRIPTOR * edesc, void **entry);
extern int lf_list_delete (LF_TRAN_ENTRY * tran, void **list_p, void *key, void *locked_entry, int *behavior_flags,
LF_ENTRY_DESCRIPTOR * edesc, LF_FREELIST * freelist, int *success);
/* TODO: Add lf_list_insert functions. So far, they are only used for lf_hash_insert. */
/*
* Lock free hash table
*/
typedef struct lf_hash_table LF_HASH_TABLE;
struct lf_hash_table
{
/* table buckets */
void **buckets;
/* backbuffer */
void **backbuffer;
/* backbuffer mutex */
pthread_mutex_t backbuffer_mutex;
/* size of hash table */
unsigned int hash_size;
/* freelist for memory reuse */
LF_FREELIST *freelist;
/* entry descriptor */
LF_ENTRY_DESCRIPTOR *entry_desc;
};
#define LF_HASH_TABLE_INITIALIZER \
{ NULL, NULL, PTHREAD_MUTEX_INITIALIZER, 0, NULL, NULL }
extern int lf_hash_init (LF_HASH_TABLE * table, LF_FREELIST * freelist, unsigned int hash_size,
LF_ENTRY_DESCRIPTOR * edesc);
extern void lf_hash_destroy (LF_HASH_TABLE * table);
extern int lf_hash_find (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, void **entry);
extern int lf_hash_find_or_insert (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, void **entry, int *inserted);
extern int lf_hash_insert (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, void **entry, int *inserted);
extern int lf_hash_insert_given (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, void **entry, int *inserted);
extern int lf_hash_delete (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, int *success);
extern int lf_hash_delete_already_locked (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table, void *key, void *locked_entry,
int *success);
extern void lf_hash_clear (LF_TRAN_ENTRY * tran, LF_HASH_TABLE * table);
/*
* Lock free hash table iterator
*/
typedef struct lf_hash_table_iterator LF_HASH_TABLE_ITERATOR;
struct lf_hash_table_iterator
{
/* hash table we iterate on */
LF_HASH_TABLE *hash_table;
/* current bucket index */
int bucket_index;
/* current entry */
void *curr;
/* transaction entry to use */
LF_TRAN_ENTRY *tran_entry;
};
extern void lf_hash_create_iterator (LF_HASH_TABLE_ITERATOR * iterator, LF_TRAN_ENTRY * tran_entry,
LF_HASH_TABLE * table);
extern void *lf_hash_iterate (LF_HASH_TABLE_ITERATOR * it);
#if defined (UNITTEST_LF)
extern void lf_reset_counters (void);
#endif /* UNITTEST_LF */
// C++ style lock-free hash
// *INDENT-OFF*
template <class Key, class T>
class lf_hash_table_cpp
{
public:
class iterator;
lf_hash_table_cpp ();
void init (lf_tran_system &transys, int hash_size, int freelist_block_count, int freelist_block_size,
lf_entry_descriptor &edes);
void destroy ();
T *find (lf_tran_entry *t_entry, Key &key);
bool find_or_insert (lf_tran_entry *t_entry, Key &key, T *&t);
bool insert (lf_tran_entry *t_entry, Key &key, T *&t);
bool insert_given (lf_tran_entry *t_entry, Key &key, T *&t);
bool erase (lf_tran_entry *t_entry, Key &key);
bool erase_locked (lf_tran_entry *t_entry, Key &key, T *&t);
void unlock (lf_tran_entry *t_entry, T *&t);
void clear (lf_tran_entry *t_entry);
T *freelist_claim (lf_tran_entry *t_entry);
void freelist_retire (lf_tran_entry *t_entry, T *&t);
void start_tran (lf_tran_entry *t_entry);
void end_tran (lf_tran_entry *t_entry);
size_t get_size () const;
size_t get_element_count () const;
size_t get_alloc_element_count () const;
lf_hash_table &get_hash_table ();
lf_freelist &get_freelist ();
private:
pthread_mutex_t *get_pthread_mutex (T *t);
template <typename F>
bool generic_insert (F &ins_func, lf_tran_entry *t_entry, Key &key, T *&t);
lf_freelist m_freelist;
lf_hash_table m_hash;
};
template <class Key, class T>
class lf_hash_table_cpp<Key, T>::iterator
{
public:
iterator () = default;
iterator (lf_tran_entry *t_entry, lf_hash_table_cpp & hash);
~iterator ();
T *iterate ();
void restart ();
private:
lf_hash_table_iterator m_iter;
T *m_crt_val;
};
//
// implementation
//
//
// lf_hash_table_cpp
//
template <class Key, class T>
lf_hash_table_cpp<Key, T>::lf_hash_table_cpp ()
: m_freelist LF_FREELIST_INITIALIZER
, m_hash LF_HASH_TABLE_INITIALIZER
{
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::init (lf_tran_system &transys, int hash_size, int freelist_block_count,
int freelist_block_size, lf_entry_descriptor &edesc)
{
if (lf_freelist_init (&m_freelist, freelist_block_count, freelist_block_size, &edesc, &transys) != NO_ERROR)
{
assert (false);
return;
}
if (lf_hash_init (&m_hash, &m_freelist, hash_size, &edesc) != NO_ERROR)
{
assert (false);
return;
}
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::destroy ()
{
lf_hash_destroy (&m_hash);
lf_freelist_destroy (&m_freelist);
}
template <class Key, class T>
pthread_mutex_t *
lf_hash_table_cpp<Key, T>::get_pthread_mutex (T *t)
{
assert (m_freelist.entry_desc->using_mutex);
return (pthread_mutex_t *) (((char *) t) + m_freelist.entry_desc->of_mutex);
}
template <class Key, class T>
T *
lf_hash_table_cpp<Key, T>::find (lf_tran_entry *t_entry, Key &key)
{
T *ret = NULL;
if (lf_hash_find (t_entry, &m_hash, &key, (void **) (&ret)) != NO_ERROR)
{
assert (false);
}
return ret;
}
template <class Key, class T>
template <typename F>
bool
lf_hash_table_cpp<Key, T>::generic_insert (F &ins_func, lf_tran_entry *t_entry, Key &key, T *&t)
{
int inserted = 0;
if (ins_func (t_entry, &m_hash, &key, reinterpret_cast<void **> (&t), &inserted) != NO_ERROR)
{
assert (false);
}
return inserted != 0;
}
template <class Key, class T>
bool
lf_hash_table_cpp<Key, T>::find_or_insert (lf_tran_entry *t_entry, Key &key, T *&t)
{
return generic_insert (lf_hash_find_or_insert, t_entry, key, t);
}
template <class Key, class T>
bool
lf_hash_table_cpp<Key, T>::insert (lf_tran_entry *t_entry, Key &key, T *&t)
{
return generic_insert (lf_hash_insert, t_entry, key, t);
}
template <class Key, class T>
bool
lf_hash_table_cpp<Key, T>::insert_given (lf_tran_entry *t_entry, Key &key, T *&t)
{
return generic_insert (lf_hash_insert_given, t_entry, key, t);
}
template <class Key, class T>
bool
lf_hash_table_cpp<Key, T>::erase (lf_tran_entry *t_entry, Key &key)
{
int success = 0;
if (lf_hash_delete (t_entry, &m_hash, &key, &success) != NO_ERROR)
{
assert (false);
}
return success != 0;
}
template <class Key, class T>
bool
lf_hash_table_cpp<Key, T>::erase_locked (lf_tran_entry *t_entry, Key &key, T *&t)
{
int success = 0;
if (lf_hash_delete_already_locked (t_entry, &m_hash, &key, t, &success) != NO_ERROR)
{
assert (false);
pthread_mutex_unlock (get_pthread_mutex (t));
}
if (success != 0)
{
t = NULL;
}
return success != 0;
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::unlock (lf_tran_entry *t_entry, T *&t)
{
assert (t != NULL);
if (m_freelist.entry_desc->using_mutex)
{
pthread_mutex_unlock (get_pthread_mutex (t));
}
else
{
lf_tran_end_with_mb (t_entry);
}
t = NULL;
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::clear (lf_tran_entry *t_entry)
{
lf_hash_clear (t_entry, &m_hash);
}
template <class Key, class T>
T *
lf_hash_table_cpp<Key, T>::freelist_claim (lf_tran_entry *t_entry)
{
return (T *) lf_freelist_claim (t_entry, &m_freelist);
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::freelist_retire (lf_tran_entry *t_entry, T *&t)
{
lf_freelist_retire (t_entry, &m_freelist, t);
t = NULL;
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::start_tran (lf_tran_entry *t_entry)
{
lf_tran_start_with_mb (t_entry, false);
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::end_tran (lf_tran_entry *t_entry)
{
lf_tran_end_with_mb (t_entry);
}
template <class Key, class T>
size_t
lf_hash_table_cpp<Key, T>::get_size () const
{
assert (m_hash.hash_size > 0);
return (size_t) m_hash.hash_size;
}
template <class Key, class T>
size_t
lf_hash_table_cpp<Key, T>::get_element_count () const
{
int alloc_count = m_freelist.alloc_cnt;
int unused_count = m_freelist.available_cnt + m_freelist.retired_cnt;
if (alloc_count > unused_count)
{
return static_cast<size_t> (alloc_count - unused_count);
}
else
{
return 0;
}
}
template <class Key, class T>
size_t
lf_hash_table_cpp<Key, T>::get_alloc_element_count () const
{
return m_freelist.alloc_cnt;
}
template <class Key, class T>
lf_hash_table &
lf_hash_table_cpp<Key, T>::get_hash_table ()
{
return m_hash;
}
template <class Key, class T>
lf_freelist &
lf_hash_table_cpp<Key, T>::get_freelist ()
{
return m_freelist;
}
//
// lf_hash_table_cpp::iterator
//
template <class Key, class T>
lf_hash_table_cpp<Key, T>::iterator::iterator (lf_tran_entry *t_entry, lf_hash_table_cpp & hash)
: m_iter ()
, m_crt_val (NULL)
{
lf_hash_create_iterator (&m_iter, t_entry, &hash.m_hash);
}
template <class Key, class T>
lf_hash_table_cpp<Key, T>::iterator::~iterator ()
{
}
template <class Key, class T>
T *
lf_hash_table_cpp<Key, T>::iterator::iterate ()
{
return static_cast<T *> (lf_hash_iterate (&m_iter));
}
template <class Key, class T>
void
lf_hash_table_cpp<Key, T>::iterator::restart ()
{
m_iter.bucket_index = -1;
if (m_iter.tran_entry->transaction_id != LF_NULL_TRANSACTION_ID)
{
lf_tran_end_with_mb (m_iter.tran_entry);
}
m_crt_val = NULL;
}
// *INDENT-ON*
#endif /* _LOCK_FREE_H_ */