阅读更多
1 内联引发的crash
下面这段逻辑可以复现该问题,大致含义如下:
Partitioner
用于存储一些键值对,且允许key
为空。当key
为空时,存储到_null_key_value
中
Partitioner::accept
用于遍历所有已存储的数据,包括_hash_map
和_null_key_value
,且遍历过程满足如下要求
consumer
可通过返回值自行决定是否继续遍历
若consumer
中断遍历,则需要能够从上一次迭代的位置继续遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 #include <any> #include <functional> #include <iostream> #include <string> #include <unordered_map> #include <utility> #include <vector> template <class DeferFunction >class DeferOp {public : explicit DeferOp (DeferFunction func) : _func(std::move(func)) { } ~DeferOp () { _func(); }; private : DeferFunction _func; }; class Partitioner {public : void offer (const int32_t key, const std::string& value) { _hash_map[key].push_back (value); } void offer_for_null (const std::string& value) { _null_key_value.push_back (value); } void accept (const std::function<bool (int , const std::string)>& consumer) { _fetch_from_hash_map(consumer); _fetch_from_null_key_value(consumer); } private : bool _fetch_from_hash_map(const std::function<bool (int , const std::string)>& consumer) { if (_hash_map_eos) { return true ; } if (!_partition_it.has_value ()) { _partition_it = _hash_map.begin (); _partition_idx = 0 ; } using PartitionIterator = typename decltype (_hash_map)::iterator; PartitionIterator partition_it = std::any_cast <PartitionIterator>(_partition_it); PartitionIterator partition_end = _hash_map.end (); using ItemIterator = typename std::vector<std::string>::iterator; ItemIterator chunk_it; DeferOp defer ([&]() { if (partition_it == partition_end) { _hash_map_eos = true ; _partition_it.reset(); _chunk_it.reset(); } else { _partition_it = partition_it; _chunk_it = chunk_it; } }) ; while (partition_it != partition_end) { std::vector<std::string>& chunks = partition_it->second; if (!_chunk_it.has_value ()) { _chunk_it = chunks.begin (); } chunk_it = std::any_cast <ItemIterator>(_chunk_it); ItemIterator chunk_end = chunks.end (); while (chunk_it != chunk_end) { if (!consumer (_partition_idx, *chunk_it++)) { return false ; } } if (chunk_it == chunk_end) { ++partition_it; ++_partition_idx; _chunk_it.reset (); } } } bool _fetch_from_null_key_value(const std::function<bool (int , const std::string)>& consumer) { if (_null_key_eos) { return true ; } std::vector<std::string>& chunks = _null_key_value; if (!_chunk_it.has_value ()) { _chunk_it = chunks.begin (); } using ChunkIterator = typename std::vector<std::string>::iterator; ChunkIterator chunk_it = std::any_cast <ChunkIterator>(_chunk_it); ChunkIterator chunk_end = chunks.end (); DeferOp defer ([&]() { if (chunk_it == chunk_end) { _null_key_eos = true ; _chunk_it.reset(); } else { _chunk_it = chunk_it; } }) ; while (chunk_it != chunk_end) { if (!consumer (_partition_idx, *chunk_it++)) { return false ; } } } private : std::unordered_map<int32_t , std::vector<std::string>> _hash_map; std::vector<std::string> _null_key_value; std::any _partition_it; std::any _chunk_it; int32_t _partition_idx = -1 ; bool _hash_map_eos = false ; bool _null_key_eos = false ; }; int main () { Partitioner partitioner; for (int i = 0 ; i < 10 ; i++) { std::string content; content.append ("null-key: " ); content.append (std::to_string (i)); partitioner.offer_for_null (content); } for (int i = 0 ; i < 10 ; i++) { for (int j = 0 ; j < 10 ; j++) { std::string content; content.append ("key: " ); content.append (std::to_string (j)); partitioner.offer (i, content); } } partitioner.accept ([](const int32_t partition_id, const std::string& content) { std::cout << "partition_id=" << partition_id << ", content=" << content << std::endl; return true ; }); return 0 ; }
分别用-O0
和-O3
对上述代码进行编译
1 2 3 4 5 gcc -o main main.cpp -O0 -lstdc++ -std=gnu++17 -Wall ./main gcc -o main main.cpp -O3 -lstdc++ -std=gnu++17 -Wall ./main
错误原因:编译上述代码时,编译器已经提示了,就是_fetch_from_hash_map
和_fetch_from_null_key_value
这两个函数,缺少返回值,导致在内联的时候出现了逻辑性的问题
同样逻辑的代码在项目中并未提示缺少返回值(项目中用到了模板,逻辑更复杂,编译器并未分析出来)。core
堆栈也十分奇怪,要么是挂在std::function
上,要么挂在std::any::has_value
上,十分具有迷惑性
2 带有默认值的函数匹配的问题
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdint.h> #include <iostream> void func (const std::string& s = "" , bool b = false ) { std::cout << "func(const std::string& s = " ", bool b = false)" << std::endl; } void func (bool b = false ) { std::cout << "func(bool b = false)" << std::endl; } int main () { func ("hello" ); return 0 ; }
输出如下:
func("hello")
匹配的是单个参数的版本,其中"hello"
自动转型为bool
3 Memory Hook引发的死锁
Fix Backend get stuck on startup
部分堆栈如下:
第一次分配内存,触发hook
,在记录内存时,会初始化ExecEnv
的一个局部静态变量
初始化ExecEnv
的一个局部静态变量时,会调用__cxa_atexit
以及__new_exitfn
(代码可以参考glibc-2.35/stdlib/cxa_atexit.c
),在__new_exitfn
有分配内存的动作,导致又一次触发hook
,又进入了ExecEnv
局部静态变量初始化的过程,阻塞在锁的获取上
1 2 3 4 5 6 7 8 9 10 #1 0x00000000084a3594 in __cxxabiv1::__cxa_guard_acquire (g=g@entry=0x86b3a60 <guard variable for starrocks::ExecEnv::GetInstance()::s_exec_env>) at ../../../../libstdc++-v3/libsupc++/guard.cc:302 #2 0x00000000055dc11e in starrocks::ExecEnv::GetInstance () at ../src/runtime/exec_env.h:115 ... #8 my_calloc (n=1, size=<optimized out>) at ../src/service/mem_hook.cpp:364 #9 0x00007f53803e1ec4 in __new_exitfn () from /lib64/libc.so.6 #10 0x00007f53803e1f49 in __cxa_atexit () from /lib64/libc.so.6 #11 0x00000000055dc349 in starrocks::ExecEnv::GetInstance () at ../src/runtime/exec_env.h:115 #12 starrocks::ExecEnv::GetInstance () at ../src/runtime/exec_env.h:114 ... #18 my_malloc (size=size@entry=57400) at ../src/service/mem_hook.cpp:297
下面用一个例子来还原,由于触发__new_exitfn
分配内存的条件比较难模拟。我们用在ExecEnv
的构造方法中通过new
分配内存来代替
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> class MemTracker ;class ExecEnv {public : ExecEnv (); static ExecEnv* get_instance () { static ExecEnv exec_env; return &exec_env; } MemTracker* mem_tracker () { return _mem_tracker; } private : MemTracker* _mem_tracker; }; class MemTracker {public : int64_t value = 0 ; void update (size_t size) { value += size; } }; void * operator new (size_t size) { std::cout << "Allocating " << size << " bytes of memory." << std::endl; ExecEnv::get_instance ()->mem_tracker ()->update (size); return alloca (size); } ExecEnv::ExecEnv () : _mem_tracker(new MemTracker ()) {} int64_t * data;int main () { data = new int64_t [1 ]; return 0 ; }
[Enhancement] Optimize a subtle inline performance problem
5 FAQ
as ‘this’ argument discards qualifiers [-fpermissive]
: Maybe try to conver a const value to a non-const value