memroot improvement: fix savepoint support

savepoint support was added a year ago as get_last_memroot_block()
and free_all_new_blocks(), but it didn't work and was disabled.

* fix it to work
* instead of freeing the memory, only mark blocks free - this feature
  is supposed to be used inside a loop (otherwise there is no need
  to free anything, end of statement will do it anyway). And freeing
  blocks inside a loop is a bad idea, as they'll be all malloc-ed
  on the next iteration again. So, don't.
* fix a bug in mark_blocks_free() - it doesn't change the number of blocks
This commit is contained in:
Sergei Golubchik 2024-09-26 11:52:05 +02:00
parent 4f4c5a2ba9
commit 9f2adffcca
4 changed files with 63 additions and 40 deletions

View file

@ -60,6 +60,14 @@ typedef struct st_mem_root
PSI_memory_key psi_key; PSI_memory_key psi_key;
} MEM_ROOT; } MEM_ROOT;
typedef struct st_mem_root_savepoint
{
MEM_ROOT *root;
USED_MEM *free;
USED_MEM *used;
unsigned short first_block_usage;
} MEM_ROOT_SAVEPOINT;
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -921,8 +921,8 @@ extern void move_root(MEM_ROOT *to, MEM_ROOT *from);
extern void set_prealloc_root(MEM_ROOT *root, char *ptr); extern void set_prealloc_root(MEM_ROOT *root, char *ptr);
extern void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size, extern void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size,
size_t prealloc_size); size_t prealloc_size);
extern USED_MEM *get_last_memroot_block(MEM_ROOT* root); extern void root_make_savepoint(MEM_ROOT *root, MEM_ROOT_SAVEPOINT *sv);
extern void free_all_new_blocks(MEM_ROOT *root, USED_MEM *last_block); extern void root_free_to_savepoint(const MEM_ROOT_SAVEPOINT *sv);
extern void protect_root(MEM_ROOT *root, int prot); extern void protect_root(MEM_ROOT *root, int prot);
extern char *strdup_root(MEM_ROOT *root,const char *str); extern char *strdup_root(MEM_ROOT *root,const char *str);
static inline char *safe_strdup_root(MEM_ROOT *root, const char *str) static inline char *safe_strdup_root(MEM_ROOT *root, const char *str)

View file

@ -266,8 +266,8 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length)
{ {
size_t get_size, block_size; size_t get_size, block_size;
uchar* point; uchar* point;
reg1 USED_MEM *next= 0; USED_MEM *next= 0;
reg2 USED_MEM **prev; USED_MEM **prev;
size_t original_length __attribute__((unused)) = length; size_t original_length __attribute__((unused)) = length;
DBUG_ENTER("alloc_root"); DBUG_ENTER("alloc_root");
DBUG_PRINT("enter",("root: %p", mem_root)); DBUG_PRINT("enter",("root: %p", mem_root));
@ -425,10 +425,10 @@ void *multi_alloc_root(MEM_ROOT *root, ...)
#if !(defined(HAVE_valgrind) && defined(EXTRA_DEBUG)) #if !(defined(HAVE_valgrind) && defined(EXTRA_DEBUG))
/** Mark all data in blocks free for reusage */ /** Mark all data in blocks free for reusage */
static inline void mark_blocks_free(MEM_ROOT* root) static void mark_blocks_free(MEM_ROOT* root)
{ {
reg1 USED_MEM *next; USED_MEM *next;
reg2 USED_MEM **last; USED_MEM **last;
/* iterate through (partially) free blocks, mark them free */ /* iterate through (partially) free blocks, mark them free */
last= &root->free; last= &root->free;
@ -451,7 +451,6 @@ static inline void mark_blocks_free(MEM_ROOT* root)
/* Now everything is set; Indicate that nothing is used anymore */ /* Now everything is set; Indicate that nothing is used anymore */
root->used= 0; root->used= 0;
root->first_block_usage= 0; root->first_block_usage= 0;
root->block_num= 4;
} }
#endif #endif
@ -477,7 +476,7 @@ static inline void mark_blocks_free(MEM_ROOT* root)
void free_root(MEM_ROOT *root, myf MyFlags) void free_root(MEM_ROOT *root, myf MyFlags)
{ {
reg1 USED_MEM *next,*old; USED_MEM *next,*old;
DBUG_ENTER("free_root"); DBUG_ENTER("free_root");
DBUG_PRINT("enter",("root: %p flags: %lu", root, MyFlags)); DBUG_PRINT("enter",("root: %p flags: %lu", root, MyFlags));
@ -567,45 +566,61 @@ void move_root(MEM_ROOT *to, MEM_ROOT *from)
from->used= 0; from->used= 0;
} }
/* /*
Remember last MEM_ROOT block. Prepare MEM_ROOT to a later truncation. Everything allocated after
that point can be freed while keeping earlier allocations intact.
This allows one to free all new allocated blocks. For this to work we cannot allow new allocations in partially filled blocks,
so remove all non-empty blocks from the memroot. For simplicity, let's
also remove all used blocks.
*/ */
void root_make_savepoint(MEM_ROOT *root, MEM_ROOT_SAVEPOINT *sv)
USED_MEM *get_last_memroot_block(MEM_ROOT* root)
{ {
return root->used ? root->used : root->pre_alloc; USED_MEM **prev= &root->free, *block= *prev;
for ( ; block; prev= &block->next, block= *prev)
if (block->left < block->size - ALIGN_SIZE(sizeof(USED_MEM)))
break;
sv->root= root;
sv->free= block;
sv->used= root->used;
sv->first_block_usage= root->first_block_usage;
*prev= 0;
root->used= 0;
} }
/* /*
Free all newly allocated blocks Restore MEM_ROOT to the state before the savepoint was made.
Restore old free and used lists.
Mark all new (after savepoint) used and partially used blocks free
and put them into the free list.
*/ */
void root_free_to_savepoint(const MEM_ROOT_SAVEPOINT *sv)
void free_all_new_blocks(MEM_ROOT *root, USED_MEM *last_block)
{ {
USED_MEM *old, *next; MEM_ROOT *root= sv->root;
if (!root->used) USED_MEM **prev= &root->free, *block= *prev;
return; /* Nothing allocated */
return;
/*
Free everying allocated up to, but not including, last_block.
However do not go past pre_alloc as we do not want to free
that one. This should not be a problem as in almost all normal
usage pre_alloc is last in the list.
*/
for (next= root->used ; /* iterate through (partially) free blocks, mark them free */
next && next != last_block && next != root->pre_alloc ; ) for ( ; block; prev= &block->next, block= *prev)
{ {
old= next; next= next->next; block->left= block->size - ALIGN_SIZE(sizeof(USED_MEM));
root_free(root, old, old->size); TRASH_MEM(block);
} }
root->used= next;
root->block_num= 4; /* Combine the free and the used list */
root->first_block_usage= 0; *prev= block=root->used;
/* now go through the used blocks and mark them free */
for ( ; block; prev= &block->next, block= *prev)
{
block->left= block->size - ALIGN_SIZE(sizeof(USED_MEM));
TRASH_MEM(block);
}
/* restore free and used lists from savepoint */
*prev= sv->free;
root->used= sv->used;
root->first_block_usage= prev == &root->free ? sv->first_block_usage : 0;
} }
/** /**
@ -615,7 +630,7 @@ void free_all_new_blocks(MEM_ROOT *root, USED_MEM *last_block)
#if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS) #if defined(HAVE_MMAP) && defined(HAVE_MPROTECT) && defined(MAP_ANONYMOUS)
void protect_root(MEM_ROOT *root, int prot) void protect_root(MEM_ROOT *root, int prot)
{ {
reg1 USED_MEM *next,*old; USED_MEM *next,*old;
DBUG_ENTER("protect_root"); DBUG_ENTER("protect_root");
DBUG_PRINT("enter",("root: %p prot: %d", root, prot)); DBUG_PRINT("enter",("root: %p prot: %d", root, prot));

View file

@ -982,7 +982,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
{ {
TABLE *tab= table->table; TABLE *tab= table->table;
Field **field_ptr= tab->field; Field **field_ptr= tab->field;
USED_MEM *memroot_block; MEM_ROOT_SAVEPOINT memroot_sv;
if (!lex->column_list) if (!lex->column_list)
{ {
@ -1084,7 +1084,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
} }
/* Ensure that number of records are updated */ /* Ensure that number of records are updated */
tab->file->info(HA_STATUS_VARIABLE); tab->file->info(HA_STATUS_VARIABLE);
memroot_block= get_last_memroot_block(thd->mem_root); root_make_savepoint(thd->mem_root, &memroot_sv);
if (!(compl_result_code= if (!(compl_result_code=
alloc_statistics_for_table(thd, tab, alloc_statistics_for_table(thd, tab,
&tab->has_value_set)) && &tab->has_value_set)) &&
@ -1092,7 +1092,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
collect_statistics_for_table(thd, tab))) collect_statistics_for_table(thd, tab)))
compl_result_code= update_statistics_for_table(thd, tab); compl_result_code= update_statistics_for_table(thd, tab);
free_statistics_for_table(tab); free_statistics_for_table(tab);
free_all_new_blocks(thd->mem_root, memroot_block); root_free_to_savepoint(&memroot_sv);
} }
else else
compl_result_code= HA_ADMIN_FAILED; compl_result_code= HA_ADMIN_FAILED;