1 /*
2  * Copyright (c) 1998, 2006, 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 
26 package com.sun.tools.jdi;
27 
28 import com.sun.jdi.*;
29 import com.sun.jdi.event.EventQueue;
30 import com.sun.jdi.event.EventSet;
31 
32 import java.util.*;
33 
34 public class EventQueueImpl extends MirrorImpl implements EventQueue {
35 
36     /*
37      * Note this is not a synchronized list. Iteration/update should be
38      * protected through the 'this' monitor.
39      */
40     LinkedList<EventSet> eventSets = new LinkedList<EventSet>();
41 
42     TargetVM target;
43     boolean closed = false;
44 
EventQueueImpl(VirtualMachine vm, TargetVM target)45     EventQueueImpl(VirtualMachine vm, TargetVM target) {
46         super(vm);
47         this.target = target;
48         target.addEventQueue(this);
49     }
50 
51     /*
52      * Override superclass back to default equality
53      */
equals(Object obj)54     public boolean equals(Object obj) {
55         return this == obj;
56     }
57 
hashCode()58     public int hashCode() {
59         return System.identityHashCode(this);
60     }
61 
enqueue(EventSet eventSet)62     synchronized void enqueue(EventSet eventSet) {
63         eventSets.add(eventSet);
64         notifyAll();
65     }
66 
size()67     synchronized int size() {
68         return eventSets.size();
69     }
70 
close()71     synchronized void close() {
72         if (!closed) {
73             closed = true; // OK for this the be first since synchronized
74 
75             // place VMDisconnectEvent into queue
76             enqueue(new EventSetImpl(vm,
77                                      (byte)JDWP.EventKind.VM_DISCONNECTED));
78         }
79     }
80 
remove()81     public EventSet remove() throws InterruptedException {
82         return remove(0);
83     }
84 
85     /**
86      * Filter out events not for user's eyes.
87      * Then filter out empty sets.
88      */
remove(long timeout)89     public EventSet remove(long timeout) throws InterruptedException {
90         if (timeout < 0) {
91             throw new IllegalArgumentException("Timeout cannot be negative");
92         }
93 
94         EventSet eventSet;
95         while (true) {
96             EventSetImpl fullEventSet = removeUnfiltered(timeout);
97             if (fullEventSet == null) {
98                 eventSet = null;  // timeout
99                 break;
100             }
101             /*
102              * Remove events from the event set for which
103              * there is no corresponding enabled request (
104              * this includes our internally requested events.)
105              * This never returns null
106              */
107             eventSet = fullEventSet.userFilter();
108             if (!eventSet.isEmpty()) {
109                 break;
110             }
111         }
112 
113         if ((eventSet != null) && (eventSet.suspendPolicy() == JDWP.SuspendPolicy.ALL)) {
114             vm.notifySuspend();
115         }
116 
117         return eventSet;
118     }
119 
removeInternal()120     EventSet removeInternal() throws InterruptedException {
121         EventSet eventSet;
122         do {
123             // Waiting forever, so removeUnfiltered() is never null
124             eventSet = removeUnfiltered(0).internalFilter();
125         } while (eventSet == null || eventSet.isEmpty());
126 
127         /*
128          * Currently, no internal events are requested with a suspend
129          * policy other than none, so we don't check for notifySuspend()
130          * here. If this changes in the future, there is much
131          * infrastructure that needs to be updated.
132          */
133 
134         return eventSet;
135     }
136 
startTimerThread(long timeout)137     private TimerThread startTimerThread(long timeout) {
138         TimerThread thread = new TimerThread(timeout);
139         thread.setDaemon(true);
140         thread.start();
141         return thread;
142     }
143 
shouldWait(TimerThread timerThread)144     private boolean shouldWait(TimerThread timerThread) {
145         return !closed && eventSets.isEmpty() &&
146                ((timerThread == null) ? true : !timerThread.timedOut());
147     }
148 
removeUnfiltered(long timeout)149     private EventSetImpl removeUnfiltered(long timeout)
150                                                throws InterruptedException {
151         EventSetImpl eventSet = null;
152 
153         /*
154          * Make sure the VM has completed initialization before
155          * trying to build events.
156          */
157         vm.waitInitCompletion();
158 
159         synchronized(this) {
160             if (!eventSets.isEmpty()) {
161                 /*
162                  * If there's already something there, no need
163                  * for anything elaborate.
164                  */
165                 eventSet = (EventSetImpl)eventSets.removeFirst();
166             } else {
167                 /*
168                  * If a timeout was specified, create a thread to
169                  * notify this one when a timeout
170                  * occurs. We can't use the timed version of wait()
171                  * because it is possible for multiple enqueue() calls
172                  * before we see something in the eventSet queue
173                  * (this is possible when multiple threads call
174                  * remove() concurrently -- not a great idea, but
175                  * it should be supported). Even if enqueue() did a
176                  * notify() instead of notifyAll() we are not able to
177                  * use a timed wait because there's no way to distinguish
178                  * a timeout from a notify.  That limitation implies a
179                  * possible race condition between a timed out thread
180                  * and a notified thread.
181                  */
182                 TimerThread timerThread = null;
183                 try {
184                     if (timeout > 0) {
185                         timerThread = startTimerThread(timeout);
186                     }
187 
188                     while (shouldWait(timerThread)) {
189                         this.wait();
190                     }
191                 } finally {
192                     if ((timerThread != null) && !timerThread.timedOut()) {
193                         timerThread.interrupt();
194                     }
195                 }
196 
197                 if (eventSets.isEmpty()) {
198                     if (closed) {
199                         throw new VMDisconnectedException();
200                     }
201                 } else {
202                     eventSet = (EventSetImpl)eventSets.removeFirst();
203                 }
204             }
205         }
206 
207         // The build is synchronized on the event set, don't hold
208         // the queue lock.
209         if (eventSet != null) {
210             target.notifyDequeueEventSet();
211             eventSet.build();
212         }
213         return eventSet;
214     }
215 
216     private class TimerThread extends Thread {
217         private boolean timedOut = false;
218         private long timeout;
219 
TimerThread(long timeout)220         TimerThread(long timeout) {
221             super(vm.threadGroupForJDI(), "JDI Event Queue Timer");
222             this.timeout = timeout;
223         }
224 
timedOut()225         boolean timedOut() {
226             return timedOut;
227         }
228 
run()229         public void run() {
230             try {
231                 Thread.sleep(timeout);
232                 EventQueueImpl queue = EventQueueImpl.this;
233                 synchronized(queue) {
234                     timedOut = true;
235                     queue.notifyAll();
236                 }
237             } catch (InterruptedException e) {
238                 // Exit without notifying
239             }
240         }
241     }
242 }
243