mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2024-12-21 05:50:18 +08:00
Prevent concurrent access to server information storage
Affects issues: - Possibly fixed #2254
This commit is contained in:
parent
ab0c000688
commit
b97f61d1de
@ -19,6 +19,7 @@ package com.djrapitops.plan.identification;
|
|||||||
import com.djrapitops.plan.delivery.webserver.Addresses;
|
import com.djrapitops.plan.delivery.webserver.Addresses;
|
||||||
import com.djrapitops.plan.exceptions.EnableException;
|
import com.djrapitops.plan.exceptions.EnableException;
|
||||||
import com.djrapitops.plan.identification.properties.ServerProperties;
|
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.ServerDBLoader;
|
||||||
import com.djrapitops.plan.identification.storage.ServerFileLoader;
|
import com.djrapitops.plan.identification.storage.ServerFileLoader;
|
||||||
import com.djrapitops.plan.identification.storage.ServerLoader;
|
import com.djrapitops.plan.identification.storage.ServerLoader;
|
||||||
@ -70,8 +71,8 @@ public class ServerServerInfo extends ServerInfo {
|
|||||||
) {
|
) {
|
||||||
super(serverProperties);
|
super(serverProperties);
|
||||||
this.currentVersion = currentVersion;
|
this.currentVersion = currentVersion;
|
||||||
this.fromFile = fromFile;
|
this.fromFile = new AtomicServerLoader(fromFile);
|
||||||
this.fromDatabase = fromDatabase;
|
this.fromDatabase = new AtomicServerLoader(fromDatabase);
|
||||||
this.processing = processing;
|
this.processing = processing;
|
||||||
this.addresses = addresses;
|
this.addresses = addresses;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user