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 dalvik.system.VMRuntime;
20import java.lang.ref.FinalizerReference;
21import java.lang.ref.Reference;
22import java.lang.ref.ReferenceQueue;
23import java.util.concurrent.TimeoutException;
24import libcore.util.EmptyArray;
25
26/**
27 * Calls Object.finalize() on objects in the finalizer reference queue. The VM
28 * will abort if any finalize() call takes more than the maximum finalize time
29 * to complete.
30 *
31 * @hide
32 */
33public final class Daemons {
34    private static final int NANOS_PER_MILLI = 1000 * 1000;
35    private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000;
36    private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;
37
38    public static void start() {
39        ReferenceQueueDaemon.INSTANCE.start();
40        FinalizerDaemon.INSTANCE.start();
41        FinalizerWatchdogDaemon.INSTANCE.start();
42    }
43
44    public static void stop() {
45        ReferenceQueueDaemon.INSTANCE.stop();
46        FinalizerDaemon.INSTANCE.stop();
47        FinalizerWatchdogDaemon.INSTANCE.stop();
48    }
49
50    /**
51     * A background task that provides runtime support to the application.
52     * Daemons can be stopped and started, but only so that the zygote can be a
53     * single-threaded process when it forks.
54     */
55    private static abstract class Daemon implements Runnable {
56        private Thread thread;
57
58        public synchronized void start() {
59            if (thread != null) {
60                throw new IllegalStateException("already running");
61            }
62            thread = new Thread(ThreadGroup.mSystem, this,
63                getClass().getSimpleName());
64            thread.setDaemon(true);
65            thread.start();
66        }
67
68        public abstract void run();
69
70        /**
71         * Returns true while the current thread should continue to run; false
72         * when it should return.
73         */
74        protected synchronized boolean isRunning() {
75            return thread != null;
76        }
77
78        public synchronized void interrupt() {
79            if (thread == null) {
80                throw new IllegalStateException("not running");
81            }
82            thread.interrupt();
83        }
84
85        /**
86         * Waits for the runtime thread to stop. This interrupts the thread
87         * currently running the runnable and then waits for it to exit.
88         */
89        public void stop() {
90            Thread threadToStop;
91            synchronized (this) {
92                threadToStop = thread;
93                thread = null;
94            }
95            if (threadToStop == null) {
96                throw new IllegalStateException("not running");
97            }
98            threadToStop.interrupt();
99            while (true) {
100                try {
101                    threadToStop.join();
102                    return;
103                } catch (InterruptedException ignored) {
104                }
105            }
106        }
107
108        /**
109         * Returns the current stack trace of the thread, or an empty stack trace
110         * if the thread is not currently running.
111         */
112        public synchronized StackTraceElement[] getStackTrace() {
113            return thread != null ? thread.getStackTrace() : EmptyArray.STACK_TRACE_ELEMENT;
114        }
115    }
116
117    /**
118     * This heap management thread moves elements from the garbage collector's
119     * pending list to the managed reference queue.
120     */
121    private static class ReferenceQueueDaemon extends Daemon {
122        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();
123
124        @Override public void run() {
125            while (isRunning()) {
126                Reference<?> list;
127                try {
128                    synchronized (ReferenceQueue.class) {
129                        while (ReferenceQueue.unenqueued == null) {
130                            ReferenceQueue.class.wait();
131                        }
132                        list = ReferenceQueue.unenqueued;
133                        ReferenceQueue.unenqueued = null;
134                    }
135                } catch (InterruptedException e) {
136                    continue;
137                }
138                enqueue(list);
139            }
140        }
141
142        private void enqueue(Reference<?> list) {
143            while (list != null) {
144                Reference<?> reference;
145                // pendingNext is owned by the GC so no synchronization is required
146                if (list == list.pendingNext) {
147                    reference = list;
148                    reference.pendingNext = null;
149                    list = null;
150                } else {
151                    reference = list.pendingNext;
152                    list.pendingNext = reference.pendingNext;
153                    reference.pendingNext = null;
154                }
155                reference.enqueueInternal();
156            }
157        }
158    }
159
160    private static class FinalizerDaemon extends Daemon {
161        private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
162        private final ReferenceQueue<Object> queue = FinalizerReference.queue;
163        private volatile Object finalizingObject;
164        private volatile long finalizingStartedNanos;
165
166        @Override public void run() {
167            while (isRunning()) {
168                // Take a reference, blocking until one is ready or the thread should stop
169                try {
170                    doFinalize((FinalizerReference<?>) queue.remove());
171                } catch (InterruptedException ignored) {
172                }
173            }
174        }
175
176        @FindBugsSuppressWarnings("FI_EXPLICIT_INVOCATION")
177        private void doFinalize(FinalizerReference<?> reference) {
178            FinalizerReference.remove(reference);
179            Object object = reference.get();
180            reference.clear();
181            try {
182                finalizingStartedNanos = System.nanoTime();
183                finalizingObject = object;
184                synchronized (FinalizerWatchdogDaemon.INSTANCE) {
185                    FinalizerWatchdogDaemon.INSTANCE.notify();
186                }
187                object.finalize();
188            } catch (Throwable ex) {
189                // The RI silently swallows these, but Android has always logged.
190                System.logE("Uncaught exception thrown by finalizer", ex);
191            } finally {
192                finalizingObject = null;
193            }
194        }
195    }
196
197    /**
198     * The watchdog exits the VM if the finalizer ever gets stuck. We consider
199     * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS
200     * on one instance.
201     */
202    private static class FinalizerWatchdogDaemon extends Daemon {
203        private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
204
205        @Override public void run() {
206            while (isRunning()) {
207                Object object = waitForObject();
208                if (object == null) {
209                    // We have been interrupted, need to see if this daemon has been stopped.
210                    continue;
211                }
212                boolean finalized = waitForFinalization(object);
213                if (!finalized && !VMRuntime.getRuntime().isDebuggerActive()) {
214                    finalizerTimedOut(object);
215                    break;
216                }
217            }
218        }
219
220        private Object waitForObject() {
221            while (true) {
222                Object object = FinalizerDaemon.INSTANCE.finalizingObject;
223                if (object != null) {
224                    return object;
225                }
226                synchronized (this) {
227                    // wait until something is ready to be finalized
228                    // http://code.google.com/p/android/issues/detail?id=22778
229                    try {
230                        wait();
231                    } catch (InterruptedException e) {
232                        // Daemon.stop may have interrupted us.
233                        return null;
234                    }
235                }
236            }
237        }
238
239        private void sleepFor(long startNanos, long durationNanos) {
240            while (true) {
241                long elapsedNanos = System.nanoTime() - startNanos;
242                long sleepNanos = durationNanos - elapsedNanos;
243                long sleepMills = sleepNanos / NANOS_PER_MILLI;
244                if (sleepMills <= 0) {
245                    return;
246                }
247                try {
248                    Thread.sleep(sleepMills);
249                } catch (InterruptedException e) {
250                    if (!isRunning()) {
251                        return;
252                    }
253                }
254            }
255        }
256
257        private boolean waitForFinalization(Object object) {
258            sleepFor(FinalizerDaemon.INSTANCE.finalizingStartedNanos, MAX_FINALIZE_NANOS);
259            return object != FinalizerDaemon.INSTANCE.finalizingObject;
260        }
261
262        private static void finalizerTimedOut(Object object) {
263            // The current object has exceeded the finalization deadline; abort!
264            String message = object.getClass().getName() + ".finalize() timed out after "
265                    + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
266            Exception syntheticException = new TimeoutException(message);
267            // We use the stack from where finalize() was running to show where it was stuck.
268            syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
269            Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler();
270            if (h == null) {
271                // If we have no handler, log and exit.
272                System.logE(message, syntheticException);
273                System.exit(2);
274            }
275            // Otherwise call the handler to do crash reporting.
276            // We don't just throw because we're not the thread that
277            // timed out; we're the thread that detected it.
278            h.uncaughtException(Thread.currentThread(), syntheticException);
279        }
280    }
281}
282