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,121 +103,166 @@ 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);
vouchersConfig.load();
}
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() {
voucherManager.clearVouchers();
synchronized (vouchersConfig) {
voucherManager.clearVouchers();
if (vouchersConfig.contains("vouchers")) {
for (String key : vouchersConfig.getConfigurationSection("vouchers").getKeys(false)) {
Voucher voucher = new Voucher(key, this);
ConfigurationSection cs = vouchersConfig.getConfigurationSection("vouchers." + key);
if (vouchersConfig.contains("vouchers")) {
for (String key : vouchersConfig.getConfigurationSection("vouchers").getKeys(false)) {
Voucher voucher = new Voucher(key, this);
ConfigurationSection cs = vouchersConfig.getConfigurationSection("vouchers." + key);
Material material;
String stringMaterial = cs.getString("material");
Material material;
String stringMaterial = cs.getString("material");
if (stringMaterial == null || stringMaterial.isEmpty()) {
material = Material.PAPER;
} else {
material = Material.matchMaterial(stringMaterial);
if (material == null) material = Material.PAPER;
if (stringMaterial == null || stringMaterial.isEmpty()) {
material = Material.PAPER;
} else {
material = Material.matchMaterial(stringMaterial);
if (material == null) material = Material.PAPER;
}
voucher.setPermission(cs.getString("permission", ""))
.setMaterial(material)
.setData((short) cs.getInt("data", 0))
.setName(cs.getString("name", "default"))
.setLore(cs.getStringList("lore"))
.setTexture(cs.getString("texture", ""))
.setGlow(cs.getBoolean("glow", false))
.setConfirm(cs.getBoolean("confirm", true))
.setUnbreakable(cs.getBoolean("unbreakable", false))
.setHideAttributes(cs.getBoolean("hide-attributes", false))
.setRemoveItem(cs.getBoolean("remove-item", true))
.setHealPlayer(cs.getBoolean("heal-player", false))
.setSmiteEffect(cs.getBoolean("smite-effect", false))
.setCoolDown(cs.getInt("coolDown", 0))
.setBroadcasts(cs.getStringList("broadcasts"))
.setMessages(cs.getStringList("messages"))
.setCommands(cs.getStringList("commands"))
.setActionBar(cs.getString("actionbar"))
.setTitle(cs.getString("titles.title"))
.setSubTitle(cs.getString("titles.subtitle"))
.setTitleFadeIn(cs.getInt("titles.fade-in", 0))
.setTitleStay(cs.getInt("titles.stay", 0))
.setTitleFadeOut(cs.getInt("titles.fade-out", 0))
.setSound(cs.getString("sounds.sound"))
.setSoundPitch(cs.getInt("sounds.pitch", 0))
.setParticle(cs.getString("particles.particle"))
.setParticleAmount(cs.getInt("particles.amount", 0))
.setEffect(cs.getString("effects.effect"))
.setEffectAmplifier(cs.getInt("effects.amplifier"))
.setItemStack(cs.getItemStack("itemstack", null));
voucherManager.addVoucher(voucher);
}
voucher.setPermission(cs.getString("permission", ""))
.setMaterial(material)
.setData((short) cs.getInt("data", 0))
.setName(cs.getString("name", "default"))
.setLore(cs.getStringList("lore"))
.setTexture(cs.getString("texture", ""))
.setGlow(cs.getBoolean("glow", false))
.setConfirm(cs.getBoolean("confirm", true))
.setUnbreakable(cs.getBoolean("unbreakable", false))
.setHideAttributes(cs.getBoolean("hide-attributes", false))
.setRemoveItem(cs.getBoolean("remove-item", true))
.setHealPlayer(cs.getBoolean("heal-player", false))
.setSmiteEffect(cs.getBoolean("smite-effect", false))
.setCoolDown(cs.getInt("coolDown", 0))
.setBroadcasts(cs.getStringList("broadcasts"))
.setMessages(cs.getStringList("messages"))
.setCommands(cs.getStringList("commands"))
.setActionBar(cs.getString("actionbar"))
.setTitle(cs.getString("titles.title"))
.setSubTitle(cs.getString("titles.subtitle"))
.setTitleFadeIn(cs.getInt("titles.fade-in", 0))
.setTitleStay(cs.getInt("titles.stay", 0))
.setTitleFadeOut(cs.getInt("titles.fade-out", 0))
.setSound(cs.getString("sounds.sound"))
.setSoundPitch(cs.getInt("sounds.pitch", 0))
.setParticle(cs.getString("particles.particle"))
.setParticleAmount(cs.getInt("particles.amount", 0))
.setEffect(cs.getString("effects.effect"))
.setEffectAmplifier(cs.getInt("effects.amplifier"))
.setItemStack(cs.getItemStack("itemstack", null));
voucherManager.addVoucher(voucher);
}
}
}
private void saveVouchers() {
Collection<Voucher> voucherList = voucherManager.getVouchers();
ThreadSync tSync = new ThreadSync();
for (String voucherName : vouchersConfig.getConfigurationSection("vouchers").getKeys(false)) {
if (voucherList.stream().noneMatch(voucher -> voucher.getKey().equals(voucherName))) {
vouchersConfig.set("vouchers." + voucherName, null);
saveVouchersAsync(ex -> {
if (ex != null) {
ex.printStackTrace();
}
}
for (Voucher voucher : voucherList) {
String prefix = "vouchers." + voucher.getKey() + ".";
tSync.release();
});
vouchersConfig.set(prefix + "permission", voucher.getPermission());
vouchersConfig.set(prefix + "material", voucher.getMaterial().name());
vouchersConfig.set(prefix + "data", voucher.getData());
vouchersConfig.set(prefix + "name", voucher.getName());
vouchersConfig.set(prefix + "lore", voucher.getLore());
vouchersConfig.set(prefix + "texture", voucher.getTexture());
vouchersConfig.set(prefix + "glow", voucher.isGlow());
vouchersConfig.set(prefix + "confirm", voucher.isConfirm());
vouchersConfig.set(prefix + "unbreakable", voucher.isUnbreakable());
vouchersConfig.set(prefix + "hide-attributes", voucher.isHideAttributes());
vouchersConfig.set(prefix + "remove-item", voucher.isRemoveItem());
vouchersConfig.set(prefix + "heal-player", voucher.isHealPlayer());
vouchersConfig.set(prefix + "smite-effect", voucher.isSmiteEffect());
vouchersConfig.set(prefix + "coolDown", voucher.getCoolDown());
vouchersConfig.set(prefix + "broadcasts", voucher.getBroadcasts());
vouchersConfig.set(prefix + "messages", voucher.getMessages());
vouchersConfig.set(prefix + "commands", voucher.getCommands());
vouchersConfig.set(prefix + "actionbar", voucher.getActionBar());
vouchersConfig.set(prefix + "titles.title", voucher.getTitle());
vouchersConfig.set(prefix + "titles.subtitle", voucher.getSubTitle());
vouchersConfig.set(prefix + "titles.fade-in", voucher.getTitleFadeIn());
vouchersConfig.set(prefix + "titles.stay", voucher.getTitleStay());
vouchersConfig.set(prefix + "titles.fade-out", voucher.getTitleFadeOut());
vouchersConfig.set(prefix + "sounds.sound", voucher.getSound());
vouchersConfig.set(prefix + "sounds.pitch", voucher.getSoundPitch());
vouchersConfig.set(prefix + "particles.particle", voucher.getParticle());
vouchersConfig.set(prefix + "particles.amount", voucher.getParticleAmount());
vouchersConfig.set(prefix + "effects.effect", voucher.getEffect());
vouchersConfig.set(prefix + "effects.amplifier", voucher.getEffectAmplifier());
vouchersConfig.set(prefix + "itemstack", voucher.getItemStack());
}
tSync.waitForRelease();
}
vouchersConfig.saveChanges();
private void saveVouchersAsync(Callback callback) {
new Thread(() -> {
try {
synchronized (vouchersConfig) {
Collection<Voucher> voucherList = voucherManager.getVouchers();
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() + ".";
vouchersConfig.set(prefix + "permission", voucher.getPermission());
vouchersConfig.set(prefix + "material", voucher.getMaterial().name());
vouchersConfig.set(prefix + "data", voucher.getData());
vouchersConfig.set(prefix + "name", voucher.getName());
vouchersConfig.set(prefix + "lore", voucher.getLore());
vouchersConfig.set(prefix + "texture", voucher.getTexture());
vouchersConfig.set(prefix + "glow", voucher.isGlow());
vouchersConfig.set(prefix + "confirm", voucher.isConfirm());
vouchersConfig.set(prefix + "unbreakable", voucher.isUnbreakable());
vouchersConfig.set(prefix + "hide-attributes", voucher.isHideAttributes());
vouchersConfig.set(prefix + "remove-item", voucher.isRemoveItem());
vouchersConfig.set(prefix + "heal-player", voucher.isHealPlayer());
vouchersConfig.set(prefix + "smite-effect", voucher.isSmiteEffect());
vouchersConfig.set(prefix + "coolDown", voucher.getCoolDown());
vouchersConfig.set(prefix + "broadcasts", voucher.getBroadcasts());
vouchersConfig.set(prefix + "messages", voucher.getMessages());
vouchersConfig.set(prefix + "commands", voucher.getCommands());
vouchersConfig.set(prefix + "actionbar", voucher.getActionBar());
vouchersConfig.set(prefix + "titles.title", voucher.getTitle());
vouchersConfig.set(prefix + "titles.subtitle", voucher.getSubTitle());
vouchersConfig.set(prefix + "titles.fade-in", voucher.getTitleFadeIn());
vouchersConfig.set(prefix + "titles.stay", voucher.getTitleStay());
vouchersConfig.set(prefix + "titles.fade-out", voucher.getTitleFadeOut());
vouchersConfig.set(prefix + "sounds.sound", voucher.getSound());
vouchersConfig.set(prefix + "sounds.pitch", voucher.getSoundPitch());
vouchersConfig.set(prefix + "particles.particle", voucher.getParticle());
vouchersConfig.set(prefix + "particles.amount", voucher.getParticleAmount());
vouchersConfig.set(prefix + "effects.effect", voucher.getEffect());
vouchersConfig.set(prefix + "effects.amplifier", voucher.getEffectAmplifier());
vouchersConfig.set(prefix + "itemstack", voucher.getItemStack());
}
vouchersConfig.saveChanges();
callback.accept(null);
}
} catch (Exception ex) {
callback.accept(ex);
}
}, getName() + "-AsyncConfigSave").start();
}
@Override
public void onConfigReload() {
vouchersConfig.load();
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);
}
}