diff options
Diffstat (limited to 'ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java')
-rw-r--r-- | ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java | 790 |
1 files changed, 0 insertions, 790 deletions
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java b/ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java deleted file mode 100644 index eae4707..0000000 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java +++ /dev/null @@ -1,790 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ddmlib; - - -import com.android.ddmlib.DebugPortManager.IDebugPortProvider; -import com.android.ddmlib.Log.LogLevel; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.channels.CancelledKeyException; -import java.nio.channels.NotYetBoundException; -import java.nio.channels.SelectionKey; -import java.nio.channels.Selector; -import java.nio.channels.ServerSocketChannel; -import java.nio.channels.SocketChannel; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -/** - * Monitor open connections. - */ -final class MonitorThread extends Thread { - - // For broadcasts to message handlers - //private static final int CLIENT_CONNECTED = 1; - - private static final int CLIENT_READY = 2; - - private static final int CLIENT_DISCONNECTED = 3; - - private volatile boolean mQuit = false; - - // List of clients we're paying attention to - private ArrayList<Client> mClientList; - - // The almighty mux - private Selector mSelector; - - // Map chunk types to handlers - private HashMap<Integer, ChunkHandler> mHandlerMap; - - // port for "debug selected" - private ServerSocketChannel mDebugSelectedChan; - - private int mNewDebugSelectedPort; - - private int mDebugSelectedPort = -1; - - /** - * "Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port. - */ - private Client mSelectedClient = null; - - // singleton - private static MonitorThread mInstance; - - /** - * Generic constructor. - */ - private MonitorThread() { - super("Monitor"); - mClientList = new ArrayList<Client>(); - mHandlerMap = new HashMap<Integer, ChunkHandler>(); - - mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort(); - } - - /** - * Creates and return the singleton instance of the client monitor thread. - */ - static MonitorThread createInstance() { - return mInstance = new MonitorThread(); - } - - /** - * Get singleton instance of the client monitor thread. - */ - static MonitorThread getInstance() { - return mInstance; - } - - - /** - * Sets or changes the port number for "debug selected". - */ - synchronized void setDebugSelectedPort(int port) throws IllegalStateException { - 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; - } - } - - /** - * Sets the client to accept debugger connection on the custom "Selected debug port". - * @param selectedClient the client. Can be null. - */ - synchronized void setSelectedClient(Client selectedClient) { - 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); - } - } - } - - /** - * Returns the client accepting debugger connection on the custom "Selected debug port". - */ - Client getSelectedClient() { - return mSelectedClient; - } - - - /** - * 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. - */ - boolean getRetryOnBadHandshake() { - return true; // TODO? make configurable - } - - /** - * Get an array of known clients. - */ - Client[] getClients() { - synchronized (mClientList) { - return mClientList.toArray(new Client[0]); - } - } - - /** - * Register "handler" as the handler for type "type". - */ - synchronized void registerChunkHandler(int type, ChunkHandler handler) { - if (mInstance == null) { - return; - } - - synchronized (mHandlerMap) { - if (mHandlerMap.get(type) == null) { - mHandlerMap.put(type, handler); - } - } - } - - /** - * Watch for activity from clients and debuggers. - */ - @Override - public void run() { - 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); - } - } - } - - - /** - * Returns the port on which the selected client listen for debugger - */ - int getDebugSelectedPort() { - return mDebugSelectedPort; - } - - /* - * Something happened. Figure out what. - */ - private void processClientActivity(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); - } - } - } - - /* - * Process an incoming DDM packet. If this is a reply to an earlier request, - * "handler" will be set to the handler responsible for the original - * request. The spec allows a JDWP message to include multiple DDM chunks. - */ - 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()); - } - } - } - - /** - * Drops a client from the monitor. - * <p/>This will lock the {@link Client} list of the {@link Device} running <var>client</var>. - * @param client - * @param notify - */ - synchronized void dropClient(Client client, boolean notify) { - 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(); - } - - /** - * Drops the provided list of clients from the monitor. This will lock the {@link Client} - * list of the {@link Device} running each of the clients. - */ - synchronized void dropClients(Collection<? extends Client> clients, boolean notify) { - for (Client c : clients) { - dropClient(c, notify); - } - } - - /* - * Process activity from one of the debugger sockets. This could be a new - * connection or a data packet. - */ - private void processDebuggerActivity(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. - } - } - - /* - * Accept a new connection from a debugger. If successful, register it with - * the Selector. - */ - private void acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan) - throws IOException { - - 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.w("ddms", "ignoring duplicate debugger"); - // new connection already closed - } - } - } - - /* - * We have incoming data from the debugger. Forward it to the client. - */ - private void processDebuggerData(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.d("ddms", "Closing connection to debugger " + dbg); - dbg.closeData(); - Client client = dbg.getClient(); - if (client.isDdmAware()) { - // TODO: soft-disconnect DDM-aware clients - Log.d("ddms", " (recycling client connection as well)"); - - // we should drop the client, but also attempt to reopen it. - // This is done by the DeviceMonitor. - client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client, - IDebugPortProvider.NO_STATIC_PORT); - } else { - Log.d("ddms", " (recycling client connection as well)"); - // we should drop the client, but also attempt to reopen it. - // This is done by the DeviceMonitor. - client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client, - IDebugPortProvider.NO_STATIC_PORT); - } - } - } - - /* - * Tell the thread that something has changed. - */ - private void wakeup() { - mSelector.wakeup(); - } - - /** - * Tell the thread to stop. Called from UI thread. - */ - synchronized void quit() { - 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; - } - - /** - * 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. - */ - synchronized void addClient(Client client) { - 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(); - } - } - } - - /* - * Broadcast an event to all message handlers. - */ - 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(); - } - } - - } - - /** - * Opens (or reopens) the "debug selected" port and listen for connections. - * @return true if the port was opened successfully. - * @throws IOException - */ - private boolean reopenDebugSelectedPort() throws IOException { - - 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; - } - } - - /* - * We have some activity on the "debug selected" port. Handle it. - */ - private void processDebugSelectedActivity(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.d("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 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); - } -} |