Summary: | Hot code replace does not replace all instances in multi-classloader scenario | ||
---|---|---|---|
Product: | [Eclipse Project] JDT | Reporter: | Michael Schierl <schierlm> |
Component: | Debug | Assignee: | JDT-Debug-Inbox <jdt-debug-inbox> |
Status: | REOPENED --- | QA Contact: | |
Severity: | normal | ||
Priority: | P3 | CC: | loskutov, sarika.sinha |
Version: | 4.6 | Keywords: | helpwanted |
Target Milestone: | --- | ||
Hardware: | All | ||
OS: | All | ||
Whiteboard: |
Description
Michael Schierl
2017-07-21 16:08:16 EDT
Did it ever worked for you? I see same behavior in 4.6.3 on OpenJDK 1.8.0_131. I honestly don't know semantic details of the JVM HCR implementation and classloader magic, but it looks like we call com.sun.jdi.VirtualMachine.redefineClasses(Map<? extends ReferenceType, byte[]>) with right argument, so probably there are some problems in the JVM to re-define classes with *same name* loaded at *same time* in different classloader instances. May be even a bug in the JVM? Have you tried to search the sun/oracle bug database? I cannot tell you how old this bug is, only that a colleague of mine has been seeing it for a few years already when debugging webapps running in Tomcat. As it seems to depend on how you interact with the application, he has run into the issue more often than me - I have seen and reproduced this bug last week for the first time. To rule out JDI issues, I tried my own quick&dirty (definitely *not* production quality) JDI debugger mockup, and it seems that as long as you ignore the event queue and only redefine classes that are returned by vm.allClasses(), it behaves like Eclipse behaves currently. But when adding a ClassPrepareRequest matching the class name, and redefining the newly prepared classes whenever the ClassPrepareRequest fires, I can no longer reproduce the issue. I am not sure whether this is a bug or a (missing) feature, i. e. whether JDI guarantees that all classes that have already started loading are included in allClasses() or not, and I also do not know whether it is feasible in Eclipse to add a ClassPrepareRequest for every class where hot code was replaced. For reference, here is my quick & dirty POC (add to a third project in the same workspace and run it: //////////////////////////////////// package jdimaster; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import com.sun.jdi.Bootstrap; import com.sun.jdi.ReferenceType; import com.sun.jdi.VirtualMachine; import com.sun.jdi.VirtualMachineManager; import com.sun.jdi.connect.Connector.Argument; import com.sun.jdi.connect.LaunchingConnector; import com.sun.jdi.event.ClassPrepareEvent; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.request.ClassPrepareRequest; public class JDITest { public static void main(String[] args) throws Exception { Path classFile = new File("../P2/bin/p2package/P2Class.class").toPath(); final byte[] classBytes = Files.readAllBytes(classFile); int stringPos = new String(classBytes, StandardCharsets.ISO_8859_1).indexOf("Change ") + 7; VirtualMachineManager vmm = Bootstrap.virtualMachineManager(); LaunchingConnector connector = vmm.defaultConnector(); Map<String, Argument> cArgs = connector.defaultArguments(); cArgs.get("options").setValue("-cp ../P1/bin"); cArgs.get("main").setValue("p1package.P1Class"); final VirtualMachine vm = connector.launch(cArgs); forward(vm.process().getInputStream(), System.out); forward(vm.process().getErrorStream(), System.err); ClassPrepareRequest req = vm.eventRequestManager().createClassPrepareRequest(); req.addClassFilter("p2package.P2Class"); req.enable(); // remove this to have Eclipse's current behaviour vm.resume(); EventQueue q = vm.eventQueue(); new Thread(() -> { try { while (true) { for (Event e : q.remove()) { if (e instanceof ClassPrepareEvent) { ClassPrepareEvent cpe = (ClassPrepareEvent) e; Map<ReferenceType, byte[]> map = new HashMap<>(); map.put(cpe.referenceType(), classBytes); vm.redefineClasses(map); vm.resume(); } System.out.println(">>> " + e.toString()); } } } catch (Exception ex) { ex.printStackTrace(); } }).start(); for (int i = 0;; i++) { Thread.sleep(7000); classBytes[stringPos] = (byte) ('0' + (i / 10 % 10)); classBytes[stringPos + 1] = (byte) ('0' + (i % 10)); Files.write(classFile, classBytes); Map<ReferenceType, byte[]> types = vm.allClasses().stream() .filter(rt -> rt.name().equals("p2package.P2Class")) .collect(Collectors.<ReferenceType, ReferenceType, byte[]>toMap(rt -> rt, rt -> classBytes)); vm.redefineClasses(types); System.out.println(">>> Redefined " + types.size()); } } private static void forward(InputStream in, PrintStream out) throws IOException { new Thread(() -> { try { byte[] buf = new byte[4096]; int len; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); out.flush(); } } catch (Exception ex) { ex.printStackTrace(); } }).start(); } } //////////////////////////////////// This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. As such, we're closing this bug. If you have further information on the current state of the bug, please add it and reopen this bug. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant. -- The automated Eclipse Genie. This bug is still present and relevant. In fact, I've hit it multiple times this month, and last time yesterday. I kind of learned to live with it and restart the debugging session when it happens, but I'm not happy about it since it sometimes takes 15 minutes or more to get back to the point where I started. In fact, of all the "stalebugs" this is the most relevant for me, and it is one of the points which makes me reconsider switching to IntelliJ sometimes (but perhaps I should have done so long time ago instead of watching Eclipse die a slow death with no real innovation any more...) Anyone willing to work on this? This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant. -- The automated Eclipse Genie. Bug is still relevant. [It is slightly creepy as I got hit by that exact bug at work today, and talked with a colleague about it (that I opened a bug report at Eclipse some years ago for it). Now when I come home and check my private email address, I see the Eclipse Genie message...] This bug hasn't had any activity in quite some time. Maybe the problem got resolved, was a duplicate of something else, or became less pressing for some reason - or maybe it's still relevant but just hasn't been looked at yet. If you have further information on the current state of the bug, please add it. The information can be, for example, that the problem still occurs, that you still want the feature, that more information is needed, or that the bug is (for whatever reason) no longer relevant. -- The automated Eclipse Genie. Bug is still relevant. Happened to me about a month ago on latest stable Eclipse. Was quite obvious as I had added a line before and the breakpoint stopped on an empty line. So I don't even need to actually try to reproduce it. |