1/*
2 * Copyright (C) 2008 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/*
17 * An async worker thread to handle certain heap operations that
18 * need to be done in a separate thread to avoid synchronization
19 * problems.  HeapWorkers and reference clearing/enqueuing are
20 * handled by this thread.
21 */
22#include "Dalvik.h"
23#include "HeapInternal.h"
24
25#include <sys/time.h>
26#include <stdlib.h>
27#include <pthread.h>
28#include <signal.h>
29#include <errno.h>  // for ETIMEDOUT, etc.
30
31static void* heapWorkerThreadStart(void* arg);
32
33/*
34 * Initialize any HeapWorker state that Heap.c
35 * cares about.  This lets the GC start before the
36 * HeapWorker thread is initialized.
37 */
38void dvmInitializeHeapWorkerState()
39{
40    assert(!gDvm.heapWorkerInitialized);
41
42    dvmInitMutex(&gDvm.heapWorkerLock);
43    pthread_cond_init(&gDvm.heapWorkerCond, NULL);
44    pthread_cond_init(&gDvm.heapWorkerIdleCond, NULL);
45
46    gDvm.heapWorkerInitialized = true;
47}
48
49/*
50 * Crank up the heap worker thread.
51 *
52 * Does not return until the thread is ready for business.
53 */
54bool dvmHeapWorkerStartup(void)
55{
56    assert(!gDvm.haltHeapWorker);
57    assert(!gDvm.heapWorkerReady);
58    assert(gDvm.heapWorkerHandle == 0);
59    assert(gDvm.heapWorkerInitialized);
60
61    /* use heapWorkerLock/heapWorkerCond to communicate readiness */
62    dvmLockMutex(&gDvm.heapWorkerLock);
63
64//BUG: If a GC happens in here or in the new thread while we hold the lock,
65//     the GC will deadlock when trying to acquire heapWorkerLock.
66    if (!dvmCreateInternalThread(&gDvm.heapWorkerHandle,
67                "HeapWorker", heapWorkerThreadStart, NULL))
68    {
69        dvmUnlockMutex(&gDvm.heapWorkerLock);
70        return false;
71    }
72
73    /*
74     * Wait for the heap worker to come up.  We know the thread was created,
75     * so this should not get stuck.
76     */
77    while (!gDvm.heapWorkerReady) {
78        int cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
79        assert(cc == 0);
80    }
81
82    dvmUnlockMutex(&gDvm.heapWorkerLock);
83    return true;
84}
85
86/*
87 * Shut down the heap worker thread if it was started.
88 */
89void dvmHeapWorkerShutdown(void)
90{
91    void* threadReturn;
92
93    /* note: assuming that (pthread_t)0 is not a valid thread handle */
94    if (gDvm.heapWorkerHandle != 0) {
95        gDvm.haltHeapWorker = true;
96        dvmSignalHeapWorker(true);
97
98        /*
99         * We may not want to wait for the heapWorkers to complete.  It's
100         * a good idea to do so, in case they're holding some sort of OS
101         * resource that doesn't get reclaimed when the process exits
102         * (e.g. an open temp file).
103         */
104        if (pthread_join(gDvm.heapWorkerHandle, &threadReturn) != 0)
105            LOGW("HeapWorker thread join failed\n");
106        else
107            LOGD("HeapWorker thread has shut down\n");
108
109        gDvm.heapWorkerReady = false;
110    }
111}
112
113/* Make sure that the HeapWorker thread hasn't spent an inordinate
114 * amount of time inside a finalizer.
115 *
116 * Aborts the VM if the thread appears to be wedged.
117 *
118 * The caller must hold the heapWorkerLock to guarantee an atomic
119 * read of the watchdog values.
120 */
121void dvmAssertHeapWorkerThreadRunning()
122{
123    if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) {
124        static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec
125
126        u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime;
127        u8 now = dvmGetRelativeTimeUsec();
128        u8 delta = now - heapWorkerInterpStartTime;
129
130        u8 heapWorkerInterpCpuStartTime =
131            gDvm.gcHeap->heapWorkerInterpCpuStartTime;
132        u8 nowCpu = dvmGetOtherThreadCpuTimeUsec(gDvm.heapWorkerHandle);
133        u8 deltaCpu = nowCpu - heapWorkerInterpCpuStartTime;
134
135        if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
136            (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
137        {
138            /*
139             * Debugger suspension can block the thread indefinitely.  For
140             * best results we should reset this explicitly whenever the
141             * HeapWorker thread is resumed.  Unfortunately this is also
142             * affected by native debuggers, and we have no visibility
143             * into how they're manipulating us.  So, we ignore the
144             * watchdog and just reset the timer.
145             */
146            LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n");
147            heapWorkerInterpStartTime = now;        /* reset timer */
148        } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) {
149            char* desc = dexProtoCopyMethodDescriptor(
150                    &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
151            LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n",
152                    delta / 1000,
153                    gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
154                    gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
155            free(desc);
156            dvmDumpAllThreads(true);
157
158            /* abort the VM */
159            dvmAbort();
160        } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) {
161            char* desc = dexProtoCopyMethodDescriptor(
162                    &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
163            LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n",
164                    delta / 1000,
165                    gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
166                    gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
167            free(desc);
168        }
169    }
170}
171
172static void callMethod(Thread *self, Object *obj, Method *method)
173{
174    JValue unused;
175
176    /* Keep track of the method we're about to call and
177     * the current time so that other threads can detect
178     * when this thread wedges and provide useful information.
179     */
180    gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec();
181    gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec();
182    gDvm.gcHeap->heapWorkerCurrentMethod = method;
183    gDvm.gcHeap->heapWorkerCurrentObject = obj;
184
185    /* Call the method.
186     *
187     * Don't hold the lock when executing interpreted
188     * code.  It may suspend, and the GC needs to grab
189     * heapWorkerLock.
190     */
191    dvmUnlockMutex(&gDvm.heapWorkerLock);
192    if (false) {
193        /* Log entry/exit; this will likely flood the log enough to
194         * cause "logcat" to drop entries.
195         */
196        char tmpTag[16];
197        sprintf(tmpTag, "HW%d", self->systemTid);
198        LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor);
199        dvmCallMethod(self, method, obj, &unused);
200        LOG(LOG_DEBUG, tmpTag, " done\n");
201    } else {
202        dvmCallMethod(self, method, obj, &unused);
203    }
204    dvmLockMutex(&gDvm.heapWorkerLock);
205
206    gDvm.gcHeap->heapWorkerCurrentObject = NULL;
207    gDvm.gcHeap->heapWorkerCurrentMethod = NULL;
208    gDvm.gcHeap->heapWorkerInterpStartTime = 0LL;
209
210    /* Exceptions thrown during these calls interrupt
211     * the method, but are otherwise ignored.
212     */
213    if (dvmCheckException(self)) {
214#if DVM_SHOW_EXCEPTION >= 1
215        LOGI("Uncaught exception thrown by finalizer (will be discarded):\n");
216        dvmLogExceptionStackTrace();
217#endif
218        dvmClearException(self);
219    }
220}
221
222/* Process all enqueued heap work, including finalizers and reference
223 * clearing/enqueueing.
224 *
225 * Caller must hold gDvm.heapWorkerLock.
226 */
227static void doHeapWork(Thread *self)
228{
229    Object *obj;
230    HeapWorkerOperation op;
231    int numFinalizersCalled, numReferencesEnqueued;
232#if FANCY_REFERENCE_SUBCLASS
233    int numReferencesCleared = 0;
234#endif
235
236    assert(gDvm.voffJavaLangObject_finalize >= 0);
237#if FANCY_REFERENCE_SUBCLASS
238    assert(gDvm.voffJavaLangRefReference_clear >= 0);
239    assert(gDvm.voffJavaLangRefReference_enqueue >= 0);
240#else
241    assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL);
242#endif
243
244    numFinalizersCalled = 0;
245    numReferencesEnqueued = 0;
246    while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) {
247        Method *method = NULL;
248
249        /* Make sure the object hasn't been collected since
250         * being scheduled.
251         */
252        assert(dvmIsValidObject(obj));
253
254        /* Call the appropriate method(s).
255         */
256        if (op == WORKER_FINALIZE) {
257            numFinalizersCalled++;
258            method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize];
259            assert(dvmCompareNameDescriptorAndMethod("finalize", "()V",
260                            method) == 0);
261            assert(method->clazz != gDvm.classJavaLangObject);
262            callMethod(self, obj, method);
263        } else {
264#if FANCY_REFERENCE_SUBCLASS
265            /* clear() *must* happen before enqueue(), otherwise
266             * a non-clear reference could appear on a reference
267             * queue.
268             */
269            if (op & WORKER_CLEAR) {
270                numReferencesCleared++;
271                method = obj->clazz->vtable[
272                        gDvm.voffJavaLangRefReference_clear];
273                assert(dvmCompareNameDescriptorAndMethod("clear", "()V",
274                                method) == 0);
275                assert(method->clazz != gDvm.classJavaLangRefReference);
276                callMethod(self, obj, method);
277            }
278            if (op & WORKER_ENQUEUE) {
279                numReferencesEnqueued++;
280                method = obj->clazz->vtable[
281                        gDvm.voffJavaLangRefReference_enqueue];
282                assert(dvmCompareNameDescriptorAndMethod("enqueue", "()Z",
283                                method) == 0);
284                /* We call enqueue() even when it isn't overridden,
285                 * so don't assert(!classJavaLangRefReference) here.
286                 */
287                callMethod(self, obj, method);
288            }
289#else
290            assert((op & WORKER_CLEAR) == 0);
291            if (op & WORKER_ENQUEUE) {
292                numReferencesEnqueued++;
293                callMethod(self, obj,
294                        gDvm.methJavaLangRefReference_enqueueInternal);
295            }
296#endif
297        }
298
299        /* Let the GC collect the object.
300         */
301        dvmReleaseTrackedAlloc(obj, self);
302    }
303    LOGV("Called %d finalizers\n", numFinalizersCalled);
304    LOGV("Enqueued %d references\n", numReferencesEnqueued);
305#if FANCY_REFERENCE_SUBCLASS
306    LOGV("Cleared %d overridden references\n", numReferencesCleared);
307#endif
308}
309
310/*
311 * The heap worker thread sits quietly until the GC tells it there's work
312 * to do.
313 */
314static void* heapWorkerThreadStart(void* arg)
315{
316    Thread *self = dvmThreadSelf();
317    int cc;
318
319    UNUSED_PARAMETER(arg);
320
321    LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId);
322
323    /* tell the main thread that we're ready */
324    dvmLockMutex(&gDvm.heapWorkerLock);
325    gDvm.heapWorkerReady = true;
326    cc = pthread_cond_signal(&gDvm.heapWorkerCond);
327    assert(cc == 0);
328    dvmUnlockMutex(&gDvm.heapWorkerLock);
329
330    dvmLockMutex(&gDvm.heapWorkerLock);
331    while (!gDvm.haltHeapWorker) {
332        struct timespec trimtime;
333        bool timedwait = false;
334
335        /* We're done running interpreted code for now. */
336        dvmChangeStatus(NULL, THREAD_VMWAIT);
337
338        /* Signal anyone who wants to know when we're done. */
339        cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond);
340        assert(cc == 0);
341
342        /* Trim the heap if we were asked to. */
343        trimtime = gDvm.gcHeap->heapWorkerNextTrim;
344        if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) {
345            struct timeval now;
346
347            gettimeofday(&now, NULL);
348            if (trimtime.tv_sec < now.tv_sec ||
349                (trimtime.tv_sec == now.tv_sec &&
350                 trimtime.tv_nsec <= now.tv_usec * 1000))
351            {
352                size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT];
353
354                /* The heap must be locked before the HeapWorker;
355                 * unroll and re-order the locks.  dvmLockHeap()
356                 * will put us in VMWAIT if necessary.  Once it
357                 * returns, there shouldn't be any contention on
358                 * heapWorkerLock.
359                 */
360                dvmUnlockMutex(&gDvm.heapWorkerLock);
361                dvmLockHeap();
362                dvmLockMutex(&gDvm.heapWorkerLock);
363
364                memset(madvisedSizes, 0, sizeof(madvisedSizes));
365                dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
366                dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
367
368                dvmUnlockHeap();
369
370                trimtime.tv_sec = 0;
371                trimtime.tv_nsec = 0;
372                gDvm.gcHeap->heapWorkerNextTrim = trimtime;
373            } else {
374                timedwait = true;
375            }
376        }
377
378        /* sleep until signaled */
379        if (timedwait) {
380            cc = pthread_cond_timedwait(&gDvm.heapWorkerCond,
381                    &gDvm.heapWorkerLock, &trimtime);
382            assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR);
383        } else {
384            cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
385            assert(cc == 0);
386        }
387
388        /* dvmChangeStatus() may block;  don't hold heapWorkerLock.
389         */
390        dvmUnlockMutex(&gDvm.heapWorkerLock);
391        dvmChangeStatus(NULL, THREAD_RUNNING);
392        dvmLockMutex(&gDvm.heapWorkerLock);
393        LOGV("HeapWorker is awake\n");
394
395        /* Process any events in the queue.
396         */
397        doHeapWork(self);
398    }
399    dvmUnlockMutex(&gDvm.heapWorkerLock);
400
401    LOGD("HeapWorker thread shutting down\n");
402    return NULL;
403}
404
405/*
406 * Wake up the heap worker to let it know that there's work to be done.
407 */
408void dvmSignalHeapWorker(bool shouldLock)
409{
410    int cc;
411
412    if (shouldLock) {
413        dvmLockMutex(&gDvm.heapWorkerLock);
414    }
415
416    cc = pthread_cond_signal(&gDvm.heapWorkerCond);
417    assert(cc == 0);
418
419    if (shouldLock) {
420        dvmUnlockMutex(&gDvm.heapWorkerLock);
421    }
422}
423
424/*
425 * Block until all pending heap worker work has finished.
426 */
427void dvmWaitForHeapWorkerIdle()
428{
429    int cc;
430
431    assert(gDvm.heapWorkerReady);
432
433    dvmChangeStatus(NULL, THREAD_VMWAIT);
434
435    dvmLockMutex(&gDvm.heapWorkerLock);
436
437    /* Wake up the heap worker and wait for it to finish. */
438    //TODO(http://b/issue?id=699704): This will deadlock if
439    //     called from finalize(), enqueue(), or clear().  We
440    //     need to detect when this is called from the HeapWorker
441    //     context and just give up.
442    dvmSignalHeapWorker(false);
443    cc = pthread_cond_wait(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock);
444    assert(cc == 0);
445
446    dvmUnlockMutex(&gDvm.heapWorkerLock);
447
448    dvmChangeStatus(NULL, THREAD_RUNNING);
449}
450
451/*
452 * Do not return until any pending heap work has finished.  This may
453 * or may not happen in the context of the calling thread.
454 * No exceptions will escape.
455 */
456void dvmRunFinalizationSync()
457{
458    if (gDvm.zygote) {
459        assert(!gDvm.heapWorkerReady);
460
461        /* When in zygote mode, there is no heap worker.
462         * Do the work in the current thread.
463         */
464        dvmLockMutex(&gDvm.heapWorkerLock);
465        doHeapWork(dvmThreadSelf());
466        dvmUnlockMutex(&gDvm.heapWorkerLock);
467    } else {
468        /* Outside of zygote mode, we can just ask the
469         * heap worker thread to do the work.
470         */
471        dvmWaitForHeapWorkerIdle();
472    }
473}
474
475/*
476 * Requests that dvmHeapSourceTrim() be called no sooner
477 * than timeoutSec seconds from now.  If timeoutSec
478 * is zero, any pending trim is cancelled.
479 *
480 * Caller must hold heapWorkerLock.
481 */
482void dvmScheduleHeapSourceTrim(size_t timeoutSec)
483{
484    GcHeap *gcHeap = gDvm.gcHeap;
485    struct timespec timeout;
486
487    if (timeoutSec == 0) {
488        timeout.tv_sec = 0;
489        timeout.tv_nsec = 0;
490        /* Don't wake up the thread just to tell it to cancel.
491         * If it wakes up naturally, we can avoid the extra
492         * context switch.
493         */
494    } else {
495        struct timeval now;
496
497        gettimeofday(&now, NULL);
498        timeout.tv_sec = now.tv_sec + timeoutSec;
499        timeout.tv_nsec = now.tv_usec * 1000;
500        dvmSignalHeapWorker(false);
501    }
502    gcHeap->heapWorkerNextTrim = timeout;
503}
504