Skip to content

Commit 0721081

Browse files
authored
Compute jar module descriptors in parallel (#57)
To compute a module descriptor, we need to compute the package list, and that is often slow. So do it in parallel! (And defer computation of the module descriptor until it is actually needed). Also added a new method to retrieve the provided services without computing the full module descriptor, for use in the `ModDirTransformerDiscoverer`. Tested in ATM8.
1 parent 7cd8481 commit 0721081

6 files changed

Lines changed: 150 additions & 40 deletions

File tree

src/main/java/cpw/mods/cl/JarModuleFinder.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ public class JarModuleFinder implements ModuleFinder {
2121
private final Map<String, ModuleReference> moduleReferenceMap;
2222

2323
JarModuleFinder(final SecureJar... jars) {
24-
record ref(SecureJar.ModuleDataProvider jar, ModuleReference ref) {}
2524
this.moduleReferenceMap = Arrays.stream(jars)
26-
.map(jar->new ref(jar.moduleDataProvider(), new JarModuleReference(jar.moduleDataProvider())))
27-
.collect(Collectors.toMap(r->r.jar.name(), r->r.ref, (r1, r2)->r1));
25+
// Computing the module descriptor can be slow so do it in parallel!
26+
// Jars are not thread safe internally, but they are independent, so this is safe.
27+
.parallel()
28+
// Note: Collectors.toMap() works fine with parallel streams.
29+
.collect(Collectors.toMap(jar -> jar.moduleDataProvider().name(), jar -> new JarModuleReference(jar.moduleDataProvider()), (r1, r2) -> r1));
2830
}
2931

3032
@Override

src/main/java/cpw/mods/jarhandling/JarMetadata.java

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import cpw.mods.jarhandling.impl.ModuleJarMetadata;
44
import cpw.mods.jarhandling.impl.SimpleJarMetadata;
5+
import org.jetbrains.annotations.Nullable;
56

67
import java.lang.module.ModuleDescriptor;
78
import java.nio.file.Path;
@@ -11,8 +12,20 @@
1112

1213
public interface JarMetadata {
1314
String name();
15+
@Nullable
1416
String version();
1517
ModuleDescriptor descriptor();
18+
19+
/**
20+
* {@return the provider declarations for this jar}
21+
*
22+
* <p>Computing the {@link #descriptor()} can be expensive as it requires scanning the jar for packages.
23+
* If only the service providers are needed, this method can be used instead.
24+
*/
25+
default List<SecureJar.Provider> providers() {
26+
return descriptor().provides().stream().map(p -> new SecureJar.Provider(p.service(), p.providers())).toList();
27+
}
28+
1629
// ALL from jdk.internal.module.ModulePath.java
1730
Pattern DASH_VERSION = Pattern.compile("-([.\\d]+)");
1831
Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
@@ -41,23 +54,24 @@ public interface JarMetadata {
4154
* from {@code Automatic-Module-Name} in the manifest.
4255
*/
4356
static JarMetadata from(JarContents jar) {
44-
final var pkgs = jar.getPackages();
4557
var mi = jar.findFile("module-info.class");
4658
if (mi.isPresent()) {
47-
return new ModuleJarMetadata(mi.get(), pkgs);
59+
return new ModuleJarMetadata(mi.get(), jar::getPackages);
4860
} else {
49-
var providers = jar.getMetaInfServices();
50-
var fileCandidate = fromFileName(jar.getPrimaryPath(), pkgs, providers);
51-
var aname = jar.getManifest().getMainAttributes().getValue("Automatic-Module-Name");
52-
if (aname != null) {
53-
return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
54-
} else {
55-
return fileCandidate;
61+
var nav = computeNameAndVersion(jar.getPrimaryPath());
62+
String name = nav.name();
63+
String version = nav.version();
64+
65+
String automaticModuleName = jar.getManifest().getMainAttributes().getValue("Automatic-Module-Name");
66+
if (automaticModuleName != null) {
67+
name = automaticModuleName;
5668
}
69+
70+
return new SimpleJarMetadata(name, version, jar::getPackages, jar.getMetaInfServices());
5771
}
5872
}
5973

60-
static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, final List<SecureJar.Provider> providers) {
74+
private static NameAndVersion computeNameAndVersion(Path path) {
6175
// detect Maven-like paths
6276
Path versionMaybe = path.getParent();
6377
if (versionMaybe != null)
@@ -73,29 +87,29 @@ static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, f
7387
if (mat.find()) {
7488
var potential = ver.substring(mat.start());
7589
ver = safeParseVersion(potential, path.getFileName().toString());
76-
return new SimpleJarMetadata(cleanModuleName(name), ver, pkgs, providers);
90+
return new NameAndVersion(cleanModuleName(name), ver);
7791
} else {
78-
return new SimpleJarMetadata(cleanModuleName(name), null, pkgs, providers);
92+
return new NameAndVersion(cleanModuleName(name), null);
7993
}
8094
}
8195
}
8296
}
8397

8498
// fallback parsing
85-
var fn = path.getFileName().toString();
99+
var fn = path.getFileName().toString();
86100
var lastDot = fn.lastIndexOf('.');
87101
if (lastDot > 0) {
88102
fn = fn.substring(0, lastDot); // strip extension if possible
89103
}
90-
104+
91105
var mat = DASH_VERSION.matcher(fn);
92106
if (mat.find()) {
93107
var potential = fn.substring(mat.start() + 1);
94108
var ver = safeParseVersion(potential, path.getFileName().toString());
95109
var name = mat.replaceAll("");
96-
return new SimpleJarMetadata(cleanModuleName(name), ver, pkgs, providers);
110+
return new NameAndVersion(cleanModuleName(name), ver);
97111
} else {
98-
return new SimpleJarMetadata(cleanModuleName(fn), null, pkgs, providers);
112+
return new NameAndVersion(cleanModuleName(fn), null);
99113
}
100114
}
101115

@@ -144,6 +158,15 @@ private static String cleanModuleName(String mn) {
144158
return mn;
145159
}
146160

161+
/**
162+
* @deprecated Build from jar contents directly using {@link #from(JarContents)}.
163+
*/
164+
@Deprecated(forRemoval = true, since = "2.1.23")
165+
static SimpleJarMetadata fromFileName(final Path path, final Set<String> pkgs, final List<SecureJar.Provider> providers) {
166+
var nav = computeNameAndVersion(path);
167+
return new SimpleJarMetadata(nav.name(), nav.version(), () -> pkgs, providers);
168+
}
169+
147170
/**
148171
* @deprecated Use {@link #from(JarContents)} instead.
149172
*/
@@ -153,13 +176,13 @@ static JarMetadata from(final SecureJar jar, final Path... path) {
153176
final var pkgs = jar.getPackages();
154177
var mi = jar.moduleDataProvider().findFile("module-info.class");
155178
if (mi.isPresent()) {
156-
return new ModuleJarMetadata(mi.get(), pkgs);
179+
return new ModuleJarMetadata(mi.get(), jar::getPackages);
157180
} else {
158181
var providers = jar.getProviders();
159182
var fileCandidate = fromFileName(path[0], pkgs, providers);
160183
var aname = jar.moduleDataProvider().getManifest().getMainAttributes().getValue("Automatic-Module-Name");
161184
if (aname != null) {
162-
return new SimpleJarMetadata(aname, fileCandidate.version(), pkgs, providers);
185+
return new SimpleJarMetadata(aname, fileCandidate.version(), () -> pkgs, providers);
163186
} else {
164187
return fileCandidate;
165188
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cpw.mods.jarhandling;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
import java.lang.module.ModuleDescriptor;
6+
7+
/**
8+
* Base class for {@link JarMetadata} implementations that lazily compute their descriptor.
9+
* This is recommended because descriptor computation can then run in parallel.
10+
*/
11+
public abstract class LazyJarMetadata implements JarMetadata {
12+
@Nullable
13+
private ModuleDescriptor descriptor;
14+
15+
@Override
16+
public final ModuleDescriptor descriptor() {
17+
if (descriptor == null) {
18+
descriptor = computeDescriptor();
19+
}
20+
return descriptor;
21+
}
22+
23+
/**
24+
* Computes the module descriptor for this jar.
25+
* This method is called at most once.
26+
*/
27+
protected abstract ModuleDescriptor computeDescriptor();
28+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cpw.mods.jarhandling;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
/**
6+
* Holder class for name and version of a module, used in {@link JarMetadata} computations.
7+
* Unfortunately, interfaces cannot hold private records (yet?).
8+
*/
9+
record NameAndVersion(String name, @Nullable String version) {}

src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package cpw.mods.jarhandling.impl;
22

33
import cpw.mods.jarhandling.JarMetadata;
4+
import cpw.mods.jarhandling.LazyJarMetadata;
5+
import org.jetbrains.annotations.Nullable;
46
import org.objectweb.asm.ClassReader;
57
import org.objectweb.asm.ClassVisitor;
68
import org.objectweb.asm.ModuleVisitor;
@@ -15,29 +17,42 @@
1517
import java.util.Arrays;
1618
import java.util.HashSet;
1719
import java.util.Set;
20+
import java.util.function.Supplier;
1821

1922
/**
2023
* {@link JarMetadata} implementation for a modular jar.
2124
* Reads the module descriptor from the jar.
2225
*/
23-
public class ModuleJarMetadata implements JarMetadata {
24-
private final ModuleDescriptor descriptor;
26+
public class ModuleJarMetadata extends LazyJarMetadata {
27+
private final ModuleClassVisitor mcv;
28+
private final Supplier<Set<String>> packagesSupplier;
2529

26-
public ModuleJarMetadata(final URI uri, final Set<String> packages) {
30+
public ModuleJarMetadata(URI uri, Supplier<Set<String>> packagesSupplier) {
2731
try (var is = Files.newInputStream(Path.of(uri))) {
2832
ClassReader cr = new ClassReader(is);
2933
var mcv = new ModuleClassVisitor();
3034
cr.accept(mcv, ClassReader.SKIP_CODE);
31-
mcv.mfv().packages().addAll(packages);
32-
mcv.mfv().builder().packages(mcv.mfv.packages());
33-
descriptor = mcv.mfv().builder().build();
35+
this.mcv = mcv;
3436
} catch (IOException e) {
3537
throw new UncheckedIOException(e);
3638
}
39+
40+
// Defer package scanning until computeDescriptor()
41+
this.packagesSupplier = packagesSupplier;
3742
}
3843

39-
private class ModuleClassVisitor extends ClassVisitor {
44+
@Override
45+
protected ModuleDescriptor computeDescriptor() {
46+
mcv.mfv().packages().addAll(packagesSupplier.get());
47+
mcv.mfv().builder().packages(mcv.mfv().packages());
48+
return mcv.mfv().builder().build();
49+
}
50+
51+
private static class ModuleClassVisitor extends ClassVisitor {
4052
private ModFileVisitor mfv;
53+
private String name;
54+
@Nullable
55+
private String version;
4156

4257
ModuleClassVisitor() {
4358
super(Opcodes.ASM9);
@@ -46,19 +61,23 @@ private class ModuleClassVisitor extends ClassVisitor {
4661
@Override
4762
public ModuleVisitor visitModule(final String name, final int access, final String version) {
4863
this.mfv = new ModFileVisitor(name, access, version);
64+
this.name = name;
65+
this.version = version;
4966
return this.mfv;
5067
}
5168

5269
public ModFileVisitor mfv() {
5370
return mfv;
5471
}
5572
}
56-
private class ModFileVisitor extends ModuleVisitor {
73+
74+
private static class ModFileVisitor extends ModuleVisitor {
5775
private final ModuleDescriptor.Builder builder;
5876
private final Set<String> packages = new HashSet<>();
5977

6078
public ModFileVisitor(final String name, final int access, final String version) {
6179
super(Opcodes.ASM9);
80+
// Note: we ignore the access flags and make every module open instead!
6281
builder = ModuleDescriptor.newOpenModule(name);
6382
if (version != null) builder.version(version);
6483
}
@@ -127,18 +146,15 @@ public Set<String> packages() {
127146
return packages;
128147
}
129148
}
149+
130150
@Override
131151
public String name() {
132-
return descriptor.name();
152+
return mcv.name;
133153
}
134154

135155
@Override
156+
@Nullable
136157
public String version() {
137-
return descriptor.version().toString();
138-
}
139-
140-
@Override
141-
public ModuleDescriptor descriptor() {
142-
return descriptor;
158+
return mcv.version;
143159
}
144160
}
Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,55 @@
11
package cpw.mods.jarhandling.impl;
22

33
import cpw.mods.jarhandling.JarMetadata;
4+
import cpw.mods.jarhandling.LazyJarMetadata;
45
import cpw.mods.jarhandling.SecureJar;
6+
import org.jetbrains.annotations.Nullable;
57

68
import java.lang.module.ModuleDescriptor;
9+
import java.util.Collections;
710
import java.util.List;
811
import java.util.Set;
12+
import java.util.function.Supplier;
913

1014
/**
1115
* {@link JarMetadata} implementation for a non-modular jar, turning it into an automatic module.
1216
*/
13-
public record SimpleJarMetadata(String name, String version, Set<String> pkgs, List<SecureJar.Provider> providers) implements JarMetadata {
17+
public class SimpleJarMetadata extends LazyJarMetadata implements JarMetadata {
18+
private final String name;
19+
private final String version;
20+
private final Supplier<Set<String>> packagesSupplier;
21+
private final List<SecureJar.Provider> providers;
22+
23+
public SimpleJarMetadata(String name, String version, Supplier<Set<String>> packagesSupplier, List<SecureJar.Provider> providers) {
24+
this.name = name;
25+
this.version = version;
26+
this.packagesSupplier = packagesSupplier;
27+
this.providers = providers.stream().filter(p -> !p.providers().isEmpty()).toList();
28+
}
29+
30+
@Override
31+
public String name() {
32+
return name;
33+
}
34+
1435
@Override
15-
public ModuleDescriptor descriptor() {
36+
@Nullable
37+
public String version() {
38+
return version;
39+
}
40+
41+
@Override
42+
public ModuleDescriptor computeDescriptor() {
1643
var bld = ModuleDescriptor.newAutomaticModule(name());
1744
if (version()!=null)
1845
bld.version(version());
19-
bld.packages(pkgs());
20-
providers.stream().filter(p->!p.providers().isEmpty()).forEach(p->bld.provides(p.serviceName(), p.providers()));
46+
bld.packages(packagesSupplier.get());
47+
providers.forEach(p->bld.provides(p.serviceName(), p.providers()));
2148
return bld.build();
2249
}
50+
51+
@Override
52+
public List<SecureJar.Provider> providers() {
53+
return Collections.unmodifiableList(providers);
54+
}
2355
}

0 commit comments

Comments
 (0)