GreatSQL的sp中新增新的sp_instr引入的bug解析

GreatSQL發表於2024-05-08

GreatSQL的sp中新增新的sp_instr引入的bug解析

一、問題發現

在一次開發中用到的sp需要新增新的sp_instr以滿足需求,但是新增了數個sp_instr以後發現執行新的sp會發生core。

注:本次使用的GreatSQL 8.0.32-25

1、sp_head.cc的init_sp_psi_keys()程式碼裡面新增10個新的sp_instr:

void init_sp_psi_keys() {
  mysql_statement_register(category, &sp_instr_stmt1::psi_info, 1);
  mysql_statement_register(category, &sp_instr_stmt2::psi_info, 1);
  mysql_statement_register(category, &sp_instr_stmt3::psi_info, 1);
  ......
  mysql_statement_register(category, &sp_instr_stmt10::psi_info, 1);
}

2、sp_instr.cc裡面新增新的sp_instr_stmt相關實現程式碼,其中sql_yacc.yy和sql_lex.cc需要相應新增新的語法。

3、sp_rcontext.h處在·class sp_rcontext裡面新增幾個新的成員變數。下面程式碼只是示例,不具有實際使用價值。

  Field *m_return_value_fld_tmp{m_return_value_fld};
  Field *m_return_value_fld_tmp1{m_return_value_fld};
  Field *m_return_value_fld_tmp2{m_return_value_fld};

4、建立新的sp,裡面包含新的sp_instr_stmt的內容,然後call該sp,結果發現程式碼邏輯處因為一個list裡面member的值被清空了,然後導致crash。下面是相關的堆疊。因為涉及程式碼機密,只截圖開源部分相關堆疊。

#0  0x0000555558f3f3d9 in base_list_iterator::next_fast (this=0x7fffe01e9de0)
    at /sql/sql_list.h:371
#1  0x0000555558fc59b7 in List_iterator_fast<Create_field>::operator++ (this=0x7fffe01e9de0)
    at /sql/sql_list.h:605
#2  0x0000555559753ea2 in create_tmp_table_from_fields (thd=0x7fff20001050, field_list=..., 
    is_virtual=false, select_options=0, alias=0x0)
    at /sql/sql_tmp_table.cc:2131
#3  0x0000555559084a09 in Item_xx::val_str (this=0x7fff20b673c8)
    at /sql/item_func.cc:10796
#4  0x0000555558fa408b in Item::save_in_field_inner (this=0x7fff20b673c8, field=0x7fff20b9b1a8, 
    no_conversions=false) at /sql/item.cc:8202
#5  0x0000555558fa3c43 in Item::save_in_field (this=0x7fff20b673c8, field=0x7fff20b9b1a8, 
    no_conversions=false) at /sql/item.cc:8144
#6  0x0000555559400322 in sp_eval_expr (thd=0x7fff20001050, result_field=0x7fff20b9b1a8, 
    expr_item_ptr=0x7fff20b67620) at /sql/sp.cc:3613
#7  0x000055555943b1d1 in sp_rcontext::set_variable (this=0x7fff20b85d80, thd=0x7fff20001050, 
    field=0x7fff20b9b1a8, value=0x7fff20b67620)
    at /sql/sp_rcontext.cc:1023
#8  0x0000555558fc3a8e in sp_rcontext::set_variable (this=0x7fff20b85d80, thd=0x7fff20001050, 
    var_idx=1, value=0x7fff20b67620)
    at /sql/sp_rcontext.h:176
列印crash處的資訊,發現list裡面的值被清空了。
(gdb) p tmp
$1 = (list_node *) 0x0

二、問題調查過程

1、仔細檢查程式碼發現程式碼邏輯沒有問題,list的值確實都有成功賦值,但是執行時候卻發現list被清空,顯然這是別的地方記憶體洩漏或者記憶體溢位導致list的元素空間被佔用了或者被清空了。把sp的程式碼換成別的,有時候會crash有時候不會crash,觸發機制也不明朗,不知道具體哪句程式碼導致的記憶體洩漏。

2、於是回頭繼續看剛開始新增程式碼的地方,猜想是不是跟我新增了10個sp_instr_stmt有關,因為相關的陣列或者記憶體沒有新增擴容,很有可能因為這個導致記憶體溢位。

3、定位出疑似問題地方,就可以著手開始調查相關程式碼了。檢視相關新增sp_instr的程式碼。

新增sp_instr實現程式碼如下:
mysql_statement_register(category, &sp_instr_stmt1::psi_info, 1);

於是繼續往下面調查mysql_statement_register實現的程式碼,看到這裡果然用到了statement_class_max:
PFS_statement_key register_statement_class(const char *name, uint name_length,
                                           PSI_statement_info *info) {
  /* See comments in register_mutex_class */
  uint32 index;
  PFS_statement_class *entry;

  REGISTER_CLASS_BODY_PART(index, statement_class_array, statement_class_max,
                           name, name_length)

接著檢視statement_class_max的賦值的地方:
int init_statement_class(uint statement_class_sizing) {
  int result = 0;
  statement_class_dirty_count = statement_class_allocated_count = 0;
  statement_class_max = statement_class_sizing;

透過搜尋程式碼查到statement_class_sizing相關的引數配置的地方,看到這裡有一個SP_PSI_STATEMENT_INFO_COUNT宏定義,這個值跟sp_instr的數量有關。
static Sys_var_ulong Sys_pfs_max_statement_classes(
    "performance_schema_max_statement_classes",
    "Maximum number of statement instruments.",
    READ_ONLY GLOBAL_VAR(pfs_param.m_statement_class_sizing),
    CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256),
    DEFAULT((ulong)SQLCOM_END + (ulong)COM_END + 5 +
            SP_PSI_STATEMENT_INFO_COUNT + CLONE_PSI_STATEMENT_COUNT),
    BLOCK_SIZE(1), PFS_TRAILING_PROPERTIES);

繼續全文搜尋,發現在sp_head.h定義了,這裡的值為16,數了一下現存的sp_instr個數剛好為16個,至此問題原因發現,因為我加了10個sp_instr,而這個宏定義的值沒有跟著增加,導致記憶體溢位。
#define SP_PSI_STATEMENT_INFO_COUNT 16

三、問題解決方案

透過以上程式碼解析後,就可以修改相關問題程式碼,只要作如下修改即可。重新編譯完,問題解決。

sp_head.h修改SP_PSI_STATEMENT_INFO_COUNT宏定義:
#define SP_PSI_STATEMENT_INFO_COUNT 26

因為增加了Sys_pfs_max_statement_classes的default值,因為相關配置範圍也要跟著增加,因此把range相應加大。
static Sys_var_ulong Sys_pfs_max_statement_classes(
    "performance_schema_max_statement_classes",
    "Maximum number of statement instruments.",
    READ_ONLY GLOBAL_VAR(pfs_param.m_statement_class_sizing),
    CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 256 * 2),
    DEFAULT((ulong)SQLCOM_END + (ulong)COM_END + 5 +
            SP_PSI_STATEMENT_INFO_COUNT + CLONE_PSI_STATEMENT_COUNT),
    BLOCK_SIZE(1), PFS_TRAILING_PROPERTIES);

四、問題總結

在GreatSQL的sp新增新的sp_instr需要相應增加對應的引數值以防止記憶體溢位,如果其他的功能也要做類似的修改,也要先仔細調查一下有沒有涉及相關的引數配置或者宏定義,不然就會遇到各種莫名其妙的問題,調查起來也很花時間。

這次發現的問題屬於新新增功能帶入的bug,在實際開發應用中類似的問題也要注意,一不小心就會踩坑。

上述問題在MySQL/Percona中同樣存在。


Enjoy GreatSQL 😃

關於 GreatSQL

GreatSQL是適用於金融級應用的國內自主開源資料庫,具備高效能、高可靠、高易用性、高安全等多個核心特性,可以作為MySQL或Percona Server的可選替換,用於線上生產環境,且完全免費併相容MySQL或Percona Server。

相關連結: GreatSQL社群 Gitee GitHub Bilibili

GreatSQL社群:

社群部落格有獎徵稿詳情:https://greatsql.cn/thread-100-1-1.html

image-20230105161905827

技術交流群:

微信:掃碼新增GreatSQL社群助手微信好友,傳送驗證資訊加群

image-20221030163217640

相關文章