全部博文(12)
分类: Mysql/postgreSQL
2013-01-18 00:19:40
现象:
备库(版本>=5.5.25)出现内存泄露,整个讨论过程:percona
分析:
这个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,只能说:能抓到太难了!