1/*
2 * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdi;
27
28import com.sun.jdi.*;
29import com.sun.jdi.event.*;
30import com.sun.jdi.connect.spi.Connection;
31import com.sun.jdi.event.EventSet;
32
33import java.util.*;
34import java.io.IOException;
35
36public class TargetVM implements Runnable {
37    private Map<String, Packet> waitingQueue = new HashMap<String, Packet>(32,0.75f);
38    private boolean shouldListen = true;
39    private List<EventQueue> eventQueues = Collections.synchronizedList(new ArrayList<EventQueue>(2));
40    private VirtualMachineImpl vm;
41    private Connection connection;
42    private Thread readerThread;
43    private EventController eventController = null;
44    private boolean eventsHeld = false;
45
46    /*
47     * TO DO: The limit numbers below are somewhat arbitrary and should
48     * be configurable in the future.
49     */
50    static private final int OVERLOADED_QUEUE = 2000;
51    static private final int UNDERLOADED_QUEUE = 100;
52
53    TargetVM(VirtualMachineImpl vm, Connection connection) {
54        this.vm = vm;
55        this.connection = connection;
56        this.readerThread = new Thread(vm.threadGroupForJDI(),
57                                       this, "JDI Target VM Interface");
58        this.readerThread.setDaemon(true);
59    }
60
61    void start() {
62        readerThread.start();
63    }
64
65    private void dumpPacket(Packet packet, boolean sending) {
66        String direction = sending ? "Sending" : "Receiving";
67        if (sending) {
68            vm.printTrace(direction + " Command. id=" + packet.id +
69                          ", length=" + packet.data.length +
70                          ", commandSet=" + packet.cmdSet +
71                          ", command=" + packet.cmd +
72                          ", flags=" + packet.flags);
73        } else {
74            String type = (packet.flags & Packet.Reply) != 0 ?
75                          "Reply" : "Event";
76            vm.printTrace(direction + " " + type + ". id=" + packet.id +
77                          ", length=" + packet.data.length +
78                          ", errorCode=" + packet.errorCode +
79                          ", flags=" + packet.flags);
80        }
81        StringBuilder line = new StringBuilder(80);
82        line.append("0000: ");
83        for (int i = 0; i < packet.data.length; i++) {
84            if ((i > 0) && (i % 16 == 0)) {
85                vm.printTrace(line.toString());
86                line.setLength(0);
87                line.append(String.valueOf(i));
88                line.append(": ");
89                int len = line.length();
90                for (int j = 0; j < 6 - len; j++) {
91                    line.insert(0, '0');
92                }
93            }
94            int val = 0xff & packet.data[i];
95            String str = Integer.toHexString(val);
96            if (str.length() == 1) {
97                line.append('0');
98            }
99            line.append(str);
100            line.append(' ');
101        }
102        if (line.length() > 6) {
103            vm.printTrace(line.toString());
104        }
105    }
106
107    public void run() {
108        if ((vm.traceFlags & VirtualMachine.TRACE_SENDS) != 0) {
109            vm.printTrace("Target VM interface thread running");
110        }
111        Packet p=null,p2;
112        String idString;
113
114        while(shouldListen) {
115
116            boolean done = false;
117            try {
118                byte b[] = connection.readPacket();
119                if (b.length == 0) {
120                    done = true;
121                }
122                p = Packet.fromByteArray(b);
123            } catch (IOException e) {
124                done = true;
125            }
126
127            if (done) {
128                shouldListen = false;
129                try {
130                    connection.close();
131                } catch (IOException ioe) { }
132                break;
133            }
134
135            if ((vm.traceFlags & VirtualMachineImpl.TRACE_RAW_RECEIVES) != 0)  {
136                dumpPacket(p, false);
137            }
138
139            if((p.flags & Packet.Reply) == 0) {
140                // It's a command
141                handleVMCommand(p);
142            } else {
143                /*if(p.errorCode != Packet.ReplyNoError) {
144                    System.err.println("Packet " + p.id + " returned failure = " + p.errorCode);
145                }*/
146
147                vm.state().notifyCommandComplete(p.id);
148                idString = String.valueOf(p.id);
149
150                synchronized(waitingQueue) {
151                    p2 = waitingQueue.get(idString);
152
153                    if (p2 != null)
154                        waitingQueue.remove(idString);
155                }
156
157                if(p2 == null) {
158                    // Whoa! a reply without a sender. Problem.
159                    // FIX ME! Need to post an error.
160
161                    System.err.println("Recieved reply with no sender!");
162                    continue;
163                }
164                p2.errorCode = p.errorCode;
165                p2.data = p.data;
166                p2.replied = true;
167
168                synchronized(p2) {
169                    p2.notify();
170                }
171            }
172        }
173
174        // inform the VM mamager that this VM is history
175        vm.vmManager.disposeVirtualMachine(vm);
176
177        // close down all the event queues
178        // Closing a queue causes a VMDisconnectEvent to
179        // be put onto the queue.
180        synchronized(eventQueues) {
181            Iterator<EventQueue> iter = eventQueues.iterator();
182            while (iter.hasNext()) {
183                ((EventQueueImpl)iter.next()).close();
184            }
185        }
186
187        // indirectly throw VMDisconnectedException to
188        // command requesters.
189        synchronized(waitingQueue) {
190            Iterator<Packet> iter = waitingQueue.values().iterator();
191            while (iter.hasNext()) {
192                Packet packet = iter.next();
193                synchronized(packet) {
194                    packet.notify();
195                }
196            }
197            waitingQueue.clear();
198        }
199
200        if ((vm.traceFlags & VirtualMachine.TRACE_SENDS) != 0) {
201            vm.printTrace("Target VM interface thread exiting");
202        }
203    }
204
205    protected void handleVMCommand(Packet p) {
206        switch (p.cmdSet) {
207            case JDWP.Event.COMMAND_SET:
208                handleEventCmdSet(p);
209                break;
210
211            default:
212                System.err.println("Ignoring cmd " + p.id + "/" +
213                                   p.cmdSet + "/" + p.cmd + " from the VM");
214                return;
215        }
216    }
217
218    /* Events should not be constructed on this thread (the thread
219     * which reads all data from the transport). This means that the
220     * packet cannot be converted to real JDI objects as that may
221     * involve further communications with the back end which would
222     * deadlock.
223     *
224     * Instead the whole packet is passed for lazy eval by a queue
225     * reading thread.
226     */
227    protected void handleEventCmdSet(Packet p) {
228        EventSet eventSet = new EventSetImpl(vm, p);
229
230        if (eventSet != null) {
231            queueEventSet(eventSet);
232        }
233    }
234
235    private EventController eventController() {
236        if (eventController == null) {
237            eventController = new EventController(vm);
238        }
239        return eventController;
240    }
241
242    private synchronized void controlEventFlow(int maxQueueSize) {
243        if (!eventsHeld && (maxQueueSize > OVERLOADED_QUEUE)) {
244            eventController().hold();
245            eventsHeld = true;
246        } else if (eventsHeld && (maxQueueSize < UNDERLOADED_QUEUE)) {
247            eventController().release();
248            eventsHeld = false;
249        }
250    }
251
252    void notifyDequeueEventSet() {
253        int maxQueueSize = 0;
254        synchronized(eventQueues) {
255            Iterator<EventQueue> iter = eventQueues.iterator();
256            while (iter.hasNext()) {
257                EventQueueImpl queue = (EventQueueImpl)iter.next();
258                maxQueueSize = Math.max(maxQueueSize, queue.size());
259            }
260        }
261        controlEventFlow(maxQueueSize);
262    }
263
264    private void queueEventSet(EventSet eventSet) {
265        int maxQueueSize = 0;
266
267        synchronized(eventQueues) {
268            Iterator<EventQueue> iter = eventQueues.iterator();
269            while (iter.hasNext()) {
270                EventQueueImpl queue = (EventQueueImpl)iter.next();
271                queue.enqueue(eventSet);
272                maxQueueSize = Math.max(maxQueueSize, queue.size());
273            }
274        }
275
276        controlEventFlow(maxQueueSize);
277    }
278
279    void send(Packet packet) {
280        String id = String.valueOf(packet.id);
281
282        synchronized(waitingQueue) {
283            waitingQueue.put(id, packet);
284        }
285
286        if ((vm.traceFlags & VirtualMachineImpl.TRACE_RAW_SENDS) != 0) {
287            dumpPacket(packet, true);
288        }
289
290        try {
291            connection.writePacket(packet.toByteArray());
292        } catch (IOException e) {
293            throw new VMDisconnectedException(e.getMessage());
294        }
295    }
296
297    void waitForReply(Packet packet) {
298        synchronized(packet) {
299            while ((!packet.replied) && shouldListen) {
300                try { packet.wait(); } catch (InterruptedException e) {;}
301            }
302
303            if (!packet.replied) {
304                throw new VMDisconnectedException();
305            }
306        }
307    }
308
309    void addEventQueue(EventQueueImpl queue) {
310        if ((vm.traceFlags & VirtualMachine.TRACE_EVENTS) != 0) {
311            vm.printTrace("New event queue added");
312        }
313        eventQueues.add(queue);
314    }
315
316    void stopListening() {
317        if ((vm.traceFlags & VirtualMachine.TRACE_EVENTS) != 0) {
318            vm.printTrace("Target VM i/f closing event queues");
319        }
320        shouldListen = false;
321        try {
322            connection.close();
323        } catch (IOException ioe) { }
324    }
325
326    static private class EventController extends Thread {
327        VirtualMachineImpl vm;
328        int controlRequest = 0;
329
330        EventController(VirtualMachineImpl vm) {
331            super(vm.threadGroupForJDI(), "JDI Event Control Thread");
332            this.vm = vm;
333            setDaemon(true);
334            setPriority((MAX_PRIORITY + NORM_PRIORITY)/2);
335            super.start();
336        }
337
338        synchronized void hold() {
339            controlRequest++;
340            notifyAll();
341        }
342
343        synchronized void release() {
344            controlRequest--;
345            notifyAll();
346        }
347
348        public void run() {
349            while(true) {
350                int currentRequest;
351                synchronized(this) {
352                    while (controlRequest == 0) {
353                        try {wait();} catch (InterruptedException e) {}
354                    }
355                    currentRequest = controlRequest;
356                    controlRequest = 0;
357                }
358                try {
359                    if (currentRequest > 0) {
360                        JDWP.VirtualMachine.HoldEvents.process(vm);
361                    } else {
362                        JDWP.VirtualMachine.ReleaseEvents.process(vm);
363                    }
364                } catch (JDWPException e) {
365                    /*
366                     * Don't want to terminate the thread, so the
367                     * stack trace is printed and we continue.
368                     */
369                    e.toJDIException().printStackTrace(System.err);
370                }
371            }
372        }
373    }
374
375}
376