I am writing a simple in-memory database. It should support transaction, meaning that BEGIN command creates a new block, the operations in which can be canceled by ROLLBACK command.
I am implementing the list of transaction blocks using a vector. In case of BEGIN a new block is created by push_back()ing an unordered_set of all keys in that transaction block. This is defined as vector<unordered_set<string>> key_in_blocks
What follows is an example of the commands. SET sets value to a variable, GET gets the value of the variable.
BEGIN
SET a 10
GET a <-- this prints 10
BEGIN
SET a 20
GET a <-- this prints 20
ROLLBACK
GET a <-- this prints 10
ROLLBACK <-- Segmentation Fault here!
So there is a default block at first, and keys_in_block would look like [()], here () is denoting set, [] denotes vector.
When BEGIN is issued, a new block is created, and SET adds a key a to the block, so keys_in_block = [(), ('a')].
The next BEGIN & SET part is similar, making keys_in_block look like [(), ('a'), ('a')].
Now ROLLBACK undoes operations done in the last block, and keys_in_block should be [(), ('a')], because it is pop_back()ed.
I think my program does what it needs to do up to this point.
On the second ROLLBACK, it throws segmentation fault, and it looks like I cannot even access the keys_in_block variable at all.
Below is my code snippet. It runs inside an infinite while loop, taking commands from user.
} else if (command == "BEGIN") {
int num_blocks = keys_in_block.size();
keys_in_block.resize(num_blocks + 1); //new transaction block
} else if (command == "ROLLBACK") {
if (keys_in_block.size() <= 1) {
cout << "NO TRANSACTION" << endl;
}
else {
auto &recent_keys = keys_in_block.back();
// undo operations for keys in the most recent transaction block
for (const string &key : recent_keys) {
//...
//... a bunch of operations that undoes what's done
//... in the most recent transaction block.
recent_keys.erase(key); // erase all keys in the last entry in `keys_in_block`.
}
auto & aa = keys_in_block; //***Just for testing, not relevant to the program.
//This throws Segmentation Fault on second ROLLBACK.
keys_in_block.pop_back(); //Pop back the last transaction block.
}
}
In the code snippet, I marked where the segmentation fault is thrown using //***.
I added that line to because keys_in_block.pop_back() threw segmentation fault, and wanted to see if it throws seg fault just by accessing it.
To me, the algorithm looked absolutely correct, and I couldn't find out what the cause of the problem was.
ROLLBACK doesn't execute when keys_in_block has one block or less, so pop_back() cannot be an issue.
If you need the code for the SET command part, let me know, but I don't think there is a problem in that code.