Commit cdb19df7 authored by Sergey Vojtovich's avatar Sergey Vojtovich

BUG#11763882 - 56652: VALGRIND WARNINGS FOR MEMORY LEAK IN

                      ALTER TABLE AND/OR PLUGIN/SEMISYNC

If a plugin was uninstalled, thread local values for plugin
variables of string type with PLUGIN_VAR_MEMALLOC flag were
not freed.

With this patch these variables are freed when thread is
done (like all other variables).

sql/sql_class.h:
  Added variable which stores memory hunks allocated
  for PLUGIN_VAR_MEMALLOC values.
sql/sql_plugin.cc:
  Normally all memory allocated for dynamic variables values must
  be freed by cleanup_variables().
  
  But if a plugin was uninstalled, descriptors of it's system
  variables are lost. Still some memory may be occupied for thread
  local values. It is ok for most kinds of variables, as they're
  stored on dynamic_variables_ptr and freed when thread is done.
  
  Values for PLUGIN_VAR_MEMALLOC variables are stored separately.
  These lost values are handled by plugin_var_memalloc_free().
parent 4c7ece26
...@@ -410,8 +410,9 @@ typedef struct system_variables ...@@ -410,8 +410,9 @@ typedef struct system_variables
*/ */
ulong dynamic_variables_version; ulong dynamic_variables_version;
char* dynamic_variables_ptr; char* dynamic_variables_ptr;
uint dynamic_variables_head; /* largest valid variable offset */ uint dynamic_variables_head; /* largest valid variable offset */
uint dynamic_variables_size; /* how many bytes are in use */ uint dynamic_variables_size; /* how many bytes are in use */
LIST *dynamic_variables_allocs; /* memory hunks for PLUGIN_VAR_MEMALLOC */
ulonglong max_heap_table_size; ulonglong max_heap_table_size;
ulonglong tmp_table_size; ulonglong tmp_table_size;
......
...@@ -255,6 +255,13 @@ static bool register_builtin(struct st_mysql_plugin *, struct st_plugin_int *, ...@@ -255,6 +255,13 @@ static bool register_builtin(struct st_mysql_plugin *, struct st_plugin_int *,
static void unlock_variables(THD *thd, struct system_variables *vars); static void unlock_variables(THD *thd, struct system_variables *vars);
static void cleanup_variables(THD *thd, struct system_variables *vars); static void cleanup_variables(THD *thd, struct system_variables *vars);
static void plugin_vars_free_values(sys_var *vars); static void plugin_vars_free_values(sys_var *vars);
static bool plugin_var_memalloc_session_update(THD *thd,
struct st_mysql_sys_var *var,
char **dest, const char *value);
static bool plugin_var_memalloc_global_update(THD *thd,
struct st_mysql_sys_var *var,
char **dest, const char *value);
static void plugin_var_memalloc_free(struct system_variables *vars);
static void restore_pluginvar_names(sys_var *first); static void restore_pluginvar_names(sys_var *first);
static void plugin_opt_set_limits(struct my_option *, static void plugin_opt_set_limits(struct my_option *,
const struct st_mysql_sys_var *); const struct st_mysql_sys_var *);
...@@ -2300,13 +2307,7 @@ static void update_func_longlong(THD *thd, struct st_mysql_sys_var *var, ...@@ -2300,13 +2307,7 @@ static void update_func_longlong(THD *thd, struct st_mysql_sys_var *var,
static void update_func_str(THD *thd, struct st_mysql_sys_var *var, static void update_func_str(THD *thd, struct st_mysql_sys_var *var,
void *tgt, const void *save) void *tgt, const void *save)
{ {
char *old= *(char **) tgt; *(char **) tgt= *(char **) save;
*(char **)tgt= *(char **) save;
if (var->flags & PLUGIN_VAR_MEMALLOC)
{
*(char **)tgt= my_strdup(*(char **) save, MYF(0));
my_free(old);
}
} }
...@@ -2565,11 +2566,13 @@ static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock) ...@@ -2565,11 +2566,13 @@ static uchar *intern_sys_var_ptr(THD* thd, int offset, bool global_lock)
if ((pi->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR && if ((pi->plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
pi->plugin_var->flags & PLUGIN_VAR_MEMALLOC) pi->plugin_var->flags & PLUGIN_VAR_MEMALLOC)
{ {
char **pp= (char**) (thd->variables.dynamic_variables_ptr + int varoff= *(int *) (pi->plugin_var + 1);
*(int*)(pi->plugin_var + 1)); char **thdvar= (char **) (thd->variables.
if ((*pp= *(char**) (global_system_variables.dynamic_variables_ptr + dynamic_variables_ptr + varoff);
*(int*)(pi->plugin_var + 1)))) char **sysvar= (char **) (global_system_variables.
*pp= my_strdup(*pp, MYF(MY_WME|MY_FAE)); dynamic_variables_ptr + varoff);
*thdvar= NULL;
plugin_var_memalloc_session_update(thd, NULL, thdvar, *sysvar);
} }
} }
...@@ -2675,33 +2678,8 @@ static void unlock_variables(THD *thd, struct system_variables *vars) ...@@ -2675,33 +2678,8 @@ static void unlock_variables(THD *thd, struct system_variables *vars)
*/ */
static void cleanup_variables(THD *thd, struct system_variables *vars) static void cleanup_variables(THD *thd, struct system_variables *vars)
{ {
st_bookmark *v; if (thd)
sys_var_pluginvar *pivar; plugin_var_memalloc_free(&thd->variables);
sys_var *var;
int flags;
uint idx;
mysql_rwlock_rdlock(&LOCK_system_variables_hash);
for (idx= 0; idx < bookmark_hash.records; idx++)
{
v= (st_bookmark*) my_hash_element(&bookmark_hash, idx);
if (v->version > vars->dynamic_variables_version ||
!(var= intern_find_sys_var(v->key + 1, v->name_len)) ||
!(pivar= var->cast_pluginvar()) ||
v->key[0] != (pivar->plugin_var->flags & PLUGIN_VAR_TYPEMASK))
continue;
flags= pivar->plugin_var->flags;
if ((flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
flags & PLUGIN_VAR_THDLOCAL && flags & PLUGIN_VAR_MEMALLOC)
{
char **ptr= (char**) pivar->real_value_ptr(thd, OPT_SESSION);
my_free(*ptr);
*ptr= NULL;
}
}
mysql_rwlock_unlock(&LOCK_system_variables_hash);
DBUG_ASSERT(vars->table_plugin == NULL); DBUG_ASSERT(vars->table_plugin == NULL);
...@@ -2796,6 +2774,136 @@ static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var) ...@@ -2796,6 +2774,136 @@ static SHOW_TYPE pluginvar_show_type(st_mysql_sys_var *plugin_var)
} }
/**
Set value for thread local variable with PLUGIN_VAR_MEMALLOC flag.
@param[in] thd Thread context.
@param[in] var Plugin variable.
@param[in,out] dest Destination memory pointer.
@param[in] value '\0'-terminated new value.
Most plugin variable values are stored on dynamic_variables_ptr.
Releasing memory occupied by these values is as simple as freeing
dynamic_variables_ptr.
An exception to the rule are PLUGIN_VAR_MEMALLOC variables, which
are stored on individual memory hunks. All of these hunks has to
be freed when it comes to cleanup.
It may happen that a plugin was uninstalled and descriptors of
it's variables are lost. In this case it is impossible to locate
corresponding values.
In addition to allocating and setting variable value, new element
is added to dynamic_variables_allocs list. When thread is done, it
has to call plugin_var_memalloc_free() to release memory used by
PLUGIN_VAR_MEMALLOC variables.
If var is NULL, variable update function is not called. This is
needed when we take snapshot of system variables during thread
initialization.
@note List element and variable value are stored on the same memory
hunk. List element is followed by variable value.
@return Completion status
@retval false Success
@retval true Failure
*/
static bool plugin_var_memalloc_session_update(THD *thd,
struct st_mysql_sys_var *var,
char **dest, const char *value)
{
LIST *old_element= NULL;
struct system_variables *vars= &thd->variables;
DBUG_ENTER("plugin_var_memalloc_session_update");
if (value)
{
size_t length= strlen(value) + 1;
LIST *element;
if (!(element= (LIST *) my_malloc(sizeof(LIST) + length, MYF(MY_WME))))
DBUG_RETURN(true);
memcpy(element + 1, value, length);
value= (const char *) (element + 1);
vars->dynamic_variables_allocs= list_add(vars->dynamic_variables_allocs,
element);
}
if (*dest)
old_element= (LIST *) (*dest - sizeof(LIST));
if (var)
var->update(thd, var, (void **) dest, (const void *) &value);
else
*dest= (char *) value;
if (old_element)
{
vars->dynamic_variables_allocs= list_delete(vars->dynamic_variables_allocs,
old_element);
my_free(old_element);
}
DBUG_RETURN(false);
}
/**
Free all elements allocated by plugin_var_memalloc_session_update().
@param[in] vars system variables structure
@see plugin_var_memalloc_session_update
*/
static void plugin_var_memalloc_free(struct system_variables *vars)
{
LIST *next, *root;
DBUG_ENTER("plugin_var_memalloc_free");
for (root= vars->dynamic_variables_allocs; root; root= next)
{
next= root->next;
my_free(root);
}
vars->dynamic_variables_allocs= NULL;
DBUG_VOID_RETURN;
}
/**
Set value for global variable with PLUGIN_VAR_MEMALLOC flag.
@param[in] thd Thread context.
@param[in] var Plugin variable.
@param[in,out] dest Destination memory pointer.
@param[in] value '\0'-terminated new value.
@return Completion status
@retval false Success
@retval true Failure
*/
static bool plugin_var_memalloc_global_update(THD *thd,
struct st_mysql_sys_var *var,
char **dest, const char *value)
{
char *old_value= *dest;
DBUG_ENTER("plugin_var_memalloc_global_update");
if (value && !(value= my_strdup(value, MYF(MY_WME))))
DBUG_RETURN(true);
var->update(thd, var, (void **) dest, (const void *) &value);
if (old_value)
my_free(old_value);
DBUG_RETURN(false);
}
bool sys_var_pluginvar::check_update_type(Item_result type) bool sys_var_pluginvar::check_update_type(Item_result type)
{ {
switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) { switch (plugin_var->flags & PLUGIN_VAR_TYPEMASK) {
...@@ -2880,6 +2988,7 @@ bool sys_var_pluginvar::do_check(THD *thd, set_var *var) ...@@ -2880,6 +2988,7 @@ bool sys_var_pluginvar::do_check(THD *thd, set_var *var)
bool sys_var_pluginvar::session_update(THD *thd, set_var *var) bool sys_var_pluginvar::session_update(THD *thd, set_var *var)
{ {
bool rc= false;
DBUG_ASSERT(!is_readonly()); DBUG_ASSERT(!is_readonly());
DBUG_ASSERT(plugin_var->flags & PLUGIN_VAR_THDLOCAL); DBUG_ASSERT(plugin_var->flags & PLUGIN_VAR_THDLOCAL);
DBUG_ASSERT(thd == current_thd); DBUG_ASSERT(thd == current_thd);
...@@ -2889,13 +2998,20 @@ bool sys_var_pluginvar::session_update(THD *thd, set_var *var) ...@@ -2889,13 +2998,20 @@ bool sys_var_pluginvar::session_update(THD *thd, set_var *var)
const void *src= var->value ? (void*)&var->save_result const void *src= var->value ? (void*)&var->save_result
: (void*)real_value_ptr(thd, OPT_GLOBAL); : (void*)real_value_ptr(thd, OPT_GLOBAL);
mysql_mutex_unlock(&LOCK_global_system_variables); mysql_mutex_unlock(&LOCK_global_system_variables);
plugin_var->update(thd, plugin_var, tgt, src);
return false; if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
plugin_var->flags & PLUGIN_VAR_MEMALLOC)
rc= plugin_var_memalloc_session_update(thd, plugin_var, (char **) tgt,
*(const char **) src);
else
plugin_var->update(thd, plugin_var, tgt, src);
return rc;
} }
bool sys_var_pluginvar::global_update(THD *thd, set_var *var) bool sys_var_pluginvar::global_update(THD *thd, set_var *var)
{ {
bool rc= false;
DBUG_ASSERT(!is_readonly()); DBUG_ASSERT(!is_readonly());
mysql_mutex_assert_owner(&LOCK_global_system_variables); mysql_mutex_assert_owner(&LOCK_global_system_variables);
...@@ -2952,9 +3068,14 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var) ...@@ -2952,9 +3068,14 @@ bool sys_var_pluginvar::global_update(THD *thd, set_var *var)
} }
} }
plugin_var->update(thd, plugin_var, tgt, src); if ((plugin_var->flags & PLUGIN_VAR_TYPEMASK) == PLUGIN_VAR_STR &&
plugin_var->flags & PLUGIN_VAR_MEMALLOC)
rc= plugin_var_memalloc_global_update(thd, plugin_var, (char **) tgt,
*(const char **) src);
else
plugin_var->update(thd, plugin_var, tgt, src);
return false; return rc;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment