mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
4ebac0fc86
MariaDB server crashes on ARM (weak memory model architecture) while concurrently executing l_find to load node->key and add_to_purgatory to store node->key = NULL. l_find then uses key (which is NULL), to pass it to a comparison function. The specific problem is the out-of-order execution that happens on a weak memory model architecture. Two essential reorderings are possible, which need to be prevented. a) As l_find has no barriers in place between the optimistic read of the key field lf_hash.cc#L117 and the verification of link lf_hash.cc#L124, the processor can reorder the load to happen after the while-loop. In that case, a concurrent thread executing add_to_purgatory on the same node can be scheduled to store NULL at the key field lf_alloc-pin.c#L253 before key is loaded in l_find. b) A node is marked as deleted by a CAS in l_delete lf_hash.cc#L247 and taken off the list with an upfollowing CAS lf_hash.cc#L252. Only if both CAS succeed, the key field is written to by add_to_purgatory. However, due to a missing barrier, the relaxed store of key lf_alloc-pin.c#L253 can be moved ahead of the two CAS operations, which makes the value of the local purgatory list stored by add_to_purgatory visible to all threads operating on the list. As the node is not marked as deleted yet, the same error occurs in l_find. This change three accesses to be atomic. * optimistic read of key in l_find lf_hash.cc#L117 * read of link for verification lf_hash.cc#L124 * write of key in add_to_purgatory lf_alloc-pin.c#L253 Reviewers: Sergei Vojtovich, Sergei Golubchik Fixes: MDEV-23510 / d30c1331a18d875e553f3fcf544997e4f33fb943
591 lines
18 KiB
C++
591 lines
18 KiB
C++
/* Copyright (c) 2006, 2018, Oracle and/or its affiliates.
|
|
Copyright (c) 2009, 2020, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
/*
|
|
extensible hash
|
|
|
|
TODO
|
|
try to get rid of dummy nodes ?
|
|
for non-unique hash, count only _distinct_ values
|
|
(but how to do it in lf_hash_delete ?)
|
|
*/
|
|
#include "mysys_priv.h"
|
|
#include <m_string.h>
|
|
#include <mysys_err.h>
|
|
#include <my_bit.h>
|
|
#include <lf.h>
|
|
#include "my_cpu.h"
|
|
#include "assume_aligned.h"
|
|
|
|
/* An element of the list */
|
|
typedef struct {
|
|
intptr link; /* a pointer to the next element in a list and a flag */
|
|
const uchar *key;
|
|
size_t keylen;
|
|
uint32 hashnr; /* reversed hash number, for sorting */
|
|
/*
|
|
data is stored here, directly after the keylen.
|
|
thus the pointer to data is (void*)(slist_element_ptr+1)
|
|
*/
|
|
} LF_SLIST;
|
|
|
|
const int LF_HASH_OVERHEAD= sizeof(LF_SLIST);
|
|
|
|
/*
|
|
a structure to pass the context (pointers two the three successive elements
|
|
in a list) from l_find to l_insert/l_delete
|
|
*/
|
|
typedef struct {
|
|
intptr *prev;
|
|
LF_SLIST *curr, *next;
|
|
} CURSOR;
|
|
|
|
/*
|
|
the last bit in LF_SLIST::link is a "deleted" flag.
|
|
the helper macros below convert it to a pure pointer or a pure flag
|
|
*/
|
|
#define PTR(V) (LF_SLIST *)((V) & (~(intptr)1))
|
|
#define DELETED(V) ((V) & 1)
|
|
|
|
/** walk the list, searching for an element or invoking a callback
|
|
|
|
Search for hashnr/key/keylen in the list starting from 'head' and
|
|
position the cursor. The list is ORDER BY hashnr, key
|
|
|
|
@param head start walking the list from this node
|
|
@param cs charset for comparing keys, NULL if callback is used
|
|
@param hashnr hash number to search for
|
|
@param key key to search for OR data for the callback
|
|
@param keylen length of the key to compare, 0 if callback is used
|
|
@param cursor for returning the found element
|
|
@param pins see lf_alloc-pin.c
|
|
@param callback callback action, invoked for every element
|
|
|
|
@note
|
|
cursor is positioned in either case
|
|
pins[0..2] are used, they are NOT removed on return
|
|
callback might see some elements twice (because of retries)
|
|
|
|
@return
|
|
if find: 0 - not found
|
|
1 - found
|
|
if callback:
|
|
0 - ok
|
|
1 - error (callbck returned 1)
|
|
*/
|
|
static int l_find(LF_SLIST **head, CHARSET_INFO *cs, uint32 hashnr,
|
|
const uchar *key, size_t keylen, CURSOR *cursor, LF_PINS *pins,
|
|
my_hash_walk_action callback)
|
|
{
|
|
uint32 cur_hashnr;
|
|
const uchar *cur_key;
|
|
size_t cur_keylen;
|
|
intptr link;
|
|
|
|
DBUG_ASSERT(!cs || !callback); /* should not be set both */
|
|
DBUG_ASSERT(!keylen || !callback); /* should not be set both */
|
|
|
|
retry:
|
|
cursor->prev= (intptr *) my_assume_aligned<sizeof(intptr)>(head);
|
|
do { /* PTR() isn't necessary below, head is a dummy node */
|
|
cursor->curr= my_assume_aligned<sizeof(LF_SLIST *)>((LF_SLIST *)(*cursor->prev));
|
|
lf_pin(pins, 1, cursor->curr);
|
|
} while (my_atomic_loadptr(
|
|
(void **)my_assume_aligned<sizeof(LF_SLIST *)>(cursor->prev))
|
|
!= cursor->curr && LF_BACKOFF());
|
|
for (;;)
|
|
{
|
|
if (unlikely(!cursor->curr))
|
|
return 0; /* end of the list */
|
|
|
|
cur_hashnr= cursor->curr->hashnr;
|
|
cur_keylen= cursor->curr->keylen;
|
|
/* The key element needs to be aligned, not necessary what it points to */
|
|
my_assume_aligned<sizeof(const uchar *)>(&cursor->curr->key);
|
|
cur_key= (const uchar *) my_atomic_loadptr_explicit((void **) &cursor->curr->key,
|
|
MY_MEMORY_ORDER_ACQUIRE);
|
|
|
|
do {
|
|
/* attempting to my_assume_aligned onlink below broke the implementation */
|
|
link= (intptr) my_atomic_loadptr_explicit((void **) &cursor->curr->link,
|
|
MY_MEMORY_ORDER_RELAXED);
|
|
cursor->next= my_assume_aligned<sizeof(LF_SLIST *)>(PTR(link));
|
|
lf_pin(pins, 0, cursor->next);
|
|
} while (link != (intptr) my_atomic_loadptr((void *volatile *) &cursor->curr->link)
|
|
&& LF_BACKOFF());
|
|
|
|
if (!DELETED(link))
|
|
{
|
|
if (unlikely(callback))
|
|
{
|
|
if (cur_hashnr & 1 && callback(cursor->curr + 1, (void*)key))
|
|
return 1;
|
|
}
|
|
else if (cur_hashnr >= hashnr)
|
|
{
|
|
int r= 1;
|
|
if (cur_hashnr > hashnr ||
|
|
(r= my_strnncoll(cs, cur_key, cur_keylen, key, keylen)) >= 0)
|
|
return !r;
|
|
}
|
|
cursor->prev= &(cursor->curr->link);
|
|
if (!(cur_hashnr & 1)) /* dummy node */
|
|
head= (LF_SLIST **)cursor->prev;
|
|
lf_pin(pins, 2, cursor->curr);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
we found a deleted node - be nice, help the other thread
|
|
and remove this deleted node
|
|
*/
|
|
if (my_atomic_casptr((void **) cursor->prev,
|
|
(void **) &cursor->curr, cursor->next) && LF_BACKOFF())
|
|
lf_alloc_free(pins, cursor->curr);
|
|
else
|
|
goto retry;
|
|
}
|
|
cursor->curr= cursor->next;
|
|
lf_pin(pins, 1, cursor->curr);
|
|
}
|
|
}
|
|
|
|
|
|
/* static l_find is the only user my_assume_aligned, keep the rest as c scoped */
|
|
C_MODE_START
|
|
|
|
/*
|
|
DESCRIPTION
|
|
insert a 'node' in the list that starts from 'head' in the correct
|
|
position (as found by l_find)
|
|
|
|
RETURN
|
|
0 - inserted
|
|
not 0 - a pointer to a duplicate (not pinned and thus unusable)
|
|
|
|
NOTE
|
|
it uses pins[0..2], on return all pins are removed.
|
|
if there're nodes with the same key value, a new node is added before them.
|
|
*/
|
|
static LF_SLIST *l_insert(LF_SLIST **head, CHARSET_INFO *cs,
|
|
LF_SLIST *node, LF_PINS *pins, uint flags)
|
|
{
|
|
CURSOR cursor;
|
|
int res;
|
|
|
|
for (;;)
|
|
{
|
|
if (l_find(head, cs, node->hashnr, node->key, node->keylen,
|
|
&cursor, pins, 0) &&
|
|
(flags & LF_HASH_UNIQUE))
|
|
{
|
|
res= 0; /* duplicate found */
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
node->link= (intptr)cursor.curr;
|
|
DBUG_ASSERT(node->link != (intptr)node); /* no circular references */
|
|
DBUG_ASSERT(cursor.prev != &node->link); /* no circular references */
|
|
if (my_atomic_casptr((void **) cursor.prev,
|
|
(void **)(char*) &cursor.curr, node))
|
|
{
|
|
res= 1; /* inserted ok */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
lf_unpin(pins, 0);
|
|
lf_unpin(pins, 1);
|
|
lf_unpin(pins, 2);
|
|
/*
|
|
Note that cursor.curr is not pinned here and the pointer is unreliable,
|
|
the object may disappear anytime. But if it points to a dummy node, the
|
|
pointer is safe, because dummy nodes are never freed - initialize_bucket()
|
|
uses this fact.
|
|
*/
|
|
return res ? 0 : cursor.curr;
|
|
}
|
|
|
|
/*
|
|
DESCRIPTION
|
|
deletes a node as identified by hashnr/keey/keylen from the list
|
|
that starts from 'head'
|
|
|
|
RETURN
|
|
0 - ok
|
|
1 - not found
|
|
|
|
NOTE
|
|
it uses pins[0..2], on return all pins are removed.
|
|
*/
|
|
static int l_delete(LF_SLIST **head, CHARSET_INFO *cs, uint32 hashnr,
|
|
const uchar *key, uint keylen, LF_PINS *pins)
|
|
{
|
|
CURSOR cursor;
|
|
int res;
|
|
|
|
for (;;)
|
|
{
|
|
if (!l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0))
|
|
{
|
|
res= 1; /* not found */
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* mark the node deleted */
|
|
if (my_atomic_casptr((void **) (char*) &(cursor.curr->link),
|
|
(void **) (char*) &cursor.next,
|
|
(void *)(((intptr)cursor.next) | 1)))
|
|
{
|
|
/* and remove it from the list */
|
|
if (my_atomic_casptr((void **)cursor.prev,
|
|
(void **)(char*)&cursor.curr, cursor.next))
|
|
lf_alloc_free(pins, cursor.curr);
|
|
else
|
|
{
|
|
/*
|
|
somebody already "helped" us and removed the node ?
|
|
Let's check if we need to help that someone too!
|
|
(to ensure the number of "set DELETED flag" actions
|
|
is equal to the number of "remove from the list" actions)
|
|
*/
|
|
l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0);
|
|
}
|
|
res= 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
lf_unpin(pins, 0);
|
|
lf_unpin(pins, 1);
|
|
lf_unpin(pins, 2);
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
DESCRIPTION
|
|
searches for a node as identified by hashnr/keey/keylen in the list
|
|
that starts from 'head'
|
|
|
|
RETURN
|
|
0 - not found
|
|
node - found
|
|
|
|
NOTE
|
|
it uses pins[0..2], on return the pin[2] keeps the node found
|
|
all other pins are removed.
|
|
*/
|
|
static LF_SLIST *l_search(LF_SLIST **head, CHARSET_INFO *cs,
|
|
uint32 hashnr, const uchar *key, uint keylen,
|
|
LF_PINS *pins)
|
|
{
|
|
CURSOR cursor;
|
|
int res= l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0);
|
|
if (res)
|
|
lf_pin(pins, 2, cursor.curr);
|
|
else
|
|
lf_unpin(pins, 2);
|
|
lf_unpin(pins, 1);
|
|
lf_unpin(pins, 0);
|
|
return res ? cursor.curr : 0;
|
|
}
|
|
|
|
static inline const uchar* hash_key(const LF_HASH *hash,
|
|
const uchar *record, size_t *length)
|
|
{
|
|
if (hash->get_key)
|
|
return (*hash->get_key)(record, length, 0);
|
|
*length= hash->key_length;
|
|
return record + hash->key_offset;
|
|
}
|
|
|
|
/*
|
|
Compute the hash key value from the raw key.
|
|
|
|
@note, that the hash value is limited to 2^31, because we need one
|
|
bit to distinguish between normal and dummy nodes.
|
|
*/
|
|
static inline my_hash_value_type calc_hash(CHARSET_INFO *cs,
|
|
const uchar *key,
|
|
size_t keylen)
|
|
{
|
|
ulong nr1= 1, nr2= 4;
|
|
my_ci_hash_sort(cs, (uchar*) key, keylen, &nr1, &nr2);
|
|
return nr1;
|
|
}
|
|
|
|
#define MAX_LOAD 1.0 /* average number of elements in a bucket */
|
|
|
|
static int initialize_bucket(LF_HASH *, LF_SLIST **, uint, LF_PINS *);
|
|
|
|
static void default_initializer(LF_HASH *hash, void *dst, const void *src)
|
|
{
|
|
memcpy(dst, src, hash->element_size);
|
|
}
|
|
|
|
|
|
/*
|
|
Initializes lf_hash, the arguments are compatible with hash_init
|
|
|
|
@note element_size sets both the size of allocated memory block for
|
|
lf_alloc and a size of memcpy'ed block size in lf_hash_insert. Typically
|
|
they are the same, indeed. But LF_HASH::element_size can be decreased
|
|
after lf_hash_init, and then lf_alloc will allocate larger block that
|
|
lf_hash_insert will copy over. It is desirable if part of the element
|
|
is expensive to initialize - for example if there is a mutex or
|
|
DYNAMIC_ARRAY. In this case they should be initialize in the
|
|
LF_ALLOCATOR::constructor, and lf_hash_insert should not overwrite them.
|
|
|
|
The above works well with PODS. For more complex cases (e.g. C++ classes
|
|
with private members) use initializer function.
|
|
*/
|
|
void lf_hash_init(LF_HASH *hash, uint element_size, uint flags,
|
|
uint key_offset, uint key_length, my_hash_get_key get_key,
|
|
CHARSET_INFO *charset)
|
|
{
|
|
lf_alloc_init(&hash->alloc, sizeof(LF_SLIST)+element_size,
|
|
offsetof(LF_SLIST, key));
|
|
lf_dynarray_init(&hash->array, sizeof(LF_SLIST *));
|
|
hash->size= 1;
|
|
hash->count= 0;
|
|
hash->element_size= element_size;
|
|
hash->flags= flags;
|
|
hash->charset= charset ? charset : &my_charset_bin;
|
|
hash->key_offset= key_offset;
|
|
hash->key_length= key_length;
|
|
hash->get_key= get_key;
|
|
hash->initializer= default_initializer;
|
|
hash->hash_function= calc_hash;
|
|
DBUG_ASSERT(get_key ? !key_offset && !key_length : key_length);
|
|
}
|
|
|
|
void lf_hash_destroy(LF_HASH *hash)
|
|
{
|
|
LF_SLIST *el, **head= (LF_SLIST **)lf_dynarray_value(&hash->array, 0);
|
|
|
|
if (head)
|
|
{
|
|
el= *head;
|
|
while (el)
|
|
{
|
|
intptr next= el->link;
|
|
if (el->hashnr & 1)
|
|
lf_alloc_direct_free(&hash->alloc, el); /* normal node */
|
|
else
|
|
my_free(el); /* dummy node */
|
|
el= (LF_SLIST *)next;
|
|
}
|
|
}
|
|
lf_alloc_destroy(&hash->alloc);
|
|
lf_dynarray_destroy(&hash->array);
|
|
}
|
|
|
|
/*
|
|
DESCRIPTION
|
|
inserts a new element to a hash. it will have a _copy_ of
|
|
data, not a pointer to it.
|
|
|
|
RETURN
|
|
0 - inserted
|
|
1 - didn't (unique key conflict)
|
|
-1 - out of memory
|
|
|
|
NOTE
|
|
see l_insert() for pin usage notes
|
|
*/
|
|
int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data)
|
|
{
|
|
int csize, bucket, hashnr;
|
|
LF_SLIST *node, **el;
|
|
|
|
node= (LF_SLIST *)lf_alloc_new(pins);
|
|
if (unlikely(!node))
|
|
return -1;
|
|
hash->initializer(hash, node + 1, data);
|
|
node->key= hash_key(hash, (uchar *)(node+1), &node->keylen);
|
|
hashnr= hash->hash_function(hash->charset, node->key, node->keylen) & INT_MAX32;
|
|
bucket= hashnr % hash->size;
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
|
if (unlikely(!el))
|
|
return -1;
|
|
if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins)))
|
|
return -1;
|
|
node->hashnr= my_reverse_bits(hashnr) | 1; /* normal node */
|
|
if (l_insert(el, hash->charset, node, pins, hash->flags))
|
|
{
|
|
lf_alloc_free(pins, node);
|
|
return 1;
|
|
}
|
|
csize= hash->size;
|
|
if ((my_atomic_add32(&hash->count, 1)+1.0) / csize > MAX_LOAD)
|
|
my_atomic_cas32(&hash->size, &csize, csize*2);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
DESCRIPTION
|
|
deletes an element with the given key from the hash (if a hash is
|
|
not unique and there're many elements with this key - the "first"
|
|
matching element is deleted)
|
|
RETURN
|
|
0 - deleted
|
|
1 - didn't (not found)
|
|
NOTE
|
|
see l_delete() for pin usage notes
|
|
*/
|
|
int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen)
|
|
{
|
|
LF_SLIST **el;
|
|
uint bucket, hashnr;
|
|
|
|
hashnr= hash->hash_function(hash->charset, (uchar *)key, keylen) & INT_MAX32;
|
|
|
|
/* hide OOM errors - if we cannot initialize a bucket, try the previous one */
|
|
for (bucket= hashnr % hash->size; ;bucket= my_clear_highest_bit(bucket))
|
|
{
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
|
if (el && (*el || initialize_bucket(hash, el, bucket, pins) == 0))
|
|
break;
|
|
if (unlikely(bucket == 0))
|
|
return 1; /* if there's no bucket==0, the hash is empty */
|
|
}
|
|
if (l_delete(el, hash->charset, my_reverse_bits(hashnr) | 1,
|
|
(uchar *)key, keylen, pins))
|
|
{
|
|
return 1;
|
|
}
|
|
my_atomic_add32(&hash->count, -1);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
RETURN
|
|
a pointer to an element with the given key (if a hash is not unique and
|
|
there're many elements with this key - the "first" matching element)
|
|
NULL if nothing is found
|
|
|
|
NOTE
|
|
see l_search() for pin usage notes
|
|
*/
|
|
void *lf_hash_search_using_hash_value(LF_HASH *hash, LF_PINS *pins,
|
|
my_hash_value_type hashnr,
|
|
const void *key, uint keylen)
|
|
{
|
|
LF_SLIST **el, *found;
|
|
uint bucket;
|
|
|
|
/* hide OOM errors - if we cannot initialize a bucket, try the previous one */
|
|
for (bucket= hashnr % hash->size; ;bucket= my_clear_highest_bit(bucket))
|
|
{
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
|
if (el && (*el || initialize_bucket(hash, el, bucket, pins) == 0))
|
|
break;
|
|
if (unlikely(bucket == 0))
|
|
return 0; /* if there's no bucket==0, the hash is empty */
|
|
}
|
|
found= l_search(el, hash->charset, my_reverse_bits(hashnr) | 1,
|
|
(uchar *)key, keylen, pins);
|
|
return found ? found+1 : 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Iterate over all elements in hash and call function with the element
|
|
|
|
@note
|
|
If one of 'action' invocations returns 1 the iteration aborts.
|
|
'action' might see some elements twice!
|
|
|
|
@retval 0 ok
|
|
@retval 1 error (action returned 1)
|
|
*/
|
|
int lf_hash_iterate(LF_HASH *hash, LF_PINS *pins,
|
|
my_hash_walk_action action, void *argument)
|
|
{
|
|
CURSOR cursor;
|
|
uint bucket= 0;
|
|
int res;
|
|
LF_SLIST **el;
|
|
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
|
if (unlikely(!el))
|
|
return 0; /* if there's no bucket==0, the hash is empty */
|
|
if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins)))
|
|
return 0; /* if there's no bucket==0, the hash is empty */
|
|
|
|
res= l_find(el, 0, 0, (uchar*)argument, 0, &cursor, pins, action);
|
|
|
|
lf_unpin(pins, 2);
|
|
lf_unpin(pins, 1);
|
|
lf_unpin(pins, 0);
|
|
return res;
|
|
}
|
|
|
|
void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen)
|
|
{
|
|
return lf_hash_search_using_hash_value(hash, pins,
|
|
hash->hash_function(hash->charset,
|
|
(uchar*) key,
|
|
keylen) & INT_MAX32,
|
|
key, keylen);
|
|
}
|
|
|
|
static const uchar *dummy_key= (uchar*)"";
|
|
|
|
/*
|
|
RETURN
|
|
0 - ok
|
|
-1 - out of memory
|
|
*/
|
|
static int initialize_bucket(LF_HASH *hash, LF_SLIST **node,
|
|
uint bucket, LF_PINS *pins)
|
|
{
|
|
uint parent= my_clear_highest_bit(bucket);
|
|
LF_SLIST *dummy= (LF_SLIST *)my_malloc(key_memory_lf_slist,
|
|
sizeof(LF_SLIST), MYF(MY_WME));
|
|
LF_SLIST **tmp= 0, *cur;
|
|
LF_SLIST **el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, parent);
|
|
if (unlikely(!el || !dummy))
|
|
return -1;
|
|
if (*el == NULL && bucket &&
|
|
unlikely(initialize_bucket(hash, el, parent, pins)))
|
|
{
|
|
my_free(dummy);
|
|
return -1;
|
|
}
|
|
dummy->hashnr= my_reverse_bits(bucket) | 0; /* dummy node */
|
|
dummy->key= dummy_key;
|
|
dummy->keylen= 0;
|
|
if ((cur= l_insert(el, hash->charset, dummy, pins, LF_HASH_UNIQUE)))
|
|
{
|
|
my_free(dummy);
|
|
dummy= cur;
|
|
}
|
|
my_atomic_casptr((void **)node, (void **)(char*) &tmp, dummy);
|
|
/*
|
|
note that if the CAS above failed (after l_insert() succeeded),
|
|
it would mean that some other thread has executed l_insert() for
|
|
the same dummy node, its l_insert() failed, it picked up our
|
|
dummy node (in "dummy= cur") and executed the same CAS as above.
|
|
Which means that even if CAS above failed we don't need to retry,
|
|
and we should not free(dummy) - there's no memory leak here
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
C_MODE_END
|