Make separated flat-file read/writes atomic (#2860)

This has become an issue as a result of removing the global per user/group/track IO locks in 478fddc486
This commit is contained in:
Luck 2021-01-29 11:42:37 +00:00
parent d12be01ecd
commit 35f5944d7b
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B

View File

@ -25,6 +25,7 @@
package me.lucko.luckperms.common.storage.implementation.file; package me.lucko.luckperms.common.storage.implementation.file;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate; import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
@ -35,6 +36,7 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.file.loader.ConfigurateLoader; import me.lucko.luckperms.common.storage.implementation.file.loader.ConfigurateLoader;
import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher; import me.lucko.luckperms.common.storage.implementation.file.watcher.FileWatcher;
import me.lucko.luckperms.common.storage.misc.NodeEntry; import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.util.CaffeineFactory;
import me.lucko.luckperms.common.util.Iterators; import me.lucko.luckperms.common.util.Iterators;
import me.lucko.luckperms.common.util.MoreFiles; import me.lucko.luckperms.common.util.MoreFiles;
import me.lucko.luckperms.common.util.Uuids; import me.lucko.luckperms.common.util.Uuids;
@ -53,6 +55,8 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -75,6 +79,8 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
private FileWatcher.WatchedLocation watcher; private FileWatcher.WatchedLocation watcher;
} }
private final LoadingCache<Path, ReentrantLock> ioLocks;
public SeparatedConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String fileExtension, String dataFolderName) { public SeparatedConfigurateStorage(LuckPermsPlugin plugin, String implementationName, ConfigurateLoader loader, String fileExtension, String dataFolderName) {
super(plugin, implementationName, loader, dataFolderName); super(plugin, implementationName, loader, dataFolderName);
this.fileExtension = fileExtension; this.fileExtension = fileExtension;
@ -89,6 +95,10 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
fileGroups.put(StorageLocation.GROUPS, this.groups); fileGroups.put(StorageLocation.GROUPS, this.groups);
fileGroups.put(StorageLocation.TRACKS, this.tracks); fileGroups.put(StorageLocation.TRACKS, this.tracks);
this.fileGroups = ImmutableMap.copyOf(fileGroups); this.fileGroups = ImmutableMap.copyOf(fileGroups);
this.ioLocks = CaffeineFactory.newBuilder()
.expireAfterAccess(10, TimeUnit.MINUTES)
.build(key -> new ReentrantLock());
} }
@Override @Override
@ -99,11 +109,17 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
} }
private ConfigurationNode readFile(Path file) throws IOException { private ConfigurationNode readFile(Path file) throws IOException {
ReentrantLock lock = Objects.requireNonNull(this.ioLocks.get(file));
lock.lock();
try {
if (!Files.exists(file)) { if (!Files.exists(file)) {
return null; return null;
} }
return this.loader.loader(file).load(); return this.loader.loader(file).load();
} finally {
lock.unlock();
}
} }
@Override @Override
@ -114,12 +130,18 @@ public class SeparatedConfigurateStorage extends AbstractConfigurateStorage {
} }
private void saveFile(Path file, ConfigurationNode node) throws IOException { private void saveFile(Path file, ConfigurationNode node) throws IOException {
ReentrantLock lock = Objects.requireNonNull(this.ioLocks.get(file));
lock.lock();
try {
if (node == null) { if (node == null) {
Files.deleteIfExists(file); Files.deleteIfExists(file);
return; return;
} }
this.loader.loader(file).save(node); this.loader.loader(file).save(node);
} finally {
lock.unlock();
}
} }
private Path getDirectory(StorageLocation location) { private Path getDirectory(StorageLocation location) {