Skip to content

Commit 99e32ba

Browse files
committed
Supporting concurrent execution of CachedCompiler.loadFromJava
1 parent 47065a0 commit 99e32ba

File tree

3 files changed

+98
-16
lines changed

3 files changed

+98
-16
lines changed

src/main/java/net/openhft/compiler/CachedCompiler.java

+23-9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import java.io.IOException;
3333
import java.io.PrintWriter;
3434
import java.util.*;
35+
import java.util.concurrent.ConcurrentHashMap;
36+
import java.util.concurrent.ConcurrentMap;
3537

3638
import static net.openhft.compiler.CompilerUtils.*;
3739

@@ -48,8 +50,7 @@ public class CachedCompiler implements Closeable {
4850
@Nullable
4951
private final File classDir;
5052

51-
private final Map<String, JavaFileObject> javaFileObjects =
52-
new HashMap<String, JavaFileObject>();
53+
private final ConcurrentMap<String, JavaFileObject> javaFileObjects = new ConcurrentHashMap<>();
5354

5455
public CachedCompiler(@Nullable File sourceDir, @Nullable File classDir) {
5556
this.sourceDir = sourceDir;
@@ -97,7 +98,7 @@ Map<String, byte[]> compileFromJava(@NotNull String className,
9798

9899
} else {
99100
javaFileObjects.put(className, new JavaSourceFromString(className, javaCode));
100-
compilationUnits = javaFileObjects.values();
101+
compilationUnits = new ArrayList<>(javaFileObjects.values()); // To prevent CME from compiler code
101102
}
102103
// reuse the same file manager to allow caching of jar files
103104
List<String> options = Arrays.asList("-g", "-nowarn");
@@ -109,7 +110,7 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
109110
}
110111
}
111112
}, options, null, compilationUnits).call();
112-
Map<String, byte[]> result = fileManager.getAllBuffers();
113+
113114
if (!ok) {
114115
// compilation error, so we want to exclude this file from future compilation passes
115116
if (sourceDir == null)
@@ -118,7 +119,11 @@ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
118119
// nothing to return due to compiler error
119120
return Collections.emptyMap();
120121
}
121-
return result;
122+
else {
123+
Map<String, byte[]> result = fileManager.getAllBuffers();
124+
125+
return result;
126+
}
122127
}
123128

124129
public Class loadFromJava(@NotNull ClassLoader classLoader,
@@ -143,7 +148,8 @@ public Class loadFromJava(@NotNull ClassLoader classLoader,
143148
StandardJavaFileManager standardJavaFileManager = s_compiler.getStandardFileManager(null, null, null);
144149
fileManagerMap.put(classLoader, fileManager = new MyJavaFileManager(standardJavaFileManager));
145150
}
146-
for (Map.Entry<String, byte[]> entry : compileFromJava(className, javaCode, printWriter, fileManager).entrySet()) {
151+
final Map<String, byte[]> compiled = compileFromJava(className, javaCode, printWriter, fileManager);
152+
for (Map.Entry<String, byte[]> entry : compiled.entrySet()) {
147153
String className2 = entry.getKey();
148154
synchronized (loadedClassesMap) {
149155
if (loadedClasses.containsKey(className2))
@@ -157,9 +163,17 @@ public Class loadFromJava(@NotNull ClassLoader classLoader,
157163
LOG.info("Updated {} in {}", className2, classDir);
158164
}
159165
}
160-
Class clazz2 = CompilerUtils.defineClass(classLoader, className2, bytes);
161-
synchronized (loadedClassesMap) {
162-
loadedClasses.put(className2, clazz2);
166+
167+
synchronized (className2.intern()) { // To prevent duplicate class definition error
168+
synchronized (loadedClassesMap) {
169+
if (loadedClasses.containsKey(className2))
170+
continue;
171+
}
172+
173+
Class<?> clazz2 = CompilerUtils.defineClass(classLoader, className2, bytes);
174+
synchronized (loadedClassesMap) {
175+
loadedClasses.put(className2, clazz2);
176+
}
163177
}
164178
}
165179
synchronized (loadedClassesMap) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2014 Higher Frequency Trading
3+
*
4+
* http://chronicle.software
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package net.openhft.compiler;
20+
21+
import java.io.ByteArrayOutputStream;
22+
import java.util.concurrent.CompletableFuture;
23+
24+
public class CloseableByteArrayOutputStream extends ByteArrayOutputStream {
25+
private final CompletableFuture<?> closeFuture = new CompletableFuture<>();
26+
27+
@Override
28+
public void close() {
29+
closeFuture.complete(null);
30+
}
31+
32+
public CompletableFuture<?> closeFuture() {
33+
return closeFuture;
34+
}
35+
}

src/main/java/net/openhft/compiler/MyJavaFileManager.java

+40-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package net.openhft.compiler;
2020

2121
import org.jetbrains.annotations.NotNull;
22+
import org.slf4j.Logger;
23+
import org.slf4j.LoggerFactory;
2224
import sun.misc.Unsafe;
2325

2426
import javax.tools.*;
@@ -30,8 +32,12 @@
3032
import java.lang.reflect.Method;
3133
import java.net.URI;
3234
import java.util.*;
35+
import java.util.concurrent.ExecutionException;
36+
import java.util.concurrent.TimeUnit;
37+
import java.util.concurrent.TimeoutException;
3338

3439
class MyJavaFileManager implements JavaFileManager {
40+
private static final Logger LOG = LoggerFactory.getLogger(MyJavaFileManager.class);
3541
private final static Unsafe unsafe;
3642
private static final long OVERRIDE_OFFSET;
3743

@@ -50,7 +56,7 @@ class MyJavaFileManager implements JavaFileManager {
5056
private final StandardJavaFileManager fileManager;
5157

5258
// synchronizing due to ConcurrentModificationException
53-
private final Map<String, ByteArrayOutputStream> buffers = Collections.synchronizedMap(new LinkedHashMap<>());
59+
private final Map<String, CloseableByteArrayOutputStream> buffers = Collections.synchronizedMap(new LinkedHashMap<>());
5460

5561
MyJavaFileManager(StandardJavaFileManager fileManager) {
5662
this.fileManager = fileManager;
@@ -115,8 +121,13 @@ public JavaFileObject getJavaFileForOutput(Location location, final String class
115121
return new SimpleJavaFileObject(URI.create(className), kind) {
116122
@NotNull
117123
public OutputStream openOutputStream() {
118-
ByteArrayOutputStream baos = new ByteArrayOutputStream();
119-
buffers.put(className, baos);
124+
// CloseableByteArrayOutputStream.closed is used to filter partial results from getAllBuffers()
125+
CloseableByteArrayOutputStream baos = new CloseableByteArrayOutputStream();
126+
127+
// Reads from getAllBuffers() should be repeatable:
128+
// let's ignore compile result in case compilation of this class was triggered before
129+
buffers.putIfAbsent(className, baos);
130+
120131
return baos;
121132
}
122133
};
@@ -148,13 +159,35 @@ public void clearBuffers() {
148159

149160
@NotNull
150161
public Map<String, byte[]> getAllBuffers() {
162+
Map<String, byte[]> ret = new LinkedHashMap<>(buffers.size() * 2);
163+
Map<String, CloseableByteArrayOutputStream> compiledClasses = new LinkedHashMap<>(ret.size());
164+
151165
synchronized (buffers) {
152-
Map<String, byte[]> ret = new LinkedHashMap<>(buffers.size() * 2);
153-
for (Map.Entry<String, ByteArrayOutputStream> entry : buffers.entrySet()) {
154-
ret.put(entry.getKey(), entry.getValue().toByteArray());
166+
compiledClasses.putAll(buffers);
167+
}
168+
169+
for (Map.Entry<String, CloseableByteArrayOutputStream> e : compiledClasses.entrySet()) {
170+
try {
171+
// Await for compilation in case class is still being compiled in previous compiler run.
172+
e.getValue().closeFuture().get(30, TimeUnit.SECONDS);
173+
} catch (InterruptedException t) {
174+
Thread.currentThread().interrupt();
175+
176+
LOG.warn("Interrupted while waiting for compilation result [class=" + e.getKey() + "]");
177+
178+
break;
179+
} catch (ExecutionException | TimeoutException t) {
180+
LOG.warn("Failed to wait for compilation result [class=" + e.getKey() + "]", t);
181+
182+
continue;
155183
}
156-
return ret;
184+
185+
final byte[] value = e.getValue().toByteArray();
186+
187+
ret.put(e.getKey(), value);
157188
}
189+
190+
return ret;
158191
}
159192

160193
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)