Prevent concurrent access to server information storage

Affects issues:
- Possibly fixed #2254
This commit is contained in:
Aurora Lahtela 2022-05-30 21:06:19 +03:00
parent ab0c000688
commit b97f61d1de
3 changed files with 139 additions and 2 deletions

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.identification;
import com.djrapitops.plan.delivery.webserver.Addresses;
import com.djrapitops.plan.exceptions.EnableException;
import com.djrapitops.plan.identification.properties.ServerProperties;
import com.djrapitops.plan.identification.storage.AtomicServerLoader;
import com.djrapitops.plan.identification.storage.ServerDBLoader;
import com.djrapitops.plan.identification.storage.ServerFileLoader;
import com.djrapitops.plan.identification.storage.ServerLoader;
@ -70,8 +71,8 @@ public class ServerServerInfo extends ServerInfo {
) {
super(serverProperties);
this.currentVersion = currentVersion;
this.fromFile = fromFile;
this.fromDatabase = fromDatabase;
this.fromFile = new AtomicServerLoader(fromFile);
this.fromDatabase = new AtomicServerLoader(fromDatabase);
this.processing = processing;
this.addresses = addresses;
this.config = config;

View File

@ -0,0 +1,54 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan 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 Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.identification.storage;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
public class AtomicServerLoader implements ServerLoader {
private final ReentrantLock reentrantLock;
private final ServerLoader original;
public AtomicServerLoader(ServerLoader original) {
this.original = original;
this.reentrantLock = new ReentrantLock();
}
@Override
public Optional<Server> load(ServerUUID serverUUID) {
try {
reentrantLock.lock();
return original.load(serverUUID);
} finally {
reentrantLock.unlock();
}
}
@Override
public void save(Server information) {
try {
reentrantLock.lock();
original.save(information);
} finally {
reentrantLock.unlock();
}
}
}

View File

@ -0,0 +1,82 @@
package com.djrapitops.plan.identification.storage;
import com.djrapitops.plan.PlanSystem;
import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerUUID;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import utilities.TestConstants;
import utilities.mocks.PluginMockComponent;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ServerFileLoaderTest {
static PlanSystem system;
static ServerFileLoader underTest;
private static ServerUUID serverUUID;
@BeforeAll
static void setUp(@TempDir Path tempDir) throws Exception {
PluginMockComponent mockComponent = new PluginMockComponent(tempDir);
system = mockComponent.getPlanSystem();
system.enable();
}
@AfterAll
static void tearDown() {
if (system != null) system.disable();
}
@BeforeEach
void setUpEach() {
underTest = new ServerFileLoader(TestConstants.VERSION, system.getPlanFiles(), system.getConfigSystem().getConfig());
Optional<Server> loaded = underTest.load(null);
assertTrue(loaded.isPresent());
if (serverUUID == null) {
serverUUID = loaded.get().getUuid();
}
}
@Test
void runParallelLoadsAndSaves() throws InterruptedException {
ExecutorService executorService = new ScheduledThreadPoolExecutor(6);
AtomicInteger runs = new AtomicInteger(1);
int expected = 10000;
AtomicInteger fails = new AtomicInteger(0);
try {
for (int i = 0; i < expected; i++) {
executorService.submit(() -> {
Optional<Server> load = underTest.load(null);
if (load.isPresent()) {
underTest.save(load.get());
} else {
System.out.println("Failure " + fails.incrementAndGet());
}
});
}
Awaitility.await()
.atMost(2, TimeUnit.MINUTES)
.until(() -> runs.get() >= expected);
} finally {
executorService.shutdown();
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
}
assertEquals(0, fails.get());
}
}