Bug #112304 | MEM_ROOT::AllocBlock() does not satisfy minimum_length bigger than wanted_length | ||
---|---|---|---|
Submitted: | 9 Sep 2023 18:07 | Modified: | 3 Jan 2024 15:22 |
Reporter: | Kaiwang CHen (OCA) | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: Optimizer | Severity: | S3 (Non-critical) |
Version: | 8.0 | OS: | Any |
Assigned to: | CPU Architecture: | Any | |
Tags: | Contribution |
[9 Sep 2023 18:07]
Kaiwang CHen
[9 Sep 2023 18:10]
Kaiwang CHen
Satisfy minimum_length, assert overflows. (*) I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it.
Contribution: my_alloc.patch (application/octet-stream, text), 3.32 KiB.
[11 Sep 2023 3:41]
Kaiwang CHen
hcduan reported that gis.gis_bugs_crashes crashed if the fix in my_alloc.patch was replaced with assert(length >= minimum_length). + // Any successful allocation should have enough memory to satisfy + // minimum_length. + // if (unlikely(length < minimum_length)) { + // length = ALIGN_SIZE(minimum_length); + // } + assert(length >= minimum_length); This is exactly the statement "The problem did not hurt much since its introduction in WL #13459: Optimize hash table in hash join [patch 3/9 and 4/9], because the work log implementation depended on Peek() rather than return value of ForceNewBlock() thus sidestep the problem by chance." By ForceNewBlock()-Peek()-and-if-peeked-size-satisfied-request, overwriting the memory chunk is avoided, however, the chunk is useless and wasted. The allocation will be attempted in m_overflow_mem_root, which is apparently abused. It could be verified with gdb when both the assert and the fix are removed: (gdb) p m_mem_root.m_current_free_end - m_mem_root.m_current_free_start $3 = 24576 (gdb) p required_value_bytes $4 = 60719 ... (gdb) n 78 if (static_cast<size_t>(block.second - block.first) >= required_value_bytes) { (gdb) 82 pointer_cast<char *>(m_overflow_mem_root.Alloc(required_value_bytes)); (gdb) 69 std::pair<char *, char *> block = m_mem_root.Peek(); 70 if (static_cast<size_t>(block.second - block.first) < required_value_bytes) { 71 // No room in this block; ask for a new one and try again. 72 m_mem_root.ForceNewBlock(required_value_bytes); 73 block = m_mem_root.Peek(); 74 } 77 LinkedImmutableString ret{nullptr}; 78 if (static_cast<size_t>(block.second - block.first) >= required_value_bytes) { 79 dptr = start_of_value = block.first; 80 } else { 81 dptr = start_of_value = 82 pointer_cast<char *>(m_overflow_mem_root.Alloc(required_value_bytes)); 83 if (dptr == nullptr) { 84 return LinkedImmutableString{nullptr}; 85 } 86 committed = true;
[11 Sep 2023 4:56]
MySQL Verification Team
Hello Kaiwang, Thank you for the report and contribution. regards, Umesh
[3 Jan 2024 15:22]
Jon Stephens
Documented fix as follows in the MySQL 8.3.0 changelog: MEM_ROOT::AllocBlock() did not satisfy the condition minimum_length > wanted_length, due to a mismatch between block size and mininum_length. A successful return (false) from ForceNewBlock() is expected to have a new block supporting minimum_length, but this assumption failed when mininum_length was larger; thus AllocBlock() did not conform to its contract. This fix is based on a contribution from Kaiwang Chen. Closed.