Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seperate the load and save data sources #7100

Merged
merged 2 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions Towny/src/main/java/com/palmergames/bukkit/towny/TownyUniverse.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ public class TownyUniverse {

private final Map<WorldCoord, TownyMapData> wildernessMapDataMap = new ConcurrentHashMap<WorldCoord, TownyMapData>();
private final String rootFolder;
private TownyDataSource dataSource;
private TownyDataSource loadDataSource;
private TownyDataSource saveDataSource;
private TownyPermissionSource permissionSource;

private TownyUniverse() {
Expand Down Expand Up @@ -141,21 +142,17 @@ public void clearAllObjects() {
* @param loadDbType - load setting from the config.
* @param saveDbType - save setting from the config.
*/
void loadAndSaveDatabase(String loadDbType, String saveDbType) {
void loadAndSaveDatabase(String loadDbType, String saveDbType) throws TownyInitException {
towny.getLogger().info("Database: [Load] " + loadDbType + " [Save] " + saveDbType);
try {
// Try loading the database.
loadDatabase(loadDbType);
} catch (TownyInitException e) {
throw new TownyInitException(e.getMessage(), e.getError());

loadDatabase(loadDbType);
saveDatabase(saveDbType);

// Dispose of the load data source if it's no longer needed
if (this.loadDataSource != this.saveDataSource) {
this.loadDataSource.finishTasks();
this.loadDataSource = null;
}

try {
// Try saving the database.
saveDatabase(saveDbType);
} catch (TownyInitException e) {
throw new TownyInitException(e.getMessage(), e.getError());
}
}

/**
Expand All @@ -174,22 +171,25 @@ private boolean loadDatabase(String loadDbType) {
switch (loadDbType.toLowerCase(Locale.ROOT)) {
case "ff":
case "flatfile": {
this.dataSource = new TownyFlatFileSource(towny, this);
this.loadDataSource = new TownyFlatFileSource(towny, this);
break;
}
case "mysql": {
this.dataSource = new TownySQLSource(towny, this);
this.loadDataSource = new TownySQLSource(towny, this);
break;
}
default: {
throw new TownyInitException("Database: Database.yml unsupported load format: " + loadDbType, TownyInitException.TownyError.DATABASE_CONFIG);
}
}

// Loading the database can cause saving, so save that back to the load source
this.saveDataSource = this.loadDataSource;

/*
* Load the actual database.
*/
if (!dataSource.loadAll())
if (!loadDataSource.loadAll())
throw new TownyInitException("Database: Failed to load database.", TownyInitException.TownyError.DATABASE);

long time = System.currentTimeMillis() - startTime;
Expand All @@ -215,11 +215,11 @@ private boolean saveDatabase(String saveDbType) {
switch (saveDbType.toLowerCase(Locale.ROOT)) {
case "ff":
case "flatfile": {
this.dataSource = new TownyFlatFileSource(towny, this);
this.saveDataSource = this.loadDataSource instanceof TownyFlatFileSource ? this.loadDataSource : new TownyFlatFileSource(towny, this);
break;
}
case "mysql": {
this.dataSource = new TownySQLSource(towny, this);
this.saveDataSource = this.loadDataSource instanceof TownySQLSource ? this.loadDataSource : new TownySQLSource(towny, this);
break;
}
default: {
Expand All @@ -229,10 +229,10 @@ private boolean saveDatabase(String saveDbType) {

if (TownySettings.getLoadDatabase().equalsIgnoreCase(saveDbType)) {
// Update all Worlds data files
dataSource.saveAllWorlds();
saveDataSource.saveAllWorlds();
} else {
//Formats are different so save ALL data.
dataSource.saveAll();
saveDataSource.saveAll();
}
return true;
} catch (UnsupportedOperationException e) {
Expand Down Expand Up @@ -261,7 +261,7 @@ public void finishTasks() {
*/

public TownyDataSource getDataSource() {
return dataSource;
return saveDataSource;
}

public TownyPermissionSource getPermissionSource() {
Expand Down
25 changes: 17 additions & 8 deletions Towny/src/main/java/com/palmergames/bukkit/towny/db/SQLTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import java.util.Map;

@ApiStatus.Internal
public class SQLTask {
public class SQLTask implements Runnable {
private final TownySQLSource source;

// Update flags this for an insert/update or delete.
public final boolean update;
Expand All @@ -21,9 +22,9 @@ public class SQLTask {
* @param tb_name - Table name.
* @param args - Arguments.
*/
public SQLTask(String tb_name, Map<String, ?> args) {
public SQLTask(TownySQLSource source, String tb_name, Map<String, ?> args) {

this(false, tb_name, args, null);
this(source, false, tb_name, args, null);

}

Expand All @@ -34,19 +35,27 @@ public SQLTask(String tb_name, Map<String, ?> args) {
* @param args - Arguments.
* @param keys - Keys to add to table.
*/
public SQLTask(String tb_name, Map<String, ?> args, List<String> keys) {
public SQLTask(TownySQLSource source, String tb_name, Map<String, ?> args, List<String> keys) {

this(true, tb_name, args, keys);
this(source, true, tb_name, args, keys);

}

private SQLTask(boolean update, String tb_name, Map<String, ?> args, List<String> keys) {

private SQLTask(TownySQLSource source, boolean update, String tb_name, Map<String, ?> args, List<String> keys) {
this.source = source;
this.update = update;
this.tb_name = tb_name;
this.args = args;
this.keys = keys;

}

}
@Override
public void run() {
if (this.update) {
source.queueUpdateDB(this.tb_name, this.args, this.keys);
} else {
source.queueDeleteDB(this.tb_name, this.args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@ protected TownyDatabaseHandler(Towny plugin, TownyUniverse universe) {
}

/*
* Start our Async queue for pushing data to the flatfile database.
* Start our async queue for pushing data to the database.
*/
task = plugin.getScheduler().runAsyncRepeating(() -> {
while (!this.queryQueue.isEmpty()) {
Runnable operation = this.queryQueue.poll();
operation.run();
synchronized(queryQueue) {
while (!this.queryQueue.isEmpty()) {
Runnable operation = this.queryQueue.poll();
operation.run();
}
Comment on lines +123 to +127
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this synchronized, is this how we get around the issue that required the SQL source to use the isPolling variable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the idea yes

}
}, 5L, 5L);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.palmergames.bukkit.towny.object.WorldCoord;
import com.palmergames.bukkit.towny.object.metadata.MetadataLoader;
import com.palmergames.bukkit.towny.object.jail.Jail;
import com.palmergames.bukkit.towny.scheduling.ScheduledTask;
import com.palmergames.bukkit.towny.tasks.CooldownTimerTask;
import com.palmergames.bukkit.towny.utils.MapUtil;
import com.palmergames.bukkit.util.BukkitTools;
Expand Down Expand Up @@ -57,19 +56,13 @@
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.stream.Collectors;

public final class TownySQLSource extends TownyDatabaseHandler {

private final Queue<SQLTask> queryQueue = new ConcurrentLinkedQueue<>();
private boolean isPolling = false;
private final ScheduledTask task;

private final String tb_prefix;

private final HikariDataSource hikariDataSource;
Expand Down Expand Up @@ -140,47 +133,12 @@ public TownySQLSource(Towny plugin, TownyUniverse universe) {
} catch (SQLException e) {
logger.error("Failed to connect to the database", e);
}

/*
* Start our Async queue for pushing data to the database.
*/
task = plugin.getScheduler().runAsyncRepeating(() -> {
if (this.isPolling)
return;

this.isPolling = true;
try {
SQLTask query;
while ((query = this.queryQueue.poll()) != null) {
if (query.update) {
TownySQLSource.this.queueUpdateDB(query.tb_name, query.args, query.keys);
} else {
TownySQLSource.this.queueDeleteDB(query.tb_name, query.args);
}
}
} finally {
this.isPolling = false;
}

}, 5L, 5L);
}

@Override
public void finishTasks() {
// Cancel the repeating task as its not needed anymore.
if (task != null)
task.cancel();

// Make sure that *all* tasks are saved before shutting down.
while (!queryQueue.isEmpty()) {
SQLTask query = TownySQLSource.this.queryQueue.poll();
super.finishTasks();

if (query.update) {
TownySQLSource.this.queueUpdateDB(query.tb_name, query.args, query.keys);
} else {
TownySQLSource.this.queueDeleteDB(query.tb_name, query.args);
}
}
// Close the database sources on shutdown to get GC
if (hikariDataSource != null)
hikariDataSource.close();
Expand Down Expand Up @@ -232,7 +190,7 @@ public boolean updateDB(String tb_name, Map<String, ?> args, List<String> keys)
* Make sure we only execute queries in async
*/

this.queryQueue.add(new SQLTask(tb_name, args, keys));
this.queryQueue.add(new SQLTask(this, tb_name, args, keys));

return true;

Expand Down Expand Up @@ -398,7 +356,7 @@ public boolean DeleteDB(String tb_name, HashMap<String, Object> args) {

// Make sure we only execute queries in async

this.queryQueue.add(new SQLTask(tb_name, args));
this.queryQueue.add(new SQLTask(this, tb_name, args));

return true;

Expand Down