Multithreading support refactoring

This commit is contained in:
konsoletyper 2014-01-27 17:15:28 +04:00
parent c9a891d9cd
commit 915add4d97
9 changed files with 327 additions and 81 deletions

View File

@ -53,6 +53,7 @@
<phase>process-test-classes</phase>
<configuration>
<minifying>false</minifying>
<numThreads>1</numThreads>
</configuration>
</execution>
</executions>

View File

@ -0,0 +1,28 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.common;
import java.util.concurrent.Executor;
/**
*
* @author Alexey Andreev
*/
public interface FiniteExecutor extends Executor {
void complete();
void executeFast(Runnable runnable);
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.common;
import java.util.LinkedList;
import java.util.Queue;
/**
*
* @author Alexey Andreev
*/
public class SimpleFiniteExecutor implements FiniteExecutor {
private Queue<Runnable> queue = new LinkedList<>();
@Override
public void execute(Runnable command) {
queue.add(command);
}
@Override
public void executeFast(Runnable runnable) {
execute(runnable);
}
@Override
public void complete() {
while (!queue.isEmpty()) {
queue.remove().run();
}
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.common;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
*
* @author Alexey Andreev
*/
public class ThreadPoolFiniteExecutor implements FiniteExecutor {
private List<Thread> threads = new ArrayList<>();
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
private AtomicInteger runningTasks = new AtomicInteger();
private final Object monitor = new Object();
private AtomicReference<RuntimeException> thrownException = new AtomicReference<>();
private ThreadLocal<Queue<Runnable>> localQueueues = new ThreadLocal<>();
public ThreadPoolFiniteExecutor(int numThreads) {
for (int i = 0; i < numThreads; ++i) {
Thread thread = new Thread() {
@Override public void run() {
takeTask();
}
};
threads.add(thread);
thread.start();
}
}
@Override
public void execute(Runnable command) {
runningTasks.incrementAndGet();
try {
queue.put(command);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void executeFast(Runnable runnable) {
localQueueues.get().add(runnable);
}
@Override
public void complete() {
synchronized (monitor) {
try {
monitor.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
if (thrownException.get() != null) {
throw thrownException.get();
}
if (runningTasks.get() == 0) {
return;
}
}
}
private void takeTask() {
Queue<Runnable> localQueue = new ArrayDeque<>();
localQueueues.set(localQueue);
try {
while (true) {
Runnable task = queue.take();
try {
task.run();
while (!localQueue.isEmpty()) {
localQueue.remove().run();
}
} catch (RuntimeException e) {
thrownException.set(e);
} finally {
if (runningTasks.decrementAndGet() == 0 || thrownException.get() != null) {
synchronized (monitor) {
monitor.notifyAll();
}
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void stop() {
for (Thread thread : threads) {
thread.interrupt();
}
}
}

View File

@ -18,10 +18,10 @@ package org.teavm.dependency;
import java.util.Collection;
import java.util.HashSet;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.teavm.common.ConcurrentCachedMapper;
import org.teavm.common.FiniteExecutor;
import org.teavm.common.Mapper;
import org.teavm.common.SimpleFiniteExecutor;
import org.teavm.common.ConcurrentCachedMapper.KeyListener;
import org.teavm.model.*;
@ -34,31 +34,21 @@ public class DependencyChecker {
static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true");
private ClassHolderSource classSource;
private ClassLoader classLoader;
private ScheduledThreadPoolExecutor executor;
private FiniteExecutor executor;
private ConcurrentMap<MethodReference, Object> abstractMethods = new ConcurrentHashMap<>();
private ConcurrentCachedMapper<MethodReference, MethodGraph> methodCache;
private ConcurrentCachedMapper<FieldReference, DependencyNode> fieldCache;
private ConcurrentMap<String, Object> achievableClasses = new ConcurrentHashMap<>();
private ConcurrentMap<String, Object> initializedClasses = new ConcurrentHashMap<>();
private AtomicReference<RuntimeException> exceptionOccured = new AtomicReference<>();
private AtomicInteger activeTaskCount = new AtomicInteger(0);
private final Object activeTaskMonitor = new Object();
public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader) {
this(classSource, classLoader, Runtime.getRuntime().availableProcessors());
this(classSource, classLoader, new SimpleFiniteExecutor());
}
public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader, int numThreads) {
public DependencyChecker(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) {
this.classSource = classSource;
this.classLoader = classLoader;
executor = new ScheduledThreadPoolExecutor(numThreads);
executor.setThreadFactory(new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
this.executor = executor;
methodCache = new ConcurrentCachedMapper<>(new Mapper<MethodReference, MethodGraph>() {
@Override public MethodGraph map(MethodReference preimage) {
return createMethodGraph(preimage);
@ -98,58 +88,15 @@ public class DependencyChecker {
}
public void schedulePropagation(final DependencyConsumer consumer, final String type) {
schedule(new Runnable() {
executor.executeFast(new Runnable() {
@Override public void run() {
consumer.consume(type);
}
});
}
void schedule(final Runnable runnable) {
synchronized (activeTaskMonitor) {
activeTaskCount.incrementAndGet();
}
try {
executor.execute(new Runnable() {
@Override public void run() {
try {
runnable.run();
} catch (RuntimeException e) {
activeTaskMonitor.notifyAll();
exceptionOccured.compareAndSet(null, e);
executor.shutdownNow();
}
synchronized (activeTaskMonitor) {
if (activeTaskCount.decrementAndGet() == 0) {
activeTaskMonitor.notifyAll();
}
}
}
});
} catch (RejectedExecutionException e) {
throw exceptionOccured.get();
}
}
public void checkDependencies() {
while (true) {
try {
synchronized (activeTaskMonitor) {
if (activeTaskCount.get() == 0 || exceptionOccured.get() != null) {
break;
}
activeTaskMonitor.wait();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
RuntimeException e = exceptionOccured.get();
if (e != null) {
throw exceptionOccured.get();
}
executor.shutdown();
public FiniteExecutor getExecutor() {
return executor;
}
boolean achieveClass(String className) {
@ -226,7 +173,7 @@ public class DependencyChecker {
}
final MethodGraph graph = new MethodGraph(parameterNodes, paramCount, resultNode, this);
final MethodHolder currentMethod = method;
schedule(new Runnable() {
executor.execute(new Runnable() {
@Override public void run() {
DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this);
graphBuilder.buildGraph(currentMethod, graph);

View File

@ -21,10 +21,10 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teavm.codegen.*;
import org.teavm.common.FiniteExecutor;
import org.teavm.dependency.DependencyChecker;
import org.teavm.javascript.ast.ClassNode;
import org.teavm.model.*;
import org.teavm.model.resource.ClasspathClassHolderSource;
import org.teavm.model.util.*;
import org.teavm.optimization.ClassSetOptimizer;
@ -35,25 +35,19 @@ import org.teavm.optimization.ClassSetOptimizer;
public class JavascriptBuilder {
private ClassHolderSource classSource;
private DependencyChecker dependencyChecker;
private FiniteExecutor executor;
private ClassLoader classLoader;
private boolean minifying = true;
private boolean bytecodeLogging = true;
private boolean bytecodeLogging;
private OutputStream logStream = System.out;
private Map<String, JavascriptEntryPoint> entryPoints = new HashMap<>();
private Map<String, String> exportedClasses = new HashMap<>();
public JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader) {
JavascriptBuilder(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) {
this.classSource = classSource;
this.classLoader = classLoader;
dependencyChecker = new DependencyChecker(classSource, classLoader);
}
public JavascriptBuilder(ClassLoader classLoader) {
this(new ClasspathClassHolderSource(classLoader), classLoader);
}
public JavascriptBuilder() {
this(JavascriptBuilder.class.getClassLoader());
dependencyChecker = new DependencyChecker(classSource, classLoader, executor);
this.executor = executor;
}
public boolean isMinifying() {
@ -107,7 +101,7 @@ public class JavascriptBuilder {
ValueType.object("java.lang.Class"))));
dependencyChecker.attachMethodGraph(new MethodReference("java.lang.String", new MethodDescriptor("<init>",
ValueType.arrayOf(ValueType.CHARACTER), ValueType.VOID)));
dependencyChecker.checkDependencies();
executor.complete();
ListableClassHolderSource classSet = dependencyChecker.cutUnachievableClasses();
Decompiler decompiler = new Decompiler(classSet, classLoader);
ClassSetOptimizer optimizer = new ClassSetOptimizer();

View File

@ -0,0 +1,58 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.javascript;
import org.teavm.common.FiniteExecutor;
import org.teavm.common.SimpleFiniteExecutor;
import org.teavm.model.ClassHolderSource;
/**
*
* @author Alexey Andreev
*/
public class JavascriptBuilderFactory {
ClassHolderSource classSource;
ClassLoader classLoader;
FiniteExecutor executor = new SimpleFiniteExecutor();
public ClassHolderSource getClassSource() {
return classSource;
}
public void setClassSource(ClassHolderSource classSource) {
this.classSource = classSource;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public FiniteExecutor getExecutor() {
return executor;
}
public void setExecutor(FiniteExecutor executor) {
this.executor = executor;
}
public JavascriptBuilder create() {
return new JavascriptBuilder(classSource, classLoader, executor);
}
}

View File

@ -34,7 +34,11 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.junit.Test;
import org.teavm.common.FiniteExecutor;
import org.teavm.common.SimpleFiniteExecutor;
import org.teavm.common.ThreadPoolFiniteExecutor;
import org.teavm.javascript.JavascriptBuilder;
import org.teavm.javascript.JavascriptBuilderFactory;
import org.teavm.model.*;
import org.teavm.model.resource.ClasspathClassHolderSource;
@ -68,6 +72,9 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo {
@Parameter
private boolean minifying = true;
@Parameter
private int numThreads = 1;
public void setProject(MavenProject project) {
this.project = project;
}
@ -88,8 +95,13 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo {
this.minifying = minifying;
}
public void setNumThreads(int numThreads) {
this.numThreads = numThreads;
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Runnable finalizer = null;
try {
ClassLoader classLoader = prepareClassLoader();
getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'");
@ -146,14 +158,29 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo {
}
int methodsGenerated = 0;
log.info("Generating test files");
FiniteExecutor executor = new SimpleFiniteExecutor();
if (numThreads != 1) {
int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors();
final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads);
finalizer = new Runnable() {
@Override public void run() {
threadedExecutor.stop();
}
};
executor = threadedExecutor;
}
for (MethodReference method : testMethods) {
log.debug("Building test for " + method);
decompileClassesForTest(classLoader, method, fileNames.get(method));
decompileClassesForTest(classLoader, method, fileNames.get(method), executor);
++methodsGenerated;
}
log.info("Test files successfully generated for " + methodsGenerated + " method(s)");
} catch (IOException e) {
throw new MojoFailureException("IO error occured generating JavaScript files", e);
} finally {
if (finalizer != null) {
finalizer.run();
}
}
}
@ -189,9 +216,13 @@ public class BuildJavascriptJUnitMojo extends AbstractMojo {
}
}
private void decompileClassesForTest(ClassLoader classLoader, MethodReference methodRef, String targetName)
throws IOException {
JavascriptBuilder builder = new JavascriptBuilder(classLoader);
private void decompileClassesForTest(ClassLoader classLoader, MethodReference methodRef, String targetName,
FiniteExecutor executor) throws IOException {
JavascriptBuilderFactory builderFactory = new JavascriptBuilderFactory();
builderFactory.setClassLoader(classLoader);
builderFactory.setClassSource(new ClasspathClassHolderSource(classLoader));
builderFactory.setExecutor(executor);
JavascriptBuilder builder = builderFactory.create();
builder.setMinifying(minifying);
File file = new File(outputDir, targetName);
try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) {

View File

@ -30,10 +30,13 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.teavm.common.ThreadPoolFiniteExecutor;
import org.teavm.javascript.JavascriptBuilder;
import org.teavm.javascript.JavascriptBuilderFactory;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.model.resource.ClasspathClassHolderSource;
/**
*
@ -71,6 +74,9 @@ public class BuildJavascriptMojo extends AbstractMojo {
@Parameter
private boolean bytecodeLogging;
@Parameter(required = false)
private int numThreads = 1;
public void setProject(MavenProject project) {
this.project = project;
}
@ -99,13 +105,31 @@ public class BuildJavascriptMojo extends AbstractMojo {
this.mainPageIncluded = mainPageIncluded;
}
public void setNumThreads(int numThreads) {
this.numThreads = numThreads;
}
@Override
public void execute() throws MojoExecutionException {
Log log = getLog();
Runnable finalizer = null;
try {
ClassLoader classLoader = prepareClassLoader();
log.info("Building JavaScript file");
JavascriptBuilder builder = new JavascriptBuilder(classLoader);
JavascriptBuilderFactory builderFactory = new JavascriptBuilderFactory();
builderFactory.setClassLoader(classLoader);
builderFactory.setClassSource(new ClasspathClassHolderSource(classLoader));
if (numThreads != 1) {
int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors();
final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads);
finalizer = new Runnable() {
@Override public void run() {
executor.stop();
}
};
builderFactory.setExecutor(executor);
}
JavascriptBuilder builder = builderFactory.create();
builder.setMinifying(minifying);
builder.setBytecodeLogging(bytecodeLogging);
MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf(
@ -133,6 +157,10 @@ public class BuildJavascriptMojo extends AbstractMojo {
throw new MojoExecutionException("Unexpected error occured", e);
} catch (IOException e) {
throw new MojoExecutionException("IO error occured", e);
} finally {
if (finalizer != null) {
finalizer.run();
}
}
}