1/*
2 * Copyright (c) 1998, 2014, 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 jdk.jshell.execution;
27
28import java.util.function.Consumer;
29import com.sun.jdi.*;
30import com.sun.jdi.event.*;
31
32/**
33 * Handler of Java Debug Interface events.
34 * Adapted from jdb EventHandler.
35 * Only exit and disconnect events processed.
36 */
37class JdiEventHandler implements Runnable {
38
39    private final Thread thread;
40    private volatile boolean connected = true;
41    private boolean completed = false;
42    private final VirtualMachine vm;
43    private final Consumer<String> reportVMExit;
44
45    /**
46     * Creates an event handler. Start with {@code start()}.
47     *
48     * @param vm the virtual machine for which to handle events
49     * @param reportVMExit callback to report exit/disconnect
50     * (passed true if the VM has died)
51     */
52    JdiEventHandler(VirtualMachine vm, Consumer<String> reportVMExit) {
53        this.vm = vm;
54        this.reportVMExit = reportVMExit;
55        this.thread = new Thread(this, "event-handler");
56        this.thread.setDaemon(true);
57    }
58
59    /**
60     * Starts the event handler.
61     */
62    void start() {
63        thread.start();
64    }
65
66    synchronized void shutdown() {
67        connected = false;  // force run() loop termination
68        thread.interrupt();
69        while (!completed) {
70            try {wait();} catch (InterruptedException exc) {}
71        }
72    }
73
74    @Override
75    public void run() {
76        EventQueue queue = vm.eventQueue();
77        while (connected) {
78            try {
79                EventSet eventSet = queue.remove();
80                boolean resumeStoppedApp = false;
81                EventIterator it = eventSet.eventIterator();
82                while (it.hasNext()) {
83                    resumeStoppedApp |= handleEvent(it.nextEvent());
84                }
85
86                if (resumeStoppedApp) {
87                    eventSet.resume();
88                }
89            } catch (InterruptedException exc) {
90                // Do nothing. Any changes will be seen at top of loop.
91            } catch (VMDisconnectedException discExc) {
92                handleDisconnectedException();
93                break;
94            }
95        }
96        synchronized (this) {
97            completed = true;
98            notifyAll();
99        }
100    }
101
102    private boolean handleEvent(Event event) {
103        handleExitEvent(event);
104        return true;
105    }
106
107    private void handleExitEvent(Event event) {
108        if (event instanceof VMDeathEvent) {
109            reportVMExit.accept("VM Died");
110        } else if (event instanceof VMDisconnectEvent) {
111            connected = false;
112            reportVMExit.accept("VM Disconnected");
113        } else {
114            // ignore everything else
115        }
116    }
117
118    private synchronized void handleDisconnectedException() {
119        /*
120         * A VMDisconnectedException has happened while dealing with
121         * another event. We need to flush the event queue, dealing only
122         * with exit events (VMDeath, VMDisconnect) so that we terminate
123         * correctly.
124         */
125        EventQueue queue = vm.eventQueue();
126        while (connected) {
127            try {
128                EventSet eventSet = queue.remove();
129                EventIterator iter = eventSet.eventIterator();
130                while (iter.hasNext()) {
131                    handleExitEvent(iter.next());
132                }
133            } catch (InterruptedException exc) {
134                // ignore
135            } catch (InternalError exc) {
136                // ignore
137            }
138        }
139    }
140}
141