Chinaunix首页 | 论坛 | 博客
  • 博客访问: 110788
  • 博文数量: 12
  • 博客积分: 226
  • 博客等级: 二等列兵
  • 技术积分: 221
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-10 23:15
文章分类

全部博文(12)

分类: Mysql/postgreSQL

2013-01-18 00:19:40

现象:

备库(版本>=5.5.25)出现内存泄露,整个讨论过程:percona bug#1042946

 

分析:

这个bug是在percona 5.5.25中引入的,引入了Deferred_log_event这个类来处理Intval_log_event, Rand_log_event, User_var_log_event,之前这些是放在THD中处理的,5.5.25将其封装了一下,作用是积累后延迟批量处理,mysql_execute_command为此增加了处理逻辑:

/*
   Execute deferred events first
*/
if (slave_execute_deferred_events(thd))
  DBUG_RETURN(-1);

 

先批量执行这些之前收集的被延迟的events后再执行当前的event,在进一步深入之前,先把sql thread的工作流程整理一下(从xmind中导出):

  


整个handle_slave_sql线程函数就是一个循环:

while(not killed)
{
  exec_relay_log_event
} 

exec_relay_event的逻辑:不断地从relay log中读出一个event(next_event),然后执行并更新pos(apply_event_and_update_pos),执行event最终会调用Log_event的具体派生类的do_apply_event函数,对于ddl/dml需要走mysql_parse-->mysql_execute_command逻辑,而对于Intvar_log_event/User_var_log_event/Rand_log_event,其do_apply_event函数只是简单地将其加入到rli->deferred_log_events 数组中,这些event最终会在下一个走mysql_execute_command流程的event之前执行,正如文中最前面交代,mysql_parse在处理完event后将调用thd->cleanup_after_query()做一些清理工作,rli->deferred_log_events数组中的events将被清空:

#ifndef EMBEDDED_LIBRARY
  if (rli_slave)
    rli_slave->cleanup_after_query();
#endif

 

最终deferred_log_events->rewind被调用,被收集的events占用的内存在此处被free了:

void Deferred_log_events::rewind()
{
  /*
    Reset preceeding Query log event events which execution was
    deferred because of slave side filtering.
  */
  if (!is_empty())
  {
    for (uint i=  0; i < array.elements; i++)
    {
      Log_event *ev= *(Log_event **) dynamic_array_ptr(&array, i);
      delete ev;
    }
    if (array.elements > array.max_element)
      freeze_size(&array);
    reset_dynamic(&array);
  }
}

 

而对于ddl/dml类型的events,内存释放是在exec_relay_log_event函数中:

/*
  Format_description_log_event should not be deleted because it will be
  used to read info about the relay log's format; it will be deleted when
  the SQL thread does not need it, i.e. when this thread terminates.
*/
if (ev->get_type_code() != FORMAT_DESCRIPTION_EVENT &&
    !rli->is_deferred_event(ev))
{
  DBUG_PRINT("info", ("Deleting the event after it has been executed"));
  delete ev;
}


而内存泄露也就出现在这儿,is_deferred_event的判断逻辑:

/*
   Returns true if the argument event resides in the containter;
   more specifically, the checking is done against the last added event.
*/
bool is_deferred_event(Log_event * ev)
{
  return deferred_events_collecting ? deferred_events->is_last(ev) : false;
};

 

is_last函数比较简单,也贴一下:

bool is_last(Log_event *ev) { return ev == last_added; };

 

last_added是在add函数中维护的:

int Deferred_log_events::add(Log_event *ev)
{
  last_added= ev;
  insert_dynamic(&array, (uchar*) &ev);
  return 0;
}

 

如果最近的event被加到rli->deferred_log_events 数组中,那么last_added被置为event,is_deferred_event函数会返回false,不会被删除,而对于ddl/dml等没有进入rli->deferred_log_events 数组中的events会在此次被删除,一切都没有什么问题,怎么会出现内存泄露呢?

 

问题在rewind函数中!rli->deferred_log_events中的events被清空后last_added没有置为NULL,此时last_added指向一个被free的内存单元,如果下一个event被分配的地址正好是last_added开始处,那么is_deferred_event函数将错误地返回true,导致delete ev无法得到执行,从而出现了内存泄露。

 

到此,该bug分析完了,对于这样的bug,只能说:能抓到太难了!

阅读(1754) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~