mirror of
https://github.com/TotalFreedomMC/TF-EssentialsX.git
synced 2025-02-11 11:49:12 +00:00
Prevent some rare cases of NPE and Deadlocks, better error handling on yaml load
This commit is contained in:
parent
019b49ef11
commit
51390a9698
8 changed files with 145 additions and 105 deletions
|
@ -8,6 +8,7 @@ import java.io.IOException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.yaml.snakeyaml.error.YAMLException;
|
||||||
|
|
||||||
|
|
||||||
public abstract class AbstractDelayedYamlFileReader<T extends StorageObject> implements Runnable
|
public abstract class AbstractDelayedYamlFileReader<T extends StorageObject> implements Runnable
|
||||||
|
@ -33,9 +34,17 @@ public abstract class AbstractDelayedYamlFileReader<T extends StorageObject> imp
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
onStart();
|
onStart();
|
||||||
reader = new FileReader(file);
|
try
|
||||||
final T object = new YamlStorageReader(reader, plugin).load(clazz);
|
{
|
||||||
onFinish(object);
|
reader = new FileReader(file);
|
||||||
|
T object = new YamlStorageReader(reader, plugin).load(clazz);
|
||||||
|
onSuccess(object);
|
||||||
|
}
|
||||||
|
catch (ObjectLoadException ex)
|
||||||
|
{
|
||||||
|
onException();
|
||||||
|
Bukkit.getLogger().log(Level.SEVERE, "File broken: " + file.toString(), ex.getCause());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException ex)
|
catch (FileNotFoundException ex)
|
||||||
{
|
{
|
||||||
|
@ -57,5 +66,7 @@ public abstract class AbstractDelayedYamlFileReader<T extends StorageObject> imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onFinish(T object);
|
public abstract void onSuccess(T object);
|
||||||
|
|
||||||
|
public abstract void onException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@ package com.earth2me.essentials.storage;
|
||||||
|
|
||||||
public interface IStorageReader
|
public interface IStorageReader
|
||||||
{
|
{
|
||||||
<T extends StorageObject> T load(final Class<? extends T> clazz);
|
<T extends StorageObject> T load(final Class<? extends T> clazz) throws ObjectLoadException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.earth2me.essentials.storage;
|
||||||
|
|
||||||
|
|
||||||
|
public class ObjectLoadException extends Exception
|
||||||
|
{
|
||||||
|
public ObjectLoadException(Throwable thrwbl)
|
||||||
|
{
|
||||||
|
super(thrwbl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@ import java.io.Reader;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.yaml.snakeyaml.TypeDescription;
|
import org.yaml.snakeyaml.TypeDescription;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
@ -14,8 +12,8 @@ import org.yaml.snakeyaml.constructor.Constructor;
|
||||||
|
|
||||||
public class YamlStorageReader implements IStorageReader
|
public class YamlStorageReader implements IStorageReader
|
||||||
{
|
{
|
||||||
private transient static Map<Class, Yaml> preparedYamls = Collections.synchronizedMap(new HashMap<Class, Yaml>());
|
private transient static final Map<Class, Yaml> PREPARED_YAMLS = Collections.synchronizedMap(new HashMap<Class, Yaml>());
|
||||||
private transient static Map<Class, ReentrantLock> locks = new HashMap<Class, ReentrantLock>();
|
private transient static final Map<Class, ReentrantLock> LOCKS = new HashMap<Class, ReentrantLock>();
|
||||||
private transient final Reader reader;
|
private transient final Reader reader;
|
||||||
private transient final Plugin plugin;
|
private transient final Plugin plugin;
|
||||||
|
|
||||||
|
@ -26,49 +24,40 @@ public class YamlStorageReader implements IStorageReader
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends StorageObject> T load(final Class<? extends T> clazz)
|
public <T extends StorageObject> T load(final Class<? extends T> clazz) throws ObjectLoadException
|
||||||
{
|
{
|
||||||
Yaml yaml = preparedYamls.get(clazz);
|
Yaml yaml = PREPARED_YAMLS.get(clazz);
|
||||||
if (yaml == null)
|
if (yaml == null)
|
||||||
{
|
{
|
||||||
yaml = new Yaml(prepareConstructor(clazz));
|
yaml = new Yaml(prepareConstructor(clazz));
|
||||||
preparedYamls.put(clazz, yaml);
|
PREPARED_YAMLS.put(clazz, yaml);
|
||||||
}
|
}
|
||||||
ReentrantLock lock;
|
ReentrantLock lock;
|
||||||
synchronized (locks)
|
synchronized (LOCKS)
|
||||||
{
|
{
|
||||||
lock = locks.get(clazz);
|
lock = LOCKS.get(clazz);
|
||||||
if (lock == null)
|
if (lock == null)
|
||||||
{
|
{
|
||||||
lock = new ReentrantLock();
|
lock = new ReentrantLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
T ret;
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ret = (T)yaml.load(reader);
|
T object = (T)yaml.load(reader);
|
||||||
|
if (object == null) {
|
||||||
|
object = clazz.newInstance();
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new ObjectLoadException(ex);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
if (ret == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ret = (T)clazz.newInstance();
|
|
||||||
}
|
|
||||||
catch (InstantiationException ex)
|
|
||||||
{
|
|
||||||
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
catch (IllegalAccessException ex)
|
|
||||||
{
|
|
||||||
Logger.getLogger(StorageObject.class.getName()).log(Level.SEVERE, null, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Constructor prepareConstructor(final Class<?> clazz)
|
private Constructor prepareConstructor(final Class<?> clazz)
|
||||||
|
|
|
@ -81,7 +81,6 @@ public class User extends UserBase implements IOfflineUser
|
||||||
new UserDataWriter();
|
new UserDataWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class UserDataWriter extends AbstractDelayedYamlFileWriter
|
private class UserDataWriter extends AbstractDelayedYamlFileWriter
|
||||||
{
|
{
|
||||||
public UserDataWriter()
|
public UserDataWriter()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.earth2me.essentials;
|
package com.earth2me.essentials;
|
||||||
|
|
||||||
import com.earth2me.essentials.settings.Settings;
|
import com.earth2me.essentials.settings.Settings;
|
||||||
|
import com.earth2me.essentials.storage.ObjectLoadException;
|
||||||
import com.earth2me.essentials.storage.StorageObject;
|
import com.earth2me.essentials.storage.StorageObject;
|
||||||
import com.earth2me.essentials.storage.YamlStorageReader;
|
import com.earth2me.essentials.storage.YamlStorageReader;
|
||||||
import com.earth2me.essentials.storage.YamlStorageWriter;
|
import com.earth2me.essentials.storage.YamlStorageWriter;
|
||||||
|
@ -11,7 +12,6 @@ import org.bukkit.World;
|
||||||
import org.bukkit.World.Environment;
|
import org.bukkit.World.Environment;
|
||||||
import org.bukkit.plugin.InvalidDescriptionException;
|
import org.bukkit.plugin.InvalidDescriptionException;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
|
||||||
|
|
||||||
|
|
||||||
public class StorageTest extends TestCase
|
public class StorageTest extends TestCase
|
||||||
|
@ -42,82 +42,94 @@ public class StorageTest extends TestCase
|
||||||
@Test
|
@Test
|
||||||
public void testSettings()
|
public void testSettings()
|
||||||
{
|
{
|
||||||
assertTrue(StorageObject.class.isAssignableFrom(Settings.class));
|
try
|
||||||
ExecuteTimer ext = new ExecuteTimer();
|
{
|
||||||
ext.start();
|
assertTrue(StorageObject.class.isAssignableFrom(Settings.class));
|
||||||
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
|
ExecuteTimer ext = new ExecuteTimer();
|
||||||
final Reader reader = new InputStreamReader(bais);
|
ext.start();
|
||||||
final Settings settings = new YamlStorageReader(reader, null).load(Settings.class);
|
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
|
||||||
ext.mark("load empty settings");
|
final Reader reader = new InputStreamReader(bais);
|
||||||
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
|
final Settings settings = new YamlStorageReader(reader, null).load(Settings.class);
|
||||||
final Reader reader3 = new InputStreamReader(bais3);
|
ext.mark("load empty settings");
|
||||||
final Settings settings3 = new YamlStorageReader(reader3, null).load(Settings.class);
|
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
|
||||||
ext.mark("load empty settings (class cached)");
|
final Reader reader3 = new InputStreamReader(bais3);
|
||||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
final Settings settings3 = new YamlStorageReader(reader3, null).load(Settings.class);
|
||||||
final PrintWriter writer = new PrintWriter(baos);
|
ext.mark("load empty settings (class cached)");
|
||||||
new YamlStorageWriter(writer).save(settings);
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
writer.close();
|
final PrintWriter writer = new PrintWriter(baos);
|
||||||
ext.mark("write settings");
|
new YamlStorageWriter(writer).save(settings);
|
||||||
byte[] written = baos.toByteArray();
|
writer.close();
|
||||||
System.out.println(new String(written));
|
ext.mark("write settings");
|
||||||
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
|
byte[] written = baos.toByteArray();
|
||||||
final Reader reader2 = new InputStreamReader(bais2);
|
System.out.println(new String(written));
|
||||||
final Settings settings2 = new YamlStorageReader(reader2, null).load(Settings.class);
|
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
|
||||||
System.out.println(settings.toString());
|
final Reader reader2 = new InputStreamReader(bais2);
|
||||||
System.out.println(settings2.toString());
|
final Settings settings2 = new YamlStorageReader(reader2, null).load(Settings.class);
|
||||||
ext.mark("reload settings");
|
System.out.println(settings.toString());
|
||||||
System.out.println(ext.end());
|
System.out.println(settings2.toString());
|
||||||
//assertEquals("Default and rewritten config should be equal", settings, settings2);
|
ext.mark("reload settings");
|
||||||
//that assertion fails, because empty list and maps return as null
|
System.out.println(ext.end());
|
||||||
|
//assertEquals("Default and rewritten config should be equal", settings, settings2);
|
||||||
|
//that assertion fails, because empty list and maps return as null
|
||||||
|
}
|
||||||
|
catch (ObjectLoadException ex)
|
||||||
|
{
|
||||||
|
fail(ex.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUserdata()
|
public void testUserdata()
|
||||||
{
|
{
|
||||||
FakeServer server = new FakeServer();
|
try
|
||||||
World world = server.createWorld("testWorld", Environment.NORMAL);
|
|
||||||
ExecuteTimer ext = new ExecuteTimer();
|
|
||||||
ext.start();
|
|
||||||
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
|
|
||||||
final Reader reader = new InputStreamReader(bais);
|
|
||||||
final com.earth2me.essentials.user.UserData userdata = new YamlStorageReader(reader, null).load(com.earth2me.essentials.user.UserData.class);
|
|
||||||
ext.mark("load empty user");
|
|
||||||
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
|
|
||||||
final Reader reader3 = new InputStreamReader(bais3);
|
|
||||||
final com.earth2me.essentials.user.UserData userdata3 = new YamlStorageReader(reader3, null).load(com.earth2me.essentials.user.UserData.class);
|
|
||||||
ext.mark("load empty user (class cached)");
|
|
||||||
|
|
||||||
for (int j = 0; j < 10000; j++)
|
|
||||||
{
|
{
|
||||||
userdata.getHomes().put("home", new Location(world, j, j, j));
|
FakeServer server = new FakeServer();
|
||||||
|
World world = server.createWorld("testWorld", Environment.NORMAL);
|
||||||
|
ExecuteTimer ext = new ExecuteTimer();
|
||||||
|
ext.start();
|
||||||
|
final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]);
|
||||||
|
final Reader reader = new InputStreamReader(bais);
|
||||||
|
final com.earth2me.essentials.user.UserData userdata = new YamlStorageReader(reader, null).load(com.earth2me.essentials.user.UserData.class);
|
||||||
|
ext.mark("load empty user");
|
||||||
|
final ByteArrayInputStream bais3 = new ByteArrayInputStream(new byte[0]);
|
||||||
|
final Reader reader3 = new InputStreamReader(bais3);
|
||||||
|
final com.earth2me.essentials.user.UserData userdata3 = new YamlStorageReader(reader3, null).load(com.earth2me.essentials.user.UserData.class);
|
||||||
|
ext.mark("load empty user (class cached)");
|
||||||
|
|
||||||
|
for (int j = 0; j < 10000; j++)
|
||||||
|
{
|
||||||
|
userdata.getHomes().put("home", new Location(world, j, j, j));
|
||||||
|
}
|
||||||
|
ext.mark("change home 10000 times");
|
||||||
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
final PrintWriter writer = new PrintWriter(baos);
|
||||||
|
new YamlStorageWriter(writer).save(userdata);
|
||||||
|
writer.close();
|
||||||
|
ext.mark("write user");
|
||||||
|
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||||
|
final PrintWriter writer2 = new PrintWriter(baos2);
|
||||||
|
new YamlStorageWriter(writer2).save(userdata);
|
||||||
|
writer2.close();
|
||||||
|
ext.mark("write user (cached)");
|
||||||
|
byte[] written = baos.toByteArray();
|
||||||
|
System.out.println(new String(written));
|
||||||
|
ext.mark("debug output");
|
||||||
|
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
|
||||||
|
final Reader reader2 = new InputStreamReader(bais2);
|
||||||
|
final com.earth2me.essentials.user.UserData userdata2 = new YamlStorageReader(reader2, null).load(com.earth2me.essentials.user.UserData.class);
|
||||||
|
ext.mark("reload file");
|
||||||
|
final ByteArrayInputStream bais4 = new ByteArrayInputStream(written);
|
||||||
|
final Reader reader4 = new InputStreamReader(bais4);
|
||||||
|
final com.earth2me.essentials.user.UserData userdata4 = new YamlStorageReader(reader4, null).load(com.earth2me.essentials.user.UserData.class);
|
||||||
|
ext.mark("reload file (cached)");
|
||||||
|
System.out.println(userdata.toString());
|
||||||
|
System.out.println(userdata2.toString());
|
||||||
|
System.out.println(ext.end());
|
||||||
|
}
|
||||||
|
catch (ObjectLoadException ex)
|
||||||
|
{
|
||||||
|
fail(ex.getMessage());
|
||||||
}
|
}
|
||||||
ext.mark("change home 10000 times");
|
|
||||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
final PrintWriter writer = new PrintWriter(baos);
|
|
||||||
new YamlStorageWriter(writer).save(userdata);
|
|
||||||
writer.close();
|
|
||||||
ext.mark("write user");
|
|
||||||
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
|
||||||
final PrintWriter writer2 = new PrintWriter(baos2);
|
|
||||||
new YamlStorageWriter(writer2).save(userdata);
|
|
||||||
writer2.close();
|
|
||||||
ext.mark("write user (cached)");
|
|
||||||
byte[] written = baos.toByteArray();
|
|
||||||
System.out.println(new String(written));
|
|
||||||
ext.mark("debug output");
|
|
||||||
final ByteArrayInputStream bais2 = new ByteArrayInputStream(written);
|
|
||||||
final Reader reader2 = new InputStreamReader(bais2);
|
|
||||||
final com.earth2me.essentials.user.UserData userdata2 = new YamlStorageReader(reader2, null).load(com.earth2me.essentials.user.UserData.class);
|
|
||||||
ext.mark("reload file");
|
|
||||||
final ByteArrayInputStream bais4 = new ByteArrayInputStream(written);
|
|
||||||
final Reader reader4 = new InputStreamReader(bais4);
|
|
||||||
final com.earth2me.essentials.user.UserData userdata4 = new YamlStorageReader(reader4, null).load(com.earth2me.essentials.user.UserData.class);
|
|
||||||
ext.mark("reload file (cached)");
|
|
||||||
System.out.println(userdata.toString());
|
|
||||||
System.out.println(userdata2.toString());
|
|
||||||
System.out.println(ext.end());
|
|
||||||
com.earth2me.essentials.user.User test = new com.earth2me.essentials.user.User(null, ess);
|
|
||||||
test.example();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ public class EssentialsSpawn extends JavaPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
spawns = new SpawnStorage(ess);
|
spawns = new SpawnStorage(ess);
|
||||||
|
ess.addReloadListener(spawns);
|
||||||
|
|
||||||
final EssentialsSpawnPlayerListener playerListener = new EssentialsSpawnPlayerListener(ess, spawns);
|
final EssentialsSpawnPlayerListener playerListener = new EssentialsSpawnPlayerListener(ess, spawns);
|
||||||
pluginManager.registerEvent(Type.PLAYER_RESPAWN, playerListener, Priority.Low, this);
|
pluginManager.registerEvent(Type.PLAYER_RESPAWN, playerListener, Priority.Low, this);
|
||||||
|
|
|
@ -6,12 +6,14 @@ import com.earth2me.essentials.IEssentialsModule;
|
||||||
import com.earth2me.essentials.settings.Spawns;
|
import com.earth2me.essentials.settings.Spawns;
|
||||||
import com.earth2me.essentials.storage.AbstractDelayedYamlFileReader;
|
import com.earth2me.essentials.storage.AbstractDelayedYamlFileReader;
|
||||||
import com.earth2me.essentials.storage.AbstractDelayedYamlFileWriter;
|
import com.earth2me.essentials.storage.AbstractDelayedYamlFileWriter;
|
||||||
|
import com.earth2me.essentials.storage.ObjectLoadException;
|
||||||
import com.earth2me.essentials.storage.StorageObject;
|
import com.earth2me.essentials.storage.StorageObject;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
@ -35,6 +37,10 @@ public class SpawnStorage implements IConf, IEssentialsModule
|
||||||
rwl.writeLock().lock();
|
rwl.writeLock().lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (spawns == null)
|
||||||
|
{
|
||||||
|
spawns = new Spawns();
|
||||||
|
}
|
||||||
if (spawns.getSpawns() == null)
|
if (spawns.getSpawns() == null)
|
||||||
{
|
{
|
||||||
spawns.setSpawns(new HashMap<String, Location>());
|
spawns.setSpawns(new HashMap<String, Location>());
|
||||||
|
@ -136,9 +142,21 @@ public class SpawnStorage implements IConf, IEssentialsModule
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFinish(final Spawns object)
|
public void onSuccess(final Spawns object)
|
||||||
{
|
{
|
||||||
spawns = object;
|
if (object != null)
|
||||||
|
{
|
||||||
|
spawns = object;
|
||||||
|
}
|
||||||
|
rwl.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException()
|
||||||
|
{
|
||||||
|
if (spawns == null) {
|
||||||
|
spawns = new Spawns();
|
||||||
|
}
|
||||||
rwl.writeLock().unlock();
|
rwl.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue