1/*
2 * Copyright (c) 1999, 2017, 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 java.lang.ref.WeakReference;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Iterator;
32import java.util.List;
33
34import com.sun.jdi.ThreadGroupReference;
35import com.sun.jdi.ThreadReference;
36import com.sun.jdi.VirtualMachine;
37
38class VMState {
39    private final VirtualMachineImpl vm;
40
41    // Listeners
42    private final List<WeakReference<VMListener>> listeners = new ArrayList<>(); // synchronized (this)
43    private boolean notifyingListeners = false;  // synchronized (this)
44
45    /*
46     * Certain information can be cached only when the entire VM is
47     * suspended and there are no pending resumes. The fields below
48     * are used to track whether there are pending resumes. (There
49     * is an assumption that JDWP command ids are increasing over time.)
50     */
51    private int lastCompletedCommandId = 0;   // synchronized (this)
52    private int lastResumeCommandId = 0;      // synchronized (this)
53
54    // This is cached only while the VM is suspended
55    private static class Cache {
56        List<ThreadGroupReference> groups = null;  // cached Top Level ThreadGroups
57        List<ThreadReference> threads = null; // cached Threads
58    }
59
60    private Cache cache = null;               // synchronized (this)
61    private static final Cache markerCache = new Cache();
62
63    private void disableCache() {
64        synchronized (this) {
65            cache = null;
66        }
67    }
68
69    private void enableCache() {
70        synchronized (this) {
71            cache = markerCache;
72        }
73    }
74
75    private Cache getCache() {
76        synchronized (this) {
77            if (cache == markerCache) {
78                cache = new Cache();
79            }
80            return cache;
81        }
82    }
83
84    VMState(VirtualMachineImpl vm) {
85        this.vm = vm;
86    }
87
88    /**
89     * Is the VM currently suspended, for the purpose of caching?
90     * Must be called synchronized on vm.state()
91     */
92    boolean isSuspended() {
93        return cache != null;
94    }
95
96    /*
97     * A JDWP command has been completed (reply has been received).
98     * Update data that tracks pending resume commands.
99     */
100    synchronized void notifyCommandComplete(int id) {
101        lastCompletedCommandId = id;
102    }
103
104    synchronized void freeze() {
105        if (cache == null && (lastCompletedCommandId >= lastResumeCommandId)) {
106            /*
107             * No pending resumes to worry about. The VM is suspended
108             * and additional state can be cached. Notify all
109             * interested listeners.
110             */
111            processVMAction(new VMAction(vm, VMAction.VM_SUSPENDED));
112            enableCache();
113        }
114    }
115
116    synchronized PacketStream thawCommand(CommandSender sender) {
117        PacketStream stream = sender.send();
118        lastResumeCommandId = stream.id();
119        thaw();
120        return stream;
121    }
122
123    /**
124     * All threads are resuming
125     */
126    void thaw() {
127        thaw(null);
128    }
129
130    /**
131     * Tell listeners to invalidate suspend-sensitive caches.
132     * If resumingThread != null, then only that thread is being
133     * resumed.
134     */
135    synchronized void thaw(ThreadReference resumingThread) {
136        if (cache != null) {
137            if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
138                vm.printTrace("Clearing VM suspended cache");
139            }
140            disableCache();
141        }
142        processVMAction(new VMAction(vm, resumingThread, VMAction.VM_NOT_SUSPENDED));
143    }
144
145    private synchronized void processVMAction(VMAction action) {
146        if (!notifyingListeners) {
147            // Prevent recursion
148            notifyingListeners = true;
149
150            Iterator<WeakReference<VMListener>> iter = listeners.iterator();
151            while (iter.hasNext()) {
152                WeakReference<VMListener> ref = iter.next();
153                VMListener listener = ref.get();
154                if (listener != null) {
155                    boolean keep = true;
156                    switch (action.id()) {
157                        case VMAction.VM_SUSPENDED:
158                            keep = listener.vmSuspended(action);
159                            break;
160                        case VMAction.VM_NOT_SUSPENDED:
161                            keep = listener.vmNotSuspended(action);
162                            break;
163                    }
164                    if (!keep) {
165                        iter.remove();
166                    }
167                } else {
168                    // Listener is unreachable; clean up
169                    iter.remove();
170                }
171            }
172
173            notifyingListeners = false;
174        }
175    }
176
177    synchronized void addListener(VMListener listener) {
178        listeners.add(new WeakReference<VMListener>(listener));
179    }
180
181    synchronized boolean hasListener(VMListener listener) {
182        return listeners.contains(listener);
183    }
184
185    synchronized void removeListener(VMListener listener) {
186        Iterator<WeakReference<VMListener>> iter = listeners.iterator();
187        while (iter.hasNext()) {
188            WeakReference<VMListener> ref = iter.next();
189            if (listener.equals(ref.get())) {
190                iter.remove();
191                break;
192            }
193        }
194    }
195
196    List<ThreadReference> allThreads() {
197        List<ThreadReference> threads = null;
198        try {
199            Cache local = getCache();
200
201            if (local != null) {
202                // may be stale when returned, but not provably so
203                threads = local.threads;
204            }
205            if (threads == null) {
206                threads = Arrays.asList((ThreadReference[])JDWP.VirtualMachine.AllThreads.
207                                        process(vm).threads);
208                if (local != null) {
209                    local.threads = threads;
210                    if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
211                        vm.printTrace("Caching all threads (count = " +
212                                      threads.size() + ") while VM suspended");
213                    }
214                }
215            }
216        } catch (JDWPException exc) {
217            throw exc.toJDIException();
218        }
219        return threads;
220    }
221
222
223    List<ThreadGroupReference> topLevelThreadGroups() {
224        List<ThreadGroupReference> groups = null;
225        try {
226            Cache local = getCache();
227
228            if (local != null) {
229                groups = local.groups;
230            }
231            if (groups == null) {
232                groups = Arrays.asList(
233                                (ThreadGroupReference[])JDWP.VirtualMachine.TopLevelThreadGroups.
234                                       process(vm).groups);
235                if (local != null) {
236                    local.groups = groups;
237                    if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
238                        vm.printTrace(
239                          "Caching top level thread groups (count = " +
240                          groups.size() + ") while VM suspended");
241                    }
242                }
243            }
244        } catch (JDWPException exc) {
245            throw exc.toJDIException();
246        }
247        return groups;
248    }
249}
250