1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package java.lang;
18
19import android.system.Os;
20import android.system.OsConstants;
21import dalvik.system.VMRuntime;
22import java.lang.ref.FinalizerReference;
23import java.lang.ref.Reference;
24import java.lang.ref.ReferenceQueue;
25import java.util.concurrent.atomic.AtomicBoolean;
26import java.util.concurrent.TimeoutException;
27import libcore.util.EmptyArray;
28
29/**
30 * Calls Object.finalize() on objects in the finalizer reference queue. The VM
31 * will abort if any finalize() call takes more than the maximum finalize time
32 * to complete.
33 *
34 * @hide
35 */
36public final class Daemons {
37    private static final int NANOS_PER_MILLI = 1000 * 1000;
38    private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000;
39    private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;
40
41    public static void start() {
42        ReferenceQueueDaemon.INSTANCE.start();
43        FinalizerDaemon.INSTANCE.start();
44        FinalizerWatchdogDaemon.INSTANCE.start();
45        HeapTaskDaemon.INSTANCE.start();
46    }
47
48    public static void stop() {
49        HeapTaskDaemon.INSTANCE.stop();
50        ReferenceQueueDaemon.INSTANCE.stop();
51        FinalizerDaemon.INSTANCE.stop();
52        FinalizerWatchdogDaemon.INSTANCE.stop();
53    }
54
55    /**
56     * A background task that provides runtime support to the application.
57     * Daemons can be stopped and started, but only so that the zygote can be a
58     * single-threaded process when it forks.
59     */
60    private static abstract class Daemon implements Runnable {
61        private Thread thread;
62        private String name;
63
64        protected Daemon(String name) {
65            this.name = name;
66        }
67
68        public synchronized void start() {
69            if (thread != null) {
70                throw new IllegalStateException("already running");
71            }
72            thread = new Thread(ThreadGroup.systemThreadGroup, this, name);
73            thread.setDaemon(true);
74            thread.start();
75        }
76
77        public abstract void run();
78
79        /**
80         * Returns true while the current thread should continue to run; false
81         * when it should return.
82         */
83        protected synchronized boolean isRunning() {
84            return thread != null;
85        }
86
87        public synchronized void interrupt() {
88            interrupt(thread);
89        }
90
91        public synchronized void interrupt(Thread thread) {
92            if (thread == null) {
93                throw new IllegalStateException("not running");
94            }
95            thread.interrupt();
96        }
97
98        /**
99         * Waits for the runtime thread to stop. This interrupts the thread
100         * currently running the runnable and then waits for it to exit.
101         */
102        public void stop() {
103            Thread threadToStop;
104            synchronized (this) {
105                threadToStop = thread;
106                thread = null;
107            }
108            if (threadToStop == null) {
109                throw new IllegalStateException("not running");
110            }
111            interrupt(threadToStop);
112            while (true) {
113                try {
114                    threadToStop.join();
115                    return;
116                } catch (InterruptedException ignored) {
117                }
118            }
119        }
120
121        /**
122         * Returns the current stack trace of the thread, or an empty stack trace
123         * if the thread is not currently running.
124         */
125        public synchronized StackTraceElement[] getStackTrace() {
126            return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT;
127        }
128    }
129
130    /**
131     * This heap management thread moves elements from the garbage collector's
132     * pending list to the managed reference queue.
133     */
134    private static class ReferenceQueueDaemon extends Daemon {
135        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();
136
137        ReferenceQueueDaemon() {
138            super("ReferenceQueueDaemon");
139        }
140
141        @Override public void run() {
142            while (isRunning()) {
143                Reference<?> list;
144                try {
145                    synchronized (ReferenceQueue.class) {
146                        while (ReferenceQueue.unenqueued == null) {
147                            ReferenceQueue.class.wait();
148                        }
149                        list = ReferenceQueue.unenqueued;
150                        ReferenceQueue.unenqueued = null;
151                    }
152                } catch (InterruptedException e) {
153                    continue;
154                }
155                enqueue(list);
156            }
157        }
158
159        private void enqueue(Reference<?> list) {
160            Reference<?> start = list;
161            do {
162                // pendingNext is owned by the GC so no synchronization is required.
163                Reference<?> next = list.pendingNext;
164                list.pendingNext = null;
165                list.enqueueInternal();
166                list = next;
167            } while (list != start);
168        }
169    }
170
171    private static class FinalizerDaemon extends Daemon {
172        private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
173        private final ReferenceQueue<Object> queue = FinalizerReference.queue;
174        private volatile Object finalizingObject;
175        private volatile long finalizingStartedNanos;
176
177        FinalizerDaemon() {
178            super("FinalizerDaemon");
179        }
180
181        @Override public void run() {
182            while (isRunning()) {
183                // Take a reference, blocking until one is ready or the thread should stop
184                try {
185                    doFinalize((FinalizerReference<?>) queue.remove());
186                } catch (InterruptedException ignored) {
187                }
188            }
189        }
190
191        @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
192        private void doFinalize(FinalizerReference<?> reference) {
193            FinalizerReference.remove(reference);
194            Object object = reference.get();
195            reference.clear();
196            try {
197                finalizingStartedNanos = System.nanoTime();
198                finalizingObject = object;
199                synchronized (FinalizerWatchdogDaemon.INSTANCE) {
200                    FinalizerWatchdogDaemon.INSTANCE.notify();
201                }
202                object.finalize();
203            } catch (Throwable ex) {
204                // The RI silently swallows these, but Android has always logged.
205                System.logE("Uncaught exception thrown by finalizer", ex);
206            } finally {
207                // Done finalizing, stop holding the object as live.
208                finalizingObject = null;
209            }
210        }
211    }
212
213    /**
214     * The watchdog exits the VM if the finalizer ever gets stuck. We consider
215     * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS
216     * on one instance.
217     */
218    private static class FinalizerWatchdogDaemon extends Daemon {
219        private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
220
221        FinalizerWatchdogDaemon() {
222            super("FinalizerWatchdogDaemon");
223        }
224
225        @Override public void run() {
226            while (isRunning()) {
227                boolean waitSuccessful = waitForObject();
228                if (waitSuccessful == false) {
229                    // We have been interrupted, need to see if this daemon has been stopped.
230                    continue;
231                }
232                boolean finalized = waitForFinalization();
233                if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) {
234                    Object finalizedObject = FinalizerDaemon.INSTANCE.finalizingObject;
235                    // At this point we probably timed out, look at the object in case the finalize
236                    // just finished.
237                    if (finalizedObject != null) {
238                        finalizerTimedOut(finalizedObject);
239                        break;
240                    }
241                }
242            }
243        }
244
245        private boolean waitForObject() {
246            while (true) {
247                Object object = FinalizerDaemon.INSTANCE.finalizingObject;
248                if (object != null) {
249                    return true;
250                }
251                synchronized (this) {
252                    // wait until something is ready to be finalized
253                    // http://code.google.com/p/android/issues/detail?id=22778
254                    try {
255                        wait();
256                    } catch (InterruptedException e) {
257                        // Daemon.stop may have interrupted us.
258                        return false;
259                    }
260                }
261            }
262        }
263
264        private void sleepFor(long startNanos, long durationNanos) {
265            while (true) {
266                long elapsedNanos = System.nanoTime() - startNanos;
267                long sleepNanos = durationNanos - elapsedNanos;
268                long sleepMills = sleepNanos / NANOS_PER_MILLI;
269                if (sleepMills <= 0) {
270                    return;
271                }
272                try {
273                    Thread.sleep(sleepMills);
274                } catch (InterruptedException e) {
275                    if (!isRunning()) {
276                        return;
277                    }
278                }
279            }
280        }
281
282        private boolean waitForFinalization() {
283            long startTime = FinalizerDaemon.INSTANCE.finalizingStartedNanos;
284            sleepFor(startTime, MAX_FINALIZE_NANOS);
285            // If we are finalizing an object and the start time is the same, it must be that we
286            // timed out finalizing something. It may not be the same object that we started out
287            // with but this doesn't matter.
288            return FinalizerDaemon.INSTANCE.finalizingObject == null ||
289                   FinalizerDaemon.INSTANCE.finalizingStartedNanos != startTime;
290        }
291
292        private static void finalizerTimedOut(Object object) {
293            // The current object has exceeded the finalization deadline; abort!
294            String message = object.getClass().getName() + ".finalize() timed out after "
295                    + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
296            Exception syntheticException = new TimeoutException(message);
297            // We use the stack from where finalize() was running to show where it was stuck.
298            syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
299            Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler();
300            // Send SIGQUIT to get native stack traces.
301            try {
302                Os.kill(Os.getpid(), OsConstants.SIGQUIT);
303                // Sleep a few seconds to let the stack traces print.
304                Thread.sleep(5000);
305            } catch (Exception e) {
306                System.logE("failed to send SIGQUIT", e);
307            }
308            if (h == null) {
309                // If we have no handler, log and exit.
310                System.logE(message, syntheticException);
311                System.exit(2);
312            }
313            // Otherwise call the handler to do crash reporting.
314            // We don't just throw because we're not the thread that
315            // timed out; we're the thread that detected it.
316            h.uncaughtException(Thread.currentThread(), syntheticException);
317        }
318    }
319
320    // Adds a heap trim task ot the heap event processor, not called from java. Left for
321    // compatibility purposes due to reflection.
322    public static void requestHeapTrim() {
323        VMRuntime.getRuntime().requestHeapTrim();
324    }
325
326    // Adds a concurrent GC request task ot the heap event processor, not called from java. Left
327    // for compatibility purposes due to reflection.
328    public static void requestGC() {
329        VMRuntime.getRuntime().requestConcurrentGC();
330    }
331
332    private static class HeapTaskDaemon extends Daemon {
333        private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();
334
335        HeapTaskDaemon() {
336            super("HeapTaskDaemon");
337        }
338
339        // Overrides the Daemon.interupt method which is called from Daemons.stop.
340        public synchronized void interrupt(Thread thread) {
341            VMRuntime.getRuntime().stopHeapTaskProcessor();
342        }
343
344        @Override public void run() {
345            synchronized (this) {
346                if (isRunning()) {
347                  // Needs to be synchronized or else we there is a race condition where we start
348                  // the thread, call stopHeapTaskProcessor before we start the heap task
349                  // processor, resulting in a deadlock since startHeapTaskProcessor restarts it
350                  // while the other thread is waiting in Daemons.stop().
351                  VMRuntime.getRuntime().startHeapTaskProcessor();
352                }
353            }
354            // This runs tasks until we are stopped and there is no more pending task.
355            VMRuntime.getRuntime().runHeapTasks();
356        }
357    }
358}
359