Make RunMojo fork a new process
By forking a new process we get to attach the agent much earlier and JPA can co-exist. Fixes gh-648
This commit is contained in:
parent
f53ee406c1
commit
240788862e
|
|
@ -43,6 +43,13 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>springloaded</artifactId>
|
||||||
|
<version>1.2.0.RELEASE</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework</groupId>
|
<groupId>org.springframework</groupId>
|
||||||
<artifactId>springloaded</artifactId>
|
<artifactId>springloaded</artifactId>
|
||||||
<version>1.1.5.RELEASE</version>
|
<version>1.2.0.RELEASE</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2014 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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.springframework.boot.loader.tools;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides access to the java binary executable, regardless of OS.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
public class JavaExecutable {
|
||||||
|
|
||||||
|
private File file;
|
||||||
|
|
||||||
|
public JavaExecutable() {
|
||||||
|
String javaHome = System.getProperty("java.home");
|
||||||
|
Assert.state(StringUtils.hasLength(javaHome),
|
||||||
|
"Unable to find java executable due to missing 'java.home'");
|
||||||
|
this.file = findInJavaHome(javaHome);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File findInJavaHome(String javaHome) {
|
||||||
|
File bin = new File(new File(javaHome), "bin");
|
||||||
|
File command = new File(bin, "java.exe");
|
||||||
|
command = (command.exists() ? command : new File(bin, "java"));
|
||||||
|
Assert.state(command.exists(), "Unable to find java in " + javaHome);
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this used?
|
||||||
|
public ProcessBuilder processBuilder(String... arguments) {
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(toString());
|
||||||
|
processBuilder.command().addAll(Arrays.asList(arguments));
|
||||||
|
return processBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
return this.file.getCanonicalPath();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2014 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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.springframework.boot.loader.tools;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special utility used to run a process.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @author Dave Syer
|
||||||
|
*/
|
||||||
|
public class RunProcess {
|
||||||
|
|
||||||
|
private static final Method INHERIT_IO_METHOD = ReflectionUtils.findMethod(
|
||||||
|
ProcessBuilder.class, "inheritIO");
|
||||||
|
|
||||||
|
private static final long JUST_ENDED_LIMIT = 500;
|
||||||
|
|
||||||
|
private final String[] command;
|
||||||
|
|
||||||
|
private volatile Process process;
|
||||||
|
|
||||||
|
private volatile long endTime;
|
||||||
|
|
||||||
|
public RunProcess(String... command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run(String... args) throws Exception {
|
||||||
|
run(Arrays.asList(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void run(Collection<String> args) throws IOException {
|
||||||
|
ProcessBuilder builder = new ProcessBuilder(this.command);
|
||||||
|
builder.command().addAll(args);
|
||||||
|
builder.redirectErrorStream(true);
|
||||||
|
boolean inheritedIO = inheritIO(builder);
|
||||||
|
try {
|
||||||
|
this.process = builder.start();
|
||||||
|
if (!inheritedIO) {
|
||||||
|
redirectOutput(this.process);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.process.waitFor();
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.endTime = System.currentTimeMillis();
|
||||||
|
this.process = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean inheritIO(ProcessBuilder builder) {
|
||||||
|
try {
|
||||||
|
INHERIT_IO_METHOD.invoke(builder);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void redirectOutput(Process process) {
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
|
process.getInputStream()));
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
String line = reader.readLine();
|
||||||
|
while (line != null) {
|
||||||
|
System.out.println(line);
|
||||||
|
line = reader.readLine();
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the running process or {@code null}
|
||||||
|
*/
|
||||||
|
public Process getRunningProcess() {
|
||||||
|
return this.process;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the process was stopped.
|
||||||
|
*/
|
||||||
|
public boolean handleSigInt() {
|
||||||
|
|
||||||
|
// if the process has just ended, probably due to this SIGINT, consider handled.
|
||||||
|
if (hasJustEnded()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy the running process
|
||||||
|
Process process = this.process;
|
||||||
|
if (process != null) {
|
||||||
|
try {
|
||||||
|
process.destroy();
|
||||||
|
process.waitFor();
|
||||||
|
this.process = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasJustEnded() {
|
||||||
|
return System.currentTimeMillis() < (this.endTime + JUST_ENDED_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,8 @@ package org.springframework.boot.maven;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.security.CodeSource;
|
import java.security.CodeSource;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -37,9 +35,10 @@ import org.apache.maven.plugins.annotations.Mojo;
|
||||||
import org.apache.maven.plugins.annotations.Parameter;
|
import org.apache.maven.plugins.annotations.Parameter;
|
||||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||||
import org.apache.maven.project.MavenProject;
|
import org.apache.maven.project.MavenProject;
|
||||||
import org.springframework.boot.loader.tools.AgentAttacher;
|
|
||||||
import org.springframework.boot.loader.tools.FileUtils;
|
import org.springframework.boot.loader.tools.FileUtils;
|
||||||
|
import org.springframework.boot.loader.tools.JavaExecutable;
|
||||||
import org.springframework.boot.loader.tools.MainClassFinder;
|
import org.springframework.boot.loader.tools.MainClassFinder;
|
||||||
|
import org.springframework.boot.loader.tools.RunProcess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MOJO that can be used to run a executable archive application directly from Maven.
|
* MOJO that can be used to run a executable archive application directly from Maven.
|
||||||
|
|
@ -106,17 +105,6 @@ public class RunMojo extends AbstractMojo {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||||
findAgent();
|
|
||||||
if (this.agent != null) {
|
|
||||||
getLog().info("Attaching agent: " + this.agent);
|
|
||||||
if (this.noverify != null && this.noverify && !AgentAttacher.hasNoVerify()) {
|
|
||||||
throw new MojoExecutionException(
|
|
||||||
"The JVM must be started with -noverify for "
|
|
||||||
+ "this agent to work. You can use MAVEN_OPTS=-noverify "
|
|
||||||
+ "to add that flag.");
|
|
||||||
}
|
|
||||||
AgentAttacher.attach(this.agent);
|
|
||||||
}
|
|
||||||
final String startClassName = getStartClass();
|
final String startClassName = getStartClass();
|
||||||
run(startClassName);
|
run(startClassName);
|
||||||
}
|
}
|
||||||
|
|
@ -139,16 +127,43 @@ public class RunMojo extends AbstractMojo {
|
||||||
catch (ClassNotFoundException ex) {
|
catch (ClassNotFoundException ex) {
|
||||||
// ignore;
|
// ignore;
|
||||||
}
|
}
|
||||||
|
if (this.noverify == null) {
|
||||||
|
this.noverify = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void run(String startClassName) throws MojoExecutionException {
|
private void run(String startClassName) throws MojoExecutionException {
|
||||||
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
|
findAgent();
|
||||||
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
|
int extras = 0;
|
||||||
this.arguments), startClassName + ".main()");
|
if (this.agent != null) {
|
||||||
launchThread.setContextClassLoader(getClassLoader());
|
getLog().info("Attaching agent: " + this.agent);
|
||||||
launchThread.start();
|
extras = 1;
|
||||||
join(threadGroup);
|
}
|
||||||
threadGroup.rethrowUncaughtException();
|
if (this.noverify) {
|
||||||
|
extras++;
|
||||||
|
}
|
||||||
|
String[] args = new String[this.arguments.length + extras + 3];
|
||||||
|
System.arraycopy(this.arguments, 0, args, extras + 3, this.arguments.length);
|
||||||
|
if (extras > 0) {
|
||||||
|
args[0] = "-javaagent:" + this.agent;
|
||||||
|
}
|
||||||
|
if (this.noverify) {
|
||||||
|
args[1] = "-noverify";
|
||||||
|
}
|
||||||
|
args[extras + 2] = startClassName;
|
||||||
|
try {
|
||||||
|
StringBuilder classpath = new StringBuilder();
|
||||||
|
for (URL ele : getClassPathUrls()) {
|
||||||
|
classpath = classpath.append((classpath.length() > 0 ? File.pathSeparator
|
||||||
|
: "") + ele);
|
||||||
|
}
|
||||||
|
args[extras] = "-cp";
|
||||||
|
args[extras + 1] = classpath.toString();
|
||||||
|
new RunProcess(new JavaExecutable().toString()).run(args);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new MojoExecutionException("Could not exec java", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String getStartClass() throws MojoExecutionException {
|
private final String getStartClass() throws MojoExecutionException {
|
||||||
|
|
@ -168,11 +183,6 @@ public class RunMojo extends AbstractMojo {
|
||||||
return mainClass;
|
return mainClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClassLoader getClassLoader() throws MojoExecutionException {
|
|
||||||
URL[] urls = getClassPathUrls();
|
|
||||||
return new URLClassLoader(urls);
|
|
||||||
}
|
|
||||||
|
|
||||||
private URL[] getClassPathUrls() throws MojoExecutionException {
|
private URL[] getClassPathUrls() throws MojoExecutionException {
|
||||||
try {
|
try {
|
||||||
List<URL> urls = new ArrayList<URL>();
|
List<URL> urls = new ArrayList<URL>();
|
||||||
|
|
@ -223,92 +233,4 @@ public class RunMojo extends AbstractMojo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void join(ThreadGroup threadGroup) {
|
|
||||||
boolean hasNonDaemonThreads;
|
|
||||||
do {
|
|
||||||
hasNonDaemonThreads = false;
|
|
||||||
Thread[] threads = new Thread[threadGroup.activeCount()];
|
|
||||||
threadGroup.enumerate(threads);
|
|
||||||
for (Thread thread : threads) {
|
|
||||||
if (thread != null && !thread.isDaemon()) {
|
|
||||||
try {
|
|
||||||
hasNonDaemonThreads = true;
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
catch (InterruptedException ex) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (hasNonDaemonThreads);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Isolated {@link ThreadGroup} to capture uncaught exceptions.
|
|
||||||
*/
|
|
||||||
class IsolatedThreadGroup extends ThreadGroup {
|
|
||||||
|
|
||||||
private Throwable exception;
|
|
||||||
|
|
||||||
public IsolatedThreadGroup(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread thread, Throwable ex) {
|
|
||||||
if (!(ex instanceof ThreadDeath)) {
|
|
||||||
synchronized (this) {
|
|
||||||
this.exception = (this.exception == null ? ex : this.exception);
|
|
||||||
}
|
|
||||||
getLog().warn(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void rethrowUncaughtException() throws MojoExecutionException {
|
|
||||||
if (this.exception != null) {
|
|
||||||
throw new MojoExecutionException("An exception occured while running. "
|
|
||||||
+ this.exception.getMessage(), this.exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runner used to launch the application.
|
|
||||||
*/
|
|
||||||
class LaunchRunner implements Runnable {
|
|
||||||
|
|
||||||
private final String startClassName;
|
|
||||||
private final String[] args;
|
|
||||||
|
|
||||||
public LaunchRunner(String startClassName, String... args) {
|
|
||||||
this.startClassName = startClassName;
|
|
||||||
this.args = (args != null ? args : new String[] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Thread thread = Thread.currentThread();
|
|
||||||
ClassLoader classLoader = thread.getContextClassLoader();
|
|
||||||
try {
|
|
||||||
Class<?> startClass = classLoader.loadClass(this.startClassName);
|
|
||||||
Method mainMethod = startClass.getMethod("main",
|
|
||||||
new Class[] { String[].class });
|
|
||||||
if (!mainMethod.isAccessible()) {
|
|
||||||
mainMethod.setAccessible(true);
|
|
||||||
}
|
|
||||||
mainMethod.invoke(null, new Object[] { this.args });
|
|
||||||
}
|
|
||||||
catch (NoSuchMethodException ex) {
|
|
||||||
Exception wrappedEx = new Exception(
|
|
||||||
"The specified mainClass doesn't contain a "
|
|
||||||
+ "main method with appropriate signature.", ex);
|
|
||||||
thread.getThreadGroup().uncaughtException(thread, wrappedEx);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
thread.getThreadGroup().uncaughtException(thread, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue