I need to track down some undefined behavior. I'm sure I violated rules somewhere. Miri says I broke a rule, but I don't see how.
test tx::test::test_tx_create_bucket ... error: Undefined Behavior: trying to retag from <326266> for SharedReadWrite permission at alloc99841[0x0], but that tag does not exist in the borrow stack for this location
204 | self.split_r().db.page(id)
| trying to retag from <326266> for SharedReadWrite permission at alloc99841[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc99841[0x0..0x18]
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <326266> was created by a SharedReadWrite retag at offsets [0x0..0x18]
63 | BCell(a.alloc((RefCell::new(t), b)))
My memory and code model works like this:
- Transactions are strictly single threaded
- All transaction memory is stored in a Bumpalo bump that's owned by the tx object
- All nodes owned by the bump are available via a RefCell in wrapped in the BCell struct.
- Each nodes with logic have a 'Cell' struct (example) wrapping it that provides the logic so I can manage the RefCell borrows. The BBolt database calls functions on both parent and child nodes and this was my workaround. I can easily copy the Go code without falling afoul of the borrow tracker.
- The transaction is created using MaybeUninit since we need to pin the Bumpalo, database RwLock, and the transaction cell.
- I have separate structs for the Read and RW transactions (an experiment that seemed like a good idea at the time!). To keep the code duplication to a minimum and follow the Go code as closely as possible, I use the SplitRef trait to give the internal API trait code access to the read data and optionally the write data in the same function.
I'm currently shaking down the code and fixing bugs. In the last day my tests have started to fail in nonsense ways. Today I rewrote the database backend so it's able to just use a big array so I can determine the cause with Miri.
Back to my error:
The transaction is created by pinning the bump first, the database lock second, and then it creates the transaction cell that will be used by the rest of the transaction. There is a cycle between the transaction cell and the root bucket cell so I'm using Rc.
What I've done runs afoul of Miri's rules when assume_init() is called to finish creating the transaction, but I don't see how.
I do have an unsafe version I can use to create the cycle, but I haven't tested it outside of a simple unit test and I'm not sure if that would cause the same problem or not.
No talk of code smells or re-architecting the code, please. I'm about out of PTO and rewriting several thousand lines of code isn't something I have time for.
Please help me save my holidays project!