Skip to content

Commit b08cd81

Browse files
authored
Fix EOF exceptions in Gradle daemon when generating new metadata (#4)
2 parents 80b6345 + f615a7b commit b08cd81

File tree

6 files changed

+78
-61
lines changed

6 files changed

+78
-61
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# AtPlug releases
22

33
## [Unreleased]
4+
### Fixed
5+
- The Gradle daemon would throw `EOFException` when adding new components in a warm daemon due to [Jar URL caching](https://stackoverflow.com/questions/36517604/closing-a-jarurlconnection), now fixed. ([#4](https://github.com/diffplug/atplug/pull/4))
46

57
## [0.1.1] - 2022-02-19
68
### Fixed

atplug-plugin-gradle/src/main/java/com/diffplug/atplug/tooling/gradle/PlugGenerateTask.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.io.BufferedInputStream;
2323
import java.io.BufferedOutputStream;
2424
import java.io.File;
25-
import java.io.FileInputStream;
26-
import java.io.FileOutputStream;
2725
import java.io.IOException;
2826
import java.io.InputStream;
2927
import java.io.OutputStream;
@@ -161,8 +159,7 @@ private File manifestFile() {
161159
private Manifest loadManifest() {
162160
Manifest manifest = new Manifest();
163161
if (manifestFile().isFile()) {
164-
try (InputStream raw = new FileInputStream(manifestFile());
165-
InputStream input = new BufferedInputStream(raw)) {
162+
try (InputStream input = new BufferedInputStream(Files.newInputStream(manifestFile().toPath()))) {
166163
manifest.read(input);
167164
} catch (IOException e) {
168165
throw new RuntimeException(e);
@@ -171,17 +168,16 @@ private Manifest loadManifest() {
171168
return manifest;
172169
}
173170

174-
private void saveManifest(Manifest manifest) throws IOException {
171+
private void saveManifest(Manifest manifest) {
175172
FileMisc.mkdirs(manifestFile().getParentFile());
176-
try (OutputStream raw = new FileOutputStream(manifestFile());
177-
OutputStream output = new BufferedOutputStream(raw)) {
173+
try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(manifestFile().toPath()))) {
178174
manifest.write(output);
179175
} catch (IOException e) {
180176
throw new RuntimeException(e);
181177
}
182178
}
183179

184-
private SortedMap<String, String> generate() throws Throwable {
180+
private SortedMap<String, String> generate() {
185181
PlugGeneratorJavaExecable input = new PlugGeneratorJavaExecable(new ArrayList<>(getClassesFolders().getFiles()), getJarsToLinkAgainst().getFiles());
186182
if (getLauncher().isPresent()) {
187183
WorkQueue workQueue = getWorkerExecutor().processIsolation(workerSpec -> {

atplug-runtime/src/main/java/com/diffplug/atplug/Metadata.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@
66
*/
77
package com.diffplug.atplug
88

9-
import java.lang.annotation.Documented
10-
119
/** Marks that a method is used to generate metadata, and should therefore return a constant. */
12-
@Documented
13-
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
10+
@Retention(AnnotationRetention.SOURCE)
1411
@Target(
1512
AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
1613
annotation class Metadata

atplug-runtime/src/main/java/com/diffplug/atplug/Plug.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ package com.diffplug.atplug
99
import kotlin.reflect.KClass
1010

1111
/** Annotation which signals that this class implements the given socket. */
12-
@kotlin.annotation.Retention(AnnotationRetention.BINARY)
12+
@Retention(AnnotationRetention.BINARY)
1313
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
1414
annotation class Plug(
1515
/** Socket type which this plug implements. */

atplug-runtime/src/main/java/com/diffplug/atplug/PlugInstanceMap.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class PlugInstanceMap {
1111
internal val instanceMap = mutableMapOf<PlugDescriptor, Any>()
1212

1313
fun putDescriptor(clazz: String, descriptor: PlugDescriptor) {
14-
val descriptors = descriptorMap.computeIfAbsent(clazz) { mutableListOf<PlugDescriptor>() }
14+
val descriptors = descriptorMap.computeIfAbsent(clazz) { mutableListOf() }
1515
descriptors.add(descriptor)
1616
}
1717

atplug-runtime/src/main/java/com/diffplug/atplug/PlugRegistry.kt

Lines changed: 69 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
package com.diffplug.atplug
88

99
import java.io.ByteArrayOutputStream
10+
import java.io.EOFException
1011
import java.lang.reflect.Constructor
1112
import java.net.URL
1213
import java.nio.charset.StandardCharsets
13-
import java.util.*
1414
import java.util.jar.Manifest
1515
import java.util.zip.ZipException
1616

@@ -33,22 +33,12 @@ interface PlugRegistry {
3333
private const val PATH_MANIFEST = "META-INF/MANIFEST.MF"
3434
private const val DS_WITHIN_MANIFEST = "AtPlug-Component"
3535

36-
internal fun parseComponent(manifestUrl: String, servicePath: String): PlugDescriptor {
37-
val serviceUrl =
38-
URL(manifestUrl.substring(0, manifestUrl.length - PATH_MANIFEST.length) + servicePath)
39-
40-
val out = ByteArrayOutputStream()
41-
serviceUrl.openStream().use { it.copyTo(out) }
42-
val serviceFileContent = String(out.toByteArray(), StandardCharsets.UTF_8)
43-
return PlugDescriptor.fromJson(serviceFileContent)
44-
}
45-
4636
fun setHarness(data: PlugInstanceMap?) {
4737
val registry = instance.value
4838
if (registry is Eager) {
4939
registry.setHarness(data)
5040
} else {
51-
throw AssertionError("Registry must not be set, was ${registry}")
41+
throw AssertionError("Registry must not be set, was $registry")
5242
}
5343
}
5444
}
@@ -62,49 +52,81 @@ interface PlugRegistry {
6252
val values = Eager::class.java.classLoader.getResources(PATH_MANIFEST)
6353
while (values.hasMoreElements()) {
6454
val manifestUrl = values.nextElement()
65-
manifestUrl.openStream().use { stream ->
66-
// parse the manifest
67-
val manifest = Manifest(stream)
68-
val services = manifest.mainAttributes.getValue(DS_WITHIN_MANIFEST)
69-
if (services != null) {
70-
// it's got declarative services!
71-
for (service in
72-
services.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
73-
val servicePath = service.trim { it <= ' ' }
74-
try {
75-
if (servicePath.isNotEmpty()) {
76-
val asString = manifestUrl.toExternalForm()
77-
val component = parseComponent(asString, servicePath)
78-
synchronized(this) {
79-
data.putDescriptor(component.provides, component)
80-
owners.get(component.provides)?.doRegister(component)
81-
}
82-
}
83-
} catch (e: ZipException) {
84-
// When a JVM loads a jar, it mmaps the jar. If that jar changes
85-
// (as it does when generating plugin metadata in a Gradle daemon)
86-
// then you get ZipException after the change. The accuracy of the
87-
// registry is irrelevant during metadata generation - the registry
88-
// exists during metadata generation only because the `SocketOwner`s
89-
// register themselves in their constructors. Therefore, it is safe to
90-
// ignore these errors during metadata generation.
91-
val prop = System.getProperty("atplug.generate")
92-
if (prop != "true") {
93-
throw e
94-
}
55+
try {
56+
parseManifest(manifestUrl, true)
57+
} catch (e: EOFException) {
58+
// do the parsing again but this time disable caching
59+
// https://stackoverflow.com/questions/36517604/closing-a-jarurlconnection
60+
parseManifest(manifestUrl, false)
61+
}
62+
}
63+
}
64+
}
65+
66+
private fun parseManifest(manifestUrl: URL, allowCaching: Boolean) {
67+
val connection = manifestUrl.openConnection()
68+
if (!allowCaching) {
69+
connection.useCaches = false
70+
}
71+
connection.getInputStream().use { stream ->
72+
// parse the manifest
73+
val manifest = Manifest(stream)
74+
val services = manifest.mainAttributes.getValue(DS_WITHIN_MANIFEST)
75+
if (services != null) {
76+
// it's got declarative services!
77+
for (service in
78+
services.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
79+
val servicePath = service.trim { it <= ' ' }
80+
try {
81+
if (servicePath.isNotEmpty()) {
82+
val asString = manifestUrl.toExternalForm()
83+
val component = parseComponent(asString, servicePath, allowCaching)
84+
synchronized(this) {
85+
data.putDescriptor(component.provides, component)
86+
owners[component.provides]?.doRegister(component)
9587
}
9688
}
89+
} catch (e: ZipException) {
90+
// When a JVM loads a jar, it mmaps the jar. If that jar changes
91+
// (as it does when generating plugin metadata in a Gradle daemon)
92+
// then you get ZipException after the change. The accuracy of the
93+
// registry is irrelevant during metadata generation - the registry
94+
// exists during metadata generation only because the `SocketOwner`s
95+
// register themselves in their constructors. Therefore, it is safe to
96+
// ignore these errors during metadata generation.
97+
val prop = System.getProperty("atplug.generate")
98+
if (prop != "true") {
99+
throw e
100+
}
97101
}
98102
}
99103
}
100104
}
101105
}
102106

107+
private fun parseComponent(
108+
manifestUrl: String,
109+
servicePath: String,
110+
allowCaching: Boolean
111+
): PlugDescriptor {
112+
val serviceUrl =
113+
URL(manifestUrl.substring(0, manifestUrl.length - PATH_MANIFEST.length) + servicePath)
114+
115+
val connection = serviceUrl.openConnection()
116+
if (!allowCaching) {
117+
connection.useCaches = false
118+
}
119+
val out = ByteArrayOutputStream()
120+
connection.getInputStream().use { it.copyTo(out) }
121+
val serviceFileContent = String(out.toByteArray(), StandardCharsets.UTF_8)
122+
return PlugDescriptor.fromJson(serviceFileContent)
123+
}
124+
103125
override fun <T> registerSocket(socketClass: Class<T>, socketOwner: SocketOwner<T>) {
104126
synchronized(this) {
105127
val prevOwner = owners.put(socketClass.name, socketOwner)
106-
assert(prevOwner == null) { "Multiple owners registered for ${socketClass}" }
107-
data.descriptorMap.get(socketClass.name)?.forEach(socketOwner::doRegister)
128+
assert(prevOwner == null) { "Multiple owners registered for $socketClass" }
129+
data.descriptorMap[socketClass.name]?.forEach(socketOwner::doRegister)
108130
}
109131
}
110132

@@ -128,7 +150,7 @@ interface PlugRegistry {
128150
"Class must have a no-arg constructor, but it didn't. " +
129151
clazz +
130152
" " +
131-
Arrays.asList(*clazz.constructors)
153+
listOf(*clazz.constructors)
132154
}
133155
return constructor.newInstance() as T
134156
}
@@ -138,12 +160,12 @@ interface PlugRegistry {
138160
fun setHarness(newHarness: PlugInstanceMap?) {
139161
val toRemove = lastHarness ?: data
140162
toRemove.descriptorMap.forEach { (clazz, plugDescriptors) ->
141-
owners.get(clazz)?.let { owner -> plugDescriptors.forEach(owner::doRemove) }
163+
owners[clazz]?.let { owner -> plugDescriptors.forEach(owner::doRemove) }
142164
}
143165

144166
val toAdd = newHarness ?: data
145167
toAdd.descriptorMap.forEach { (clazz, plugDescriptors) ->
146-
owners.get(clazz)?.let { owner -> plugDescriptors.forEach(owner::doRegister) }
168+
owners[clazz]?.let { owner -> plugDescriptors.forEach(owner::doRegister) }
147169
}
148170
lastHarness = newHarness
149171
}

0 commit comments

Comments
 (0)