Skip to content

Commit 0047b0a

Browse files
committed
Improve startup cache loading performance
- use Data*Streams instead of Object*Streams - simplify file format (no XML) - code renovation and minor smaller optimizations result are 3-4x faster cache load times (readCache() and calculateParents methods) minor: - List -> Set for field solely used for contains() in inner loop - removed a test cases testing ThreadDeath as part of the cleanup
1 parent 26a05a6 commit 0047b0a

11 files changed

Lines changed: 241 additions & 211 deletions

File tree

platform/core.startup/src/org/netbeans/core/startup/ModuleList.java

Lines changed: 84 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@
2424
import java.io.BufferedInputStream;
2525
import java.io.ByteArrayInputStream;
2626
import java.io.CharArrayWriter;
27+
import java.io.DataInputStream;
2728
import java.io.DataOutputStream;
2829
import java.io.File;
2930
import java.io.FileNotFoundException;
3031
import java.io.IOException;
3132
import java.io.InputStream;
32-
import java.io.ObjectInputStream;
33-
import java.io.ObjectOutputStream;
3433
import java.io.OutputStream;
3534
import java.io.OutputStreamWriter;
3635
import java.io.PushbackInputStream;
@@ -51,6 +50,7 @@
5150
import java.util.jar.JarFile;
5251
import java.util.logging.Level;
5352
import java.util.logging.Logger;
53+
import java.util.stream.Collectors;
5454
import org.netbeans.DuplicateException;
5555
import org.netbeans.Events;
5656
import org.netbeans.InvalidException;
@@ -111,7 +111,7 @@ final class ModuleList implements Stamps.Updater {
111111
/** to fire events with */
112112
private final Events ev;
113113
/** map from code name (base)s to statuses of modules on disk */
114-
private final Map<String,DiskStatus> statuses = new HashMap<String,DiskStatus>(100);
114+
private final Map<String, DiskStatus> statuses = new HashMap<>(100);
115115
/** whether the initial round has been triggered or not */
116116
private boolean triggered = false;
117117
/** listener for changes in modules, etc.; see comment on class Listener */
@@ -197,8 +197,7 @@ private File findJarByName(String jar, String name) throws IOException {
197197
for (File candidate : jars) {
198198
int candidateMajor = -1;
199199
SpecificationVersion candidateSpec = null;
200-
JarFile jf = new JarFile(candidate);
201-
try {
200+
try (JarFile jf = new JarFile(candidate)) {
202201
java.util.jar.Attributes attr = jf.getManifest().getMainAttributes();
203202
String codename = attr.getValue("OpenIDE-Module");
204203
if (codename != null) {
@@ -211,8 +210,6 @@ private File findJarByName(String jar, String name) throws IOException {
211210
if (sv != null) {
212211
candidateSpec = new SpecificationVersion(sv);
213212
}
214-
} finally {
215-
jf.close();
216213
}
217214
if (newest == null || candidateMajor > major || (spec != null && candidateSpec != null && candidateSpec.compareTo(spec) > 0)) {
218215
newest = candidate;
@@ -434,7 +431,10 @@ private Object processStatusParam(String k, String v) throws NumberFormatExcepti
434431

435432
/** Just checks that all the right stuff is there.
436433
*/
437-
private void sanityCheckStatus(Map<String,Object> m) throws IOException {
434+
private static void sanityCheckStatus(Map<String, Object> m) throws IOException {
435+
if (m.isEmpty()) {
436+
throw new IOException("Must define properties"); // NOI18N
437+
}
438438
String jar = (String) m.get("jar"); // NOI18N
439439
if (jar == null) {
440440
throw new IOException("Must define jar param"); // NOI18N
@@ -612,45 +612,62 @@ private String readTo(InputStream is, char delim) throws IOException {
612612
}
613613
}
614614

615-
final Map<String,Map<String,Object>> readCache() {
615+
final Map<String, Map<String, Object>> readCache() {
616616
InputStream is = Stamps.getModulesJARs().asStream("all-modules.dat"); // NOI18N
617617
if (is == null) {
618618
// schedule write for later
619619
writeCache();
620620
return null;
621621
}
622622
LOG.log(Level.FINEST, "Reading cache all-modules.dat");
623-
try {
624-
ObjectInputStream ois = new ObjectInputStream(is);
625-
626-
Map<String,Map<String,Object>> ret = new HashMap<String, Map<String, Object>>(1333);
627-
while (is.available() > 0) {
628-
Map<String, Object> prop = readStatus(ois, false);
629-
if (prop == null) {
630-
LOG.log(Level.CONFIG, "Cache is invalid all-modules.dat");
631-
return null;
632-
}
633-
Set<?> deps;
634-
try {
635-
deps = (Set<?>) ois.readObject();
636-
} catch (ClassNotFoundException ex) {
637-
throw new IOException(ex);
638-
}
639-
prop.put("deps", deps);
640-
String cnb = (String)prop.get("name"); // NOI18N
641-
ret.put(cnb, prop);
623+
try (DataInputStream dis = new DataInputStream(is)) {
624+
Map<String, Map<String, Object>> cache = new HashMap<>(1333);
625+
while (dis.available() > 0) {
626+
Map<String, Object> props = readProps(dis);
627+
props.put("deps", readDeps(dis));
628+
cache.put((String) props.get("name"), props);
642629
}
643-
644-
645-
is.close();
646-
return ret;
630+
return cache;
647631
} catch (IOException ex) {
648632
LOG.log(Level.INFO, "Cannot read cache", ex);
649633
writeCache();
650634
return null;
651635
}
652636
}
653637

638+
private static Set<Dependency> readDeps(DataInputStream is) throws IOException {
639+
int depCount = is.readInt();
640+
if (depCount == 0) {
641+
return Set.of();
642+
}
643+
Set<Dependency> deps = new HashSet<>((int) Math.ceil(depCount / 0.75));
644+
for (int i = 0; i < depCount; i++) {
645+
deps.add(Dependency.read(is));
646+
}
647+
return deps;
648+
}
649+
650+
private static Map<String, Object> readProps(DataInputStream is) throws IOException {
651+
Map<String, Object> props = new HashMap<>(8);
652+
for (String str : is.readUTF().split(",")) {
653+
String[] entry = str.split("=");
654+
// must match computeProperties()
655+
try {
656+
Object val = switch (entry[0]) {
657+
case "name", "jar" -> entry[1];
658+
case "enabled", "autoload", "eager", "reloadable" -> Boolean.valueOf(entry[1]);
659+
case "startlevel" -> Integer.valueOf(entry[1]);
660+
default -> throw new IOException("unknown key " + entry[0]);
661+
};
662+
props.put(entry[0], val);
663+
} catch (IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
664+
throw new IOException("unexpected value", ex);
665+
}
666+
}
667+
sanityCheckStatus(props);
668+
return props;
669+
}
670+
654671
final void writeCache() {
655672
Stamps.getModulesJARs().scheduleSave(this, "all-modules.dat", false);
656673
}
@@ -661,14 +678,23 @@ public void cacheReady() {
661678

662679
@Override
663680
public void flushCaches(DataOutputStream os) throws IOException {
664-
ObjectOutputStream oss = new ObjectOutputStream(os);
665681
for (Module m : mgr.getModules()) {
666682
if (m.isFixed()) {
667683
continue;
668684
}
669-
Map<String, Object> prop = computeProperties(m);
670-
writeStatus(prop, oss);
671-
oss.writeObject(m.getDependencies());
685+
// props
686+
String list = computeProperties(m).entrySet()
687+
.stream()
688+
.map(e -> e.getKey() + "=" + e.getValue())
689+
.collect(Collectors.joining(","));
690+
os.writeUTF(list);
691+
692+
// deps
693+
Set<Dependency> deps = m.getDependencies();
694+
os.writeInt(deps.size());
695+
for (Dependency dep : deps) {
696+
dep.write(os);
697+
}
672698
}
673699
}
674700

@@ -694,7 +720,7 @@ private void writeStatus(Map<String, Object> m, OutputStream os) throws IOExcept
694720

695721
// Use TreeMap to sort the keys by name; since the module status files might
696722
// be version-controlled we want to avoid gratuitous format changes.
697-
for (Map.Entry<String, Object> entry: new TreeMap<String, Object>(m).entrySet()) {
723+
for (Map.Entry<String, Object> entry : new TreeMap<>(m).entrySet()) {
698724
String name = entry.getKey();
699725
if (
700726
name.equals("name") || // NOI18N
@@ -749,11 +775,8 @@ public void run() throws IOException {
749775
LOG.fine("ModuleList: (re)writing " + nue.file);
750776
FileLock lock = nue.file.lock();
751777
try {
752-
OutputStream os = nue.file.getOutputStream(lock);
753-
try {
778+
try (OutputStream os = nue.file.getOutputStream(lock)) {
754779
writeStatus(nue.diskProps, os);
755-
} finally {
756-
os.close();
757780
}
758781
} finally {
759782
lock.releaseLock();
@@ -947,9 +970,9 @@ private void moduleChanged(Module m, DiskStatus status) {
947970
/** Compute what properties we would want to store in XML
948971
* for this module. I.e. 'name', 'reloadable', etc.
949972
*/
950-
private Map<String,Object> computeProperties(Module m) {
973+
private Map<String, Object> computeProperties(Module m) {
951974
if (m.isFixed() || ! m.isValid()) throw new IllegalArgumentException("fixed or invalid: " + m); // NOI18N
952-
Map<String,Object> p = new HashMap<String,Object>();
975+
Map<String, Object> p = new HashMap<>(8);
953976
p.put("name", m.getCodeNameBase()); // NOI18N
954977
if (!m.isAutoload() && !m.isEager()) {
955978
p.put("enabled", m.isEnabled()); // NOI18N
@@ -960,8 +983,7 @@ private Map<String,Object> computeProperties(Module m) {
960983
if (m.getStartLevel() > 0) {
961984
p.put("startlevel", m.getStartLevel()); // NOI18N
962985
}
963-
if (m.getHistory() instanceof ModuleHistory) {
964-
ModuleHistory hist = (ModuleHistory) m.getHistory();
986+
if (m.getHistory() instanceof ModuleHistory hist) {
965987
p.put("jar", hist.getJar()); // NOI18N
966988
}
967989
return p;
@@ -990,6 +1012,7 @@ private final class Listener implements PropertyChangeListener, ErrorHandler, En
9901012
// Property change coming from ModuleManager or some known Module.
9911013

9921014
private boolean listening = true;
1015+
@Override
9931016
public void propertyChange(PropertyChangeEvent evt) {
9941017
if (! triggered) throw new IllegalStateException("Property change before trigger()"); // NOI18N
9951018
// REMEMBER this is inside *read* mutex, it is forbidden to even attempt
@@ -1036,15 +1059,19 @@ public void propertyChange(PropertyChangeEvent evt) {
10361059

10371060
// SAX stuff.
10381061

1062+
@Override
10391063
public void warning(SAXParseException e) throws SAXException {
10401064
LOG.log(Level.WARNING, null, e);
10411065
}
1066+
@Override
10421067
public void error(SAXParseException e) throws SAXException {
10431068
throw e;
10441069
}
1070+
@Override
10451071
public void fatalError(SAXParseException e) throws SAXException {
10461072
throw e;
10471073
}
1074+
@Override
10481075
public InputSource resolveEntity(String pubid, String sysid) throws SAXException, IOException {
10491076
if (pubid.equals(PUBLIC_ID)) {
10501077
if (VALIDATE_XML) {
@@ -1062,6 +1089,7 @@ public InputSource resolveEntity(String pubid, String sysid) throws SAXException
10621089

10631090
// Changes in Modules/ folder.
10641091

1092+
@Override
10651093
public void fileDeleted(FileEvent ev) {
10661094
if (isOurs(ev)) {
10671095
if (LOG.isLoggable(Level.FINE)) {
@@ -1073,6 +1101,7 @@ public void fileDeleted(FileEvent ev) {
10731101
fileDeleted0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
10741102
}
10751103

1104+
@Override
10761105
public void fileDataCreated(FileEvent ev) {
10771106
if (isOurs(ev)) {
10781107
if (LOG.isLoggable(Level.FINE)) {
@@ -1084,6 +1113,7 @@ public void fileDataCreated(FileEvent ev) {
10841113
fileCreated0(fo.getName(), fo.getExt()/*, ev.getTime()*/);
10851114
}
10861115

1116+
@Override
10871117
public void fileRenamed(FileRenameEvent ev) {
10881118
if (isOurs(ev)) {
10891119
throw new IllegalStateException("I don't rename anything! " + ev); // NOI18N
@@ -1124,6 +1154,7 @@ private void fileDeleted0(String name, String ext/*, long time*/) {
11241154
} // else ignore
11251155
}
11261156

1157+
@Override
11271158
public void fileChanged(FileEvent ev) {
11281159
if (isOurs(ev)) {
11291160
if (LOG.isLoggable(Level.FINE)) {
@@ -1150,9 +1181,11 @@ public void fileChanged(FileEvent ev) {
11501181
} // else ignore
11511182
}
11521183

1184+
@Override
11531185
public void fileFolderCreated(FileEvent ev) {
11541186
// ignore
11551187
}
1188+
@Override
11561189
public void fileAttributeChanged(FileAttributeEvent ev) {
11571190
// ignore
11581191
}
@@ -1549,7 +1582,7 @@ public void run() {
15491582
Map<String, Map<String, Object>> cache = readCache();
15501583
String[] names;
15511584
if (cache != null) {
1552-
names = cache.keySet().toArray(new String[cache.size()]);
1585+
names = cache.keySet().toArray(String[]::new);
15531586
} else {
15541587
FileObject[] children = folder.getChildren();
15551588
List<String> arr = new ArrayList<String>(children.length);
@@ -1572,7 +1605,7 @@ public void run() {
15721605
LOG.fine("Strange file encountered in modules folder: " + f);
15731606
}
15741607
}
1575-
names = arr.toArray(new String[0]);
1608+
names = arr.toArray(String[]::new);
15761609
}
15771610
ev.log(Events.MODULES_FILE_SCANNED, names.length);
15781611
XMLReader reader = null;
@@ -1635,12 +1668,12 @@ public void run() {
16351668
}
16361669
ModuleHistory history = new ModuleHistory(jar, "loaded from " + f); // NOI18N
16371670
Boolean reloadableB = (Boolean) props.get("reloadable"); // NOI18N
1638-
boolean reloadable = reloadableB != null ? reloadableB.booleanValue() : false;
1639-
boolean enabled = enabledB != null ? enabledB.booleanValue() : false;
1671+
boolean reloadable = reloadableB != null ? reloadableB : false;
1672+
boolean enabled = enabledB != null ? enabledB : false;
16401673
Boolean autoloadB = (Boolean) props.get("autoload"); // NOI18N
1641-
boolean autoload = autoloadB != null ? autoloadB.booleanValue() : false;
1674+
boolean autoload = autoloadB != null ? autoloadB : false;
16421675
Boolean eagerB = (Boolean) props.get("eager"); // NOI18N
1643-
boolean eager = eagerB != null ? eagerB.booleanValue() : false;
1676+
boolean eager = eagerB != null ? eagerB : false;
16441677
NbInstaller.register(name, props.get("deps")); // NOI18N
16451678
Integer startLevel = (Integer)props.get("startlevel"); // NOI18N
16461679
Module m = createModule(jarFile, history, reloadable, autoload, eager, startLevel);

0 commit comments

Comments
 (0)