mirror of
https://github.com/BentoBoxWorld/BentoBox.git
synced 2024-11-30 14:43:49 +01:00
Improved islands async deletion from database (#605)
Addresses https://github.com/BentoBoxWorld/BentoBox/issues/591
This commit is contained in:
parent
55b813b746
commit
75bbc13cf5
@ -120,8 +120,7 @@ public abstract class AbstractDatabaseHandler<T> {
|
|||||||
/**
|
/**
|
||||||
* Attempts to delete the object with the uniqueId
|
* Attempts to delete the object with the uniqueId
|
||||||
* @param uniqueId - uniqueId of object
|
* @param uniqueId - uniqueId of object
|
||||||
* @return true if successful, false if there is no such uniqueId
|
|
||||||
* @since 1.1
|
* @since 1.1
|
||||||
*/
|
*/
|
||||||
public abstract boolean deleteID(String uniqueId);
|
public abstract void deleteID(String uniqueId);
|
||||||
}
|
}
|
||||||
|
@ -79,9 +79,9 @@ public class Database<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save config object
|
* Save config object. Saving may be done async.
|
||||||
* @param instance to save
|
* @param instance to save
|
||||||
* @return true if successfully saved
|
* @return true if no immediate errors. If async, errors may occur later.
|
||||||
*/
|
*/
|
||||||
public boolean saveObject(T instance) {
|
public boolean saveObject(T instance) {
|
||||||
try {
|
try {
|
||||||
@ -106,11 +106,10 @@ public class Database<T> {
|
|||||||
/**
|
/**
|
||||||
* Attempts to delete the object with the uniqueId
|
* Attempts to delete the object with the uniqueId
|
||||||
* @param uniqueId - uniqueId of object
|
* @param uniqueId - uniqueId of object
|
||||||
* @return true if successful, false if there is no such uniqueId
|
|
||||||
* @since 1.1
|
* @since 1.1
|
||||||
*/
|
*/
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
return handler.deleteID(uniqueId);
|
handler.deleteID(uniqueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +127,7 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
|
// The filename of the JSON file is the value of uniqueId field plus .json. Sometimes the .json is already appended.
|
||||||
if (!uniqueId.endsWith(JSON)) {
|
if (!uniqueId.endsWith(JSON)) {
|
||||||
uniqueId = uniqueId + JSON;
|
uniqueId = uniqueId + JSON;
|
||||||
@ -140,12 +140,10 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
File file = new File(tableFolder, uniqueId);
|
File file = new File(tableFolder, uniqueId);
|
||||||
try {
|
try {
|
||||||
Files.delete(file.toPath());
|
Files.delete(file.toPath());
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
|
plugin.logError("Could not delete json database object! " + file.getName() + " - " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -158,7 +158,7 @@ public class MariaDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
String sb = "DELETE FROM `" +
|
String sb = "DELETE FROM `" +
|
||||||
dataObject.getCanonicalName() +
|
dataObject.getCanonicalName() +
|
||||||
"` WHERE uniqueId = ?";
|
"` WHERE uniqueId = ?";
|
||||||
@ -166,10 +166,8 @@ public class MariaDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
// UniqueId needs to be placed in quotes
|
// UniqueId needs to be placed in quotes
|
||||||
preparedStatement.setString(1, "\"" + uniqueId + "\"");
|
preparedStatement.setString(1, "\"" + uniqueId + "\"");
|
||||||
preparedStatement.execute();
|
preparedStatement.execute();
|
||||||
return preparedStatement.getUpdateCount() > 0;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,12 +117,11 @@ public class MongoDBDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
try {
|
try {
|
||||||
return collection.findOneAndDelete(new Document(MONGO_ID, uniqueId)) != null;
|
collection.findOneAndDelete(new Document(MONGO_ID, uniqueId));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,12 +42,12 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
private Connection connection;
|
private Connection connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIFO queue for saves. Note that the assumption here is that most database objects will be held
|
* FIFO queue for saves or deletions. Note that the assumption here is that most database objects will be held
|
||||||
* in memory because loading is not handled with this queue. That means that it is theoretically
|
* in memory because loading is not handled with this queue. That means that it is theoretically
|
||||||
* possible to load something before it has been saved. So, in general, load your objects and then
|
* possible to load something before it has been saved. So, in general, load your objects and then
|
||||||
* save them async only when you do not need the data again immediately.
|
* save them async only when you do not need the data again immediately.
|
||||||
*/
|
*/
|
||||||
private Queue<Runnable> saveQueue;
|
private Queue<Runnable> processQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async save task that runs repeatedly
|
* Async save task that runs repeatedly
|
||||||
@ -72,13 +72,13 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
// Check if the table exists in the database and if not, create it
|
// Check if the table exists in the database and if not, create it
|
||||||
createSchema();
|
createSchema();
|
||||||
saveQueue = new ConcurrentLinkedQueue<>();
|
processQueue = new ConcurrentLinkedQueue<>();
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
// Loop continuously
|
// Loop continuously
|
||||||
while (plugin.isEnabled() || !saveQueue.isEmpty()) {
|
while (plugin.isEnabled() || !processQueue.isEmpty()) {
|
||||||
while (!saveQueue.isEmpty()) {
|
while (!processQueue.isEmpty()) {
|
||||||
saveQueue.poll().run();
|
processQueue.poll().run();
|
||||||
}
|
}
|
||||||
// Clear the queue and then sleep
|
// Clear the queue and then sleep
|
||||||
try {
|
try {
|
||||||
@ -189,7 +189,7 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
String toStore = gson.toJson(instance);
|
String toStore = gson.toJson(instance);
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
// Async
|
// Async
|
||||||
saveQueue.add(() -> store(instance, toStore, sb));
|
processQueue.add(() -> store(instance, toStore, sb));
|
||||||
} else {
|
} else {
|
||||||
// Sync
|
// Sync
|
||||||
store(instance, toStore, sb);
|
store(instance, toStore, sb);
|
||||||
@ -206,8 +206,19 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see world.bentobox.bentobox.database.AbstractDatabaseHandler#deleteID(java.lang.String)
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
|
if (plugin.isEnabled()) {
|
||||||
|
processQueue.add(() -> delete(uniqueId));
|
||||||
|
} else {
|
||||||
|
delete(uniqueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(String uniqueId) {
|
||||||
String sb = "DELETE FROM `" +
|
String sb = "DELETE FROM `" +
|
||||||
dataObject.getCanonicalName() +
|
dataObject.getCanonicalName() +
|
||||||
"` WHERE uniqueId = ?";
|
"` WHERE uniqueId = ?";
|
||||||
@ -215,10 +226,8 @@ public class MySQLDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
|
|||||||
// UniqueId needs to be placed in quotes
|
// UniqueId needs to be placed in quotes
|
||||||
preparedStatement.setString(1, "\"" + uniqueId + "\"");
|
preparedStatement.setString(1, "\"" + uniqueId + "\"");
|
||||||
preparedStatement.execute();
|
preparedStatement.execute();
|
||||||
return preparedStatement.getUpdateCount() > 0;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
plugin.logError("Could not delete object " + dataObject.getCanonicalName() + " " + uniqueId + " " + e.getMessage());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +44,15 @@ import world.bentobox.bentobox.util.Util;
|
|||||||
* Managed by IslandsManager
|
* Managed by IslandsManager
|
||||||
* Responsible for team information as well.
|
* Responsible for team information as well.
|
||||||
*
|
*
|
||||||
* @author Tastybento
|
* @author tastybento
|
||||||
* @author Poslovitch
|
* @author Poslovitch
|
||||||
*/
|
*/
|
||||||
public class Island implements DataObject {
|
public class Island implements DataObject {
|
||||||
|
|
||||||
|
// True if this island is deleted and pending deletion from the database
|
||||||
|
@Expose
|
||||||
|
private boolean deleted = false;
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private String uniqueId = UUID.randomUUID().toString();
|
private String uniqueId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
@ -850,4 +854,18 @@ public class Island implements DataObject {
|
|||||||
public void setDoNotLoad(boolean doNotLoad) {
|
public void setDoNotLoad(boolean doNotLoad) {
|
||||||
this.doNotLoad = doNotLoad;
|
this.doNotLoad = doNotLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the deleted
|
||||||
|
*/
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param deleted the deleted to set
|
||||||
|
*/
|
||||||
|
public void setDeleted(boolean deleted) {
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
}
|
}
|
@ -65,7 +65,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
* possible to load something before it has been saved. So, in general, load your objects and then
|
* possible to load something before it has been saved. So, in general, load your objects and then
|
||||||
* save them async only when you do not need the data again immediately.
|
* save them async only when you do not need the data again immediately.
|
||||||
*/
|
*/
|
||||||
private Queue<Runnable> saveQueue;
|
private Queue<Runnable> processQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async save task that runs repeatedly
|
* Async save task that runs repeatedly
|
||||||
@ -87,13 +87,13 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
*/
|
*/
|
||||||
YamlDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
|
YamlDatabaseHandler(BentoBox plugin, Class<T> type, DatabaseConnector databaseConnector) {
|
||||||
super(plugin, type, databaseConnector);
|
super(plugin, type, databaseConnector);
|
||||||
saveQueue = new ConcurrentLinkedQueue<>();
|
processQueue = new ConcurrentLinkedQueue<>();
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
asyncSaveTask = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||||
// Loop continuously
|
// Loop continuously
|
||||||
while (plugin.isEnabled() || !saveQueue.isEmpty()) {
|
while (plugin.isEnabled() || !processQueue.isEmpty()) {
|
||||||
while (!saveQueue.isEmpty()) {
|
while (!processQueue.isEmpty()) {
|
||||||
saveQueue.poll().run();
|
processQueue.poll().run();
|
||||||
}
|
}
|
||||||
// Clear the queue and then sleep
|
// Clear the queue and then sleep
|
||||||
try {
|
try {
|
||||||
@ -439,7 +439,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
private void save(String name, String data, String path, Map<String, String> yamlComments) {
|
private void save(String name, String data, String path, Map<String, String> yamlComments) {
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
// Async
|
// Async
|
||||||
saveQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments));
|
processQueue.add(() -> ((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments));
|
||||||
} else {
|
} else {
|
||||||
// Sync for shutdown
|
// Sync for shutdown
|
||||||
((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments);
|
((YamlDatabaseConnector)databaseConnector).saveYamlFile(data, path, name, yamlComments);
|
||||||
@ -640,7 +640,15 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteID(String uniqueId) {
|
public void deleteID(String uniqueId) {
|
||||||
|
if (plugin.isEnabled()) {
|
||||||
|
processQueue.add(() -> delete(uniqueId));
|
||||||
|
} else {
|
||||||
|
delete(uniqueId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(String uniqueId) {
|
||||||
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
|
// The filename of the YAML file is the value of uniqueId field plus .yml. Sometimes the .yml is already appended.
|
||||||
if (!uniqueId.endsWith(YML)) {
|
if (!uniqueId.endsWith(YML)) {
|
||||||
uniqueId = uniqueId + YML;
|
uniqueId = uniqueId + YML;
|
||||||
@ -653,12 +661,10 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
File file = new File(tableFolder, uniqueId);
|
File file = new File(tableFolder, uniqueId);
|
||||||
try {
|
try {
|
||||||
Files.delete(file.toPath());
|
Files.delete(file.toPath());
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
|
plugin.logError("Could not delete yml database object! " + file.getName() + " - " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@ -668,7 +674,7 @@ public class YamlDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
|
|||||||
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
|
public void deleteObject(T instance) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
|
||||||
// Null check
|
// Null check
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
plugin.logError("YAM database request to delete a null. ");
|
plugin.logError("YAML database request to delete a null. ");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(instance instanceof DataObject)) {
|
if (!(instance instanceof DataObject)) {
|
||||||
|
@ -34,6 +34,7 @@ import world.bentobox.bentobox.api.events.IslandBaseEvent;
|
|||||||
import world.bentobox.bentobox.api.events.island.IslandEvent;
|
import world.bentobox.bentobox.api.events.island.IslandEvent;
|
||||||
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
|
import world.bentobox.bentobox.api.events.island.IslandEvent.Reason;
|
||||||
import world.bentobox.bentobox.api.localization.TextVariables;
|
import world.bentobox.bentobox.api.localization.TextVariables;
|
||||||
|
import world.bentobox.bentobox.api.logs.LogEntry;
|
||||||
import world.bentobox.bentobox.api.user.User;
|
import world.bentobox.bentobox.api.user.User;
|
||||||
import world.bentobox.bentobox.database.Database;
|
import world.bentobox.bentobox.database.Database;
|
||||||
import world.bentobox.bentobox.database.objects.Island;
|
import world.bentobox.bentobox.database.objects.Island;
|
||||||
@ -72,8 +73,12 @@ public class IslandsManager {
|
|||||||
// Island Cache
|
// Island Cache
|
||||||
@NonNull
|
@NonNull
|
||||||
private IslandCache islandCache;
|
private IslandCache islandCache;
|
||||||
|
// Quarantined islands
|
||||||
@NonNull
|
@NonNull
|
||||||
private Map<UUID, List<Island>> quarantineCache;
|
private Map<UUID, List<Island>> quarantineCache;
|
||||||
|
// Deleted islands
|
||||||
|
@NonNull
|
||||||
|
private List<String> deletedIslands;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Islands Manager
|
* Islands Manager
|
||||||
@ -87,6 +92,9 @@ public class IslandsManager {
|
|||||||
quarantineCache = new HashMap<>();
|
quarantineCache = new HashMap<>();
|
||||||
spawn = new HashMap<>();
|
spawn = new HashMap<>();
|
||||||
last = new HashMap<>();
|
last = new HashMap<>();
|
||||||
|
// This list should always be empty unless database deletion failed
|
||||||
|
// In that case a purge utility may be required in the future
|
||||||
|
deletedIslands = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -266,7 +274,13 @@ public class IslandsManager {
|
|||||||
if (removeBlocks) {
|
if (removeBlocks) {
|
||||||
// Remove island from the cache
|
// Remove island from the cache
|
||||||
islandCache.deleteIslandFromCache(island);
|
islandCache.deleteIslandFromCache(island);
|
||||||
// Remove the island from the database
|
// Log the deletion (it shouldn't matter but may be useful)
|
||||||
|
island.log(new LogEntry.Builder("DELETED").build());
|
||||||
|
// Set the delete flag which will prevent it from being loaded even if database deletion fails
|
||||||
|
island.setDeleted(true);
|
||||||
|
// Save the island
|
||||||
|
handler.saveObject(island);
|
||||||
|
// Delete the island
|
||||||
handler.deleteObject(island);
|
handler.deleteObject(island);
|
||||||
// Remove players from island
|
// Remove players from island
|
||||||
removePlayersFromIsland(island);
|
removePlayersFromIsland(island);
|
||||||
@ -703,9 +717,12 @@ public class IslandsManager {
|
|||||||
islandCache.clear();
|
islandCache.clear();
|
||||||
quarantineCache.clear();
|
quarantineCache.clear();
|
||||||
List<Island> toQuarantine = new ArrayList<>();
|
List<Island> toQuarantine = new ArrayList<>();
|
||||||
// Only load non-quarantined island
|
// Attempt to load islands
|
||||||
handler.loadObjects().stream().forEach(island -> {
|
handler.loadObjects().stream().forEach(island -> {
|
||||||
if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) {
|
if (island.isDeleted()) {
|
||||||
|
// These will be deleted later
|
||||||
|
deletedIslands.add(island.getUniqueId());
|
||||||
|
} else if (island.isDoNotLoad() && island.getWorld() != null && island.getCenter() != null) {
|
||||||
// Add to quarantine cache
|
// Add to quarantine cache
|
||||||
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
|
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
|
||||||
} else {
|
} else {
|
||||||
@ -809,8 +826,7 @@ public class IslandsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This removes players from an island overworld and nether - used when reseting or deleting an island
|
* This teleports players away from an island - used when reseting or deleting an island
|
||||||
* Mobs are killed when the chunks are refreshed.
|
|
||||||
* @param island to remove players from
|
* @param island to remove players from
|
||||||
*/
|
*/
|
||||||
public void removePlayersFromIsland(Island island) {
|
public void removePlayersFromIsland(Island island) {
|
||||||
|
Loading…
Reference in New Issue
Block a user