From e04cad2726141653e9e2559a95a91d83ae5e4fa4 Mon Sep 17 00:00:00 2001 From: Ingo Lafrenz Date: Thu, 18 Mar 2021 15:45:02 +0100 Subject: bugfix: fix CPU hog bug in config save --- src/main/java/com/gitblit/ConfigUserService.java | 2 +- src/main/java/com/gitblit/StoredUserConfig.java | 172 +++++++++++++++++++++++ 2 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/gitblit/StoredUserConfig.java (limited to 'src') diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java index 025b1d8c..22d1c190 100644 --- a/src/main/java/com/gitblit/ConfigUserService.java +++ b/src/main/java/com/gitblit/ConfigUserService.java @@ -676,7 +676,7 @@ public class ConfigUserService implements IUserService { // Write a temporary copy of the users file File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp"); - StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect()); + StoredUserConfig config = new StoredUserConfig(realmFileCopy); // write users for (UserModel model : users.values()) { diff --git a/src/main/java/com/gitblit/StoredUserConfig.java b/src/main/java/com/gitblit/StoredUserConfig.java new file mode 100644 index 00000000..2161ebb2 --- /dev/null +++ b/src/main/java/com/gitblit/StoredUserConfig.java @@ -0,0 +1,172 @@ +package com.gitblit; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; + +/** + * Simple class with the only purpose to save the realm file (users.conf) in + * a fast efficient manner. The JGit Config classes used previously caused + * a massive CPU hog if the users file got bigger than about 30000 lines. + * + * @author Ingo Lafrenz + * + */ +public class StoredUserConfig { + + private final File realmFileCopy; + private SortedMap sections = new TreeMap<>(); + + public StoredUserConfig(File realmFileCopy) { + this.realmFileCopy = realmFileCopy; + } + + public void setString(final String section, final String subsection, String name, String value) { + Section s = sections.computeIfAbsent(generateKey(section, subsection), new Function() { + @Override + public Section apply(String k) { + return new Section(section, subsection); + } + }); + s.addEntry(name, value); + } + + public void setBoolean(String section, String subsection, String name, boolean value) { + setString(section, subsection, name, String.valueOf(value)); + } + + public void setStringList(String section, String subsection, String name, List list) { + for (String value : list) { + setString(section, subsection, name, value); + } + } + + public void save() throws IOException { + try (FileWriter fileWriter = new FileWriter(realmFileCopy); + PrintWriter printWriter = new PrintWriter(fileWriter);) { + for (Map.Entry entry : sections.entrySet()) { + writeSection(printWriter, entry.getKey(), entry.getValue()); + } + } + } + + private static void writeSection(PrintWriter printWriter, String key, Section section) { + printWriter.printf("[%s \"%s\"]\n", section.getName(), section.getSubSection()); + for (Entry entry : section.getEntries().values()) { + writeEntry(printWriter, entry.getKey(), entry.getValue()); + } + } + + private static void writeEntry(PrintWriter printWriter, String key, String value) { + printWriter.printf("\t%s = %s\n", key, escape(value)); + } + + private static String escape(String value) { + String fixedValue = '#' == value.charAt(0) ? "\"" + value + "\"" : value; + fixedValue = fixedValue.replace("\\", "\\\\"); + return fixedValue; + } + + private static String generateKey(String key, String subKey) { + return "k:" + key + "s:" + subKey; + } + + private static class Section { + private final String name; + private final String subSection; + private final SortedMap entries = new TreeMap<>(); + + public Section(String name, String subSection) { + this.name = name; + this.subSection = subSection; + } + + public void addEntry(final String key, final String value) { + entries.put(generateKey(key, value), new Entry(key, value)); + } + + public String getName() { + return name; + } + + public String getSubSection() { + return subSection; + } + + public SortedMap getEntries() { + return entries; + } + + @Override + public int hashCode() { + return Objects.hash(name, subSection); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Section other = (Section) obj; + return Objects.equals(name, other.name) && Objects.equals(subSection, other.subSection); + } + + @Override + public String toString() { + return String.format("Section [name=%s, subSection=%s]", name, subSection); + } + + } + + private static class Entry { + private final String key; + private final String value; + + public Entry(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Entry other = (Entry) obj; + return Objects.equals(key, other.key) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + return String.format("Entry [key=%s, value=%s]", key, value); + } + + } + +} -- cgit v1.2.3