package org.rubypeople.rdt.internal.debug.core.model; import java.util.Vector; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.model.IBreakpoint; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IStackFrame; import org.rubypeople.rdt.debug.core.RdtDebugCorePlugin; import org.rubypeople.rdt.debug.core.model.IRubyThread; import org.rubypeople.rdt.internal.debug.core.RubyDebuggerProxy; import org.rubypeople.rdt.internal.debug.core.SuspensionPoint; public class RubyThread extends RubyDebugElement implements IRubyThread { private RubyStackFrame[] frames; private boolean isSuspended = false; private boolean isTerminated = false; private boolean isStepping = false; private String name; private String status; private int id; private ThreadJob fRunningAsyncJob; private ThreadJob fAsyncJob; public RubyThread(IDebugTarget target, int id, String status) { super(target); this.setId(id); this.status = status; this.updateName(); } public IStackFrame[] getStackFrames() { // Not all clients ask hasStackFrames before calling this method // (DeferredThread) // Therefore we must not return null // Since 3.2: It seems as if the first method called on a thread is // hasStackFrames // this is done frome AsynchronousContentAdapter and therefore we have // the time to // send this call to the debuggger ; if (frames == null) { createStackFrames(); } return frames; } private synchronized void createStackFrames() { if (isSuspended()) { getRubyDebuggerProxy().readFrames(this); // for (int i = 0; i < frames.length; i++) // { // RubyStackFrame frame = frames[i]; // // DebugEvent ev = new DebugEvent(frame, DebugEvent.CREATE); // // DebugPlugin.getDefault().fireDebugEventSet( // // new DebugEvent[] { ev }); // } } else { frames = new RubyStackFrame[] {}; } } public int getStackFramesSize() { return frames.length; } public boolean hasStackFrames() { return isSuspended; // TODO: changegetStackFrames().length > 0; } public int getPriority() throws DebugException { return 0; } public IStackFrame getTopStackFrame() throws DebugException { IStackFrame[] frames = getStackFrames(); if (frames == null || frames.length == 0) return null; return frames[0]; } public IBreakpoint[] getBreakpoints() { // TODO: Experimental Code return new IBreakpoint[] { DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(getModelIdentifier())[0] }; } public boolean canResume() { return isSuspended; } public boolean canSuspend() { // TODO: manually suspending a thread is not yet possible with ruby-debug // return !isSuspended; return false; } public boolean isSuspended() { return isSuspended; } protected void setSuspended(boolean isSuspended) { this.isSuspended = isSuspended; } /* * call after user wants to resume or step */ protected void resume(boolean isStep) { isStepping = isStep; isSuspended = false; this.updateName(); this.frames = new RubyStackFrame[] {}; } public void resume() throws DebugException { resume(false /* isStep */); ((RubyDebugTarget) this.getDebugTarget()).getRubyDebuggerProxy().resume(this); // TODO: resume should be sent from ruby debugger DebugEvent ev = new DebugEvent(this, DebugEvent.RESUME, DebugEvent.CLIENT_REQUEST); DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { ev }); } /* * called when suspension event was sent from ruby debugger */ public void doSuspend(SuspensionPoint suspensionPoint) { int suspensionReason = 0; if (suspensionPoint.isStep()) { suspensionReason = DebugEvent.STEP_END; } else { suspensionReason = DebugEvent.BREAKPOINT; } frames = null; isSuspended = true; isStepping = false; this.createName(suspensionPoint); DebugEvent ev = new DebugEvent(this, DebugEvent.SUSPEND, suspensionReason); DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { ev }); } public void suspend() { // TODO: the following 3 steps should be performed when suspend event // comes back from the debugger frames = null; isStepping = false; isSuspended = true; getRubyDebuggerProxy().sendThreadStop(this); } public boolean canStepInto() { return isSuspended && this.hasStackFrames(); } public boolean canStepOver() { return isSuspended && this.hasStackFrames(); } public boolean canStepReturn() { return false; } public boolean isStepping() { return isStepping; } public void stepInto() throws DebugException { isStepping = true; this.updateName(); if (frames != null && frames.length > 0) { frames[0].stepInto(); } } public void stepOver() throws DebugException { if (frames != null && frames.length > 0) { frames[0].stepOver(); } } public void stepReturn() throws DebugException { } public boolean canTerminate() { return !isTerminated; } public boolean isTerminated() { return isTerminated; } public void terminate() throws DebugException { this.getDebugTarget().terminate(); isTerminated = true; this.frames = null; } public RubyDebuggerProxy getRubyDebuggerProxy() { return ((RubyDebugTarget) this.getDebugTarget()).getRubyDebuggerProxy(); } public void setStackFrames(RubyStackFrame[] frames) { this.frames = frames; } public String getName() { return name; } public void setName(String name) { this.name = name; } protected void updateName() { this.createName(null); } protected void createName(SuspensionPoint suspensionPoint) { this.name = "Ruby Thread - " + this.getId(); if (suspensionPoint != null) { this.name += " (" + suspensionPoint + ")"; } else { this.name += " (" + status + ")"; } } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } /** * @see org.eclipse.rdt.debug.core.IRubyThread#queueRunnable(Runnable) */ public void queueRunnable(Runnable evaluation) { if (fAsyncJob == null) { fAsyncJob = new ThreadJob(this); } fAsyncJob.addRunnable(evaluation); } /** * Class which managed the queue of runnable associated with this thread. */ static class ThreadJob extends Job { private Vector fRunnables; private RubyThread fJDIThread; public ThreadJob(RubyThread thread) { super("RDT thread evaluations"); fJDIThread = thread; fRunnables = new Vector(5); setSystem(true); } public void addRunnable(Runnable runnable) { synchronized (fRunnables) { fRunnables.add(runnable); } schedule(); } public boolean isEmpty() { return fRunnables.isEmpty(); } /* * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */ public IStatus run(IProgressMonitor monitor) { fJDIThread.fRunningAsyncJob = this; Object[] runnables; synchronized (fRunnables) { runnables = fRunnables.toArray(); fRunnables.clear(); } MultiStatus failed = null; monitor.beginTask(this.getName(), runnables.length); int i = 0; while (i < runnables.length && !fJDIThread.isTerminated() && !monitor.isCanceled()) { try { ((Runnable) runnables[i]).run(); } catch (Exception e) { if (failed == null) { failed = new MultiStatus(RdtDebugCorePlugin.getPluginIdentifier(), RdtDebugCorePlugin.INTERNAL_ERROR, "Exception processing async thread queue", null); } failed.add(new Status(IStatus.ERROR, RdtDebugCorePlugin.getPluginIdentifier(), RdtDebugCorePlugin.INTERNAL_ERROR, "Exception processing async thread queue", e)); } i++; monitor.worked(1); } fJDIThread.fRunningAsyncJob = null; monitor.done(); if (failed == null) { return Status.OK_STATUS; } return failed; } /* * @see org.eclipse.core.runtime.jobs.Job#shouldRun() */ public boolean shouldRun() { return !fJDIThread.isTerminated() && !fRunnables.isEmpty(); } } }