Improvement of debugger plugin. Debugger itself became multithreaded

This commit is contained in:
konsoletyper 2014-07-31 21:38:32 +04:00
parent 930d2087ab
commit c490e2f9f8
11 changed files with 127 additions and 121 deletions

View File

@ -2,6 +2,9 @@ package org.teavm.chromerdp;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.teavm.chromerdp.data.CallFrameDTO;
@ -16,16 +19,21 @@ import org.teavm.debugging.*;
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer {
private static final Object dummy = new Object();
private ChromeRDPExchange exchange;
private List<JavaScriptDebuggerListener> listeners = new ArrayList<>();
private Set<RDPBreakpoint> breakpoints = new HashSet<>();
private RDPCallFrame[] callStack = new RDPCallFrame[0];
private Map<String, String> scripts = new HashMap<>();
private Map<String, String> scriptIds = new HashMap<>();
private ConcurrentMap<JavaScriptDebuggerListener, Object> listeners = new ConcurrentHashMap<>();
private ConcurrentMap<RDPBreakpoint, Object> breakpoints = new ConcurrentHashMap<>();
private volatile RDPCallFrame[] callStack = new RDPCallFrame[0];
private ConcurrentMap<String, String> scripts = new ConcurrentHashMap<>();
private ConcurrentMap<String, String> scriptIds = new ConcurrentHashMap<>();
private boolean suspended = false;
private ObjectMapper mapper = new ObjectMapper();
private Map<Integer, ResponseHandler> responseHandlers = new HashMap<>();
private int messageIdGenerator;
private ConcurrentMap<Integer, ResponseHandler> responseHandlers = new ConcurrentHashMap<>();
private AtomicInteger messageIdGenerator = new AtomicInteger();
private List<JavaScriptDebuggerListener> getListeners() {
return new ArrayList<>(listeners.keySet());
}
@Override
public void setExchange(ChromeRDPExchange exchange) {
@ -37,15 +45,15 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
this.exchange = exchange;
if (exchange != null) {
for (RDPBreakpoint breakpoint : breakpoints) {
for (RDPBreakpoint breakpoint : breakpoints.keySet().toArray(new RDPBreakpoint[0])) {
updateBreakpoint(breakpoint);
}
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.attached();
}
} else {
suspended = false;
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.detached();
}
}
@ -86,7 +94,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
callStack[i] = map(callFrameDTOs[i]);
}
this.callStack = callStack;
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.paused();
}
}
@ -94,18 +102,17 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
private synchronized void fireResumed() {
suspended = false;
callStack = null;
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.resumed();
}
}
private synchronized void scriptParsed(ScriptParsedNotification params) {
if (scripts.containsKey(params.getScriptId())) {
if (scripts.putIfAbsent(params.getScriptId(), params.getUrl()) != null) {
return;
}
scripts.put(params.getScriptId(), params.getUrl());
scriptIds.put(params.getUrl(), params.getScriptId());
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.scriptAdded(params.getUrl());
}
}
@ -113,7 +120,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
@Override
public void addListener(JavaScriptDebuggerListener listener) {
listeners.add(listener);
listeners.put(listener, dummy);
}
@Override
@ -194,23 +201,32 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return exchange != null;
}
@Override
public void detach() {
if (exchange != null) {
exchange.disconnect();
}
}
@Override
public JavaScriptCallFrame[] getCallStack() {
if (exchange == null) {
return null;
}
JavaScriptCallFrame[] callStack = this.callStack;
return callStack != null ? callStack.clone() : null;
}
@Override
public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) {
RDPBreakpoint breakpoint = new RDPBreakpoint(this, location);
breakpoints.add(breakpoint);
breakpoints.put(breakpoint, dummy);
updateBreakpoint(breakpoint);
return breakpoint;
}
void destroyBreakpoint(RDPBreakpoint breakpoint) {
breakpoints.remove(breakpoint);
if (breakpoint.chromeId != null) {
Message message = new Message();
message.setMethod("Debugger.removeBreakpoint");
@ -222,7 +238,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
void fireScriptAdded(String script) {
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.scriptAdded(script);
}
}
@ -232,7 +248,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
return;
}
Message message = new Message();
message.setId(++messageIdGenerator);
message.setId(messageIdGenerator.incrementAndGet());
message.setMethod("Debugger.setBreakpoint");
SetBreakpointCommand params = new SetBreakpointCommand();
params.setLocation(unmap(breakpoint.getLocation()));
@ -241,7 +257,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
@Override public void received(JsonNode node) throws IOException {
SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node);
breakpoint.chromeId = response.getBreakpointId();
for (JavaScriptDebuggerListener listener : listeners) {
for (JavaScriptDebuggerListener listener : getListeners()) {
listener.breakpointChanged(breakpoint);
}
}

View File

@ -52,6 +52,15 @@ public class ChromeRDPDebuggerEndpoint implements ChromeRDPExchange {
}
}
@Override
public void disconnect() {
try {
session.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@OnMessage
public void receive(String message) throws IOException {
for (ChromeRDPExchangeListener listener : listeners) {

View File

@ -7,6 +7,8 @@ package org.teavm.chromerdp;
public interface ChromeRDPExchange {
void send(String message);
void disconnect();
void addListener(ChromeRDPExchangeListener listener);
void removeListener(ChromeRDPExchangeListener listener);

View File

@ -48,6 +48,6 @@ public class RDPBreakpoint implements JavaScriptBreakpoint {
@Override
public boolean isValid() {
return chromeId != null;
return chromeId != null && debugger != null && debugger.isAttached();
}
}

View File

@ -24,7 +24,7 @@ import java.util.List;
*/
public class Breakpoint {
private Debugger debugger;
List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
volatile List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
private SourceLocation location;
boolean valid;

View File

@ -16,6 +16,10 @@
package org.teavm.debugging;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import org.teavm.model.MethodReference;
/**
@ -25,16 +29,18 @@ import org.teavm.model.MethodReference;
// TODO: variable name handling
// TODO: class fields handling
public class Debugger {
private List<DebuggerListener> listeners = new ArrayList<>();
private static final Object dummyObject = new Object();
private ConcurrentMap<DebuggerListener, Object> listeners = new ConcurrentHashMap<>();
private JavaScriptDebugger javaScriptDebugger;
private DebugInformationProvider debugInformationProvider;
private List<JavaScriptBreakpoint> temporaryJsBreakpoints = new ArrayList<>();
private Map<String, DebugInformation> debugInformationMap = new HashMap<>();
private Map<String, List<DebugInformation>> debugInformationFileMap = new HashMap<>();
private Map<DebugInformation, String> scriptMap = new HashMap<>();
Map<JavaScriptBreakpoint, Breakpoint> breakpointMap = new HashMap<>();
Set<Breakpoint> breakpoints = new HashSet<>();
private CallFrame[] callStack;
private BlockingQueue<JavaScriptBreakpoint> temporaryBreakpoints = new LinkedBlockingQueue<>();
private ConcurrentMap<String, DebugInformation> debugInformationMap = new ConcurrentHashMap<>();
private ConcurrentMap<String, ConcurrentMap<DebugInformation, Object>> debugInformationFileMap =
new ConcurrentHashMap<>();
private ConcurrentMap<DebugInformation, String> scriptMap = new ConcurrentHashMap<>();
ConcurrentMap<JavaScriptBreakpoint, Breakpoint> breakpointMap = new ConcurrentHashMap<>();
ConcurrentMap<Breakpoint, Object> breakpoints = new ConcurrentHashMap<>();
private volatile CallFrame[] callStack;
public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) {
this.javaScriptDebugger = javaScriptDebugger;
@ -43,36 +49,36 @@ public class Debugger {
}
public void addListener(DebuggerListener listener) {
listeners.add(listener);
listeners.put(listener, dummyObject);
}
public void removeListener(DebuggerListener listener) {
listeners.remove(listener);
}
public void suspend() {
public synchronized void suspend() {
javaScriptDebugger.suspend();
}
public void resume() {
public synchronized void resume() {
javaScriptDebugger.resume();
}
public void stepInto() {
public synchronized void stepInto() {
javaScriptDebugger.stepInto();
}
public void stepOut() {
public synchronized void stepOut() {
javaScriptDebugger.stepOut();
}
public void stepOver() {
public synchronized void stepOver() {
javaScriptDebugger.stepOver();
}
private List<DebugInformation> debugInformationBySource(String sourceFile) {
List<DebugInformation> list = debugInformationFileMap.get(sourceFile);
return list != null ? list : Collections.<DebugInformation>emptyList();
Map<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
return list != null ? new ArrayList<>(list.keySet()) : Collections.<DebugInformation>emptyList();
}
public void continueToLocation(SourceLocation location) {
@ -90,7 +96,7 @@ public class Debugger {
location.getLine(), location.getColumn());
JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation);
if (jsBreakpoint != null) {
temporaryJsBreakpoints.add(jsBreakpoint);
temporaryBreakpoints.add(jsBreakpoint);
}
}
}
@ -107,14 +113,14 @@ public class Debugger {
public Breakpoint createBreakpoint(SourceLocation location) {
Breakpoint breakpoint = new Breakpoint(this, location);
breakpoints.add(breakpoint);
breakpoints.put(breakpoint, dummyObject);
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, false);
return breakpoint;
}
public Set<Breakpoint> getBreakpoints() {
return new HashSet<>(breakpoints);
return new HashSet<>(breakpoints.keySet());
}
void updateInternalBreakpoints(Breakpoint breakpoint) {
@ -122,7 +128,7 @@ public class Debugger {
breakpointMap.remove(jsBreakpoint);
jsBreakpoint.destroy();
}
breakpoint.jsBreakpoints.clear();
List<JavaScriptBreakpoint> jsBreakpoints = new ArrayList<>();
SourceLocation location = breakpoint.getLocation();
for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) {
Collection<GeneratedLocation> locations = debugInformation.getGeneratedLocations(location);
@ -130,10 +136,15 @@ public class Debugger {
JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation),
genLocation.getLine(), genLocation.getColumn());
JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation);
breakpoint.jsBreakpoints.add(jsBreakpoint);
jsBreakpoints.add(jsBreakpoint);
breakpointMap.put(jsBreakpoint, breakpoint);
}
}
breakpoint.jsBreakpoints = jsBreakpoints;
}
private DebuggerListener[] getListeners() {
return listeners.keySet().toArray(new DebuggerListener[0]);
}
void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) {
@ -146,7 +157,7 @@ public class Debugger {
if (breakpoint.valid != valid) {
breakpoint.valid = valid;
if (fireEvent) {
for (DebuggerListener listener : listeners) {
for (DebuggerListener listener : getListeners()) {
listener.breakpointStatusChanged(breakpoint);
}
}
@ -192,17 +203,23 @@ public class Debugger {
if (debugInfo == null) {
return;
}
debugInformationMap.put(name, debugInfo);
if (debugInformationMap.putIfAbsent(name, debugInfo) != null) {
return;
}
for (String sourceFile : debugInfo.getCoveredSourceFiles()) {
List<DebugInformation> list = debugInformationFileMap.get(sourceFile);
ConcurrentMap<DebugInformation, Object> list = debugInformationFileMap.get(sourceFile);
if (list == null) {
list = new ArrayList<>();
debugInformationFileMap.put(sourceFile, list);
list = new ConcurrentHashMap<>();
ConcurrentMap<DebugInformation, Object> existing = debugInformationFileMap.putIfAbsent(
sourceFile, list);
if (existing != null) {
list = existing;
}
}
list.add(debugInfo);
list.put(debugInfo, dummyObject);
}
scriptMap.put(debugInfo, name);
for (Breakpoint breakpoint : breakpoints) {
for (Breakpoint breakpoint : breakpoints.keySet()) {
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, true);
}
@ -212,40 +229,45 @@ public class Debugger {
return javaScriptDebugger.isAttached();
}
public void detach() {
javaScriptDebugger.detach();
}
void destroyBreakpoint(Breakpoint breakpoint) {
for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) {
jsBreakpoint.destroy();
breakpointMap.remove(jsBreakpoint);
}
breakpoint.jsBreakpoints.clear();
breakpoint.jsBreakpoints = new ArrayList<>();
breakpoints.remove(this);
}
private void fireResumed() {
for (JavaScriptBreakpoint jsBreakpoint : temporaryJsBreakpoints) {
List<JavaScriptBreakpoint> termporaryBreakpoints = new ArrayList<>();
this.temporaryBreakpoints.drainTo(termporaryBreakpoints);
for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) {
jsBreakpoint.destroy();
}
temporaryJsBreakpoints.clear();
for (DebuggerListener listener : listeners) {
for (DebuggerListener listener : getListeners()) {
listener.resumed();
}
}
private void fireAttached() {
for (Breakpoint breakpoint : breakpoints) {
for (Breakpoint breakpoint : breakpoints.keySet()) {
updateInternalBreakpoints(breakpoint);
updateBreakpointStatus(breakpoint, false);
}
for (DebuggerListener listener : listeners) {
for (DebuggerListener listener : getListeners()) {
listener.attached();
}
}
private void fireDetached() {
for (Breakpoint breakpoint : breakpoints) {
for (Breakpoint breakpoint : breakpoints.keySet()) {
updateBreakpointStatus(breakpoint, false);
}
for (DebuggerListener listener : listeners) {
for (DebuggerListener listener : getListeners()) {
listener.detached();
}
}
@ -266,7 +288,7 @@ public class Debugger {
@Override
public void paused() {
callStack = null;
for (DebuggerListener listener : listeners) {
for (DebuggerListener listener : getListeners()) {
listener.paused();
}
}

View File

@ -40,6 +40,8 @@ public interface JavaScriptDebugger {
boolean isAttached();
void detach();
JavaScriptCallFrame[] getCallStack();
JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location);

View File

@ -14,6 +14,7 @@ import org.teavm.chromerdp.ChromeRDPServer;
import org.teavm.debugging.Breakpoint;
import org.teavm.debugging.Debugger;
import org.teavm.debugging.DebuggerListener;
import org.teavm.debugging.JavaScriptDebugger;
/**
*
@ -23,6 +24,7 @@ import org.teavm.debugging.DebuggerListener;
public class TeaVMDebugTarget implements IDebugTarget, IStep {
ILaunch launch;
Debugger teavmDebugger;
JavaScriptDebugger jsDebugger;
private ChromeRDPServer server;
private boolean terminated;
private TeaVMDebugProcess process;
@ -90,6 +92,7 @@ public class TeaVMDebugTarget implements IDebugTarget, IStep {
terminated = true;
server.stop();
fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
fireEvent(new DebugEvent(thread, DebugEvent.TERMINATE));
}
@Override
@ -187,6 +190,7 @@ public class TeaVMDebugTarget implements IDebugTarget, IStep {
@Override
public void disconnect() throws DebugException {
teavmDebugger.detach();
}
@Override

View File

@ -1,16 +1,13 @@
package org.teavm.eclipse.debugger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.ILaunchConfigurationDelegate;
import org.eclipse.ui.PlatformUI;
import org.teavm.chromerdp.*;
import org.teavm.chromerdp.ChromeRDPDebugger;
import org.teavm.chromerdp.ChromeRDPServer;
import org.teavm.debugging.Debugger;
import org.teavm.debugging.URLDebugInformationProvider;
@ -29,64 +26,15 @@ public class TeaVMLaunchConfigurationDelegate implements ILaunchConfigurationDel
final ChromeRDPServer server = new ChromeRDPServer();
server.setPort(port);
ChromeRDPDebugger jsDebugger = new ChromeRDPDebugger();
server.setExchangeConsumer(new SynchronousMessageExchange(jsDebugger));
server.setExchangeConsumer(jsDebugger);
Debugger debugger = new Debugger(jsDebugger, new URLDebugInformationProvider(""));
new Thread() {
@Override public void run() {
server.start();
}
}.start();
launch.addDebugTarget(new TeaVMDebugTarget(launch, debugger, server));
}
private static class SynchronousMessageExchange implements ChromeRDPExchangeConsumer,
ChromeRDPExchange {
private List<ChromeRDPExchangeListener> listeners = new ArrayList<>();
private ChromeRDPDebugger debugger;
private ChromeRDPExchange exchange;
public SynchronousMessageExchange(ChromeRDPDebugger debugger) {
this.debugger = debugger;
}
@Override
public void setExchange(ChromeRDPExchange exchange) {
this.exchange = exchange;
debugger.setExchange(this);
exchange.addListener(new ChromeRDPExchangeListener() {
@Override public void received(final String message) throws IOException {
postToUIThread(message);
}
});
}
private void postToUIThread(final String message) {
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
@Override public void run() {
try {
for (ChromeRDPExchangeListener listener : listeners) {
listener.received(message);
}
} catch (IOException e) {
// TODO: add logging
}
}
});
}
@Override
public void send(String message) {
exchange.send(message);
}
@Override
public void addListener(ChromeRDPExchangeListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(ChromeRDPExchangeListener listener) {
listeners.remove(listener);
}
TeaVMDebugTarget target = new TeaVMDebugTarget(launch, debugger, server);
launch.addDebugTarget(target);
launch.addProcess(target.getProcess());
}
}

View File

@ -74,7 +74,7 @@ public class TeaVMStackFrame implements IStackFrame {
@Override
public boolean canResume() {
return false;
return thread.getTopStackFrame() == this;
}
@Override

View File

@ -29,11 +29,13 @@ public class TeaVMThread implements IThread {
@Override
public void resumed() {
updateStackTrace();
fireEvent(new DebugEvent(TeaVMThread.this, DebugEvent.RESUME));
}
@Override
public void paused() {
updateStackTrace();
fireEvent(new DebugEvent(TeaVMThread.this, DebugEvent.SUSPEND));
}
@Override
@ -70,16 +72,17 @@ public class TeaVMThread implements IThread {
@Override
public boolean canTerminate() {
return false;
return true;
}
@Override
public boolean isTerminated() {
return false;
return debugTarget.isTerminated();
}
@Override
public void terminate() throws DebugException {
debugTarget.terminate();
}
@SuppressWarnings("rawtypes")
@ -190,6 +193,6 @@ public class TeaVMThread implements IThread {
@Override
public boolean hasStackFrames() throws DebugException {
return true;
return stackTrace != null;
}
}