Write custom timeout logic for DelayedDataSourceCache
This should make the code a bit more transparent vs using the CacheBuilder, plus hopefully resolve a concurrent writing issue that causes monoliths
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tests;
|
||||
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
|
||||
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* A few very basic tests to confirm {@link DelayedFullDataSourceSaveCache}
|
||||
* is working properly.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2025-10-02
|
||||
*/
|
||||
public class DelayedSaveCacheTest
|
||||
{
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void CacheExpirationAndPoolingTest() throws InterruptedException
|
||||
{
|
||||
// how many times any data source has been "written to disk"
|
||||
AtomicInteger diskSaveCountRef = new AtomicInteger(0);
|
||||
|
||||
DelayedFullDataSourceSaveCache cache = new DelayedFullDataSourceSaveCache((FullDataSourceV2 fullDataSource) ->
|
||||
{
|
||||
diskSaveCountRef.getAndIncrement();
|
||||
return this.onDataSourceSaveAsync(fullDataSource);
|
||||
}, 1_000);
|
||||
|
||||
|
||||
|
||||
//==============================//
|
||||
// single item and manual flush //
|
||||
//==============================//
|
||||
|
||||
PhantomArrayListCheckout initialCheckout;
|
||||
try (FullDataSourceV2 initialSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte)6, 0, 0)))
|
||||
{
|
||||
initialCheckout = initialSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
cache.writeDataSourceToMemoryAndQueueSave(initialSource);
|
||||
}
|
||||
Assert.assertEquals("only 1 item should be in the cache", 1, cache.getUnsavedCount());
|
||||
Assert.assertEquals("no disk saves should have happened yet", 0, diskSaveCountRef.get());
|
||||
|
||||
// manual flush
|
||||
cache.flush();
|
||||
Assert.assertEquals("memory cache should be empty after", 0, cache.getUnsavedCount());
|
||||
Assert.assertEquals("1 manual flush was expected", 1, diskSaveCountRef.get());
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// quick group position //
|
||||
//======================//
|
||||
|
||||
// write multiple items for the same position
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
try (FullDataSourceV2 loopSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte) 6, 0, 0)))
|
||||
{
|
||||
PhantomArrayListCheckout loopCheckout = loopSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
Assert.assertEquals(initialCheckout, loopCheckout);
|
||||
|
||||
cache.writeDataSourceToMemoryAndQueueSave(loopSource);
|
||||
}
|
||||
}
|
||||
// each item writes to the same place
|
||||
Assert.assertEquals("exactly 1 item should be in the cache", 1, cache.getUnsavedCount());
|
||||
Assert.assertEquals("no new saves should have happened yet", 1, diskSaveCountRef.get());
|
||||
|
||||
// wait for the cache to clear
|
||||
Thread.sleep(2_000);
|
||||
Assert.assertEquals("Cache should have automatically cleared due to inactivity", 0, cache.getUnsavedCount());
|
||||
Assert.assertEquals("second save after timeout expected", 2, diskSaveCountRef.get());
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// slow group position //
|
||||
//=====================//
|
||||
|
||||
// write multiple items for the same position
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
try (FullDataSourceV2 loopSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte) 6, 0, 0)))
|
||||
{
|
||||
PhantomArrayListCheckout loopCheckout = loopSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
Assert.assertEquals(initialCheckout, loopCheckout);
|
||||
|
||||
cache.writeDataSourceToMemoryAndQueueSave(loopSource);
|
||||
}
|
||||
|
||||
// long enough to prevent a timeout, but short enough that they don't happen all at once
|
||||
Thread.sleep(500);
|
||||
}
|
||||
// each item writes to the same place
|
||||
Assert.assertEquals("exactly 1 item should be in the cache", 1, cache.getUnsavedCount());
|
||||
Assert.assertEquals("no new saves should have happened yet", 2, diskSaveCountRef.get());
|
||||
|
||||
// wait for the cache to clear
|
||||
Thread.sleep(2_000);
|
||||
Assert.assertEquals("Cache should have automatically cleared due to inactivity", 0, cache.getUnsavedCount());
|
||||
Assert.assertEquals("third timeout expected", 3, diskSaveCountRef.get());
|
||||
|
||||
}
|
||||
private CompletableFuture<Void> onDataSourceSaveAsync(FullDataSourceV2 fullDataSource)
|
||||
{ return CompletableFuture.completedFuture(null); }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tests;
|
||||
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @see KeyedLockContainer
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2025-10-02
|
||||
*/
|
||||
public class KeyedLockTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void BasicKeyedLockTest()
|
||||
{
|
||||
KeyedLockContainer<Long> lockContainer = new KeyedLockContainer<>();
|
||||
|
||||
for (long a = -10; a < 10; a++)
|
||||
{
|
||||
ReentrantLock aLock = lockContainer.getLockForPos(a);
|
||||
|
||||
for (long b = -10; b < 10; b++)
|
||||
{
|
||||
ReentrantLock bLock = lockContainer.getLockForPos(a);
|
||||
|
||||
// we only care that the same position always map to the same object
|
||||
// if different positions map to the same object,
|
||||
// that's expected hash-collision behavior and is fine
|
||||
if (a == b)
|
||||
{
|
||||
Assert.assertEquals("long values ["+a+"] and ["+b+"] should have returned the same lock", aLock, bLock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package tests;
|
||||
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
|
||||
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.KeyedLockContainer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @see PhantomArrayListCheckout
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2025-10-02
|
||||
*/
|
||||
public class PooledDataSourceCheckoutTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void TestCheckouts()
|
||||
{
|
||||
PhantomArrayListCheckout initialCheckout;
|
||||
try (FullDataSourceV2 initialSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte)6, 0, 0)))
|
||||
{
|
||||
initialCheckout = initialSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
}
|
||||
|
||||
try (FullDataSourceV2 outerSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte) 6, 0, 0)))
|
||||
{
|
||||
PhantomArrayListCheckout outerCheckout = outerSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
Assert.assertEquals("the first checkout object should be pooled", initialCheckout, outerCheckout);
|
||||
|
||||
try (FullDataSourceV2 innerSource = FullDataSourceV2.createEmpty(DhSectionPos.encode((byte) 6, 0, 0)))
|
||||
{
|
||||
PhantomArrayListCheckout innerCheckout = innerSource.getPhantomArrayCheckoutForUnitTesting();
|
||||
Assert.assertNotEquals("the second checkout object should not be shared when the first is still in use", initialCheckout, innerCheckout);
|
||||
Assert.assertNotEquals("the second checkout object should not be shared when the first is still in use", outerCheckout, innerCheckout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user