diff --git a/springloaded/logging.properties b/springloaded/logging.properties index 7287864..c33fe3f 100644 --- a/springloaded/logging.properties +++ b/springloaded/logging.properties @@ -12,4 +12,4 @@ java.util.logging.ConsoleHandler.formatter = org.springsource.loaded.infra.SLFor # java.util.logging.ConsoleHandler.level = OFF # Set the default logging level for the logger named com.mycompany -org.springsource.level = ALL +org.springsource.level = FINEST diff --git a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java index 59050f7..25471a8 100644 --- a/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java +++ b/springloaded/src/main/java/org/springsource/loaded/agent/SpringLoadedPreProcessor.java @@ -159,6 +159,10 @@ public class SpringLoadedPreProcessor implements Constants { boolean isReloadableTypeName = typeRegistry.isReloadableTypeName(slashedClassName, protectionDomain, bytes); + if (isReloadableTypeName && GlobalConfiguration.explainMode && log.isLoggable(Level.INFO)) { + log.info("[explanation] Based on the name, type "+slashedClassName+" is considered to be reloadable"); + } + // logging causes a ClassCircularity problem when reporting on: // SL: Type 'org/codehaus/groovy/grails/cli/logging/GrailsConsolePrintStream' is not being made reloadable // if (GlobalConfiguration.verboseMode && isReloadableTypeName) { diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java b/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java index bf70abb..6608cb8 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ReloadableTypeTests.java @@ -77,6 +77,21 @@ public class ReloadableTypeTests extends SpringLoadedTests { assertEquals(7, r.returnValue); } + @Test + public void removingStaticMethod() throws Exception { + String t = "remote.Perf1"; + TypeRegistry typeRegistry = getTypeRegistry(t); + byte[] sc = loadBytesForClass(t); + ReloadableType rtype = typeRegistry.addType(t, sc); + + Class clazz = rtype.getClazz(); + runUnguarded(clazz, "time"); + + rtype.loadNewVersion("002", retrieveRename(t, "remote.Perf2")); + + runUnguarded(clazz, "time"); + } + @Test public void protectedFieldAccessors() throws Exception { TypeRegistry tr = getTypeRegistry("prot.SubOne"); diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java index 35f7c56..525580c 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVM.java @@ -37,6 +37,7 @@ public class ReloadingJVM { String javaclasspath; File testdataDirectory; Process process; + private boolean debug = false; DataInputStream reader; DataOutputStream writer; DataInputStream readerErrors; @@ -65,8 +66,9 @@ public class ReloadingJVM { agentJarLocation = search(searchLocation); } - private ReloadingJVM(String agentOptions) { + private ReloadingJVM(String agentOptions, boolean debug) { try { + this.debug = debug; javaclasspath = System.getProperty("java.class.path"); // Create a temporary folder where we can load/replace class files for the file watcher to observe @@ -81,7 +83,7 @@ public class ReloadingJVM { if (DEBUG_CLIENT_SIDE) { System.out.println("(client) Classpath for JVM that is being launched: " + javaclasspath); } - String OPTS = "";//"-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=y"; + String OPTS = debug?"-Xdebug -Xrunjdwp:transport=dt_socket,address=5100,server=y,suspend=y":""; String AGENT_OPTION_STRING = ""; if (agentOptions!=null && agentOptions.length()>0) { AGENT_OPTION_STRING = "-Dspringloaded="+agentOptions; @@ -93,6 +95,9 @@ public class ReloadingJVM { writer = new DataOutputStream(process.getOutputStream()); reader = new DataInputStream(process.getInputStream()); readerErrors = new DataInputStream(process.getErrorStream()); + if (debug) { + System.out.println("Debugging launched VM, port 5000"); + } JVMOutput text = waitFor("ReloadingJVM:started"); if (DEBUG_CLIENT_SIDE) { System.out.println(text); @@ -103,9 +108,14 @@ public class ReloadingJVM { } public static ReloadingJVM launch(String options) { - return new ReloadingJVM(options); + return new ReloadingJVM(options,false); } + public static ReloadingJVM launch(String options,boolean debug) { + return new ReloadingJVM(options,debug); + } + + private JVMOutput waitFor(String message) { return captureOutput(message); } @@ -144,25 +154,28 @@ public class ReloadingJVM { private JVMOutput captureOutput(String terminationString) { try { long time = System.currentTimeMillis(); - int timeout = 1000; // 1s timeout + int timeout = 1000+(debug?60000:0); // 1s timeout byte[] buf = new byte[1024]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); - while ((System.currentTimeMillis() - time) < timeout) { - while (reader.available() != 0) { - int read = reader.read(buf); + boolean found = false; + while ((System.currentTimeMillis() - time) < timeout && !found) { +// System.out.println("Waiting on ["+terminationString+"] so far: ["+baos.toString()+"]"); + while (readerErrors.available() != 0) { + int read = readerErrors.read(buf); baos.write(buf, 0, read); - if (baos.toString().indexOf(terminationString) != -1) { - break; - } } - } - String stdout = baos.toString(); - baos = new ByteArrayOutputStream(); - while (readerErrors.available() != 0) { - int read = readerErrors.read(buf); - baos.write(buf, 0, read); + if (baos.toString().indexOf(terminationString) != -1) { + found = true; + } + try { Thread.sleep(100); } catch (Exception e) {} } String stderr = baos.toString(); + baos = new ByteArrayOutputStream(); + while (reader.available() != 0) { + int read = reader.read(buf); + baos.write(buf, 0, read); + } + String stdout = baos.toString(); if (DEBUG_CLIENT_SIDE) { System.out.println("(client) >> received \n== STDOUT ==\n" + stdout + "\n== STDERR==\n" + stderr); } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java index 9121d5d..79bc9a0 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/ReloadingJVMCommandProcess.java @@ -36,7 +36,7 @@ import org.springsource.loaded.TypeRegistry; */ public class ReloadingJVMCommandProcess { public static void main(String[] argv) throws IOException { - System.err.println("(jvm) started"); + System.err.println("ReloadingJVM:started");System.err.flush(); try { DataInputStream br = new DataInputStream((System.in)); do { @@ -52,6 +52,7 @@ public class ReloadingJVMCommandProcess { // String[] args = (arguments.size() > 0 ? arguments.toArray(new String[arguments.size()]) : null); if (commandName.equals("exit")) { System.err.println("ReloadingJVM:terminating!!"); + System.exit(0); return; } else if (commandName.equals("echo")) { echoCommand(arguments); @@ -133,6 +134,7 @@ public class ReloadingJVMCommandProcess { Class clazz = o.getClass(); Method m = clazz.getDeclaredMethod(methodName); m.invoke(o); + System.err.println("!!"); } catch (Exception e) { e.printStackTrace(System.out); } @@ -148,6 +150,7 @@ public class ReloadingJVMCommandProcess { if (!b) { throw new IllegalStateException("Failed to reload new verion of "+classname); } + System.err.println("!!"); } catch (Exception e) { e.printStackTrace(System.out); } @@ -167,9 +170,9 @@ public class ReloadingJVMCommandProcess { System.err.println("(jvm) creating new instance '" + instanceName + "' of type '" + classname + "'"); Class clazz = Class.forName(classname); instances.put(instanceName, clazz.newInstance()); - System.err.println("(jvm) instance successfully created"); + System.err.println("(jvm) instance successfully created!!"); } catch (Exception e) { - System.out.println("(jvm) failed to create instance " + e.getMessage()); + System.out.println("(jvm) failed to create instance " + e.getMessage()+"!!"); e.printStackTrace(System.out); } diff --git a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java index c21808d..2cc8df1 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTests.java @@ -95,6 +95,8 @@ public abstract class SpringLoadedTests implements Constants { protected String GroovyTestDataPath = TestUtils.getPathToClasses("../testdata-groovy"); protected String AspectjrtJar = "../testdata/aspectjrt.jar"; protected String CodeJar = "../testdata/code.jar"; + // TODO [java8] replace this with project dependency when Java8 is out + protected String Java8CodeJar = "../testdata-java8/build/libs/testdata-java8.jar"; protected String GroovyrtJar = "../testdata-groovy/groovy-1.8.2.jar"; protected Result result; protected TypeRegistry registry; @@ -103,7 +105,7 @@ public abstract class SpringLoadedTests implements Constants { public void setup() throws Exception { SpringLoadedPreProcessor.disabled = true; NameRegistry.reset(); - binLoader = new TestClassLoader(toURLs(TestDataPath, AspectjrtJar,CodeJar), this.getClass().getClassLoader()); + binLoader = new TestClassLoader(toURLs(TestDataPath, AspectjrtJar, CodeJar, Java8CodeJar), this.getClass().getClassLoader()); } @After diff --git a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java index f71a2e8..a3458a9 100644 --- a/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java +++ b/springloaded/src/test/java/org/springsource/loaded/test/SpringLoadedTestsInSeparateJVM.java @@ -80,6 +80,36 @@ public class SpringLoadedTestsInSeparateJVM extends SpringLoadedTests { assertStdout("jvmtwo.Runner.run1() running", jvm.call("a", "run1")); } + @Test + public void reloadedPerformance() throws Exception { +// debug(); + jvm.newInstance("a","remote.Perf1"); + JVMOutput jo = jvm.call("a","time"); // 75ms + jo = jvm.call("a","time"); // 75ms + pause(5); + jvm.updateClass("remote.Perf1",retrieveRename("remote.Perf1","remote.Perf2")); + pause(2); + // In Perf2 the static method is gone, why does it give us a NSME? + jo = jvm.call("a","time"); // 150ms + System.out.println(jo); + } + + private final static void debug() { + jvm.shutdown(); + jvm = ReloadingJVM.launch("",true); + } + + private final static void debug(String options) { + jvm = ReloadingJVM.launch(options,true); + } + + private final static void pause(int seconds) { + try { + Thread.sleep(seconds*1000); + } catch (Exception e) {} + } + + @Test public void testReloadingInOtherVM() throws Exception { jvm.newInstance("a", "remote.One"); diff --git a/testdata/src/main/java/remote/Perf1.java b/testdata/src/main/java/remote/Perf1.java new file mode 100644 index 0000000..cb9ae05 --- /dev/null +++ b/testdata/src/main/java/remote/Perf1.java @@ -0,0 +1,55 @@ +package remote; + +import java.util.Random; + +public class Perf1 { + + int repeats = 100; + + public static void main(String[] args) { + time(); + } + + public static void time() { + timedrun(); + timedrun(); + timedrun(); + } + + private static void timedrun() { + long stime = System.currentTimeMillis(); + System.out.println(computepi(10000000)); + long etime = System.currentTimeMillis(); + System.out.println("took "+(etime-stime)+"ms"); + } + + public static double computepi(int iterations) { + Random randomGen = new Random(System.currentTimeMillis()); + int insideCount = 0; + for (int i = 1; i <= iterations; i++) { + insideCount += doOneIteration(randomGen); + } + return calc(iterations, insideCount); + } + + private static int doOneIteration(Random randomGen) { + double xPos = getRandom(randomGen); + double yPos = getRandom(randomGen); + return isInside( xPos, yPos); + } + + private static int isInside( double xPos, double yPos) { + double distance = Math.sqrt((xPos * xPos) + (yPos * yPos)); + if (distance<1.0) return 1; + else return 0; + } + + private static double getRandom(Random randomGen) { + return (randomGen.nextDouble()) * 2 - 1.0; + } + + private static double calc(int iterations, int insideCount) { + return 4.0 * (insideCount / (double)iterations); + } + +} diff --git a/testdata/src/main/java/remote/Perf2.java b/testdata/src/main/java/remote/Perf2.java new file mode 100644 index 0000000..8cadad5 --- /dev/null +++ b/testdata/src/main/java/remote/Perf2.java @@ -0,0 +1,49 @@ +package remote; + +import java.util.Random; + +public class Perf2 { + + int repeats = 100; + + public static void main(String[] args) { + time(); + } + + public static void time() { + long stime = System.currentTimeMillis(); + System.out.println(computepi(1000000)); + long etime = System.currentTimeMillis(); + System.out.println("took "+(etime-stime)+"ms"); + } + + public static double computepi(int iterations) { + Random randomGen = new Random(System.currentTimeMillis()); + int insideCount = 0; + for (int i = 1; i <= iterations; i++) { + insideCount += doOneIteration(randomGen); + } + return calc(iterations, insideCount); + } + + private static int doOneIteration(Random randomGen) { + double xPos = getRandom(randomGen); + double yPos = getRandom(randomGen); + return isInside( xPos, yPos); + } + + private static int isInside( double xPos, double yPos) { + double distance = Math.sqrt((xPos * xPos) + (yPos * yPos)); + if (distance<1.0) return 1; + else return 0; + } + + private static double getRandom(Random randomGen) { + return (randomGen.nextDouble()) * 2 - 1.0; + } + + private static double calc(int iterations, int insideCount) { + return 4.0 * (insideCount / (double)iterations); + } + +}