Skip to content

Commit e04cad2

Browse files
Curly060flaix
authored andcommitted
bugfix: fix CPU hog bug in config save
1 parent 0910fbc commit e04cad2

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

src/main/java/com/gitblit/ConfigUserService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ private synchronized void write() throws IOException {
676676
// Write a temporary copy of the users file
677677
File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
678678

679-
StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
679+
StoredUserConfig config = new StoredUserConfig(realmFileCopy);
680680

681681
// write users
682682
for (UserModel model : users.values()) {
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.gitblit;
2+
3+
import java.io.File;
4+
import java.io.FileWriter;
5+
import java.io.IOException;
6+
import java.io.PrintWriter;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
import java.util.SortedMap;
11+
import java.util.TreeMap;
12+
import java.util.function.Function;
13+
14+
/**
15+
* Simple class with the only purpose to save the realm file (users.conf) in
16+
* a fast efficient manner. The JGit Config classes used previously caused
17+
* a massive CPU hog if the users file got bigger than about 30000 lines.
18+
*
19+
* @author Ingo Lafrenz
20+
*
21+
*/
22+
public class StoredUserConfig {
23+
24+
private final File realmFileCopy;
25+
private SortedMap<String, Section> sections = new TreeMap<>();
26+
27+
public StoredUserConfig(File realmFileCopy) {
28+
this.realmFileCopy = realmFileCopy;
29+
}
30+
31+
public void setString(final String section, final String subsection, String name, String value) {
32+
Section s = sections.computeIfAbsent(generateKey(section, subsection), new Function<String, Section>() {
33+
@Override
34+
public Section apply(String k) {
35+
return new Section(section, subsection);
36+
}
37+
});
38+
s.addEntry(name, value);
39+
}
40+
41+
public void setBoolean(String section, String subsection, String name, boolean value) {
42+
setString(section, subsection, name, String.valueOf(value));
43+
}
44+
45+
public void setStringList(String section, String subsection, String name, List<String> list) {
46+
for (String value : list) {
47+
setString(section, subsection, name, value);
48+
}
49+
}
50+
51+
public void save() throws IOException {
52+
try (FileWriter fileWriter = new FileWriter(realmFileCopy);
53+
PrintWriter printWriter = new PrintWriter(fileWriter);) {
54+
for (Map.Entry<String,Section> entry : sections.entrySet()) {
55+
writeSection(printWriter, entry.getKey(), entry.getValue());
56+
}
57+
}
58+
}
59+
60+
private static void writeSection(PrintWriter printWriter, String key, Section section) {
61+
printWriter.printf("[%s \"%s\"]\n", section.getName(), section.getSubSection());
62+
for (Entry entry : section.getEntries().values()) {
63+
writeEntry(printWriter, entry.getKey(), entry.getValue());
64+
}
65+
}
66+
67+
private static void writeEntry(PrintWriter printWriter, String key, String value) {
68+
printWriter.printf("\t%s = %s\n", key, escape(value));
69+
}
70+
71+
private static String escape(String value) {
72+
String fixedValue = '#' == value.charAt(0) ? "\"" + value + "\"" : value;
73+
fixedValue = fixedValue.replace("\\", "\\\\");
74+
return fixedValue;
75+
}
76+
77+
private static String generateKey(String key, String subKey) {
78+
return "k:" + key + "s:" + subKey;
79+
}
80+
81+
private static class Section {
82+
private final String name;
83+
private final String subSection;
84+
private final SortedMap<String, Entry> entries = new TreeMap<>();
85+
86+
public Section(String name, String subSection) {
87+
this.name = name;
88+
this.subSection = subSection;
89+
}
90+
91+
public void addEntry(final String key, final String value) {
92+
entries.put(generateKey(key, value), new Entry(key, value));
93+
}
94+
95+
public String getName() {
96+
return name;
97+
}
98+
99+
public String getSubSection() {
100+
return subSection;
101+
}
102+
103+
public SortedMap<String, Entry> getEntries() {
104+
return entries;
105+
}
106+
107+
@Override
108+
public int hashCode() {
109+
return Objects.hash(name, subSection);
110+
}
111+
112+
@Override
113+
public boolean equals(Object obj) {
114+
if (this == obj)
115+
return true;
116+
if (obj == null)
117+
return false;
118+
if (getClass() != obj.getClass())
119+
return false;
120+
Section other = (Section) obj;
121+
return Objects.equals(name, other.name) && Objects.equals(subSection, other.subSection);
122+
}
123+
124+
@Override
125+
public String toString() {
126+
return String.format("Section [name=%s, subSection=%s]", name, subSection);
127+
}
128+
129+
}
130+
131+
private static class Entry {
132+
private final String key;
133+
private final String value;
134+
135+
public Entry(String key, String value) {
136+
this.key = key;
137+
this.value = value;
138+
}
139+
140+
public String getKey() {
141+
return key;
142+
}
143+
144+
public String getValue() {
145+
return value;
146+
}
147+
148+
@Override
149+
public int hashCode() {
150+
return Objects.hash(key, value);
151+
}
152+
153+
@Override
154+
public boolean equals(Object obj) {
155+
if (this == obj)
156+
return true;
157+
if (obj == null)
158+
return false;
159+
if (getClass() != obj.getClass())
160+
return false;
161+
Entry other = (Entry) obj;
162+
return Objects.equals(key, other.key) && Objects.equals(value, other.value);
163+
}
164+
165+
@Override
166+
public String toString() {
167+
return String.format("Entry [key=%s, value=%s]", key, value);
168+
}
169+
170+
}
171+
172+
}

0 commit comments

Comments
 (0)