MonitorThreadpublic final class MonitorThread extends Thread Monitor open connections. |
Fields Summary |
---|
private static final int | CLIENT_READY | private static final int | CLIENT_DISCONNECTED | private volatile boolean | mQuit | private ArrayList | mClientList | private Selector | mSelector | private HashMap | mHandlerMap | private ServerSocketChannel | mDebugSelectedChan | private int | mNewDebugSelectedPort | private int | mDebugSelectedPort | private Client | mSelectedClient"Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port. | private static MonitorThread | mInstance |
Constructors Summary |
---|
private MonitorThread()Generic constructor.
super("Monitor");
mClientList = new ArrayList<Client>();
mHandlerMap = new HashMap<Integer, ChunkHandler>();
mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
|
Methods Summary |
---|
private void | acceptNewDebugger(Debugger dbg, java.nio.channels.ServerSocketChannel acceptChan)
synchronized (mClientList) {
SocketChannel chan;
if (acceptChan == null)
chan = dbg.accept();
else
chan = dbg.accept(acceptChan);
if (chan != null) {
chan.socket().setTcpNoDelay(true);
wakeup();
try {
chan.register(mSelector, SelectionKey.OP_READ, dbg);
} catch (IOException ioe) {
// failed, drop the connection
dbg.closeData();
throw ioe;
} catch (RuntimeException re) {
// failed, drop the connection
dbg.closeData();
throw re;
}
} else {
Log.i("ddms", "ignoring duplicate debugger");
// new connection already closed
}
}
| synchronized void | addClient(Client client)Add a new Client to the list of things we monitor. Also adds the client's
channel and the client's debugger listener to the selection list. This
should only be called from one thread (the VMWatcherThread) to avoid a
race between "alreadyOpen" and Client creation.
if (mInstance == null) {
return;
}
Log.d("ddms", "Adding new client " + client);
synchronized (mClientList) {
mClientList.add(client);
/*
* Register the Client's socket channel with the selector. We attach
* the Client to the SelectionKey. If you try to register a new
* channel with the Selector while it is waiting for I/O, you will
* block. The solution is to call wakeup() and then hold a lock to
* ensure that the registration happens before the Selector goes
* back to sleep.
*/
try {
wakeup();
client.register(mSelector);
Debugger dbg = client.getDebugger();
if (dbg != null) {
dbg.registerListener(mSelector);
}
} catch (IOException ioe) {
// not really expecting this to happen
ioe.printStackTrace();
}
}
| private void | broadcast(int event, Client client)
Log.d("ddms", "broadcast " + event + ": " + client);
/*
* The handler objects appear once in mHandlerMap for each message they
* handle. We want to notify them once each, so we convert the HashMap
* to a HashSet before we iterate.
*/
HashSet<ChunkHandler> set;
synchronized (mHandlerMap) {
Collection<ChunkHandler> values = mHandlerMap.values();
set = new HashSet<ChunkHandler>(values);
}
Iterator<ChunkHandler> iter = set.iterator();
while (iter.hasNext()) {
ChunkHandler handler = iter.next();
switch (event) {
case CLIENT_READY:
try {
handler.clientReady(client);
} catch (IOException ioe) {
// Something failed with the client. It should
// fall out of the list the next time we try to
// do something with it, so we discard the
// exception here and assume cleanup will happen
// later. May need to propagate farther. The
// trouble is that not all values for "event" may
// actually throw an exception.
Log.w("ddms",
"Got exception while broadcasting 'ready'");
return;
}
break;
case CLIENT_DISCONNECTED:
handler.clientDisconnected(client);
break;
default:
throw new UnsupportedOperationException();
}
}
| private void | callHandler(Client client, JdwpPacket packet, ChunkHandler handler)
// on first DDM packet received, broadcast a "ready" message
if (!client.ddmSeen())
broadcast(CLIENT_READY, client);
ByteBuffer buf = packet.getPayload();
int type, length;
boolean reply = true;
type = buf.getInt();
length = buf.getInt();
if (handler == null) {
// not a reply, figure out who wants it
synchronized (mHandlerMap) {
handler = mHandlerMap.get(type);
reply = false;
}
}
if (handler == null) {
Log.w("ddms", "Received unsupported chunk type "
+ ChunkHandler.name(type) + " (len=" + length + ")");
} else {
Log.d("ddms", "Calling handler for " + ChunkHandler.name(type)
+ " [" + handler + "] (len=" + length + ")");
ByteBuffer ibuf = buf.slice();
ByteBuffer roBuf = ibuf.asReadOnlyBuffer(); // enforce R/O
roBuf.order(ChunkHandler.CHUNK_ORDER);
// do the handling of the chunk synchronized on the client list
// to be sure there's no concurrency issue when we look for HOME
// in hasApp()
synchronized (mClientList) {
handler.handleChunk(client, type, roBuf, reply, packet.getId());
}
}
| static com.android.ddmlib.MonitorThread | createInstance()Creates and return the singleton instance of the client monitor thread.
return mInstance = new MonitorThread();
| private void | displayDebugSelectedBindError(int port)
String message = String.format(
"Could not open Selected VM debug port (%1$d). Make sure you do not have another instance of DDMS or of the eclipse plugin running. If it's being used by something else, choose a new port number in the preferences.",
port);
Log.logAndDisplay(LogLevel.ERROR, "ddms", message);
| synchronized void | dropClient(Client client, boolean notify)Drops a client from the monitor.
This will lock the {@link Client} list of the {@link Device} running client.
if (mInstance == null) {
return;
}
synchronized (mClientList) {
if (mClientList.remove(client) == false) {
return;
}
}
client.close(notify);
broadcast(CLIENT_DISCONNECTED, client);
/*
* http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
*/
wakeup();
| Client[] | getClients()Get an array of known clients.
synchronized (mClientList) {
return mClientList.toArray(new Client[0]);
}
| int | getDebugSelectedPort()Returns the port on which the selected client listen for debugger
return mDebugSelectedPort;
| static com.android.ddmlib.MonitorThread | getInstance()Get singleton instance of the client monitor thread.
return mInstance;
| boolean | getRetryOnBadHandshake()Returns "true" if we want to retry connections to clients if we get a bad
JDWP handshake back, "false" if we want to just mark them as bad and
leave them alone.
return true; // TODO? make configurable
| Client | getSelectedClient()Returns the client accepting debugger connection on the custom "Selected debug port".
return mSelectedClient;
| private void | processClientActivity(java.nio.channels.SelectionKey key)
Client client = (Client)key.attachment();
try {
if (key.isReadable() == false || key.isValid() == false) {
Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
dropClient(client, true /* notify */);
return;
}
client.read();
/*
* See if we have a full packet in the buffer. It's possible we have
* more than one packet, so we have to loop.
*/
JdwpPacket packet = client.getJdwpPacket();
while (packet != null) {
if (packet.isDdmPacket()) {
// unsolicited DDM request - hand it off
assert !packet.isReply();
callHandler(client, packet, null);
packet.consume();
} else if (packet.isReply()
&& client.isResponseToUs(packet.getId()) != null) {
// reply to earlier DDM request
ChunkHandler handler = client
.isResponseToUs(packet.getId());
if (packet.isError())
client.packetFailed(packet);
else if (packet.isEmpty())
Log.d("ddms", "Got empty reply for 0x"
+ Integer.toHexString(packet.getId())
+ " from " + client);
else
callHandler(client, packet, handler);
packet.consume();
client.removeRequestId(packet.getId());
} else {
Log.v("ddms", "Forwarding client "
+ (packet.isReply() ? "reply" : "event") + " 0x"
+ Integer.toHexString(packet.getId()) + " to "
+ client.getDebugger());
client.forwardPacketToDebugger(packet);
}
// find next
packet = client.getJdwpPacket();
}
} catch (CancelledKeyException e) {
// key was canceled probably due to a disconnected client before we could
// read stuff coming from the client, so we drop it.
dropClient(client, true /* notify */);
} catch (IOException ex) {
// something closed down, no need to print anything. The client is simply dropped.
dropClient(client, true /* notify */);
} catch (Exception ex) {
Log.e("ddms", ex);
/* close the client; automatically un-registers from selector */
dropClient(client, true /* notify */);
if (ex instanceof BufferOverflowException) {
Log.w("ddms",
"Client data packet exceeded maximum buffer size "
+ client);
} else {
// don't know what this is, display it
Log.e("ddms", ex);
}
}
| private void | processDebugSelectedActivity(java.nio.channels.SelectionKey key)
assert key.isAcceptable();
ServerSocketChannel acceptChan = (ServerSocketChannel)key.channel();
/*
* Find the debugger associated with the currently-selected client.
*/
if (mSelectedClient != null) {
Debugger dbg = mSelectedClient.getDebugger();
if (dbg != null) {
Log.i("ddms", "Accepting connection on 'debug selected' port");
try {
acceptNewDebugger(dbg, acceptChan);
} catch (IOException ioe) {
// client should be gone, keep going
}
return;
}
}
Log.w("ddms",
"Connection on 'debug selected' port, but none selected");
try {
SocketChannel chan = acceptChan.accept();
chan.close();
} catch (IOException ioe) {
// not expected; client should be gone, keep going
} catch (NotYetBoundException e) {
displayDebugSelectedBindError(mDebugSelectedPort);
}
| private void | processDebuggerActivity(java.nio.channels.SelectionKey key)
Debugger dbg = (Debugger)key.attachment();
try {
if (key.isAcceptable()) {
try {
acceptNewDebugger(dbg, null);
} catch (IOException ioe) {
Log.w("ddms", "debugger accept() failed");
ioe.printStackTrace();
}
} else if (key.isReadable()) {
processDebuggerData(key);
} else {
Log.d("ddm-debugger", "key in unknown state");
}
} catch (CancelledKeyException cke) {
// key has been cancelled we can ignore that.
}
| private void | processDebuggerData(java.nio.channels.SelectionKey key)
Debugger dbg = (Debugger)key.attachment();
try {
/*
* Read pending data.
*/
dbg.read();
/*
* See if we have a full packet in the buffer. It's possible we have
* more than one packet, so we have to loop.
*/
JdwpPacket packet = dbg.getJdwpPacket();
while (packet != null) {
Log.v("ddms", "Forwarding dbg req 0x"
+ Integer.toHexString(packet.getId()) + " to "
+ dbg.getClient());
dbg.forwardPacketToClient(packet);
packet = dbg.getJdwpPacket();
}
} catch (IOException ioe) {
/*
* Close data connection; automatically un-registers dbg from
* selector. The failure could be caused by the debugger going away,
* or by the client going away and failing to accept our data.
* Either way, the debugger connection does not need to exist any
* longer. We also need to recycle the connection to the client, so
* that the VM sees the debugger disconnect. For a DDM-aware client
* this won't be necessary, and we can just send a "debugger
* disconnected" message.
*/
Log.i("ddms", "Closing connection to debugger " + dbg);
dbg.closeData();
Client client = dbg.getClient();
if (client.isDdmAware()) {
// TODO: soft-disconnect DDM-aware clients
Log.i("ddms", " (recycling client connection as well)");
// we should drop the client, but also attempt to reopen it.
// This is done by the DeviceMonitor.
client.getDevice().getMonitor().addClientToDropAndReopen(client,
IDebugPortProvider.NO_STATIC_PORT);
} else {
Log.i("ddms", " (recycling client connection as well)");
// we should drop the client, but also attempt to reopen it.
// This is done by the DeviceMonitor.
client.getDevice().getMonitor().addClientToDropAndReopen(client,
IDebugPortProvider.NO_STATIC_PORT);
}
}
| synchronized void | quit()Tell the thread to stop. Called from UI thread.
mQuit = true;
wakeup();
Log.d("ddms", "Waiting for Monitor thread");
try {
this.join();
// since we're quitting, lets drop all the client and disconnect
// the DebugSelectedPort
synchronized (mClientList) {
for (Client c : mClientList) {
c.close(false /* notify */);
broadcast(CLIENT_DISCONNECTED, c);
}
mClientList.clear();
}
if (mDebugSelectedChan != null) {
mDebugSelectedChan.close();
mDebugSelectedChan.socket().close();
mDebugSelectedChan = null;
}
mSelector.close();
} catch (InterruptedException ie) {
ie.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mInstance = null;
| synchronized void | registerChunkHandler(int type, ChunkHandler handler)Register "handler" as the handler for type "type".
if (mInstance == null) {
return;
}
synchronized (mHandlerMap) {
if (mHandlerMap.get(type) == null) {
mHandlerMap.put(type, handler);
}
}
| private boolean | reopenDebugSelectedPort()Opens (or reopens) the "debug selected" port and listen for connections.
Log.d("ddms", "reopen debug-selected port: " + mNewDebugSelectedPort);
if (mDebugSelectedChan != null) {
mDebugSelectedChan.close();
}
mDebugSelectedChan = ServerSocketChannel.open();
mDebugSelectedChan.configureBlocking(false); // required for Selector
InetSocketAddress addr = new InetSocketAddress(
InetAddress.getByName("localhost"), //$NON-NLS-1$
mNewDebugSelectedPort);
mDebugSelectedChan.socket().setReuseAddress(true); // enable SO_REUSEADDR
try {
mDebugSelectedChan.socket().bind(addr);
if (mSelectedClient != null) {
mSelectedClient.update(Client.CHANGE_PORT);
}
mDebugSelectedChan.register(mSelector, SelectionKey.OP_ACCEPT, this);
return true;
} catch (java.net.BindException e) {
displayDebugSelectedBindError(mNewDebugSelectedPort);
// do not attempt to reopen it.
mDebugSelectedChan = null;
mNewDebugSelectedPort = -1;
return false;
}
| public void | run()Watch for activity from clients and debuggers.
Log.d("ddms", "Monitor is up");
// create a selector
try {
mSelector = Selector.open();
} catch (IOException ioe) {
Log.logAndDisplay(LogLevel.ERROR, "ddms",
"Failed to initialize Monitor Thread: " + ioe.getMessage());
return;
}
while (!mQuit) {
try {
/*
* sync with new registrations: we wait until addClient is done before going through
* and doing mSelector.select() again.
* @see {@link #addClient(Client)}
*/
synchronized (mClientList) {
}
// (re-)open the "debug selected" port, if it's not opened yet or
// if the port changed.
try {
if (AndroidDebugBridge.getClientSupport()) {
if ((mDebugSelectedChan == null ||
mNewDebugSelectedPort != mDebugSelectedPort) &&
mNewDebugSelectedPort != -1) {
if (reopenDebugSelectedPort()) {
mDebugSelectedPort = mNewDebugSelectedPort;
}
}
}
} catch (IOException ioe) {
Log.e("ddms",
"Failed to reopen debug port for Selected Client to: " + mNewDebugSelectedPort);
Log.e("ddms", ioe);
mNewDebugSelectedPort = mDebugSelectedPort; // no retry
}
int count;
try {
count = mSelector.select();
} catch (IOException ioe) {
ioe.printStackTrace();
continue;
} catch (CancelledKeyException cke) {
continue;
}
if (count == 0) {
// somebody called wakeup() ?
// Log.i("ddms", "selector looping");
continue;
}
Set<SelectionKey> keys = mSelector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
try {
if (key.attachment() instanceof Client) {
processClientActivity(key);
}
else if (key.attachment() instanceof Debugger) {
processDebuggerActivity(key);
}
else if (key.attachment() instanceof MonitorThread) {
processDebugSelectedActivity(key);
}
else {
Log.e("ddms", "unknown activity key");
}
} catch (Exception e) {
// we don't want to have our thread be killed because of any uncaught
// exception, so we intercept all here.
Log.e("ddms", "Exception during activity from Selector.");
Log.e("ddms", e);
}
}
} catch (Exception e) {
// we don't want to have our thread be killed because of any uncaught
// exception, so we intercept all here.
Log.e("ddms", "Exception MonitorThread.run()");
Log.e("ddms", e);
}
}
| synchronized void | setDebugSelectedPort(int port)Sets or changes the port number for "debug selected".
if (mInstance == null) {
return;
}
if (AndroidDebugBridge.getClientSupport() == false) {
return;
}
if (mDebugSelectedChan != null) {
Log.d("ddms", "Changing debug-selected port to " + port);
mNewDebugSelectedPort = port;
wakeup();
} else {
// we set mNewDebugSelectedPort instead of mDebugSelectedPort so that it's automatically
// opened on the first run loop.
mNewDebugSelectedPort = port;
}
| synchronized void | setSelectedClient(Client selectedClient)Sets the client to accept debugger connection on the custom "Selected debug port".
if (mInstance == null) {
return;
}
if (mSelectedClient != selectedClient) {
Client oldClient = mSelectedClient;
mSelectedClient = selectedClient;
if (oldClient != null) {
oldClient.update(Client.CHANGE_PORT);
}
if (mSelectedClient != null) {
mSelectedClient.update(Client.CHANGE_PORT);
}
}
| private void | wakeup()
mSelector.wakeup();
|
|