Fix multithreaded save calls to AbstractDhRepo

This commit is contained in:
James Seibel
2024-03-04 07:08:32 -06:00
parent 0f04453134
commit 0c5c4f3a74
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
@@ -29,6 +30,7 @@ import org.jetbrains.annotations.Nullable;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* Handles interfacing with SQL databases.
@@ -51,6 +53,11 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>>
public final Class<? extends TDTO> dtoClass;
protected final ReentrantLock[] saveLockArray;
/** Based on the stack overflow post: https://stackoverflow.com/a/45909920 */
protected ReentrantLock getSaveLockForKey(TKey key) { return this.saveLockArray[Math.abs(key.hashCode()) % this.saveLockArray.length]; }
//=============//
@@ -65,6 +72,16 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>>
this.dtoClass = dtoClass;
// the lock array's length is 2x the number of CPU cores so the number of collisions
// should be relatively low without having too many extra locks
int lockCount = Runtime.getRuntime().availableProcessors() * 2;
this.saveLockArray = new ReentrantLock[lockCount];
for (int i = 0; i < lockCount; i++)
{
this.saveLockArray[i] = new ReentrantLock();
}
try
{
// needed by Forge to load the Java database connection
@@ -126,13 +143,27 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>>
public void save(TDTO dto)
{
if (this.getByKey(dto.getKey()) != null)
// a lock is necessary to prevent concurrent modification between
// existsWithKey and insert/update,
// otherwise another thread might cause the insert/update to fail.
ReentrantLock saveLock = this.getSaveLockForKey(dto.getKey());
try
{
this.update(dto);
saveLock.lock();
if (this.existsWithKey(dto.getKey()))
{
this.update(dto);
}
else
{
this.insert(dto);
}
}
else
finally
{
this.insert(dto);
saveLock.unlock();
}
}
private void insert(TDTO dto)