Repository navigation
fix(TipCache): Committed transactions becoming invisible after TransactionStatusBlock is re-mapped - #9233
fix(TipCache): Committed transactions becoming invisible after TransactionStatusBlock is re-mapped#9233TreeHunter9 wants to merge 6 commits into
TransactionStatusBlock is re-mapped#9233Conversation
- It is responsibility of the caller to memset new mapped region; - Bring this in line with Linux logic;
|
Try this branch first: work/gh-9017-trace |
No, file was truncated before that patch. Lines 2227-2237 in file before patch |
Yeah, I cannot reproduce the problem using my example on this branch, either on Linux or Windows. At first glance the patch looks correct, but I will take a closer look at it.
Hm, indeed, then I don't know why such difference between platforms was made. |
I have found several issues with your patch: if (dbb->dbb_next_transaction < next_transaction)
dbb->dbb_next_transaction = next_transaction;
if (dbb->dbb_oldest_transaction < oldest_transaction)
dbb->dbb_oldest_transaction = oldest_transaction;
if (!cache->m_exclusive)
{
PathName fullName = makeSharedMemoryFileName(dbb, blockNumber, true);
struct stat fileStat;
if (os_utils::stat(fullName.c_str(), &fileStat) == 0)
fileExists = (fileStat.st_size >= blockSize);
}In
|
|
But overall I like your version of patch more, my version is more like a brute-force approach of deleting TSBs, so I think we will stick with your version. But maybe some parts of my patch can be accepted, for example directory iterator reimplementation? |
Description of the problem
The TIP cache consists of fixed-size blocks called
TransactionStatusBlock (TSB)that contain a CN (Commit/Snapshot Number), where the transaction number serves as the index within that TSB. When a TSB fills up, the next TSB is created; that is, there can be several of them, and they are allocated as needed. A TSB is initialized upon the first connection to the database; it is then filled at runtime as transactions are executed. TSBs are also unloaded if no one is using a given block anymore. This is where the key problem lies: after a block is unloaded, it disappears, and if it is needed again, a new TSB will be allocated and initialized to zero, while the TIP will not be read from disk to load the current transaction statuses. In other words, when reading this TSB, we’ll get a CN equal to 0, where 0 represents the transaction statustra_active. This confusion leads to the following: a check begins to determine whether the transaction is actually active; the answer is negative; next, we read the TIP to obtain the current state, and it returnstra_committed. The two states receivedtra_active+tra_committedare interpreted as the transaction was committed after our transaction began, which means it should not be visible in our snapshot. As a result, these records become invisible to us, and we skip them, even though we should actually see them.Reproducable example
firebird.conf, setServerMode = ClassicandTipCacheBlockSize = 65536to fill the TSB more quickly (a single TSB can hold ~8k transactions);GlobalTpcHeader(which storeslatest_commit_number, used to generate the CN) remains active;The number 11240 was chosen to ensure we go exactly beyond the first TSB. Commit and disconnect;
create table tab1(a int);, commit, insert a recordinsert into tab1 values (1);, commit, and disconnect;select * from tab1, and see that nothing is returned;The main idea behind the fix
Basically, do not purge or memset to zero TSB when there is at least one connection to the database. Do it only when:
Description of changes that is not straight forward related to TipCache
statfor each file.SharedMemoryinitialization logic on Windows with Linux implementation. I discovered that on Windows there is initialization of zeros to newly mapped region, whereas on Linux the newly mapped region is left untouched, so this is creates some differences between platforms.I looked at Vlad's patch, which adds this initialization of zeros on Windows (by doing truncation of the file to 0 and then extend it back), and as I understand it, the main idea was not to perform memset, but to trigger an error to see whether any other instance of our server is holding the file (as I understand, because of IPC namespace/session differences, our
Eventwill not be visible to other instance). I have reimplement this check by doing exclusive access to file, so if there is any other instance that holds the file, we will get an error about it. I hope I'm not missing something about it, so I think @hvlad should look at this.And I think, the
SharedMemoryshould not memset mapped region to zero anyway, his responsibility is to simply perform ammap. The decision of whether to performmemsetor not should be left to the caller, not to the mapping mechanism.Affected versions
v6 and v5. I haven't tested this on other versions, but if the code is the same, then the issue applies to them as well.