From 0c5c4f3a74e9f309af30cc9f1b42c59b4cd49bac Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 4 Mar 2024 07:08:32 -0600 Subject: [PATCH] Fix multithreaded save calls to AbstractDhRepo --- .../core/sql/repo/AbstractDhRepo.java | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java index 2420a0478..1a5dfd945 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/AbstractDhRepo.java @@ -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> public final Class 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> 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> 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)