FileDocCategorySizeDatePackage
MonitorThread.javaAPI DocAndroid 1.5 API27001Wed May 06 22:41:08 BST 2009com.android.ddmlib

MonitorThread

public 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 voidacceptNewDebugger(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 voidaddClient(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 voidbroadcast(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 voidcallHandler(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.MonitorThreadcreateInstance()
Creates and return the singleton instance of the client monitor thread.

        return mInstance = new MonitorThread();
    
private voiddisplayDebugSelectedBindError(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 voiddropClient(Client client, boolean notify)
Drops a client from the monitor.

This will lock the {@link Client} list of the {@link Device} running client.

param
client
param
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();
    
Client[]getClients()
Get an array of known clients.

        synchronized (mClientList) {
            return mClientList.toArray(new Client[0]);
        }
    
intgetDebugSelectedPort()
Returns the port on which the selected client listen for debugger

        return mDebugSelectedPort;
    
static com.android.ddmlib.MonitorThreadgetInstance()
Get singleton instance of the client monitor thread.

        return mInstance;
    
booleangetRetryOnBadHandshake()
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
    
ClientgetSelectedClient()
Returns the client accepting debugger connection on the custom "Selected debug port".

        return mSelectedClient;
    
private voidprocessClientActivity(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 voidprocessDebugSelectedActivity(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 voidprocessDebuggerActivity(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 voidprocessDebuggerData(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 voidquit()
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 voidregisterChunkHandler(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 booleanreopenDebugSelectedPort()
Opens (or reopens) the "debug selected" port and listen for connections.

return
true if the port was opened successfully.
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;
        }
    
public voidrun()
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 voidsetDebugSelectedPort(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 voidsetSelectedClient(Client selectedClient)
Sets the client to accept debugger connection on the custom "Selected debug port".

param
selectedClient the client. Can be null.

        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 voidwakeup()

        mSelector.wakeup();