/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * An async worker thread to handle certain heap operations that
 * need to be done in a separate thread to avoid synchronization
 * problems.  HeapWorkers and reference clearing/enqueuing are
 * handled by this thread.
 */
#include "Dalvik.h"
#include "HeapInternal.h"

#include <sys/time.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>  // for ETIMEDOUT, etc.

static void* heapWorkerThreadStart(void* arg);

/*
 * Initialize any HeapWorker state that Heap.c
 * cares about.  This lets the GC start before the
 * HeapWorker thread is initialized.
 */
void dvmInitializeHeapWorkerState()
{
    assert(!gDvm.heapWorkerInitialized);

    dvmInitMutex(&gDvm.heapWorkerLock);
    pthread_cond_init(&gDvm.heapWorkerCond, NULL);
    pthread_cond_init(&gDvm.heapWorkerIdleCond, NULL);

    gDvm.heapWorkerInitialized = true;
}

/*
 * Crank up the heap worker thread.
 *
 * Does not return until the thread is ready for business.
 */
bool dvmHeapWorkerStartup(void)
{
    assert(!gDvm.haltHeapWorker);
    assert(!gDvm.heapWorkerReady);
    assert(gDvm.heapWorkerHandle == 0);
    assert(gDvm.heapWorkerInitialized);

    /* use heapWorkerLock/heapWorkerCond to communicate readiness */
    dvmLockMutex(&gDvm.heapWorkerLock);

//BUG: If a GC happens in here or in the new thread while we hold the lock,
//     the GC will deadlock when trying to acquire heapWorkerLock.
    if (!dvmCreateInternalThread(&gDvm.heapWorkerHandle,
                "HeapWorker", heapWorkerThreadStart, NULL))
    {
        dvmUnlockMutex(&gDvm.heapWorkerLock);
        return false;
    }

    /*
     * Wait for the heap worker to come up.  We know the thread was created,
     * so this should not get stuck.
     */
    while (!gDvm.heapWorkerReady) {
        int cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
        assert(cc == 0);
    }

    dvmUnlockMutex(&gDvm.heapWorkerLock);
    return true;
}

/*
 * Shut down the heap worker thread if it was started.
 */
void dvmHeapWorkerShutdown(void)
{
    void* threadReturn;

    /* note: assuming that (pthread_t)0 is not a valid thread handle */
    if (gDvm.heapWorkerHandle != 0) {
        gDvm.haltHeapWorker = true;
        dvmSignalHeapWorker(true);

        /*
         * We may not want to wait for the heapWorkers to complete.  It's
         * a good idea to do so, in case they're holding some sort of OS
         * resource that doesn't get reclaimed when the process exits
         * (e.g. an open temp file).
         */
        if (pthread_join(gDvm.heapWorkerHandle, &threadReturn) != 0)
            LOGW("HeapWorker thread join failed\n");
        else
            LOGD("HeapWorker thread has shut down\n");

        gDvm.heapWorkerReady = false;
    }
}

/* Make sure that the HeapWorker thread hasn't spent an inordinate
 * amount of time inside a finalizer.
 *
 * Aborts the VM if the thread appears to be wedged.
 *
 * The caller must hold the heapWorkerLock to guarantee an atomic
 * read of the watchdog values.
 */
void dvmAssertHeapWorkerThreadRunning()
{
    if (gDvm.gcHeap->heapWorkerCurrentObject != NULL) {
        static const u8 HEAP_WORKER_WATCHDOG_TIMEOUT = 10*1000*1000LL; // 10sec

        u8 heapWorkerInterpStartTime = gDvm.gcHeap->heapWorkerInterpStartTime;
        u8 now = dvmGetRelativeTimeUsec();
        u8 delta = now - heapWorkerInterpStartTime;

        u8 heapWorkerInterpCpuStartTime =
            gDvm.gcHeap->heapWorkerInterpCpuStartTime;
        u8 nowCpu = dvmGetOtherThreadCpuTimeUsec(gDvm.heapWorkerHandle);
        u8 deltaCpu = nowCpu - heapWorkerInterpCpuStartTime;

        if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT &&
            (gDvm.debuggerActive || gDvm.nativeDebuggerActive))
        {
            /*
             * Debugger suspension can block the thread indefinitely.  For
             * best results we should reset this explicitly whenever the
             * HeapWorker thread is resumed.  Unfortunately this is also
             * affected by native debuggers, and we have no visibility
             * into how they're manipulating us.  So, we ignore the
             * watchdog and just reset the timer.
             */
            LOGI("Debugger is attached -- suppressing HeapWorker watchdog\n");
            heapWorkerInterpStartTime = now;        /* reset timer */
        } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT) {
            char* desc = dexProtoCopyMethodDescriptor(
                    &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
            LOGE("HeapWorker is wedged: %lldms spent inside %s.%s%s\n",
                    delta / 1000,
                    gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
                    gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
            free(desc);
            dvmDumpAllThreads(true);

            /* abort the VM */
            dvmAbort();
        } else if (delta > HEAP_WORKER_WATCHDOG_TIMEOUT / 2) {
            char* desc = dexProtoCopyMethodDescriptor(
                    &gDvm.gcHeap->heapWorkerCurrentMethod->prototype);
            LOGW("HeapWorker may be wedged: %lldms spent inside %s.%s%s\n",
                    delta / 1000,
                    gDvm.gcHeap->heapWorkerCurrentObject->clazz->descriptor,
                    gDvm.gcHeap->heapWorkerCurrentMethod->name, desc);
            free(desc);
        }
    }
}

static void callMethod(Thread *self, Object *obj, Method *method)
{
    JValue unused;

    /* Keep track of the method we're about to call and
     * the current time so that other threads can detect
     * when this thread wedges and provide useful information.
     */
    gDvm.gcHeap->heapWorkerInterpStartTime = dvmGetRelativeTimeUsec();
    gDvm.gcHeap->heapWorkerInterpCpuStartTime = dvmGetThreadCpuTimeUsec();
    gDvm.gcHeap->heapWorkerCurrentMethod = method;
    gDvm.gcHeap->heapWorkerCurrentObject = obj;

    /* Call the method.
     *
     * Don't hold the lock when executing interpreted
     * code.  It may suspend, and the GC needs to grab
     * heapWorkerLock.
     */
    dvmUnlockMutex(&gDvm.heapWorkerLock);
    if (false) {
        /* Log entry/exit; this will likely flood the log enough to
         * cause "logcat" to drop entries.
         */
        char tmpTag[16];
        sprintf(tmpTag, "HW%d", self->systemTid);
        LOG(LOG_DEBUG, tmpTag, "Call %s\n", method->clazz->descriptor);
        dvmCallMethod(self, method, obj, &unused);
        LOG(LOG_DEBUG, tmpTag, " done\n");
    } else {
        dvmCallMethod(self, method, obj, &unused);
    }
    dvmLockMutex(&gDvm.heapWorkerLock);

    gDvm.gcHeap->heapWorkerCurrentObject = NULL;
    gDvm.gcHeap->heapWorkerCurrentMethod = NULL;
    gDvm.gcHeap->heapWorkerInterpStartTime = 0LL;

    /* Exceptions thrown during these calls interrupt
     * the method, but are otherwise ignored.
     */
    if (dvmCheckException(self)) {
#if DVM_SHOW_EXCEPTION >= 1
        LOGI("Uncaught exception thrown by finalizer (will be discarded):\n");
        dvmLogExceptionStackTrace();
#endif
        dvmClearException(self);
    }
}

/* Process all enqueued heap work, including finalizers and reference
 * clearing/enqueueing.
 *
 * Caller must hold gDvm.heapWorkerLock.
 */
static void doHeapWork(Thread *self)
{
    Object *obj;
    HeapWorkerOperation op;
    int numFinalizersCalled, numReferencesEnqueued;
#if FANCY_REFERENCE_SUBCLASS
    int numReferencesCleared = 0;
#endif

    assert(gDvm.voffJavaLangObject_finalize >= 0);
#if FANCY_REFERENCE_SUBCLASS
    assert(gDvm.voffJavaLangRefReference_clear >= 0);
    assert(gDvm.voffJavaLangRefReference_enqueue >= 0);
#else
    assert(gDvm.methJavaLangRefReference_enqueueInternal != NULL);
#endif

    numFinalizersCalled = 0;
    numReferencesEnqueued = 0;
    while ((obj = dvmGetNextHeapWorkerObject(&op)) != NULL) {
        Method *method = NULL;

        /* Make sure the object hasn't been collected since
         * being scheduled.
         */
        assert(dvmIsValidObject(obj));

        /* Call the appropriate method(s).
         */
        if (op == WORKER_FINALIZE) {
            numFinalizersCalled++;
            method = obj->clazz->vtable[gDvm.voffJavaLangObject_finalize];
            assert(dvmCompareNameDescriptorAndMethod("finalize", "()V",
                            method) == 0);
            assert(method->clazz != gDvm.classJavaLangObject);
            callMethod(self, obj, method);
        } else {
#if FANCY_REFERENCE_SUBCLASS
            /* clear() *must* happen before enqueue(), otherwise
             * a non-clear reference could appear on a reference
             * queue.
             */
            if (op & WORKER_CLEAR) {
                numReferencesCleared++;
                method = obj->clazz->vtable[
                        gDvm.voffJavaLangRefReference_clear];
                assert(dvmCompareNameDescriptorAndMethod("clear", "()V",
                                method) == 0);
                assert(method->clazz != gDvm.classJavaLangRefReference);
                callMethod(self, obj, method);
            }
            if (op & WORKER_ENQUEUE) {
                numReferencesEnqueued++;
                method = obj->clazz->vtable[
                        gDvm.voffJavaLangRefReference_enqueue];
                assert(dvmCompareNameDescriptorAndMethod("enqueue", "()Z",
                                method) == 0);
                /* We call enqueue() even when it isn't overridden,
                 * so don't assert(!classJavaLangRefReference) here.
                 */
                callMethod(self, obj, method);
            }
#else
            assert((op & WORKER_CLEAR) == 0);
            if (op & WORKER_ENQUEUE) {
                numReferencesEnqueued++;
                callMethod(self, obj,
                        gDvm.methJavaLangRefReference_enqueueInternal);
            }
#endif
        }

        /* Let the GC collect the object.
         */
        dvmReleaseTrackedAlloc(obj, self);
    }
    LOGV("Called %d finalizers\n", numFinalizersCalled);
    LOGV("Enqueued %d references\n", numReferencesEnqueued);
#if FANCY_REFERENCE_SUBCLASS
    LOGV("Cleared %d overridden references\n", numReferencesCleared);
#endif
}

/*
 * The heap worker thread sits quietly until the GC tells it there's work
 * to do.
 */
static void* heapWorkerThreadStart(void* arg)
{
    Thread *self = dvmThreadSelf();
    int cc;

    UNUSED_PARAMETER(arg);

    LOGV("HeapWorker thread started (threadid=%d)\n", self->threadId);

    /* tell the main thread that we're ready */
    dvmLockMutex(&gDvm.heapWorkerLock);
    gDvm.heapWorkerReady = true;
    cc = pthread_cond_signal(&gDvm.heapWorkerCond);
    assert(cc == 0);
    dvmUnlockMutex(&gDvm.heapWorkerLock);

    dvmLockMutex(&gDvm.heapWorkerLock);
    while (!gDvm.haltHeapWorker) {
        struct timespec trimtime;
        bool timedwait = false;

        /* We're done running interpreted code for now. */
        dvmChangeStatus(NULL, THREAD_VMWAIT);

        /* Signal anyone who wants to know when we're done. */
        cc = pthread_cond_broadcast(&gDvm.heapWorkerIdleCond);
        assert(cc == 0);

        /* Trim the heap if we were asked to. */
        trimtime = gDvm.gcHeap->heapWorkerNextTrim;
        if (trimtime.tv_sec != 0 && trimtime.tv_nsec != 0) {
            struct timeval now;

            gettimeofday(&now, NULL);
            if (trimtime.tv_sec < now.tv_sec ||
                (trimtime.tv_sec == now.tv_sec && 
                 trimtime.tv_nsec <= now.tv_usec * 1000))
            {
                size_t madvisedSizes[HEAP_SOURCE_MAX_HEAP_COUNT];

                /* The heap must be locked before the HeapWorker;
                 * unroll and re-order the locks.  dvmLockHeap()
                 * will put us in VMWAIT if necessary.  Once it
                 * returns, there shouldn't be any contention on
                 * heapWorkerLock.
                 */
                dvmUnlockMutex(&gDvm.heapWorkerLock);
                dvmLockHeap();
                dvmLockMutex(&gDvm.heapWorkerLock);

                memset(madvisedSizes, 0, sizeof(madvisedSizes));
                dvmHeapSourceTrim(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);
                dvmLogMadviseStats(madvisedSizes, HEAP_SOURCE_MAX_HEAP_COUNT);

                dvmUnlockHeap();

                trimtime.tv_sec = 0;
                trimtime.tv_nsec = 0;
                gDvm.gcHeap->heapWorkerNextTrim = trimtime;
            } else {
                timedwait = true;
            }
        }

        /* sleep until signaled */
        if (timedwait) {
            cc = pthread_cond_timedwait(&gDvm.heapWorkerCond,
                    &gDvm.heapWorkerLock, &trimtime);
            assert(cc == 0 || cc == ETIMEDOUT || cc == EINTR);
        } else {
            cc = pthread_cond_wait(&gDvm.heapWorkerCond, &gDvm.heapWorkerLock);
            assert(cc == 0);
        }

        /* dvmChangeStatus() may block;  don't hold heapWorkerLock.
         */
        dvmUnlockMutex(&gDvm.heapWorkerLock);
        dvmChangeStatus(NULL, THREAD_RUNNING);
        dvmLockMutex(&gDvm.heapWorkerLock);
        LOGV("HeapWorker is awake\n");

        /* Process any events in the queue.
         */
        doHeapWork(self);
    }
    dvmUnlockMutex(&gDvm.heapWorkerLock);

    LOGD("HeapWorker thread shutting down\n");
    return NULL;
}

/*
 * Wake up the heap worker to let it know that there's work to be done.
 */
void dvmSignalHeapWorker(bool shouldLock)
{
    int cc;

    if (shouldLock) {
        dvmLockMutex(&gDvm.heapWorkerLock);
    }

    cc = pthread_cond_signal(&gDvm.heapWorkerCond);
    assert(cc == 0);

    if (shouldLock) {
        dvmUnlockMutex(&gDvm.heapWorkerLock);
    }
}

/*
 * Block until all pending heap worker work has finished.
 */
void dvmWaitForHeapWorkerIdle()
{
    int cc;

    assert(gDvm.heapWorkerReady);

    dvmChangeStatus(NULL, THREAD_VMWAIT);

    dvmLockMutex(&gDvm.heapWorkerLock);

    /* Wake up the heap worker and wait for it to finish. */
    //TODO(http://b/issue?id=699704): This will deadlock if
    //     called from finalize(), enqueue(), or clear().  We
    //     need to detect when this is called from the HeapWorker
    //     context and just give up.
    dvmSignalHeapWorker(false);
    cc = pthread_cond_wait(&gDvm.heapWorkerIdleCond, &gDvm.heapWorkerLock);
    assert(cc == 0);

    dvmUnlockMutex(&gDvm.heapWorkerLock);

    dvmChangeStatus(NULL, THREAD_RUNNING);
}

/*
 * Do not return until any pending heap work has finished.  This may
 * or may not happen in the context of the calling thread.
 * No exceptions will escape.
 */
void dvmRunFinalizationSync()
{
    if (gDvm.zygote) {
        assert(!gDvm.heapWorkerReady);

        /* When in zygote mode, there is no heap worker.
         * Do the work in the current thread.
         */
        dvmLockMutex(&gDvm.heapWorkerLock);
        doHeapWork(dvmThreadSelf());
        dvmUnlockMutex(&gDvm.heapWorkerLock);
    } else {
        /* Outside of zygote mode, we can just ask the
         * heap worker thread to do the work.
         */
        dvmWaitForHeapWorkerIdle();
    }
}

/*
 * Requests that dvmHeapSourceTrim() be called no sooner
 * than timeoutSec seconds from now.  If timeoutSec
 * is zero, any pending trim is cancelled.
 *
 * Caller must hold heapWorkerLock.
 */
void dvmScheduleHeapSourceTrim(size_t timeoutSec)
{
    GcHeap *gcHeap = gDvm.gcHeap;
    struct timespec timeout;

    if (timeoutSec == 0) {
        timeout.tv_sec = 0;
        timeout.tv_nsec = 0;
        /* Don't wake up the thread just to tell it to cancel.
         * If it wakes up naturally, we can avoid the extra
         * context switch.
         */
    } else {
        struct timeval now;

        gettimeofday(&now, NULL);
        timeout.tv_sec = now.tv_sec + timeoutSec;
        timeout.tv_nsec = now.tv_usec * 1000;
        dvmSignalHeapWorker(false);
    }
    gcHeap->heapWorkerNextTrim = timeout;
}
