MDEV-40378 heap: roll back key changes when a blob write fails in heap_update() - #5407
Open
arcivanov wants to merge 1 commit into
Open
MDEV-40378 heap: roll back key changes when a blob write fails in heap_update()#5407arcivanov wants to merge 1 commit into
heap_update()#5407arcivanov wants to merge 1 commit into
Conversation
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
gkodinov
approved these changes
Jul 20, 2026
gkodinov
left a comment
Member
There was a problem hiding this comment.
Thank you for your contribution. This is a preliminary review.
From the remarks in the Jira I'm assuming these are problems found with the main contribution. I still think it's best if this goes to main. But, after discussing with my colleagues, I'm approving it as is and leaving this to the final reviewer to reply.
Contributor
Author
|
No, this code is not on main, it's in preview 13.1. I literally can't PR this into main. |
…ap_update()` `heap_update()` moves all changed key entries to the new key values **before** writing the new blob chains. When a blob chain write then failed (e.g. with `HA_ERR_RECORD_FILE_FULL`), the rollback restored the record bytes and blob chain pointers, but the `err:` label only undid key changes for `HA_ERR_FOUND_DUPP_KEY` -- historically the only possible failure once the key loop had run. The hash/btree entries were left keyed on the new values while pointing at a record holding the old values, corrupting the index: 1. index lookups by the old key value missed the row 2. `CHECK TABLE` reported the table corrupt 3. on debug builds the heap consistency check in `ha_heap::external_lock()` raised a second error into an already-set diagnostics area, firing a `Diagnostics_area` assertion on the next statement Fix: widen the `err:` recovery to run for `HA_ERR_RECORD_FILE_FULL`, `HA_ERR_OUT_OF_MEM` and `ENOMEM` as well, so a failure raised after the key loop also moves every changed key back to its old value. One recovery path now serves every failure that leaves keys moved to their new values, including any future error source in the key loop itself. The `err:` block assumed the failure happened **inside** the key loop, so that `keydef` addresses the partially processed keydef. A blob-chain write fails after that loop has run to completion, and therefore arrives with `keydef == keydef_end`. Reading `info->errkey` and `keydef->algorithm` from there addresses `share->keydef[share->keys]`; as `sizeof(HP_KEYDEF)` (888) far exceeds the key segments and blob descriptors that follow the keydef array, that read runs past the end of the `HP_SHARE` allocation, and the rollback sweep then dereferences a garbage `keydef->seg` in `hp_rec_key_cmp()`. So the recovery distinguishes the two failure sites: with `keydef == keydef_end` there is no partly updated key to repair and none to name in `info->errkey`, and the sweep starts at the last keydef instead. The same branch also covers a table with no keys at all (`share->keys == 0`), where the sweep has nothing to do. `info->errkey` is initialized to `-1` on entry to `err:`, so a failure that is not a key error can never expose a stale key number from an earlier operation. The original errno is captured before the recovery and restored after it, so that a rollback `write_key` failure (which `hp_rb_write_key()` reports as `HA_ERR_FOUND_DUPP_KEY` with a stale `errkey`) cannot mask it. The new test `heap.blob_update_key_rollback` exercises hash, BTREE, two changed indexes, an index on an unchanged column (which the rollback must leave untouched), a partial multi-row UPDATE, and a table with no indexes at all; each asserts the table stays consistent after the failure via `CHECK TABLE` and index lookups.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
heap_update()moves all changed key entries to the new key values before writing the new blob chains. When a blob chain write then fails (e.g.HA_ERR_RECORD_FILE_FULLon a full HEAP table), the rollback restores the record bytes and blob chain pointers, but theerr:label only undoes key changes forHA_ERR_FOUND_DUPP_KEY— historically the only possible failure once the key loop had run.The hash/btree entries are left keyed on the new values while pointing at a record holding the old values, corrupting the index:
CHECK TABLEreports the table corrupt;ha_heap::external_lock()raises a second error into an already-set diagnostics area, firing aDiagnostics_areaassertion on the next statement.Fix
Widen the
err:recovery gate to also run forHA_ERR_RECORD_FILE_FULL,HA_ERR_OUT_OF_MEMandENOMEM, so a failure raised after the key loop also moves every changed key back to its old value. One recovery path now serves every failure that leaves keys moved to their new values, including any future error source in the key loop itself.The
err:block assumed the failure happened inside the key loop, so thatkeydefaddresses the partially processed keydef. A blob-chain write fails after that loop has run to completion, and therefore arrives withkeydef == keydef_end. Readinginfo->errkeyandkeydef->algorithmfrom there addressesshare->keydef[share->keys]; sincesizeof(HP_KEYDEF)(888) far exceeds the key segments and blob descriptors that follow the keydef array, that read runs past the end of theHP_SHAREallocation, and the rollback sweep then dereferences a garbagekeydef->seginhp_rec_key_cmp(). The recovery therefore distinguishes the two failure sites: withkeydef == keydef_endthere is no partly updated key to repair and none to name ininfo->errkey, and the sweep starts at the last keydef instead. The same branch also covers a table with no keys at all (share->keys == 0), where the sweep has nothing to do.info->errkeyis initialized to-1on entry toerr:, so a failure that is not a key error can never expose a stale key number from an earlier operation.The original errno is captured before the recovery and restored after it, so a rollback
write_keyfailure (whichhp_rb_write_key()reports asHA_ERR_FOUND_DUPP_KEYwith a staleerrkey) cannot mask it.Test
New
heap.blob_update_key_rollbackexercises:keydef == keydef_endbranch withshare->keys == 0).Each asserts table consistency after the failure via
CHECK TABLEand index lookups. Verified to crash the unfixed server (DA assertion / index-corrupt) and pass with the fix; fullheapsuite passes.