Hotfix async vouchers.yml write task as large files cause lag [SD-8155]

I've some a TODO/FIXME explaining what to do. Wanna focus on bringing the tickets down first
This commit is contained in:
Christian Koop 2021-07-12 16:09:21 +02:00
parent 063f7b0fee
commit d7801dd64f
No known key found for this signature in database
GPG Key ID: 89A8181384E010A3
3 changed files with 172 additions and 92 deletions

View File

@ -20,6 +20,8 @@ import com.songoda.epicvouchers.libraries.inventory.IconInv;
import com.songoda.epicvouchers.listeners.PlayerCommandListener;
import com.songoda.epicvouchers.listeners.PlayerInteractListener;
import com.songoda.epicvouchers.settings.Settings;
import com.songoda.epicvouchers.utils.Callback;
import com.songoda.epicvouchers.utils.ThreadSync;
import com.songoda.epicvouchers.voucher.CoolDownManager;
import com.songoda.epicvouchers.voucher.Voucher;
import com.songoda.epicvouchers.voucher.VoucherExecutor;
@ -101,18 +103,33 @@ public class EpicVouchers extends SongodaPlugin {
@Override
public void onDataLoad() {
if (!new File(this.getDataFolder(), "vouchers.yml").exists())
if (!new File(this.getDataFolder(), "vouchers.yml").exists()) {
saveResource("vouchers.yml", false);
}
synchronized (vouchersConfig) {
vouchersConfig.load();
}
loadVouchersFromFile();
connections.openMySQL();
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, this::saveVouchers, 6000, 6000);
// FIXME: Config system needs to be greatly redone and only write changes when changes were made - Maybe even split it into multiple smaler files
// Issue https://support.songoda.com/browse/SD-8155 has been hotfixed by writing changes to the file async and blocking the main thread when needed. This requires the use of `synchronized`
// and expects every modifying code to use it (thread-safety)
// Large vouchers.yml files cause huge performance problems otherwise...
// Example file for testing: https://support.songoda.com/secure/attachment/17258/17258_vouchers.yml
Bukkit.getScheduler().scheduleSyncRepeatingTask(this,
() -> saveVouchersAsync(ex -> {
if (ex != null) {
ex.printStackTrace();
}
}), 5 * 60 * 20, 5 * 60 * 20); // 5 minutes
}
private void loadVouchersFromFile() {
synchronized (vouchersConfig) {
voucherManager.clearVouchers();
if (vouchersConfig.contains("vouchers")) {
@ -165,15 +182,36 @@ public class EpicVouchers extends SongodaPlugin {
}
}
}
}
private void saveVouchers() {
ThreadSync tSync = new ThreadSync();
saveVouchersAsync(ex -> {
if (ex != null) {
ex.printStackTrace();
}
tSync.release();
});
tSync.waitForRelease();
}
private void saveVouchersAsync(Callback callback) {
new Thread(() -> {
try {
synchronized (vouchersConfig) {
Collection<Voucher> voucherList = voucherManager.getVouchers();
for (String voucherName : vouchersConfig.getConfigurationSection("vouchers").getKeys(false)) {
ConfigurationSection cfgSec = vouchersConfig.getConfigurationSection("vouchers");
if (cfgSec != null) {
for (String voucherName : cfgSec.getKeys(false)) {
if (voucherList.stream().noneMatch(voucher -> voucher.getKey().equals(voucherName))) {
vouchersConfig.set("vouchers." + voucherName, null);
}
}
}
for (Voucher voucher : voucherList) {
String prefix = "vouchers." + voucher.getKey() + ".";
@ -211,11 +249,20 @@ public class EpicVouchers extends SongodaPlugin {
}
vouchersConfig.saveChanges();
callback.accept(null);
}
} catch (Exception ex) {
callback.accept(ex);
}
}, getName() + "-AsyncConfigSave").start();
}
@Override
public void onConfigReload() {
synchronized (vouchersConfig) {
vouchersConfig.load();
}
loadVouchersFromFile();
@ -240,10 +287,6 @@ public class EpicVouchers extends SongodaPlugin {
return this.voucherExecutor;
}
public Config getVouchersConfig() {
return this.vouchersConfig;
}
public CommandManager getCommandManager() {
return commandManager;
}

View File

@ -0,0 +1,6 @@
package com.songoda.epicvouchers.utils;
// TODO: Copied from EpicAnchors - Move to SongodaCore (maybe rename too?)
public interface Callback {
void accept(Exception ex);
}

View File

@ -0,0 +1,31 @@
package com.songoda.epicvouchers.utils;
import java.util.concurrent.atomic.AtomicReference;
// TODO: Copied from EpicAnchors - Move to SongodaCore
public class ThreadSync {
private final Object syncObj = new Object();
private final AtomicReference<Boolean> waiting = new AtomicReference<>(true);
public void waitForRelease() {
synchronized (syncObj) {
while (waiting.get()) {
try {
syncObj.wait();
} catch (Exception ignore) {
}
}
}
}
public void release() {
synchronized (syncObj) {
waiting.set(false);
syncObj.notifyAll();
}
}
public void reset() {
waiting.set(true);
}
}