/**
 * @file llheartbeat.cpp
 * @brief Class encapsulating logic for telling a watchdog that we live.
 *
 * $LicenseInfo:firstyear=2008&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include <errno.h>
#include <signal.h>

#include "linden_common.h"
#include "llapp.h"

#include "llheartbeat.h"

LLHeartbeat::LLHeartbeat(F32 secs_between_heartbeat,
             F32 aggressive_heartbeat_panic_secs,
             F32 aggressive_heartbeat_max_blocking_secs)
    : mSecsBetweenHeartbeat(secs_between_heartbeat),
      mAggressiveHeartbeatPanicSecs(aggressive_heartbeat_panic_secs),
      mAggressiveHeartbeatMaxBlockingSecs(aggressive_heartbeat_max_blocking_secs),
      mSuppressed(false)
{
    mBeatTimer.reset();
    mBeatTimer.setTimerExpirySec(mSecsBetweenHeartbeat);
    mPanicTimer.reset();
    mPanicTimer.setTimerExpirySec(mAggressiveHeartbeatPanicSecs);
}

LLHeartbeat::~LLHeartbeat()
{
    // do nothing.
}

void
LLHeartbeat::setSuppressed(bool is_suppressed)
{
    mSuppressed = is_suppressed;
}

// returns 0 on success, -1 on permanent failure, 1 on temporary failure
int
LLHeartbeat::rawSend()
{
#if LL_WINDOWS
    return 0; // Pretend we succeeded.
#else
    if (mSuppressed)
        return 0; // Pretend we succeeded.

    int result;
#ifndef LL_DARWIN
    union sigval dummy;
    result = sigqueue(getppid(), LL_HEARTBEAT_SIGNAL, dummy);
#else
    result = kill(getppid(), LL_HEARTBEAT_SIGNAL);
#endif
    if (result == 0)
        return 0; // success

    int err = errno;
    if (err == EAGAIN)
        return 1; // failed to queue, try again

    return -1; // other failure.
#endif
}

int
LLHeartbeat::rawSendWithTimeout(F32 timeout_sec)
{
    int result = 0;

    // Spin tightly until our heartbeat is digested by the watchdog
    // or we time-out.  We don't really want to sleep because our
    // wake-up time might be undesirably synchronised to a hidden
    // clock by the system's scheduler.
    mTimeoutTimer.reset();
    mTimeoutTimer.setTimerExpirySec(timeout_sec);
    do {
        result = rawSend();
        //LL_INFOS() << " HEARTSENDc=" << result << LL_ENDL;
    } while (result==1 && !mTimeoutTimer.hasExpired());

    return result;
}

bool
LLHeartbeat::send(F32 timeout_sec)
{
    bool total_success = false;
    int result = 1;

    if (timeout_sec > 0.f) {
        // force a spin until success or timeout
        result = rawSendWithTimeout(timeout_sec);
    } else {
        if (mBeatTimer.hasExpired()) {
            // zero-timeout; we don't care too much whether our
            // heartbeat was digested.
            result = rawSend();
            //LL_INFOS() << " HEARTSENDb=" << result << LL_ENDL;
        }
    }

    if (result == -1) {
        // big failure.
    } else if (result == 0) {
        total_success = true;
    } else {
        // need to retry at some point
    }

    if (total_success) {
        mBeatTimer.reset();
        mBeatTimer.setTimerExpirySec(mSecsBetweenHeartbeat);
        // reset the time until we start panicking about lost
        // heartbeats again.
        mPanicTimer.reset();
        mPanicTimer.setTimerExpirySec(mAggressiveHeartbeatPanicSecs);
    } else {
        // leave mBeatTimer as expired so we'll lazily poke the
        // watchdog again next time through.
    }

    if (mPanicTimer.hasExpired()) {
        // It's been ages since we successfully had a heartbeat
        // digested by the watchdog.  Sit here and spin a while
        // in the hope that we can force it through.
        LL_WARNS() << "Unable to deliver heartbeat to launcher for " << mPanicTimer.getElapsedTimeF32() << " seconds.  Going to try very hard for up to " << mAggressiveHeartbeatMaxBlockingSecs << " seconds." << LL_ENDL;
        result = rawSendWithTimeout(mAggressiveHeartbeatMaxBlockingSecs);
        if (result == 0) {
            total_success = true;
        } else {
            // we couldn't even force it through.  That's bad,
            // but we'll try again in a while.
            LL_WARNS() << "Could not deliver heartbeat to launcher even after trying very hard for " << mAggressiveHeartbeatMaxBlockingSecs << " seconds." << LL_ENDL;
        }

        // in any case, reset the panic timer.
        mPanicTimer.reset();
        mPanicTimer.setTimerExpirySec(mAggressiveHeartbeatPanicSecs);
    }

    return total_success;
}