/// <summary> /// Reads all of the bytes referenced by this location into the buffer supplied /// </summary> public void ReadInto( ITransaction transaction, CacheHints hints, byte[] record) { #if DEBUG if ((ulong)record.LongLength != Length) { throw new FileLayerException("The buffer passed is the wrong length"); } #endif if (ContunuationPages == null) { using (var page = PageStore.Get(transaction, PageNumber, hints)) Array.Copy(page.Data, Offset, record, 0, record.Length); } else { var recordOffset = 0UL; var bytesToCopy = PageStore.PageSize - Offset; using (var page = PageStore.Get(transaction, PageNumber, hints)) Array.Copy(page.Data, Offset, record, (long)recordOffset, bytesToCopy); foreach (var pageNumber in ContunuationPages) { recordOffset += bytesToCopy; bytesToCopy = PageStore.PageSize; if (recordOffset + bytesToCopy > Length) { bytesToCopy = (uint)(Length - recordOffset); } using (var page = PageStore.Get(transaction, pageNumber, hints)) Array.Copy(page.Data, 0, record, (long)recordOffset, bytesToCopy); } } }
PageLocation ISequentialRecordAccessor.Append(ulong firstPageNumber, ITransaction transaction, ulong recordSize) { if (recordSize == 0) { return(null); } // Find the last index page var indexPageNumber = firstPageNumber; while (true) { _pageStore.Lock(transaction, indexPageNumber); using (var indexPage = _pageStore.Get(transaction, indexPageNumber, CacheHints.MetaData | CacheHints.WithLock)) { var nextIndexPageNumber = BitConverter.ToUInt64(indexPage.Data, 0); if (nextIndexPageNumber == 0UL) { break; } indexPageNumber = nextIndexPageNumber; } } var indexEntryOffset = 8U; var lastRecordSize = 0UL; var lastRecordEndingPageNumber = 0UL; var lastRecordBytesOnLastPage = 0U; using (var lastIndexPage = _pageStore.Get(transaction, indexPageNumber, CacheHints.MetaData | CacheHints.WithLock)) { while (lastIndexPage.Data[indexEntryOffset] != 0) { var indexEntryType = lastIndexPage.Data[indexEntryOffset + 1]; if (indexEntryType == 1 || indexEntryType == 2) { // This is the start of a record lastRecordSize = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 2); ulong lastRecordStartingPageNumber = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 10); uint lastRecordOffset = BitConverter.ToUInt32(lastIndexPage.Data, (int)indexEntryOffset + 18); lastRecordBytesOnLastPage = (uint)((lastRecordSize + lastRecordOffset) % _pageSize); ulong lastRecordPageCount = (lastRecordSize + lastRecordOffset + _pageSize - 1) / _pageSize; lastRecordEndingPageNumber = lastRecordStartingPageNumber; } else if (indexEntryType == 3 && lastRecordSize != 0) { // This is a record continuation var pageCount = lastIndexPage.Data[indexEntryOffset + 2]; lastRecordEndingPageNumber = BitConverter.ToUInt64(lastIndexPage.Data, (int)indexEntryOffset + 3 + (pageCount - 1) * 8); } // Find the last record and last index entry on this page uint priorIndexEntryOffset = indexEntryOffset; indexEntryOffset += lastIndexPage.Data[indexEntryOffset]; } } var thisRecordStartingPageNumber = (lastRecordEndingPageNumber > 0 && lastRecordBytesOnLastPage < _pageSize) ? lastRecordEndingPageNumber : _pageStore.Allocate(); var thisRecordOffset = thisRecordStartingPageNumber == lastRecordEndingPageNumber ? lastRecordBytesOnLastPage : 0U; var updates = new List <PageUpdate>(); var sequence = 1U; var additionalPageNumbers = AddRecordIndex( updates, ref sequence, indexPageNumber, indexEntryOffset, recordSize, thisRecordStartingPageNumber, thisRecordOffset); _pageStore.Update(transaction, updates); return(new PageLocation { PageStore = _pageStore, PageNumber = thisRecordStartingPageNumber, Offset = thisRecordOffset, Length = recordSize, ContunuationPages = additionalPageNumbers }); }
PageLocation ISequentialRecordAccessor.Append( ulong firstPageNumber, ITransaction transaction, ulong recordSize) { if (recordSize == 0) { return(null); } var pageSize = _pageStore.PageSize; if (recordSize > pageSize) { throw new FileLayerException("Invalid attempt to write " + recordSize + " bytes into a page store with " + pageSize + " byte pages"); } var updates = new List <PageUpdate>(); var sequence = 1U; PageLocation indexLocation = new PageLocation { PageStore = _pageStore, PageNumber = firstPageNumber, Offset = _indexPageHeadSize, Length = _indexEntrySize }; PageLocation recordLocation = new PageLocation { PageStore = _pageStore, Length = recordSize }; while (true) { _pageStore.Lock(transaction, indexLocation.PageNumber); using (var indexPage = _pageStore.Get(transaction, indexLocation.PageNumber, CacheHints.MetaData | CacheHints.WithLock)) { var nextIndexPageNumber = BitConverter.ToUInt64(indexPage.Data, 0); if (nextIndexPageNumber == 0UL) { var nextIndexOffset = _indexPageHeadSize; recordLocation.PageNumber = 0UL; while (true) { var nextRecordPageNumber = BitConverter.ToUInt64(indexPage.Data, (int)nextIndexOffset); var nextRecordOffset = BitConverter.ToUInt32(indexPage.Data, (int)(nextIndexOffset + _pageNumberSize)); if (nextRecordPageNumber == 0UL) { indexLocation.Offset = nextIndexOffset; break; } recordLocation.PageNumber = nextRecordPageNumber; recordLocation.Offset = nextRecordOffset; nextIndexOffset += _indexEntrySize; if (nextIndexOffset + _indexEntrySize > pageSize) { var newIndexPageNumber = _pageStore.Allocate(); updates.Add(new PageUpdate { SequenceNumber = sequence++, PageNumber = indexLocation.PageNumber, Offset = 0U, Data = BitConverter.GetBytes(newIndexPageNumber) }); indexLocation.PageNumber = newIndexPageNumber; indexLocation.Offset = _indexPageHeadSize; break; } } break; } indexLocation.PageNumber = nextIndexPageNumber; } } if (recordLocation.PageNumber == 0UL || recordLocation.Offset + recordSize > pageSize) { recordLocation.PageNumber = _pageStore.Allocate(); recordLocation.Offset = 0U; } var newIndexEntry = new byte[_indexEntrySize]; BitConverter.GetBytes(recordLocation.PageNumber).CopyTo(newIndexEntry, 0); BitConverter.GetBytes(recordLocation.Offset + (uint)recordSize).CopyTo(newIndexEntry, _pageNumberSize); updates.Add(new PageUpdate { SequenceNumber = sequence++, PageNumber = indexLocation.PageNumber, Offset = indexLocation.Offset, Data = newIndexEntry }); _pageStore.Update(transaction, updates); return(recordLocation); }
public void should_allow_multiple_transactions_to_append_records() { const ushort objectType = 128; const int threadCount = 5; var threads = new List <Thread>(); var exceptions = new List <Exception>(); var firstPageNumber = _pageStore.GetFirstIndexPage(objectType); for (var i = 1; i <= threadCount; i++) { var transactionNumber = i; var thread = new Thread(() => { try { var writeTransaction = _database.BeginTransaction(null); _pageStore.BeginTransaction(writeTransaction); var buffer = Encoding.UTF8.GetBytes("Transaction " + transactionNumber); var location = _accessor.Append(firstPageNumber, writeTransaction, (uint)buffer.LongLength); _accessor.Write(writeTransaction, location, buffer); Thread.Sleep(50); _database.CommitTransaction(writeTransaction); _pageStore.CommitTransaction(writeTransaction); _pageStore.FinalizeTransaction(writeTransaction); } catch (Exception ex) { lock (exceptions) exceptions.Add(ex); } }) { Name = "Transaction " + i, IsBackground = true }; threads.Add(thread); } foreach (var thread in threads) { thread.Start(); } foreach (var thread in threads) { thread.Join(); } Assert.AreEqual(0, exceptions.Count); var transaction = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction); var results = new HashSet <string>(); Action <PageLocation> check = (location) => { using (var page = _pageStore.Get(transaction, location.PageNumber, CacheHints.None)) { var actual = Encoding.UTF8.GetString(page.Data, (int)location.Offset, (int)location.Length); Assert.IsTrue(results.Add(actual)); } }; var record = _accessor.LocateFirst(firstPageNumber, transaction, out object indexLocation); Assert.IsNotNull(record); for (var i = 1; i <= threadCount; i++) { check(record); record = _accessor.LocateNext(firstPageNumber, transaction, indexLocation); } for (var i = 1; i <= threadCount; i++) { Assert.IsTrue(results.Contains("Transaction " + i)); } Assert.IsNull(record); }
public void should_update_pages() { var transaction = _database.BeginTransaction(null); _pageStore.BeginTransaction(transaction); _pageStore.Update( transaction, new[] { new PageUpdate { SequenceNumber = 1, PageNumber = 1, Offset = 10, Data = new byte[] { 1, 2, 3 } } }); using (var page = _pageStore.Get(transaction, 1, CacheHints.None)) { Assert.AreEqual(1, page.Data[10]); Assert.AreEqual(2, page.Data[11]); Assert.AreEqual(3, page.Data[12]); } _database.CommitTransaction(transaction); _pageStore.CommitTransaction(transaction); _pageStore.FinalizeTransaction(transaction); }