/* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package slotting import ( "testing" "devt.de/krotik/eliasdb/storage/file" "devt.de/krotik/eliasdb/storage/paging" "devt.de/krotik/eliasdb/storage/paging/view" "devt.de/krotik/eliasdb/storage/slotting/pageview" "devt.de/krotik/eliasdb/storage/util" ) func TestFreeLogicalSlotManager(t *testing.T) { sf, err := file.NewDefaultStorageFile(DBDIR+"/test6", false) if err != nil { t.Error(err.Error()) return } psf, err := paging.NewPagedStorageFile(sf) if err != nil { t.Error(err) return } flsm := NewFreeLogicalSlotManager(psf) testAddPanic(t, flsm) // Add some locations flsm.Add(util.PackLocation(5, 22)) flsm.Add(util.PackLocation(6, 23)) out := flsm.String() if out != "FreeLogicalSlotManager: buckettest/test6\n"+ "Ids :[327702 393239]\n" { t.Error("Unexpected output of FreeLogicalSlotManager:", out) } if err = flsm.Flush(); err != nil { t.Error(err) return } if len(flsm.slots) != 0 { t.Error("Nothing should be left in the slot cache after a flush") return } // Check pages are allocated cursor := paging.NewPageCursor(flsm.pager, view.TypeFreeLogicalSlotPage, 0) if page, err := cursor.Next(); page != 1 || err != nil { t.Error("Unexpected free logical slot page:", page, err) return } if page, err := cursor.Next(); page != 0 || err != nil { t.Error("Unexpected free logical slot page:", page, err) return } page := flsm.pager.First(view.TypeFreeLogicalSlotPage) if page != 1 { t.Error("Unexpected first free logical slot page") return } flspRec, err := sf.Get(1) if err != nil { t.Error(err) } flsp := pageview.NewFreeLogicalSlotPage(flspRec) if fsc := flsp.FreeSlotCount(); fsc != 2 { t.Error("Unexpected number of stored free slots", fsc) } // Check that both slotinfos have been written if flsp.SlotInfoLocation(0) != util.PackLocation(5, 22) { t.Error("Unexpected free slot info") return } if flsp.SlotInfoLocation(1) != util.PackLocation(6, 23) { t.Error("Unexpected free slot info") return } sf.ReleaseInUse(flspRec) // Check that we can find them loc, err := flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 5, 22) if fsc := flsp.FreeSlotCount(); fsc != 1 { t.Error("Unexpected number of stored free slots", fsc) } // Test error handling in Flush flsm.Add(util.PackLocation(4, 21)) rec, err := sf.Get(1) if err != nil { t.Error(err) return } err = flsm.Flush() if sfe, ok := err.(*file.StorageFileError); !ok || sfe.Type != file.ErrAlreadyInUse { t.Error("Unexpected Get result:", err) return } // Can get something without error from the unflushed slot list loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 4, 21) err = sf.ReleaseInUseID(rec.ID(), false) if err != nil { t.Error(err) return } loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 6, 23) // Test multiple insert flsm.Add(util.PackLocation(9, 1)) if err = flsm.Flush(); err != nil { t.Error(err) return } flsm.Add(util.PackLocation(9, 2)) flsm.Add(util.PackLocation(9, 3)) flsm.Add(util.PackLocation(9, 4)) if err = flsm.Flush(); err != nil { t.Error(err) return } // Test get error when record in use and slot list is empty rec, err = sf.Get(1) if err != nil { t.Error(err) return } _, err = flsm.Get() if sfe, ok := err.(*file.StorageFileError); !ok || sfe.Type != file.ErrAlreadyInUse { t.Error(err) return } err = sf.ReleaseInUseID(rec.ID(), false) if err != nil { t.Error(err) return } loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 9, 1) loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 9, 2) loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 9, 3) loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 9, 4) if fsc := flsp.FreeSlotCount(); fsc != 0 { t.Error("Unexpected number of stored free slots", fsc) } // Test special case when a page is in the pager list but has no slots allocated for i := 1; i < 1001; i++ { flsm.Add(util.PackLocation(uint64(i), uint16(i%1000))) } if err = flsm.Flush(); err != nil { t.Error(err) return } // Manually free all slots on the first page flspRec, err = sf.Get(1) if err != nil { t.Error(err) } flsp = pageview.NewFreeLogicalSlotPage(flspRec) var j uint16 for j = 0; j < flsp.MaxSlots(); j++ { flsp.ReleaseSlotInfo(j) } sf.ReleaseInUse(flspRec) // Check we get slotlocation 0 from second page loc, err = flsm.Get() if err != nil { t.Error(err) return } checkLocation(t, loc, 510, 510) if err := psf.Close(); err != nil { t.Error(err) return } } func testAddPanic(t *testing.T, flsm *FreeLogicalSlotManager) { defer func() { if r := recover(); r == nil { t.Error("Adding location 0 did not cause a panic.") } }() flsm.Add(0) } func TestFreeLogiclaSlotManagerScale(t *testing.T) { sf, err := file.NewDefaultStorageFile(DBDIR+"/test7", false) if err != nil { t.Error(err.Error()) return } shadow, err := file.NewDefaultStorageFile(DBDIR+"/test7_", false) if err != nil { t.Error(err.Error()) return } psf, err := paging.NewPagedStorageFile(sf) if err != nil { t.Error(err) return } flsm := NewFreeLogicalSlotManager(psf) // Add a lot of locations for i := 1; i < 5001; i++ { flsm.Add(util.PackLocation(uint64(i), uint16(i%1000))) } // Check Flush and low level doFlush if a page can't be accessed if _, err := sf.Get(1); err != nil { t.Error(err) return } err = flsm.Flush() if sfe, ok := err.(*file.StorageFileError); !ok || sfe.Type != file.ErrAlreadyInUse { t.Error("Unexpected flush result:", err) return } i, err := flsm.doFlush(1, 0) if sfe, ok := err.(*file.StorageFileError); i != 0 || !ok || sfe.Type != file.ErrAlreadyInUse { t.Error("Unexpected doFlush result:", i, err) return } if err := sf.ReleaseInUseID(1, false); err != nil { t.Error(err) return } // Check the doFlush error return in Flush when allocating new pages flsm.storagefile = shadow if _, err := shadow.Get(1); err != nil { t.Error(err) return } err = flsm.Flush() if sfe, ok := err.(*file.StorageFileError); !ok || sfe.Type != file.ErrAlreadyInUse { t.Error("Unexpected flush result:", err) return } if err := shadow.ReleaseInUseID(1, false); err != nil { t.Error(err) return } flsm.storagefile = sf // Now do the real flush if err := flsm.Flush(); err != nil { t.Error(err) return } // Count the allocated pages c, err := paging.CountPages(flsm.pager, view.TypeFreeLogicalSlotPage) if c != 10 || err != nil { t.Error("Unexpected counting result:", c, err) return } // Remove some free slots from the list for i := 1; i < 1001; i++ { if res, err := flsm.Get(); res != util.PackLocation(uint64(i), uint16(i%1000)) || err != nil { t.Error("Unexpected Get result", util.LocationRecord(res), util.LocationOffset(res), i, err) return } } // Count the allocated pages (one page should be free now) c, err = paging.CountPages(flsm.pager, view.TypeFreeLogicalSlotPage) if c != 9 || err != nil { t.Error("Unexpected counting result:", c, err) return } // Now add new free slots with a different pattern for i := 1; i < 1001; i++ { flsm.Add(util.PackLocation(uint64(i+666), uint16(i%1000))) } if err := flsm.Flush(); err != nil { t.Error(err) return } c, err = paging.CountPages(flsm.pager, view.TypeFreeLogicalSlotPage) if c != 10 || err != nil { t.Error("Unexpected counting result:", c, err) return } // All slots from page 1 were removed during the first request for 1000 slots. The page was // subsequently deallocated. Page 2 was partly cleared. Adding again 1000 slots filled up page 2 again // and allocated page 1 again this time however at the end of the list. In the following loop we empty // all pages. // First we empty page 2 containing partly the new pattern and partly the old one // We then empty all pages with the old pattern // Finally we empty the remaining page with the new pattern for i := 1; i < 1001; i++ { if res, err := flsm.Get(); res != util.PackLocation(uint64(i+666), uint16(i%1000)) || err != nil { t.Error("Unexpected Get result", util.LocationRecord(res), util.LocationOffset(res), i, err) return } if i == 491 { for j := 1001; j < 5001; j++ { if res, err := flsm.Get(); res != util.PackLocation(uint64(j), uint16(j%1000)) || err != nil { t.Error("*Unexpected Get result", util.LocationRecord(res), util.LocationOffset(res), j, err) return } } } } // Check that all empty slots have been retrieved and nothing is left on the free pages if res, err := flsm.Get(); res != 0 || err != nil { t.Error("Unexpected final Get call result", res, err) return } if err := psf.Close(); err != nil { t.Error(err) return } if err := shadow.Close(); err != nil { t.Error(err) return } }