2018-04-19 22:27:02 +02:00
|
|
|
/* Copyright (c) 2006, 2018, Oracle and/or its affiliates.
|
2020-01-26 17:27:13 +01:00
|
|
|
Copyright (c) 2009, 2020, MariaDB
|
2009-11-18 03:31:40 +01:00
|
|
|
|
|
|
|
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
|
2019-05-11 20:29:06 +02:00
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
2009-11-18 03:31:40 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
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 ?)
|
|
|
|
*/
|
2020-01-29 13:50:26 +01:00
|
|
|
#include "mysys_priv.h"
|
2009-11-18 03:31:40 +01:00
|
|
|
#include <m_string.h>
|
2014-11-27 23:49:45 +01:00
|
|
|
#include <mysys_err.h>
|
2009-11-18 03:31:40 +01:00
|
|
|
#include <my_bit.h>
|
|
|
|
#include <lf.h>
|
2020-04-15 19:23:12 +02:00
|
|
|
#include "my_cpu.h"
|
2021-02-15 02:31:31 +01:00
|
|
|
#include "assume_aligned.h"
|
2009-11-18 03:31:40 +01:00
|
|
|
|
|
|
|
/* An element of the list */
|
|
|
|
typedef struct {
|
2021-02-15 02:31:31 +01:00
|
|
|
intptr link; /* a pointer to the next element in a list and a flag */
|
2009-11-18 03:31:40 +01:00
|
|
|
const uchar *key;
|
|
|
|
size_t keylen;
|
2021-02-15 02:31:31 +01:00
|
|
|
uint32 hashnr; /* reversed hash number, for sorting */
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
|
|
|
data is stored here, directly after the keylen.
|
|
|
|
thus the pointer to data is (void*)(slist_element_ptr+1)
|
|
|
|
*/
|
|
|
|
} LF_SLIST;
|
|
|
|
|
2009-01-15 22:27:36 +01:00
|
|
|
const int LF_HASH_OVERHEAD= sizeof(LF_SLIST);
|
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
|
|
|
a structure to pass the context (pointers two the three successive elements
|
2015-11-06 17:24:23 +01:00
|
|
|
in a list) from l_find to l_insert/l_delete
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
|
|
|
typedef struct {
|
2021-02-15 02:31:31 +01:00
|
|
|
intptr *prev;
|
2009-11-18 03:31:40 +01:00
|
|
|
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)
|
|
|
|
|
2014-11-27 23:49:45 +01:00
|
|
|
/** walk the list, searching for an element or invoking a callback
|
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
Search for hashnr/key/keylen in the list starting from 'head' and
|
|
|
|
position the cursor. The list is ORDER BY hashnr, key
|
|
|
|
|
2014-11-27 23:49:45 +01:00
|
|
|
@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
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2014-11-27 23:49:45 +01:00
|
|
|
@note
|
2009-11-18 03:31:40 +01:00
|
|
|
cursor is positioned in either case
|
|
|
|
pins[0..2] are used, they are NOT removed on return
|
2014-11-27 23:49:45 +01:00
|
|
|
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)
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
2021-02-15 02:31:31 +01:00
|
|
|
static int l_find(LF_SLIST **head, CHARSET_INFO *cs, uint32 hashnr,
|
2018-02-06 13:55:58 +01:00
|
|
|
const uchar *key, size_t keylen, CURSOR *cursor, LF_PINS *pins,
|
2014-11-27 23:49:45 +01:00
|
|
|
my_hash_walk_action callback)
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
|
|
|
uint32 cur_hashnr;
|
|
|
|
const uchar *cur_key;
|
2018-02-06 13:55:58 +01:00
|
|
|
size_t cur_keylen;
|
2009-11-18 03:31:40 +01:00
|
|
|
intptr link;
|
|
|
|
|
2014-11-27 23:49:45 +01:00
|
|
|
DBUG_ASSERT(!cs || !callback); /* should not be set both */
|
|
|
|
DBUG_ASSERT(!keylen || !callback); /* should not be set both */
|
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
retry:
|
2021-02-15 02:31:31 +01:00
|
|
|
cursor->prev= (intptr *) my_assume_aligned<sizeof(intptr)>(head);
|
2009-11-18 03:31:40 +01:00
|
|
|
do { /* PTR() isn't necessary below, head is a dummy node */
|
2021-02-15 02:31:31 +01:00
|
|
|
cursor->curr= my_assume_aligned<sizeof(LF_SLIST *)>((LF_SLIST *)(*cursor->prev));
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_pin(pins, 1, cursor->curr);
|
MDEV-27088: Server crash on ARM (WMM architecture) due to missing barriers in lf-hash (10.5)
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
2021-11-20 04:22:25 +01:00
|
|
|
} while (my_atomic_loadptr(
|
|
|
|
(void **)my_assume_aligned<sizeof(LF_SLIST *)>(cursor->prev))
|
|
|
|
!= cursor->curr && LF_BACKOFF());
|
2009-11-18 03:31:40 +01:00
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (unlikely(!cursor->curr))
|
|
|
|
return 0; /* end of the list */
|
2014-11-27 20:51:23 +01:00
|
|
|
|
|
|
|
cur_hashnr= cursor->curr->hashnr;
|
|
|
|
cur_keylen= cursor->curr->keylen;
|
2021-02-15 02:31:31 +01:00
|
|
|
/* The key element needs to be aligned, not necessary what it points to */
|
|
|
|
my_assume_aligned<sizeof(const uchar *)>(&cursor->curr->key);
|
MDEV-27088: Server crash on ARM (WMM architecture) due to missing barriers in lf-hash (10.5)
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
2021-11-20 04:22:25 +01:00
|
|
|
cur_key= (const uchar *) my_atomic_loadptr_explicit((void **) &cursor->curr->key,
|
|
|
|
MY_MEMORY_ORDER_ACQUIRE);
|
2014-11-27 20:51:23 +01:00
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
do {
|
2021-02-15 02:31:31 +01:00
|
|
|
/* attempting to my_assume_aligned onlink below broke the implementation */
|
MDEV-27088: Server crash on ARM (WMM architecture) due to missing barriers in lf-hash (10.5)
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
2021-11-20 04:22:25 +01:00
|
|
|
link= (intptr) my_atomic_loadptr_explicit((void **) &cursor->curr->link,
|
|
|
|
MY_MEMORY_ORDER_RELAXED);
|
2021-02-15 02:31:31 +01:00
|
|
|
cursor->next= my_assume_aligned<sizeof(LF_SLIST *)>(PTR(link));
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_pin(pins, 0, cursor->next);
|
MDEV-27088: Server crash on ARM (WMM architecture) due to missing barriers in lf-hash (10.5)
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
2021-11-20 04:22:25 +01:00
|
|
|
} while (link != (intptr) my_atomic_loadptr((void *volatile *) &cursor->curr->link)
|
|
|
|
&& LF_BACKOFF());
|
2014-11-27 20:51:23 +01:00
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
if (!DELETED(link))
|
|
|
|
{
|
2014-11-27 23:49:45 +01:00
|
|
|
if (unlikely(callback))
|
|
|
|
{
|
2014-12-28 16:42:17 +01:00
|
|
|
if (cur_hashnr & 1 && callback(cursor->curr + 1, (void*)key))
|
2014-11-27 23:49:45 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (cur_hashnr >= hashnr)
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
|
|
|
int r= 1;
|
|
|
|
if (cur_hashnr > hashnr ||
|
2014-11-27 20:51:23 +01:00
|
|
|
(r= my_strnncoll(cs, cur_key, cur_keylen, key, keylen)) >= 0)
|
2009-11-18 03:31:40 +01:00
|
|
|
return !r;
|
|
|
|
}
|
|
|
|
cursor->prev= &(cursor->curr->link);
|
2014-11-27 20:51:23 +01:00
|
|
|
if (!(cur_hashnr & 1)) /* dummy node */
|
|
|
|
head= (LF_SLIST **)cursor->prev;
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_pin(pins, 2, cursor->curr);
|
2009-11-18 03:31:40 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
we found a deleted node - be nice, help the other thread
|
|
|
|
and remove this deleted node
|
|
|
|
*/
|
2009-11-30 00:08:56 +01:00
|
|
|
if (my_atomic_casptr((void **) cursor->prev,
|
2017-12-07 14:03:59 +01:00
|
|
|
(void **) &cursor->curr, cursor->next) && LF_BACKOFF())
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_alloc_free(pins, cursor->curr);
|
2009-11-18 03:31:40 +01:00
|
|
|
else
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
cursor->curr= cursor->next;
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_pin(pins, 1, cursor->curr);
|
2009-11-18 03:31:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-15 02:31:31 +01:00
|
|
|
|
|
|
|
/* static l_find is the only user my_assume_aligned, keep the rest as c scoped */
|
|
|
|
C_MODE_START
|
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
|
|
|
DESCRIPTION
|
|
|
|
insert a 'node' in the list that starts from 'head' in the correct
|
2015-11-06 17:24:23 +01:00
|
|
|
position (as found by l_find)
|
2009-11-18 03:31:40 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2021-02-15 02:31:31 +01:00
|
|
|
static LF_SLIST *l_insert(LF_SLIST **head, CHARSET_INFO *cs,
|
2009-11-18 03:31:40 +01:00
|
|
|
LF_SLIST *node, LF_PINS *pins, uint flags)
|
|
|
|
{
|
|
|
|
CURSOR cursor;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2015-11-06 17:24:23 +01:00
|
|
|
if (l_find(head, cs, node->hashnr, node->key, node->keylen,
|
2014-11-27 23:49:45 +01:00
|
|
|
&cursor, pins, 0) &&
|
2009-11-18 03:31:40 +01:00
|
|
|
(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 */
|
2009-11-30 00:08:56 +01:00
|
|
|
if (my_atomic_casptr((void **) cursor.prev,
|
|
|
|
(void **)(char*) &cursor.curr, node))
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
|
|
|
res= 1; /* inserted ok */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_unpin(pins, 0);
|
|
|
|
lf_unpin(pins, 1);
|
|
|
|
lf_unpin(pins, 2);
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
|
|
|
Note that cursor.curr is not pinned here and the pointer is unreliable,
|
2017-02-26 16:49:47 +01:00
|
|
|
the object may disappear anytime. But if it points to a dummy node, the
|
2009-11-18 03:31:40 +01:00
|
|
|
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.
|
|
|
|
*/
|
2021-02-15 02:31:31 +01:00
|
|
|
static int l_delete(LF_SLIST **head, CHARSET_INFO *cs, uint32 hashnr,
|
2009-11-18 03:31:40 +01:00
|
|
|
const uchar *key, uint keylen, LF_PINS *pins)
|
|
|
|
{
|
|
|
|
CURSOR cursor;
|
|
|
|
int res;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
2015-11-19 15:52:14 +01:00
|
|
|
if (!l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0))
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
|
|
|
res= 1; /* not found */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* mark the node deleted */
|
2009-11-30 00:08:56 +01:00
|
|
|
if (my_atomic_casptr((void **) (char*) &(cursor.curr->link),
|
|
|
|
(void **) (char*) &cursor.next,
|
2009-11-18 03:31:40 +01:00
|
|
|
(void *)(((intptr)cursor.next) | 1)))
|
|
|
|
{
|
|
|
|
/* and remove it from the list */
|
|
|
|
if (my_atomic_casptr((void **)cursor.prev,
|
2009-11-30 00:08:56 +01:00
|
|
|
(void **)(char*)&cursor.curr, cursor.next))
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_alloc_free(pins, cursor.curr);
|
2009-11-18 03:31:40 +01:00
|
|
|
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)
|
|
|
|
*/
|
2015-11-19 15:52:14 +01:00
|
|
|
l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0);
|
2009-11-18 03:31:40 +01:00
|
|
|
}
|
|
|
|
res= 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_unpin(pins, 0);
|
|
|
|
lf_unpin(pins, 1);
|
|
|
|
lf_unpin(pins, 2);
|
2009-11-18 03:31:40 +01:00
|
|
|
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.
|
|
|
|
*/
|
2021-02-15 02:31:31 +01:00
|
|
|
static LF_SLIST *l_search(LF_SLIST **head, CHARSET_INFO *cs,
|
2009-11-18 03:31:40 +01:00
|
|
|
uint32 hashnr, const uchar *key, uint keylen,
|
|
|
|
LF_PINS *pins)
|
|
|
|
{
|
|
|
|
CURSOR cursor;
|
2015-11-19 15:52:14 +01:00
|
|
|
int res= l_find(head, cs, hashnr, key, keylen, &cursor, pins, 0);
|
2009-11-18 03:31:40 +01:00
|
|
|
if (res)
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_pin(pins, 2, cursor.curr);
|
2012-09-04 19:11:06 +02:00
|
|
|
else
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_unpin(pins, 2);
|
|
|
|
lf_unpin(pins, 1);
|
|
|
|
lf_unpin(pins, 0);
|
2009-11-18 03:31:40 +01:00
|
|
|
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.
|
|
|
|
*/
|
2016-06-24 01:25:14 +02:00
|
|
|
static inline my_hash_value_type calc_hash(CHARSET_INFO *cs,
|
2015-02-26 21:01:38 +01:00
|
|
|
const uchar *key,
|
|
|
|
size_t keylen)
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
|
|
|
ulong nr1= 1, nr2= 4;
|
2020-01-26 17:27:13 +01:00
|
|
|
my_ci_hash_sort(cs, (uchar*) key, keylen, &nr1, &nr2);
|
2015-02-26 21:01:38 +01:00
|
|
|
return nr1;
|
2009-11-18 03:31:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#define MAX_LOAD 1.0 /* average number of elements in a bucket */
|
|
|
|
|
2021-02-15 02:31:31 +01:00
|
|
|
static int initialize_bucket(LF_HASH *, LF_SLIST **, uint, LF_PINS *);
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2015-02-26 18:17:33 +01:00
|
|
|
static void default_initializer(LF_HASH *hash, void *dst, const void *src)
|
|
|
|
{
|
|
|
|
memcpy(dst, src, hash->element_size);
|
|
|
|
}
|
|
|
|
|
2021-02-15 02:31:31 +01:00
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
|
|
|
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
|
2017-02-26 16:49:47 +01:00
|
|
|
lf_hash_insert will copy over. It is desirable if part of the element
|
2009-11-18 03:31:40 +01:00
|
|
|
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.
|
2015-02-26 18:17:33 +01:00
|
|
|
|
|
|
|
The above works well with PODS. For more complex cases (e.g. C++ classes
|
|
|
|
with private members) use initializer function.
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
|
|
|
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;
|
2015-02-26 18:17:33 +01:00
|
|
|
hash->initializer= default_initializer;
|
2015-02-26 21:01:38 +01:00
|
|
|
hash->hash_function= calc_hash;
|
2009-11-18 03:31:40 +01:00
|
|
|
DBUG_ASSERT(get_key ? !key_offset && !key_length : key_length);
|
|
|
|
}
|
|
|
|
|
|
|
|
void lf_hash_destroy(LF_HASH *hash)
|
|
|
|
{
|
2015-01-12 17:03:45 +01:00
|
|
|
LF_SLIST *el, **head= (LF_SLIST **)lf_dynarray_value(&hash->array, 0);
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2011-12-02 15:35:05 +01:00
|
|
|
if (head)
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
2011-12-02 15:35:05 +01:00
|
|
|
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;
|
|
|
|
}
|
2009-11-18 03:31:40 +01:00
|
|
|
}
|
|
|
|
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
|
2015-11-06 17:24:23 +01:00
|
|
|
see l_insert() for pin usage notes
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
|
|
|
int lf_hash_insert(LF_HASH *hash, LF_PINS *pins, const void *data)
|
|
|
|
{
|
|
|
|
int csize, bucket, hashnr;
|
2021-02-15 02:31:31 +01:00
|
|
|
LF_SLIST *node, **el;
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2015-01-12 17:03:45 +01:00
|
|
|
node= (LF_SLIST *)lf_alloc_new(pins);
|
2009-11-18 03:31:40 +01:00
|
|
|
if (unlikely(!node))
|
|
|
|
return -1;
|
2015-02-26 18:17:33 +01:00
|
|
|
hash->initializer(hash, node + 1, data);
|
2009-11-18 03:31:40 +01:00
|
|
|
node->key= hash_key(hash, (uchar *)(node+1), &node->keylen);
|
2015-02-26 21:01:38 +01:00
|
|
|
hashnr= hash->hash_function(hash->charset, node->key, node->keylen) & INT_MAX32;
|
2009-11-18 03:31:40 +01:00
|
|
|
bucket= hashnr % hash->size;
|
2021-02-15 02:31:31 +01:00
|
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
2009-11-18 03:31:40 +01:00
|
|
|
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 */
|
2015-11-06 17:24:23 +01:00
|
|
|
if (l_insert(el, hash->charset, node, pins, hash->flags))
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_alloc_free(pins, node);
|
2009-11-18 03:31:40 +01:00
|
|
|
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
|
2015-11-06 17:24:23 +01:00
|
|
|
see l_delete() for pin usage notes
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
|
|
|
int lf_hash_delete(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen)
|
|
|
|
{
|
2021-02-15 02:31:31 +01:00
|
|
|
LF_SLIST **el;
|
2015-02-26 21:01:38 +01:00
|
|
|
uint bucket, hashnr;
|
|
|
|
|
|
|
|
hashnr= hash->hash_function(hash->charset, (uchar *)key, keylen) & INT_MAX32;
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2017-02-26 16:49:47 +01:00
|
|
|
/* hide OOM errors - if we cannot initialize a bucket, try the previous one */
|
2015-01-07 11:36:46 +01:00
|
|
|
for (bucket= hashnr % hash->size; ;bucket= my_clear_highest_bit(bucket))
|
|
|
|
{
|
2021-02-15 02:31:31 +01:00
|
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
2015-01-07 11:36:46 +01:00
|
|
|
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 */
|
|
|
|
}
|
2015-11-06 17:24:23 +01:00
|
|
|
if (l_delete(el, hash->charset, my_reverse_bits(hashnr) | 1,
|
2009-11-18 03:31:40 +01:00
|
|
|
(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
|
2015-11-06 17:24:23 +01:00
|
|
|
see l_search() for pin usage notes
|
2009-11-18 03:31:40 +01:00
|
|
|
*/
|
2014-12-28 16:42:17 +01:00
|
|
|
void *lf_hash_search_using_hash_value(LF_HASH *hash, LF_PINS *pins,
|
|
|
|
my_hash_value_type hashnr,
|
|
|
|
const void *key, uint keylen)
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
2021-02-15 02:31:31 +01:00
|
|
|
LF_SLIST **el, *found;
|
2015-01-07 11:36:46 +01:00
|
|
|
uint bucket;
|
2009-11-18 03:31:40 +01:00
|
|
|
|
2017-02-26 16:49:47 +01:00
|
|
|
/* hide OOM errors - if we cannot initialize a bucket, try the previous one */
|
2015-01-07 11:36:46 +01:00
|
|
|
for (bucket= hashnr % hash->size; ;bucket= my_clear_highest_bit(bucket))
|
|
|
|
{
|
2021-02-15 02:31:31 +01:00
|
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
2015-01-07 11:36:46 +01:00
|
|
|
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 */
|
|
|
|
}
|
2015-11-06 17:24:23 +01:00
|
|
|
found= l_search(el, hash->charset, my_reverse_bits(hashnr) | 1,
|
2009-11-18 03:31:40 +01:00
|
|
|
(uchar *)key, keylen, pins);
|
|
|
|
return found ? found+1 : 0;
|
|
|
|
}
|
|
|
|
|
2014-11-27 23:49:45 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
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;
|
2021-02-15 02:31:31 +01:00
|
|
|
LF_SLIST **el;
|
2014-11-27 23:49:45 +01:00
|
|
|
|
2021-02-15 02:31:31 +01:00
|
|
|
el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, bucket);
|
2014-11-27 23:49:45 +01:00
|
|
|
if (unlikely(!el))
|
2015-01-07 11:36:46 +01:00
|
|
|
return 0; /* if there's no bucket==0, the hash is empty */
|
2014-11-27 23:49:45 +01:00
|
|
|
if (*el == NULL && unlikely(initialize_bucket(hash, el, bucket, pins)))
|
2015-01-07 11:36:46 +01:00
|
|
|
return 0; /* if there's no bucket==0, the hash is empty */
|
2014-11-27 23:49:45 +01:00
|
|
|
|
2015-11-19 15:52:14 +01:00
|
|
|
res= l_find(el, 0, 0, (uchar*)argument, 0, &cursor, pins, action);
|
2014-11-27 23:49:45 +01:00
|
|
|
|
2015-01-12 17:03:45 +01:00
|
|
|
lf_unpin(pins, 2);
|
|
|
|
lf_unpin(pins, 1);
|
|
|
|
lf_unpin(pins, 0);
|
2014-11-27 23:49:45 +01:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2014-12-28 16:42:17 +01:00
|
|
|
void *lf_hash_search(LF_HASH *hash, LF_PINS *pins, const void *key, uint keylen)
|
|
|
|
{
|
|
|
|
return lf_hash_search_using_hash_value(hash, pins,
|
2015-02-26 21:01:38 +01:00
|
|
|
hash->hash_function(hash->charset,
|
|
|
|
(uchar*) key,
|
|
|
|
keylen) & INT_MAX32,
|
2014-12-28 16:42:17 +01:00
|
|
|
key, keylen);
|
|
|
|
}
|
|
|
|
|
2009-11-18 03:31:40 +01:00
|
|
|
static const uchar *dummy_key= (uchar*)"";
|
|
|
|
|
|
|
|
/*
|
|
|
|
RETURN
|
|
|
|
0 - ok
|
|
|
|
-1 - out of memory
|
|
|
|
*/
|
2021-02-15 02:31:31 +01:00
|
|
|
static int initialize_bucket(LF_HASH *hash, LF_SLIST **node,
|
2009-11-18 03:31:40 +01:00
|
|
|
uint bucket, LF_PINS *pins)
|
|
|
|
{
|
|
|
|
uint parent= my_clear_highest_bit(bucket);
|
2020-01-29 13:50:26 +01:00
|
|
|
LF_SLIST *dummy= (LF_SLIST *)my_malloc(key_memory_lf_slist,
|
|
|
|
sizeof(LF_SLIST), MYF(MY_WME));
|
2009-11-18 03:31:40 +01:00
|
|
|
LF_SLIST **tmp= 0, *cur;
|
2021-02-15 02:31:31 +01:00
|
|
|
LF_SLIST **el= (LF_SLIST **)lf_dynarray_lvalue(&hash->array, parent);
|
2009-11-18 03:31:40 +01:00
|
|
|
if (unlikely(!el || !dummy))
|
|
|
|
return -1;
|
|
|
|
if (*el == NULL && bucket &&
|
|
|
|
unlikely(initialize_bucket(hash, el, parent, pins)))
|
2017-05-17 15:16:24 +02:00
|
|
|
{
|
|
|
|
my_free(dummy);
|
2009-11-18 03:31:40 +01:00
|
|
|
return -1;
|
2017-05-17 15:16:24 +02:00
|
|
|
}
|
2009-11-18 03:31:40 +01:00
|
|
|
dummy->hashnr= my_reverse_bits(bucket) | 0; /* dummy node */
|
|
|
|
dummy->key= dummy_key;
|
|
|
|
dummy->keylen= 0;
|
2015-11-06 17:24:23 +01:00
|
|
|
if ((cur= l_insert(el, hash->charset, dummy, pins, LF_HASH_UNIQUE)))
|
2009-11-18 03:31:40 +01:00
|
|
|
{
|
Bug#34043: Server loops excessively in _checkchunk() when safemalloc is enabled
Essentially, the problem is that safemalloc is excruciatingly
slow as it checks all allocated blocks for overrun at each
memory management primitive, yielding a almost exponential
slowdown for the memory management functions (malloc, realloc,
free). The overrun check basically consists of verifying some
bytes of a block for certain magic keys, which catches some
simple forms of overrun. Another minor problem is violation
of aliasing rules and that its own internal list of blocks
is prone to corruption.
Another issue with safemalloc is rather the maintenance cost
as the tool has a significant impact on the server code.
Given the magnitude of memory debuggers available nowadays,
especially those that are provided with the platform malloc
implementation, maintenance of a in-house and largely obsolete
memory debugger becomes a burden that is not worth the effort
due to its slowness and lack of support for detecting more
common forms of heap corruption.
Since there are third-party tools that can provide the same
functionality at a lower or comparable performance cost, the
solution is to simply remove safemalloc. Third-party tools
can provide the same functionality at a lower or comparable
performance cost.
The removal of safemalloc also allows a simplification of the
malloc wrappers, removing quite a bit of kludge: redefinition
of my_malloc, my_free and the removal of the unused second
argument of my_free. Since free() always check whether the
supplied pointer is null, redudant checks are also removed.
Also, this patch adds unit testing for my_malloc and moves
my_realloc implementation into the same file as the other
memory allocation primitives.
client/mysqldump.c:
Pass my_free directly as its signature is compatible with the
callback type -- which wasn't the case for free_table_ent.
2010-07-08 23:20:08 +02:00
|
|
|
my_free(dummy);
|
2009-11-18 03:31:40 +01:00
|
|
|
dummy= cur;
|
|
|
|
}
|
2009-11-30 00:08:56 +01:00
|
|
|
my_atomic_casptr((void **)node, (void **)(char*) &tmp, dummy);
|
2009-11-18 03:31:40 +01:00
|
|
|
/*
|
2015-11-06 17:24:23 +01:00
|
|
|
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
|
2009-11-18 03:31:40 +01:00
|
|
|
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;
|
|
|
|
}
|
2021-02-15 02:31:31 +01:00
|
|
|
|
|
|
|
C_MODE_END
|