1
/*******************************************************************************
2
* Copyright (c) 2008, 2009 Wind River Systems and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* Wind River Systems - initial API and implementation
10
*******************************************************************************/
11
package org.eclipse.cdt.examples.dsf.pda.service;
13
import java.io.BufferedReader;
14
import java.io.IOException;
15
import java.io.InputStreamReader;
16
import java.io.PrintWriter;
17
import java.util.ArrayList;
18
import java.util.Hashtable;
19
import java.util.Iterator;
20
import java.util.LinkedList;
21
import java.util.List;
22
import java.util.concurrent.BlockingQueue;
23
import java.util.concurrent.LinkedBlockingQueue;
24
import java.util.concurrent.RejectedExecutionException;
26
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
27
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
28
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
29
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
30
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
31
import org.eclipse.cdt.dsf.debug.service.command.ICommand;
32
import org.eclipse.cdt.dsf.debug.service.command.ICommandControl;
33
import org.eclipse.cdt.dsf.debug.service.command.ICommandControlService;
34
import org.eclipse.cdt.dsf.debug.service.command.ICommandListener;
35
import org.eclipse.cdt.dsf.debug.service.command.ICommandResult;
36
import org.eclipse.cdt.dsf.debug.service.command.ICommandToken;
37
import org.eclipse.cdt.dsf.debug.service.command.IEventListener;
38
import org.eclipse.cdt.dsf.service.AbstractDsfService;
39
import org.eclipse.cdt.dsf.service.DsfSession;
40
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
41
import org.eclipse.cdt.examples.dsf.pda.service.commands.AbstractPDACommand;
42
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDACommandResult;
43
import org.eclipse.cdt.examples.dsf.pda.service.commands.PDAExitCommand;
44
import org.eclipse.core.runtime.IProgressMonitor;
45
import org.eclipse.core.runtime.IStatus;
46
import org.eclipse.core.runtime.Status;
47
import org.eclipse.core.runtime.jobs.Job;
48
import org.osgi.framework.BundleContext;
52
* Service that handles communication with a PDA debugger back end.
54
public class PDACommandControl extends AbstractDsfService implements ICommandControlService {
56
// Structure used to store command information in services internal queues.
57
private static class CommandHandle {
58
final private ICommandToken fToken;
59
final private AbstractPDACommand<PDACommandResult> fCommand;
60
final private DataRequestMonitor<PDACommandResult> fRequestMonitor;
62
CommandHandle(ICommandToken token, AbstractPDACommand<PDACommandResult> c, DataRequestMonitor<PDACommandResult> rm) {
69
private PDABackend fBackend;
71
// Queue of commands waiting to be sent to the debugger. As long as commands
72
// are in this queue, they can still be removed by clients.
73
private final List<CommandHandle> fCommandQueue = new LinkedList<CommandHandle>();
75
// Queue of commands that are being sent to the debugger. This queue is read
76
// by the send job, so as soon as commands are inserted into this queue, they can
77
// be considered as sent.
79
private final BlockingQueue<CommandHandle> fTxCommands = new LinkedBlockingQueue<CommandHandle>();
81
// Flag indicating that the PDA debugger started
82
private boolean fStarted = false;
84
// Flag indicating that the PDA debugger has been disconnected
86
private boolean fTerminated = false;
88
// Data Model context of this command control.
89
private PDAVirtualMachineDMContext fDMContext;
91
// Synchronous listeners for commands and events.
92
private final List<ICommandListener> fCommandListeners = new ArrayList<ICommandListener>();
93
private final List<IEventListener> fEventListeners = new ArrayList<IEventListener>();
95
// Sockets for communicating with PDA debugger
97
private PrintWriter fRequestWriter;
99
private BufferedReader fRequestReader;
101
private BufferedReader fEventReader;
103
// Jobs servicing the sockets.
104
private EventDispatchJob fEventDispatchJob;
105
private CommandSendJob fCommandSendJob;
108
* Command control constructor.
109
* @param session The DSF session that this service is a part of.
111
public PDACommandControl(DsfSession session) {
116
public void initialize(final RequestMonitor rm) {
117
// Call the super-class to perform initialization first.
118
super.initialize( new RequestMonitor(getExecutor(), rm) {
120
protected void handleSuccess() {
126
private void doInitialize(final RequestMonitor rm) {
127
fBackend = getServicesTracker().getService(PDABackend.class);
129
// Create the control's data model context.
130
fDMContext = new PDAVirtualMachineDMContext(getSession().getId(), fBackend.getPorgramName());
132
// Add a listener for PDA events to track the started/terminated state.
133
addEventListener(new IEventListener() {
134
public void eventReceived(Object output) {
135
if ("started 1".equals(output)) {
137
} else if ("terminated".equals(output)) {
143
// Get intput/output streams from the backend service.
145
fRequestWriter = new PrintWriter(fBackend.getRequestOutputStream());
146
fRequestReader = new BufferedReader(new InputStreamReader(fBackend.getRequestInputStream()));
147
fEventReader = new BufferedReader(new InputStreamReader(fBackend.getEventInputStream()));
149
fEventDispatchJob = new EventDispatchJob();
150
fEventDispatchJob.schedule();
152
fCommandSendJob = new CommandSendJob();
153
fCommandSendJob.schedule();
155
// Register the service with OSGi as the last step in initialization of
159
ICommandControl.class.getName(),
160
ICommandControlService.class.getName(),
161
PDACommandControl.class.getName()
163
new Hashtable<String,String>());
169
public void shutdown(final RequestMonitor requestMonitor) {
170
// Unregister the service first, so that clients may no longer gain access to it.
173
if (!isTerminated()) {
174
// If the debugger is still connected, send it the exit command.
175
terminate(new RequestMonitor(getExecutor(), requestMonitor) {
177
protected void handleCompleted() {
178
// Mark the command control as terminated.
181
// Ignore any error resulting from the exit command.
182
// Errors will most likely result if the PDA process is
183
// already terminated.
184
requestMonitor.done();
188
requestMonitor.done();
193
protected BundleContext getBundleContext() {
194
return PDAPlugin.getBundleContext();
198
* Job that services the send command queue.
200
private class CommandSendJob extends Job {
202
super("PDA Command Send");
207
protected IStatus run(IProgressMonitor monitor) {
208
while (!isTerminated()) {
209
synchronized (fTxCommands) {
211
// Remove command from send queue.
212
final CommandHandle commandHandle = fTxCommands.take();
214
// Send the request to PDA
215
fRequestWriter.println(commandHandle.fCommand.getRequest());
216
fRequestWriter.flush();
220
final String response = fRequestReader.readLine();
222
// Process the reply in the executor thread.
224
getExecutor().execute(new DsfRunnable() {
226
processCommandDone(commandHandle, response);
229
} catch (RejectedExecutionException e) {
230
// Acceptable race condition may see the session shut down
231
// while we're waiting for command response. Still complete
232
// the request monitor.
233
assert isTerminated();
234
assert isTerminated();
235
PDAPlugin.failRequest(commandHandle.fRequestMonitor, REQUEST_FAILED, "Command control shut down.");
237
} catch (final IOException e) {
238
// Process error it in the executor thread
240
getExecutor().execute(new DsfRunnable() {
242
processCommandException(commandHandle, e);
245
} catch (RejectedExecutionException re) {
246
// Acceptable race condition... see above
247
assert isTerminated();
248
PDAPlugin.failRequest(commandHandle.fRequestMonitor, REQUEST_FAILED, "Command control shut down.");
251
} catch (InterruptedException e) {
252
break; // Shutting down.
256
return Status.OK_STATUS;
262
* Job that services the PDA event socket.
264
class EventDispatchJob extends Job {
266
public EventDispatchJob() {
267
super("PDA Event Listner");
272
protected IStatus run(IProgressMonitor monitor) {
273
while (!isTerminated()) {
275
// Wait for an event.
276
final String event = fEventReader.readLine();
279
// Process the event in executor thread.
280
getExecutor().execute(new DsfRunnable() {
282
processEventReceived(event);
285
} catch (RejectedExecutionException e) {}
289
} catch (IOException e) {
293
if (!isTerminated()) {
294
// Exception from the event socket is an indicator that the PDA debugger
295
// has exited. Call setTerminated() in executor thread.
297
getExecutor().execute(new DsfRunnable() {
302
} catch (RejectedExecutionException e) {}
304
return Status.OK_STATUS;
309
public <V extends ICommandResult> ICommandToken queueCommand(final ICommand<V> command, DataRequestMonitor<V> rm) {
310
ICommandToken token = new ICommandToken() {
311
public ICommand<?> getCommand() {
316
if (command instanceof AbstractPDACommand<?>) {
317
// Cast from command with "<V extends ICommandResult>" to a more concrete
318
// type to use internally in the command control.
319
@SuppressWarnings("unchecked")
320
AbstractPDACommand<PDACommandResult> pdaCommand = (AbstractPDACommand<PDACommandResult>)command;
322
// Similarly, cast the request monitor to a more concrete type.
323
@SuppressWarnings("unchecked")
324
DataRequestMonitor<PDACommandResult> pdaRM = (DataRequestMonitor<PDACommandResult>)rm;
326
// Add the command to the queue and notify command listeners.
327
fCommandQueue.add( new CommandHandle(token, pdaCommand, pdaRM) );
328
for (ICommandListener listener : fCommandListeners) {
329
listener.commandQueued(token);
332
// In a separate dispatch cycle. This allows command listeners to respond to the
333
// command queued event.
334
getExecutor().execute(new DsfRunnable() {
340
PDAPlugin.failRequest(rm, INTERNAL_ERROR, "Unrecognized command: " + command);
345
public void removeCommand(ICommandToken token) {
346
// Removes given command from the queue and notify the listeners
347
for (Iterator<CommandHandle> itr = fCommandQueue.iterator(); itr.hasNext();) {
348
CommandHandle handle = itr.next();
349
if (token.equals(handle.fToken)) {
351
for (ICommandListener listener : fCommandListeners) {
352
listener.commandRemoved(token);
358
public void addCommandListener(ICommandListener processor) {
359
fCommandListeners.add(processor);
362
public void removeCommandListener(ICommandListener processor) {
363
fCommandListeners.remove(processor);
366
public void addEventListener(IEventListener processor) {
367
fEventListeners.add(processor);
370
public void removeEventListener(IEventListener processor) {
371
fEventListeners.remove(processor);
374
private void processCommandDone(CommandHandle handle, String response) {
375
// Trace to debug output.
376
PDAPlugin.debug("R: " + response);
378
PDACommandResult result = null;
380
if (response.startsWith("error:")) {
381
// Create a generic result with the error response
382
result = new PDACommandResult(response);
384
// Set the error status to the request monitor.
385
handle.fRequestMonitor.setStatus(new Status(
386
IStatus.ERROR, PDAPlugin.PLUGIN_ID,
387
IDsfStatusConstants.REQUEST_FAILED, response, null));
389
// Given the PDA response string, create the result using the command
391
result = handle.fCommand.createResult(response);
393
// Set the result to the request monitor and return to sender.
394
// Note: as long as PDA sends some response, a PDA command will never
396
handle.fRequestMonitor.setData(result);
398
handle.fRequestMonitor.done();
400
// Notify listeners of the response
401
for (ICommandListener listener : fCommandListeners) {
402
listener.commandDone(handle.fToken, result);
405
// Process next command in queue.
410
private void processCommandException(CommandHandle handle, Throwable exception) {
412
// If sending a command resulted in an exception, notify the client.
413
handle.fRequestMonitor.setStatus(new Status(
414
IStatus.ERROR, PDAPlugin.PLUGIN_ID, REQUEST_FAILED, "Exception reading request response", exception));
415
handle.fRequestMonitor.done();
417
// Notify listeners also.
418
for (ICommandListener listener : fCommandListeners) {
419
listener.commandDone(handle.fToken, null);
423
private void processEventReceived(String event) {
424
// Notify the listeners only.
425
PDAPlugin.debug("E: " + event);
426
for (IEventListener listener : fEventListeners) {
427
listener.eventReceived(event);
431
private synchronized void processQueues() {
432
if (isTerminated()) {
433
// If the PDA debugger is terminated. Return all submitted commands
435
for (CommandHandle handle : fCommandQueue) {
436
handle.fRequestMonitor.setStatus(new Status(
437
IStatus.ERROR, PDAPlugin.PLUGIN_ID, INVALID_STATE, "Command control is terminated", null));
438
handle.fRequestMonitor.done();
440
fCommandQueue.clear();
441
} else if (fStarted && fTxCommands.isEmpty() && !fCommandQueue.isEmpty()) {
442
// Process the queues if:
443
// - the PDA debugger has started,
444
// - there are no pending commands in the send queue,
445
// - and there are commands waiting to be sent.
446
CommandHandle handle = fCommandQueue.remove(0);
447
fTxCommands.add(handle);
448
PDAPlugin.debug("C: " + handle.fCommand.getRequest());
449
for (ICommandListener listener : fCommandListeners) {
450
listener.commandSent(handle.fToken);
456
* Return the PDA Debugger top-level Data Model context.
457
* @see PDAVirtualMachineDMContext
460
public PDAVirtualMachineDMContext getContext() {
464
public String getId() {
465
return fBackend.getPorgramName();
468
private void setStarted() {
469
// Mark the command control as started and ready to process commands.
472
// Process any commands which may have been queued before the
475
// Issue a data model event.
476
getSession().dispatchEvent(new PDAStartedEvent(getContext()), getProperties());
480
* Returns whether the PDA debugger has started and is processing commands.
482
public boolean isActive() {
483
return fStarted && !isTerminated();
488
private synchronized void setTerminated() {
489
// Set terminated may be called more than once: by event listener thread,
490
// by the terminate command, etc, so protect against sending events multiple
495
// Process any waiting commands, they all should return with an error.
498
// Issue a data model event.
499
getSession().dispatchEvent(new PDATerminatedEvent(getContext()), getProperties());
504
* Returns whether the PDA debugger has been terminated.
507
public synchronized boolean isTerminated() {
512
* Sends a command to PDA debugger to terminate.
514
public void terminate(RequestMonitor rm) {
515
if (!isTerminated()) {
517
new PDAExitCommand(fDMContext),
518
new DataRequestMonitor<PDACommandResult>(getExecutor(), rm));
520
// If already terminated, indicate success.